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:

  1. Detect the OS (OL9 / OL10 supported at v0.2; refuses anything else)
  2. Refuse to proceed if / is not LUKS-encrypted unless --allow-unencrypted is passed (see SECURITY.md)
  3. dnf -y update + dnf -y install epel* + install runtime deps (httpd, php-fpm, mariadb, python3, paramiko, cryptography, fail2ban, firewalld)
  4. Initialize MariaDB, create app DB + app DB user with a random password (stored at /etc/customsso-manager/db.creds, mode 0600)
  5. Generate the master AES-256-GCM key (/etc/customsso-manager/master.key, mode 0600 owned by apache)
  6. Place app files under /var/www/customsso-manager/ (web) + /opt/customsso-manager/sidecar/ (Python)
  7. Install schema (src/schema/001_initial.sql)
  8. Configure Apache vhost with self-signed TLS cert (swap to LetsEncrypt later if you want)
  9. Open firewalld services (https, http, ssh)
  10. Configure SELinux contexts (httpd_sys_content_t on web root, httpd_sys_rw_content_t on /var/spool/customsso/)
  11. Install fail2ban jail for the manager's audit log
  12. Install systemd unit for the Python sidecar; enable + start
  13. Prompt for a first-admin username + password (or use --admin-user / --admin-pass flags for scripted installs)
  14. 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:

  1. Make sure the PBX has SSH root enabled and reachable from the manager.
  2. In the manager UI, click + Add PBX in the sidebar (or Bulk pair to CSV-load many).
  3. Enter the PBX's SSH host, port (usually 22), root password, and an optional label.
  4. 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).
  5. 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_keys with a from="<manager-ip>" restriction, enables the Apache config, and — if a repo URL is set — registers it and runs fwconsole ma downloadinstall customsso so 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

  1. Dashboard should show the PBX with status=ok (might briefly show unknown until sidecar refreshes in <60s)
  2. Click the PBX → Refresh status — should immediately update timestamps + show the JSON status payload in the DB
  3. Open native UI ↗ — opens a new tab landing in the PBX's FreePBX admin dashboard, already logged in as admin
  4. Run commandfwconsole 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:

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:

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)