openvibers/openvibe.host Commits

Every openvibe-* and openre-* service starts after PostgreSQL, PgBouncer and Valkey, and PgBouncer after PostgreSQL (boot-order drop-ins) (#145)

OP OpenVibers committed 21 days ago · 46d0606327 · 6 files, +73 −1

Changes

ddiff --git a/README.md b/README.md
iindex d70fddc..084ead8 100644
---- a/README.md
++++ b/README.md
@@@ -293,6 +293,11 @@ Two things deploy from this repository:
then `ovhost --version` and `sudo ovhost capabilities` (ovhost does not deploy itself; an update is
undone by checking out the previous commit there). Its timers (`openvibe-backup`, `openvibe-alerts`, `openvibe-browsercheck`,
`openvibe-devpath`, `openvibe-toolsjob`) are unit files in [deploy/systemd/](deploy/systemd/).
++- **Boot order.** Three drop-ins in [deploy/systemd/](deploy/systemd/) order every `openvibe-*` and `openre-*`
++ service after PostgreSQL, PgBouncer and Valkey (systemd prefix drop-ins, `openvibe-.service.d/`), and PgBouncer
++ after PostgreSQL. Without them a reboot starts services against a PgBouncer that caches PostgreSQL's refused
++ logins (`server_login_retry`) and they exit once. They are `unitSources` of the host service, so
++ `sudo ovhost validate host` reports drift and `sudo ovhost deploy host --install-units` installs them.
- **The catalog service** deploys like every other service, with `sudo ovhost deploy host` (strategy
`git-checkout`: fetch, fast-forward `/opt/openvibe.host`, install on a lockfile change, restart, wait for `/api/ready`).
The unit is `openvibe-host.service` on `127.0.0.1:4910`, the env file `/etc/openvibe/host.env`. Its state is
ddiff --git a/deploy/systemd/openre-.service.d/10-database-order.conf b/deploy/systemd/openre-.service.d/10-database-order.conf
nnew file mode 100644
iindex 0000000..5c8d6cf
---- /dev/null
++++ b/deploy/systemd/openre-.service.d/10-database-order.conf
@@@ -0,0 +1,6 @@
++# A prefix drop-in: systemd applies it to every openre-*.service (OpenRestream). Each starts after PostgreSQL, PgBouncer
++# and Valkey have started, so a service connecting at boot never meets a database that is not accepting
++# logins yet. Ordering only: a unit that uses none of them does not pull them in.
++# Installed by `ovhost deploy host --install-units` (unitSources of the host service).
++[Unit]
++After=postgresql.service pgbouncer.service valkey-server.service
ddiff --git a/deploy/systemd/openvibe-.service.d/10-database-order.conf b/deploy/systemd/openvibe-.service.d/10-database-order.conf
nnew file mode 100644
iindex 0000000..c7789da
---- /dev/null
++++ b/deploy/systemd/openvibe-.service.d/10-database-order.conf
@@@ -0,0 +1,6 @@
++# A prefix drop-in: systemd applies it to every openvibe-*.service. Each starts after PostgreSQL, PgBouncer
++# and Valkey have started, so a service connecting at boot never meets a database that is not accepting
++# logins yet. Ordering only: a unit that uses none of them does not pull them in.
++# Installed by `ovhost deploy host --install-units` (unitSources of the host service).
++[Unit]
++After=postgresql.service pgbouncer.service valkey-server.service
ddiff --git a/deploy/systemd/pgbouncer.service.d/10-after-postgresql.conf b/deploy/systemd/pgbouncer.service.d/10-after-postgresql.conf
nnew file mode 100644
iindex 0000000..2c54a92
---- /dev/null
++++ b/deploy/systemd/pgbouncer.service.d/10-after-postgresql.conf
@@@ -0,0 +1,6 @@
++# PgBouncer starts after PostgreSQL. Started first, it answers clients while PostgreSQL is still refusing
++# logins and then caches that failure for server_login_retry seconds, so every service that connected
++# early exits once (the 2026-10-23 reboot: ten services and OpenRestream's coordinator).
++# Installed by `ovhost deploy host --install-units` (unitSources of the host service).
++[Unit]
++After=postgresql.service
ddiff --git a/host.example.json b/host.example.json
iindex cd10394..84d527c 100644
---- a/host.example.json
++++ b/host.example.json
@@@ -863,7 +863,10 @@
"openvibe-host.service"
],
"unitSources": {
-- "openvibe-host.service": "deploy/systemd/openvibe-host.service"
++ "openvibe-host.service": "deploy/systemd/openvibe-host.service",
++ "openvibe-.service.d/10-database-order.conf": "deploy/systemd/openvibe-.service.d/10-database-order.conf",
++ "openre-.service.d/10-database-order.conf": "deploy/systemd/openre-.service.d/10-database-order.conf",
++ "pgbouncer.service.d/10-after-postgresql.conf": "deploy/systemd/pgbouncer.service.d/10-after-postgresql.conf"
},
"envFile": "/etc/openvibe/host.env",
"env": {
ddiff --git a/test/boot-order-dropins.test.js b/test/boot-order-dropins.test.js
nnew file mode 100644
iindex 0000000..09dbbc5
---- /dev/null
++++ b/test/boot-order-dropins.test.js
@@@ -0,0 +1,46 @@
++'use strict';
++// The boot-order drop-ins (deploy/systemd/*.service.d): every openvibe-* and openre-* service starts after
++// PostgreSQL, PgBouncer and Valkey, and PgBouncer after PostgreSQL. On the 2026-10-23 reboot ten services met a
++// PgBouncer that had cached PostgreSQL's refused logins (server_login_retry) and exited once. The host service
++// installs the drop-ins (unitSources), so validate reports drift and `deploy host --install-units` fixes it.
++const assert = require('assert');
++const fs = require('fs');
++const path = require('path');
++const { normalise } = require('../lib/inventory');
++const { runTests, test } = require('./helpers');
++
++const root = path.join(__dirname, '..');
++const raw = JSON.parse(fs.readFileSync(path.join(root, 'host.example.json'), 'utf8'));
++const host = normalise(raw).services.host;
++
++const WANT = {
++ 'openvibe-.service.d/10-database-order.conf': ['postgresql.service', 'pgbouncer.service', 'valkey-server.service'],
++ 'openre-.service.d/10-database-order.conf': ['postgresql.service', 'pgbouncer.service', 'valkey-server.service'],
++ 'pgbouncer.service.d/10-after-postgresql.conf': ['postgresql.service'],
++};
++
++function unitSection(text) {
++ const lines = text.split('\n').filter((l) => l.trim() && !l.startsWith('#'));
++ assert.strictEqual(lines[0], '[Unit]', 'the drop-in is one [Unit] section');
++ return lines.slice(1);
++}
++
++runTests([
++ test('the host service installs each drop-in from deploy/systemd', () => {
++ for (const unit of Object.keys(WANT)) {
++ assert.strictEqual(host.unitSources[unit], `deploy/systemd/${unit}`, `${unit} is not in the host service's unitSources`);
++ assert.ok(fs.existsSync(path.join(root, 'deploy', 'systemd', unit)), `deploy/systemd/${unit} is missing`);
++ }
++ }),
++ test('each drop-in only orders (After=), never pulls a unit in or changes the service', () => {
++ for (const [unit, after] of Object.entries(WANT)) {
++ const body = unitSection(fs.readFileSync(path.join(root, 'deploy', 'systemd', unit), 'utf8'));
++ assert.deepStrictEqual(body, [`After=${after.join(' ')}`], unit);
++ }
++ }),
++ test('the prefix drop-ins are named for systemd prefix matching (a name ending in "-")', () => {
++ for (const unit of Object.keys(WANT).filter((u) => !u.startsWith('pgbouncer'))) {
++ assert.match(unit, /^[a-z]+-\.service\.d\//, unit);
++ }
++ }),
++]);