One interactive installer, two modes. It asks for IP, port, admin password and standalone / cluster — and handles everything else: packages, PostgreSQL, PKI, systemd, nginx TLS.
Fetches OS packages and Python wheels as it goes. Detects apt, dnf/yum, zypper or pacman and maps package names per family; PostgreSQL paths are interrogated from the engine itself, so Debian, RHEL, SUSE and Arch layouts all work.
Self-contained bundles per family — OS packages, Python wheels and the application — for hosts with no internet access. The installer auto-detects bundle/ and refuses a bundle built for the wrong family with a clear message.
Cluster setup is part of the same installer run — no separate procedure.
Choose cluster → primary. At the end the installer prints a one-line join key that encodes everything the secondary needs.
Run the same installer on the second host, choose cluster → secondary, and paste the key. Streaming replication, shared secrets and the data sync configure themselves.
The secondary receives the internal CA through the join key and mints its own leaf certificate during installation — private keys never travel between nodes.
| Component | Detail |
|---|---|
| Packages | Python ≥ 3.10, PostgreSQL, nginx, rsync, openssl — installed or upgraded per distro family |
| Application | Code deployed with a dedicated virtualenv; pinned dependencies from wheels (offline) or PyPI (online) |
| Database | PostgreSQL cluster initialized if needed; app database, roles and schema created; replication on clusters |
| PKI | Internal CA on the primary; per-node leaf certificates; nginx TLS on your chosen port |
| Services | systemd units for the web app and the scheduler sidecar, enabled and health-checked (/healthz) |
| Hardening | SELinux and firewalld handled best-effort on RHEL/SUSE; a manual covers sudoers policy, hardening and uninstall |
INSTALL.md) ships inside every bundle — requirements, permissions to request, sudo/sudoers options, hardening checklist