Meditate's deploy step (ADR 0038, step 7; ADR 0039, D14), as Todo's: the server's Dockerfile with the library's check compiled beside it, its production config on its own database and role, compose.meditate.yaml as a member of the register, its site sending /library/* to the web server and the rest to the API server, the operator's library read-only in a compose file of its own, and redeploy.sh, backup.sh and the image replay taking it in. Every server's public address now comes from .env (Serverpod's SERVERPOD_API_SERVER_PUBLIC_HOST and _WEB_SERVER_, which win over the config file): Train, Todo and Meditate from their domains, Learn from LEARN_PUBLIC_HOST, unset learn.halkyon.eu as before. Without it a server on halkyon.li would name halkyon.eu in its SimpleLogin callback, profile pages and privacy page. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
178 lines
5.8 KiB
Dart
178 lines
5.8 KiB
Dart
import 'dart:io';
|
|
|
|
import 'package:test/test.dart';
|
|
import 'package:yaml/yaml.dart';
|
|
|
|
import '../../tool/src/images.dart';
|
|
|
|
/// Meditate's server beside the instance (Halkyon Meditate's ADR 0038, step
|
|
/// 7; ADR 0039, D14), as Todo's: a member of the register, its own database
|
|
/// and role, its site, and the operator's library read-only (D16).
|
|
void main() {
|
|
YamlMap services(String path) =>
|
|
(loadYaml(File(path).readAsStringSync()) as YamlMap)['services']
|
|
as YamlMap;
|
|
String read(String path) => File(path).readAsStringSync();
|
|
|
|
group('its server', () {
|
|
final server =
|
|
services('deploy/compose.meditate.yaml')['meditate-server'] as YamlMap;
|
|
final environment = server['environment'] as YamlMap;
|
|
|
|
test('signs in through the register, with the instance secret', () {
|
|
expect(environment['ACCOUNT_REGISTER_URL'], 'http://server:8082/');
|
|
expect(
|
|
environment['SERVERPOD_PASSWORD_instanceSecret'],
|
|
r'${INSTANCE_SECRET:?set INSTANCE_SECRET in deploy/.env}',
|
|
);
|
|
});
|
|
|
|
test('has a database password and pepper of its own', () {
|
|
expect(
|
|
environment['SERVERPOD_DATABASE_PASSWORD'],
|
|
startsWith(r'${MEDITATE_POSTGRES_PASSWORD:?'),
|
|
);
|
|
expect(
|
|
environment['SERVERPOD_PASSWORD_serverSideSessionKeyHashPepper'],
|
|
startsWith(r'${MEDITATE_SESSION_KEY_HASH_PEPPER:?'),
|
|
);
|
|
});
|
|
|
|
test('names its own domain as its public address', () {
|
|
for (final variable in [
|
|
'SERVERPOD_API_SERVER_PUBLIC_HOST',
|
|
'SERVERPOD_WEB_SERVER_PUBLIC_HOST',
|
|
]) {
|
|
expect(environment[variable], startsWith(r'${MEDITATE_DOMAIN:?'));
|
|
}
|
|
});
|
|
|
|
test('has no library unless its own file mounts one', () {
|
|
expect(environment.keys, isNot(contains('MEDITATE_LIBRARY_DIR')));
|
|
expect(server['volumes'], isNull);
|
|
});
|
|
|
|
test("is told to Learn's register as a member", () {
|
|
final learn =
|
|
services('deploy/compose.meditate.yaml')['server'] as YamlMap;
|
|
expect(
|
|
(learn['environment'] as YamlMap)['INSTANCE_MEMBER_MEDITATE'],
|
|
'http://meditate-server:8082/',
|
|
);
|
|
});
|
|
|
|
test('connects to its own database as its own role', () {
|
|
final config = loadYaml(
|
|
read('apps/meditate/halkyon_meditate_server/config/production.yaml'),
|
|
) as YamlMap;
|
|
final database = config['database'] as YamlMap;
|
|
expect(database['name'], 'halkyon_meditate');
|
|
expect(database['user'], 'halkyon_meditate');
|
|
expect(database['host'], 'postgres');
|
|
expect((config['sessionLogs'] as YamlMap)['persistentEnabled'], isFalse);
|
|
expect(config.keys, isNot(contains('insightsServer')));
|
|
});
|
|
});
|
|
|
|
group("the operator's library (D16)", () {
|
|
final server =
|
|
services('deploy/compose.meditate-library.yaml')['meditate-server']
|
|
as YamlMap;
|
|
|
|
test('is mounted read-only where the server reads it', () {
|
|
expect(server['volumes'], [
|
|
r'${MEDITATE_LIBRARY:?set MEDITATE_LIBRARY in deploy/.env}:/library:ro',
|
|
]);
|
|
expect(
|
|
(server['environment'] as YamlMap)['MEDITATE_LIBRARY_DIR'],
|
|
'/library',
|
|
);
|
|
});
|
|
|
|
test('is checked by a program in the image', () {
|
|
final dockerfile = read(
|
|
'apps/meditate/halkyon_meditate_server/Dockerfile',
|
|
);
|
|
expect(dockerfile, contains('--target bin/check_library.dart'));
|
|
expect(dockerfile, contains('bin/check_library'));
|
|
});
|
|
});
|
|
|
|
group('its site', () {
|
|
// The site's directives, without its comments.
|
|
final site = read('deploy/sidecars/meditate.caddy')
|
|
.split('\n')
|
|
.where((line) => !line.trim().startsWith('#'))
|
|
.join('\n');
|
|
|
|
test("sends the library's files to the web server, everything else to "
|
|
'the API server', () {
|
|
expect(site, contains('handle /library/* {'));
|
|
expect(site, contains('reverse_proxy meditate-server:8082'));
|
|
expect(site, contains('reverse_proxy meditate-server:8080'));
|
|
});
|
|
|
|
test("never passes on the calls between the instance's services", () {
|
|
expect(site, isNot(contains('/halkyon-base')));
|
|
expect(
|
|
RegExp('reverse_proxy meditate-server:8082').allMatches(site),
|
|
hasLength(1),
|
|
);
|
|
});
|
|
|
|
test('is mounted into Caddy by the compose file', () {
|
|
final caddy =
|
|
services('deploy/compose.meditate.yaml')['caddy'] as YamlMap;
|
|
expect(
|
|
caddy['volumes'],
|
|
contains(
|
|
'./sidecars/meditate.caddy:/etc/caddy/sidecars/meditate.caddy:ro',
|
|
),
|
|
);
|
|
});
|
|
});
|
|
|
|
group('the deploy and the backups', () {
|
|
test('build, dump, migrate and wait for it as for Todo', () {
|
|
expect(
|
|
read('deploy/redeploy.sh'),
|
|
contains('for app in train todo meditate; do'),
|
|
);
|
|
});
|
|
|
|
test('back its database up beside the others', () {
|
|
expect(
|
|
read('deploy/backup.sh'),
|
|
contains('for app in train todo meditate; do'),
|
|
);
|
|
});
|
|
|
|
test("have tool/check.dart replay its image's resolution", () {
|
|
expect(
|
|
dartImages,
|
|
contains('apps/meditate/halkyon_meditate_server/Dockerfile'),
|
|
);
|
|
});
|
|
});
|
|
|
|
group("every app's server names the instance's domain, not halkyon.eu's", () {
|
|
for (final (file, service, variable) in [
|
|
('compose.learn.yaml', 'server', 'LEARN_PUBLIC_HOST'),
|
|
('compose.train.yaml', 'train-server', 'TRAIN_DOMAIN'),
|
|
('compose.todo.yaml', 'todo-server', 'TODO_DOMAIN'),
|
|
('compose.meditate.yaml', 'meditate-server', 'MEDITATE_DOMAIN'),
|
|
]) {
|
|
test(service, () {
|
|
final environment =
|
|
(services('deploy/$file')[service] as YamlMap)['environment']
|
|
as YamlMap;
|
|
for (final key in [
|
|
'SERVERPOD_API_SERVER_PUBLIC_HOST',
|
|
'SERVERPOD_WEB_SERVER_PUBLIC_HOST',
|
|
]) {
|
|
expect(environment[key], startsWith('\${$variable'));
|
|
}
|
|
});
|
|
}
|
|
});
|
|
}
|