Deployment guide
End-to-end walkthrough for setting up customsso-manager on Oracle Linux 9/10, pairing your first PBX, and validating the full pipeline.
Prerequisites
| Component | Requirement |
|---|---|
| Manager host | Oracle Linux 9 or 10 (minimal install), 1 CPU / 1 GB RAM / 10 GB disk minimum. EPEL repo must be enabled (the install script does dnf install -y epel* but it's faster if you do it ahead of time on minimal images). |
| Network | Manager must reach each PBX's HTTPS port (typically 443 or 8080 if NAT'd). PBXes must reach the manager's HTTPS port to complete the pair handshake. Admin browser must reach the manager. |
| PBX side | FreePBX 16 or 17. Root SSH access from the manager at pair time (the manager wipes the password immediately after handshake). The customsso agent module is installed by the manager over SSH. |
| Optional | Disk encryption (LUKS / Tang+Clevis / TPM) on the manager — see SECURITY.md for tradeoffs |
| Optional | DNS hostname for the manager (e.g., sso.yourdomain.tld). Auto-detected IP works fine for LAN-only deployments but doesn't survive PBXes behind NAT — see "Public URL" step. |
Manager install
1. One-line install
ssh root@your-manager-host
curl -sSfL https://git.voip-stuff.net/customsso-manager/install.sh -o /tmp/setup.sh && sudo bash /tmp/setup.sh
The bootstrap fetches latest.tar.gz, verifies its SHA256 against the same-origin SHA256SUMS file, then execs the real installer with any flags you pass through.
2. Optional flags
sudo bash /tmp/setup.sh -- --listen-host sso.yourdomain.tld
The installer will:
- Detect the OS (OL9 / OL10 supported at v0.2; refuses anything else)
- Refuse to proceed if
/is not LUKS-encrypted unless--allow-unencryptedis passed (see SECURITY.md) dnf -y update+dnf -y install epel*+ install runtime deps (httpd, php-fpm, mariadb, python3, paramiko, cryptography, fail2ban, firewalld)- Initialize MariaDB, create app DB + app DB user with a random password (stored at
/etc/customsso-manager/db.creds, mode 0600) - Generate the master AES-256-GCM key (
/etc/customsso-manager/master.key, mode 0600 owned by apache) - Place app files under
/var/www/customsso-manager/(web) +/opt/customsso-manager/sidecar/(Python) - Install schema (
src/schema/001_initial.sql) - Configure Apache vhost with self-signed TLS cert (swap to LetsEncrypt later if you want)
- Open firewalld services (
https,http,ssh) - Configure SELinux contexts (
httpd_sys_content_ton web root,httpd_sys_rw_content_ton/var/spool/customsso/) - Install fail2ban jail for the manager's audit log
- Install systemd unit for the Python sidecar; enable + start
- Prompt for a first-admin username + password (or use
--admin-user / --admin-passflags for scripted installs) - Print the URL to log in
Development (HTTP only, no TLS — convenience for LAN testing):
./install.sh --dev --admin-user admin --admin-pass <yourpw> --listen-host <your-ip-or-hostname>
3. First login
Browse to the URL the installer printed. Log in with your first-admin credentials.
The first-admin user is automatically promoted to superuser (highest role — can manage other users, rotate master key, etc.). See SECURITY.md for role definitions.
4. Set the public manager URL (if behind NAT or different from auto-detect)
Settings → Public manager URL
Fill in the URL paired PBXes should use to identify this manager (e.g., https://sso.yourdomain.tld or https://10.0.0.42:8443). This value gets baked into the pair handshake — change before pairing or you'll need to re-pair existing pairs to update them.
Auto-detect (the default) uses whatever URL your browser is hitting the manager at. Fine for LAN-only deployments; insufficient when the manager has a public hostname different from its internal IP.
5. Configure outbound SMTP (required for self-serve password recovery)
The password-recovery flow at /forgot-password emails users a new random password when they submit a matching username + email pair. It shells out to /usr/bin/msmtp -t — installed by install.sh — and reads its config from /etc/customsso-manager/msmtprc.
install.sh drops a commented-out placeholder at /etc/customsso-manager/msmtprc; you MUST edit it before password recovery can send mail.
Minimum required fields (example uses STARTTLS on 587 — matches most providers):
defaults
auth on
tls on
tls_starttls on
tls_trust_file /etc/ssl/certs/ca-bundle.crt
logfile /var/log/msmtp.log
account default
host smtp.example.com
port 587
from no-reply@example.com
user no-reply@example.com
password CHANGEME
Perms must be tight AND owned by the Apache user (msmtp refuses to read a config file whose owner isn't the invoking user):
chown apache:apache /etc/customsso-manager/msmtprc
chmod 600 /etc/customsso-manager/msmtprc
Smoke-test AS apache:
sudo -u apache msmtp -C /etc/customsso-manager/msmtprc --serverinfo --account=default
sudo -u apache bash -c 'echo -e "To: you@example.com\nSubject: msmtp test\n\nhello" | msmtp -C /etc/customsso-manager/msmtprc -t'
tail /var/log/msmtp.log
Until msmtprc is filled in:
- Reset REQUESTS still get audited (action=password.reset_request).
- The subsequent send attempt is audited too (action=password.reset_action, success=0) with the msmtp error stored in detail_json.mail_error.
- The affected user's password IS reset in the DB but they never receive it.
- A superuser can find the failure in the audit log and use Settings → Users → Reset password to set a fresh one to give the user out-of-band.
Also required for the flow:
- Each user account must have an email address on file (the email column on users, added in schema migration 003_users_email.sql). Existing accounts without an email can't self-recover — a superuser has to reset them directly.
No /usr/sbin/sendmail msmtp by default
install.sh deliberately does NOT create a /usr/sbin/sendmail → /usr/bin/msmtp symlink. customsso-manager shells out to msmtp directly (with -C /etc/customsso-manager/msmtprc); nothing in the codebase uses PHP's mail() or a sendmail command.
Creating that symlink to make msmtp the system-wide MTA would trip msmtp's config-owner-must-equal-invoker check: our msmtprc is owned by apache:apache, so a sendmail-piped message from cron / logrotate / anacron / any root or system-daemon caller would fail with Permission denied on the config file — same failure mode we already fixed on the Apache path, just recreated for every other user on the box.
If this host also needs to relay mail from cron reports or other daemons, install a real MTA (postfix as a null-client to your msmtp SMTP relay is a common pattern) instead of trying to reuse customsso-manager's msmtprc. Keep msmtprc scoped to the app that owns it.
Pair a PBX (v0.5 manager-initiated flow)
Under v0.5 there is no per-PBX shell install step. The manager does everything over SSH during pairing. On each PBX you want to manage:
- Make sure the PBX has SSH root enabled and reachable from the manager.
- In the manager UI, click + Add PBX in the sidebar (or Bulk pair to CSV-load many).
- Enter the PBX's SSH host, port (usually 22), root password, and an optional label.
- Optionally override the Module repo URL — the default comes from whatever the operator set via
install.sh --repo <url>at manager install time. Blank means skip the module install (SSO still works via the sso_entry.php Apache alias). - Submit. The manager SSHes in as root, drops the agent files (
sso_entry.php,customsso-headers.conf,customsso-unpair,agent.conf, cleanup manifest), installs its own SSH pubkey to/root/.ssh/authorized_keyswith afrom="<manager-ip>"restriction, enables the Apache config, and — if a repo URL is set — registers it and runsfwconsole ma downloadinstall customssoso future upgrades pull from the same repo. The root password stays in the manager's RAM for the SSH session, then gets zeroed and discarded. It never touches manager disk.
Watch the live transcript on /pair/progress/{job}; every step is audited under phase='pair'. The transcript is retained for 7 days.
Set the public PBX URL (if NAT'd or port-forwarded)
If the PBX's browser-facing URL differs from the SSH-reachable one, edit the Web URL on the manager's Status tab for that PBX (Edit button next to the Access panel). Manager iframe / SSO redirect targets use whatever is set there.
The sidecar re-fetches this from the PBX on every status refresh (agent-side is source of truth), so a change made on the manager's Access panel is pushed on the next 60 s tick.
Pairing
Pairing is entirely manager-initiated over SSH — there is no PBX-side wizard, no token to paste. See step 5 under "Add a PBX" above for the sequence the sidecar runs.
See PAIRING.md for the full protocol detail.
Smoke-test the pairing
- Dashboard should show the PBX with
status=ok(might briefly showunknownuntil sidecar refreshes in <60s) - Click the PBX → Refresh status — should immediately update timestamps + show the JSON status payload in the DB
- Open native UI ↗ — opens a new tab landing in the PBX's FreePBX admin dashboard, already logged in as
admin - Run command →
fwconsole ma list→ submits an async SSH job → completes in ~8s with the full module list in the output
If any of the above fails, check:
- SSH reachability: from the manager host,
ssh -i /etc/customsso-manager/keys/<pbx-uuid>.pem root@<pbx-host>should log you in silently - Pair state both sides:
mysql customsso_manager -e 'SELECT * FROM pbxes\G'on manager; on the PBX, verify/etc/customsso/pbx.manifest.jsonexists and lists the manager's UUID - Audit log on both sides —
audittable on manager,customsso_audittable on PBX
Operations cheat sheet
| Operation | How |
|---|---|
| Add another PBX | + Add PBX in the sidebar |
| Run a bulk SSH command across N PBXes | Top nav Bulk action, tick PBXes, pick command, dispatch |
| Open a PBX's FreePBX UI without re-login | PBX detail page → Open native UI ↗ button or per-page link |
| Run an ad-hoc shell command (escape-hatch) | PBX detail page → Ad-hoc shell form (SSH channel) |
| Re-run a previous job with same args | Job detail page → Run again ↻ button |
| See who did what when | Top nav Audit → filter by user/PBX/action/time |
| Export audit to CSV | Audit page → CSV button (respects current filter) |
| Rotate the manager's master key | Settings → Advanced → Rotate master key (superuser only) |
| Add another admin operator | Settings → Users → + Add a new user (superuser only) |
| Unpair a PBX | PBX detail page → Unpair — reads the pair manifest, removes SSH key from PBX's authorized_keys + every file the pair placed, deletes the manager row, leaves the PBX online but unmanaged |
| Update the manager app | Run the install one-liner again with --update on the end |
| Update an agent | Bulk actions → fwconsole.ma.upgrade @MODNAME@ with customsso as the module name, dispatch fleet-wide |
Backup & restore
See BACKUP.md. Critical files to back up off-host:
/etc/customsso-manager/master.key← without this the DB is unrecoverable/etc/customsso-manager/db.creds- MariaDB dump of
customsso_managerdatabase - (Optional) the manager's TLS key + cert
Common install issues
| Symptom | Cause | Fix |
|---|---|---|
dnf install -y epel* returns "No package matched" |
Already installed | Ignored — install.sh continues |
| Apache fails to start after install with "Invalid command 'Header'" | mod_headers not enabled (rare on OL httpd, but seen) |
a2enmod headers equivalent — on httpd: ensure mod_headers.so is loaded in /etc/httpd/conf.modules.d/00-base.conf |
Login screen renders but /settings 500's |
SELinux contexts not applied after deploy | restorecon -R /var/www/customsso-manager /opt/customsso-manager/sidecar |
customsso-sidecar.service won't start |
Master key unreadable by root | chmod 600 /etc/customsso-manager/master.key && chown apache:apache /etc/customsso-manager/master.key (sidecar runs as root which can read either way) |
Pair job fails with ssh_auth_failed |
Root SSH password rejected | Wrong password, PermitRootLogin set to prohibit-password, or PAM lockout. Fix, retry from the Add PBX form. |
Pair job stuck at installing customsso module |
PBX can't reach the module repo URL configured on the manager | Set Settings → Advanced → PBX module repo URL to a URL reachable from the PBX, then retry the pair. |
| Output column on job detail shows "(waiting...)" placeholder | SELinux contexts on /var/spool/customsso/ need httpd_sys_rw_content_t |
restorecon -R /var/spool/customsso/ (install.sh does this; only an issue if you copied files manually) |