openvibers/openvibe.host Commits
Every openvibe-* and openre-* service starts after PostgreSQL, PgBouncer and Valkey, and PgBouncer after PostgreSQL (boot-order drop-ins) (#145)
Changes
@@@ -293,6 +293,11 @@ Two things deploy from this repository:then `ovhost --version` and `sudo ovhost capabilities` (ovhost does not deploy itself; an update isundone 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 isnnew file mode 100644@@@ -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.servicennew file mode 100644@@@ -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.servicennew file mode 100644@@@ -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@@@ -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": {nnew file mode 100644@@@ -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);++ }++ }),++]);
@@ -293,6 +293,11 @@ Two things deploy from this repository:
@@ -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
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`,
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/).
`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
- **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`).
`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
The unit is `openvibe-host.service` on `127.0.0.1:4910`, the env file `/etc/openvibe/host.env`. Its state is
new file mode 100644
new file mode 100644
@@ -0,0 +1,6 @@
@@ -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
new file mode 100644
new file mode 100644
@@ -0,0 +1,6 @@
@@ -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
new file mode 100644
new file mode 100644
@@ -0,0 +1,6 @@
@@ -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
@@ -863,7 +863,10 @@
@@ -863,7 +863,10 @@
"openvibe-host.service"
"openvibe-host.service"
],
],
"unitSources": {
"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",
"envFile": "/etc/openvibe/host.env",
"env": {
"env": {
new file mode 100644
new file mode 100644
@@ -0,0 +1,46 @@
@@ -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);
+ }
+ }),
+]);