The Rust pm-ca crate (crates/pm-ca/src/ca.rs) only parses PKCS#8
format private keys. openssl ecparam -genkey produces SEC1 format
(BEGIN EC PRIVATE KEY), which the Rust ring/RSA parser rejects
with "parse CA private-key PEM", causing the service to crash-loop
on startup.
Proven on LPM: converting ca.key with openssl pkcs8 -topk8 -nocrypt
and restarting patch-manager-web results in:
Root CA loaded successfully
Listening (HTTPS) on 0.0.0.0:443
CI Pipeline / Rust Format Check (push) Successful in 4s
CI Pipeline / Clippy Lints (push) Successful in 52s
CI Pipeline / Rust Unit Tests (push) Failing after 1m22s
CI Pipeline / Security Audit (push) Successful in 5s
CI Pipeline / Frontend Lint & Type Check (push) Successful in 15s
CI Pipeline / Build .deb & Release (push) Has been skipped
- Generate internal CA + CA-signed web TLS cert in postinst (port 443 was
falling back to plain HTTP because no cert files existed)
- Repair stale sqlx migration checksums for upgrades from <= 1.1.7
- Restore health check as advisory only (never fails the install)
- Use runuser instead of sudo (sudo is not guaranteed on minimal images)
- Replace predictable /tmp password file with mktemp under /run
- Frontend assets root-owned read-only (security)
- Drop Pre-Depends: postgresql-16 (misuse); drop argon2 dep (unused)
- Add openssl, curl, cron, util-linux as proper dependencies
CI Pipeline / Rust Format Check (push) Successful in 10s
CI Pipeline / Clippy Lints (push) Successful in 51s
CI Pipeline / Rust Unit Tests (push) Failing after 1m32s
CI Pipeline / Security Audit (push) Successful in 5s
CI Pipeline / Frontend Lint & Type Check (push) Successful in 15s
CI Pipeline / Build .deb & Release (push) Has been skipped
Root cause: postinst ran sqlx migrate as postgres (superuser), creating ALL
database objects owned by postgres. When pm-web connects as patch_manager, it
cannot ALTER TABLE during migrations because it does not own them. The
reassign_ownership() function never worked because REASSIGN OWNED BY postgres
TO patch_manager fails for superuser-owned objects.
Fix: Create the database owned by patch_manager (already done) and run all
migrations as patch_manager via PGPASSWORD auth. When all objects are owned by
patch_manager from the start, pm-web can ALTER them during upgrades.
Changes:
- Add psql_run_as_pm() helper that authenticates as patch_manager via PGPASSWORD
- Replace all psql_run_db calls in apply_migrations() with psql_run_as_pm
- Remove reassign_ownership() function entirely (it never worked)
- Remove reassign_ownership call from main()
- Add ALTER DEFAULT PRIVILEGES FOR ROLE postgres in setup_database() as safety
net for any future migration that might run as postgres
- Upgrade GRANT USAGE/CREATE to GRANT ALL PRIVILEGES on schema public
- Keep pgcrypto extension creation as postgres (requires superuser)
- Renumber sections after removing reassign_ownership
Proven on live LPM system: service active, port 443 listening, all tables
owned by patch_manager.
The postinst script runs migrations as the postgres superuser, which
means all created tables, enum types, and sequences are owned by
postgres. When pm-web connects as patch_manager and tries to ALTER
tables during upgrades, it fails with 'must be owner of table groups'.
Add reassign_ownership() function that runs after apply_migrations()
and before systemctl start. This function:
- REASSIGN OWNED BY postgres TO patch_manager (tables, types, sequences)
- ALTER SCHEMA public OWNER TO patch_manager
- GRANT ALL PRIVILEGES on database, schema, tables, sequences, functions
- ALTER DEFAULT PRIVILEGES for future objects in public schema
Renumbered sections 6-10 to 6-12 to accommodate the new function.
- write_config(): Replace CHANGEME placeholder on upgrade instead of
skipping entirely; preserve existing real passwords unchanged
- setup_database(): When DB user already exists, recover password from
existing config and sync to PostgreSQL, or generate a fresh password;
fixes crash-loop when config password diverges from PostgreSQL
- generate_jwt_keys(): Regenerate missing verify.pem from existing
signing.pem instead of silently skipping
- Password extraction uses @localhost anchor to correctly handle
passwords containing @ characters
- debian/postinst: auto-restart patch-manager-web and patch-manager-worker
on upgrade (not fresh install)
- debian/postinst: list pending database migrations after upgrade
- scripts/build-package.sh: update debian/control Version from VERSION
variable to ensure dpkg handles upgrades correctly
- tasks/lessons.md: added lessons about service restarts and version sync