This commit is contained in:
1 parent
732cc8cb54
commit
b1e6d9cd0b
9 files changed
+55
-30
No files matched your search
@@ -359,16 +359,22 @@ missing. PostgreSQL may also start after the services at boot.
|
||||
Decision: The web service makes its signing key and creates the refresh tokens' database itself,
|
||||
and the Router Manager its sessions database; neither `vpn-router-setup` nor a maintainer script
|
||||
creates them. Each service does it before it listens, retrying every 15 seconds and logging each
|
||||
failure, then listens. The first step is the PostgreSQL driver: psycopg finds libpq through the
|
||||
linker cache, which the package manager refreshes only at the end of an installation, so a
|
||||
service started during the installation cannot import it yet. The Manager imports psycopg only in
|
||||
its retry loop. On the router, `vpnrouter_auth` alone imports it, in `driver()`, which the web
|
||||
service's retry loop calls first, and turns a failed import and every database failure into
|
||||
failure, then listens. `vpnrouter_auth` turns every failure of the database into
|
||||
`DatabaseError`, the only database exception the web service and `vpn-router-user` handle.
|
||||
|
||||
psycopg (Ubuntu ships only its pure-Python implementation) finds libpq through the dynamic linker
|
||||
cache, which dpkg refreshes with its deferred `ldconfig` only at the end of an installation, so a
|
||||
service started during the installation cannot import it yet; a failed import cannot be repeated
|
||||
in the same process either, as psycopg caches the lookup. The services therefore import psycopg
|
||||
plainly, and their units wait instead: `ExecStartPre=wait-for-libpq`, a shell script of each
|
||||
package, waits until `ldconfig -p` lists `libpq.so.5`, within `TimeoutStartSec=5min`. The
|
||||
maintainer scripts start the units with `systemctl --no-block` (debhelper's start is turned off),
|
||||
because a start that waited would hold dpkg before its `ldconfig`.
|
||||
|
||||
Consequences: The order of installation and of the start of PostgreSQL and of the linker cache
|
||||
does not matter; a driver that cannot be loaded or a database that cannot be created is retried
|
||||
until the cause is gone, and the reason is in the service's log, without restarts. The web unit may write `/etc/vpn-router` for the key and the database password.
|
||||
does not matter; a database that cannot be created is retried until the cause is gone, and the
|
||||
reason is in the service's log. Outside an installation the wait ends at once. The web unit may
|
||||
write `/etc/vpn-router` for the key and the database password.
|
||||
|
||||
## ADR-17: Bootstrap diagnostics run only until the first setup succeeds
|
||||
|
||||
|
||||
@@ -1,3 +1,14 @@
|
||||
vpn-router (0.9.2-1) unstable; urgency=medium
|
||||
|
||||
fix: the web service starts once libpq can be found.
|
||||
|
||||
* The web service unit waits, with wait-for-libpq, until libpq is in the
|
||||
linker cache before it starts the service, for at most 5 minutes; the
|
||||
service imports psycopg at start again.
|
||||
* The package starts the web service without waiting for it.
|
||||
|
||||
-- Slawomir Koszewski <slawek@koszewscy.waw.pl> Sun, 25 Oct 2026 06:00:00 +0200
|
||||
|
||||
vpn-router (0.9.1-1) unstable; urgency=medium
|
||||
|
||||
fix: the web service waits for the PostgreSQL driver instead of failing at start.
|
||||
|
||||
@@ -15,6 +15,7 @@ src/usr/lib/vpn-router/docker-rules usr/lib/vpn-router/
|
||||
src/usr/lib/vpn-router/setup usr/lib/vpn-router/
|
||||
src/usr/lib/vpn-router/web usr/lib/vpn-router/
|
||||
src/usr/lib/vpn-router/bootstrap usr/lib/vpn-router/
|
||||
src/usr/lib/vpn-router/wait-for-libpq usr/lib/vpn-router/
|
||||
src/usr/lib/vpn-router/generate-config usr/lib/vpn-router/
|
||||
src/usr/lib/vpn-router/vpnrouter_platforms/*.py usr/lib/vpn-router/vpnrouter_platforms/
|
||||
src/usr/share/vpn-router/templates/* usr/share/vpn-router/templates/
|
||||
|
||||
@@ -70,6 +70,12 @@ case "$1" in
|
||||
# The firewall is not touched here. Enabling it, allowing SSH and
|
||||
# setting the forward policy all belong to vpn-router-setup, which does
|
||||
# them only once the configuration says this host is a router.
|
||||
|
||||
# The web service waits for libpq in the linker cache, which dpkg
|
||||
# refreshes after this script, so it is started without waiting for it.
|
||||
if [ -d /run/systemd/system ]; then
|
||||
systemctl --no-block restart vpn-router-web.service >/dev/null 2>&1 || true
|
||||
fi
|
||||
;;
|
||||
esac
|
||||
|
||||
|
||||
@@ -5,5 +5,5 @@
|
||||
override_dh_installsystemd:
|
||||
dh_installsystemd --name=vpn-router-setup
|
||||
dh_installsystemd --name=vpn-router-docker
|
||||
dh_installsystemd --name=vpn-router-web
|
||||
dh_installsystemd --name=vpn-router-web --no-start
|
||||
dh_installsystemd --name=vpn-router-bootstrap --no-enable --no-start
|
||||
@@ -6,7 +6,9 @@ Wants=network-online.target
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
ExecStartPre=/usr/lib/vpn-router/wait-for-libpq
|
||||
ExecStart=/usr/bin/python3 /usr/lib/vpn-router/web
|
||||
TimeoutStartSec=5min
|
||||
Restart=on-failure
|
||||
RestartSec=15
|
||||
NoNewPrivileges=yes
|
||||
|
||||
@@ -5,9 +5,7 @@ router and that never leaves it. /auth issues an access token naming the user in
|
||||
a refresh token valid REFRESH_SECONDS; vpn-router-token issues tokens with the admin claim and
|
||||
any lifetime. A token without the admin claim must expire within MAX_LIFETIME seconds.
|
||||
|
||||
psycopg is imported by driver() when the database is used, not when the module is loaded, so the
|
||||
module loads while libpq cannot be found yet. A driver that cannot be imported and every
|
||||
failure of the database raise DatabaseError, the only database exception callers see.
|
||||
Every failure of the database raises DatabaseError, the only database exception callers see.
|
||||
|
||||
Refresh tokens are random strings kept in PostgreSQL as their SHA-256, in the database of
|
||||
web.database, by default the vpn-router database on this host that provision() creates on the
|
||||
@@ -24,6 +22,8 @@ import secrets
|
||||
import subprocess
|
||||
import time
|
||||
|
||||
import psycopg
|
||||
|
||||
WEB_KEY = pathlib.Path('/etc/vpn-router/web.key')
|
||||
DATABASE_SECRET = pathlib.Path('/etc/vpn-router/database.secret')
|
||||
|
||||
@@ -44,7 +44,7 @@ SCHEMA = 'CREATE TABLE IF NOT EXISTS refresh_tokens (hash text PRIMARY KEY, name
|
||||
|
||||
|
||||
class DatabaseError(Exception):
|
||||
"""The PostgreSQL driver cannot be loaded, or the database of the refresh tokens failed."""
|
||||
"""The database of the refresh tokens failed."""
|
||||
|
||||
|
||||
def key():
|
||||
@@ -117,15 +117,6 @@ def admin_token(seconds):
|
||||
return {'token': sign({'admin': True, 'iat': now, 'exp': now + seconds}, secret), 'expires': now + seconds}
|
||||
|
||||
|
||||
def driver():
|
||||
"""The psycopg module; DatabaseError while libpq cannot be found."""
|
||||
try:
|
||||
import psycopg
|
||||
except ImportError as exc:
|
||||
raise DatabaseError(f'the PostgreSQL driver cannot be loaded: {exc}') from exc
|
||||
return psycopg
|
||||
|
||||
|
||||
def connection_string(cfg):
|
||||
"""web.database, or the database provision() creates; its password, of URL-safe characters, is written unquoted."""
|
||||
configured = cfg['web']['database'].strip()
|
||||
@@ -138,7 +129,6 @@ def connection_string(cfg):
|
||||
@contextlib.contextmanager
|
||||
def database(cfg):
|
||||
"""A connection to the database of the refresh tokens, with their table, closed at the end; DatabaseError on any failure."""
|
||||
psycopg = driver()
|
||||
try:
|
||||
with psycopg.connect(connection_string(cfg), autocommit=True) as connection:
|
||||
connection.execute(SCHEMA)
|
||||
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
#!/bin/sh
|
||||
# Wait until libpq is in the dynamic linker cache, where psycopg looks it up.
|
||||
# ExecStartPre= of vpn-router-web.service; TimeoutStartSec= of the unit bounds the wait.
|
||||
|
||||
reported=
|
||||
until ldconfig -p | grep -q '^[[:space:]]*libpq\.so\.5 '; do
|
||||
if [ -z "$reported" ]; then
|
||||
echo 'waiting for libpq.so.5 in the linker cache'
|
||||
reported=yes
|
||||
fi
|
||||
sleep 2
|
||||
done
|
||||
@@ -30,11 +30,10 @@ default the address the service listens on, is the last entry of its X-Forwarded
|
||||
Docker is installed, and the state of site-to-site, road-warrior (IPsec P2S), OpenVPN and
|
||||
WireGuard. No configuration data, key or address is shown.
|
||||
|
||||
The service first initializes, without listening: it loads the PostgreSQL driver
|
||||
(vpnrouter_auth.driver; psycopg finds libpq through the linker cache, which the package manager
|
||||
refreshes only at the end of an installation), then it makes the signing key and the refresh
|
||||
The service first initializes, without listening: it makes the signing key and the refresh
|
||||
tokens' database (vpnrouter_auth), retrying every INIT_RETRY_SECONDS until both are ready and
|
||||
logging each failure. Then it listens.
|
||||
logging each failure. Then it listens. Its unit starts it only once wait-for-libpq has found libpq
|
||||
in the linker cache, where psycopg looks it up.
|
||||
|
||||
The service needs nothing else to start and does not end on its own. Every DISCOVERY_SECONDS it
|
||||
works out the internal address from /etc/vpn-router/vpn-router.json as general.mode directs,
|
||||
@@ -697,13 +696,11 @@ def log(message):
|
||||
|
||||
|
||||
def initialize():
|
||||
"""Load the PostgreSQL driver, make the signing key and the refresh tokens' database, retrying every INIT_RETRY_SECONDS until all are ready."""
|
||||
"""Make the signing key and the refresh tokens' database, retrying every INIT_RETRY_SECONDS until both are ready."""
|
||||
reported = None
|
||||
while True:
|
||||
phase = 'loading the PostgreSQL driver'
|
||||
phase = 'reading the configuration'
|
||||
try:
|
||||
vpnrouter_auth.driver()
|
||||
phase = 'reading the configuration'
|
||||
cfg = vpnrouter.load_config()
|
||||
phase = 'making the signing key'
|
||||
vpnrouter_auth.ensure_key()
|
||||
|
||||
Reference in new issue
Block a user