pbx-quadlet-setup

Da Planet Security · Albany, NY · 2026.QA.v4
27/27 tests passing ShellCheck clean v4.1.25

Stack

Storage
ZFS
Identity
POSIX useradd
Containers
Podman Quadlets
PBX
Asterisk 22 (Sangoma)
TLS
HAProxy wildcard
MOH
Icecast2 / klaxon

Extensions

ExtFunction
0Operator intercept → 2600
0100Milliwatt tone (1004 Hz)
0101Echo test
0102Speaking clock (Eastern)
0103Intercept / SIT tone
0200Open conference bridge
0201Private conference (PIN)
0300MOTD / info line
0301Da Planet Security info
1000Radio stream — klaxon/icecast2
2600Operator handset

NANP format: PREFIX-XXXX — PhreakNet prefix replaces 555 in v5.0

Changelog

VersionSummary
v4.1.25Core sounds in image; speaking clock confirmed; sounds in separate RUN layer
v4.1.24musiconhold.conf [general] section fix; SayUnixTime format
v4.1.234-digit NANP dialplan; confbridge; from-phreaknet stub
v4.1.22DRY firewall helpers; ufw and pf support
v4.1.21firewalld automation in preflight
v4.1.20Playback prompts; core sounds; echo test (1010)
v4.1.19SIP registration — AOR naming fix; Sangoma asterisk22; firewalld
v4.1.17MOH via HAProxy HTTPS → klaxon.dapla.net validated
v4.1.14Golden image; coturn STUN/TURN sidecar; host networking
v4.1.11Identity: POSIX useradd replaces homectl
v4.1Initial release — ZFS + Quadlets + PJSIP + HAProxy + MOH

Full history in CHANGELOG.md

Verified install

# Download and install
curl -fsSL https://denzuko.github.io/pbx-quadlet-setup/pbx_setup.sh -o pbx_setup.sh
# Review before running
less pbx_setup.sh
sudo bash pbx_setup.sh

QA compliance — 16 rules, 27 tests

RuleDescriptionStatus
R01PJSIP section naming + Dial() consistencyPASS
R02POSIX useradd/groupadd for service accountsPASS
R03machinectl shell for user unit activationPASS
R04Podman image store visibilityPASS
R05Config chmod 640 after heredocPASS
R06No TLS inside container — HAProxy handles terminationPASS
R07FFmpeg reconnect flags for MOH streamPASS
R08RTP port range consistency rtp.conf ↔ QuadletPASS
R09podman build explicit context pathPASS
R10Home path set explicitly via useradd --home-dirPASS
R11Credentials to /dev/shm via mktempPASS
R12Config paths audited against Quadlet Volume mountsPASS
R13Quadlet .build units for image lifecyclePASS
R14klaxon.dapla.net via HAProxy for co-located servicesPASS
R15PUBLIC_IP from keepalived VIP — never hardcodedPASS
R16All ephemeral secrets via mktemp /dev/shmPASS

QA review prompt

Use this prompt when submitting the script to an LLM for peer review:

You are a senior infrastructure engineer reviewing a bash deployment script.
Review pbx_setup.sh against these rules and flag any violations:

R01: PJSIP endpoint names match Dial() targets in extensions.conf
R02: Service account uses POSIX useradd/groupadd — not homectl or userdb
R03: machinectl shell used for user unit activation — not su or runuser
R04: Podman image built in user context — visible to pbxadmin only
R05: All config files chmod 640 after heredoc generation
R06: No TLS config inside container — HAProxy terminates externally
R07: FFmpeg has -reconnect 1 -reconnect_streamed 1 -reconnect_delay_max 5
R08: RTP port range consistent between rtp.conf and Quadlet PublishPort
R09: podman build includes explicit context path argument
R10: Home directory set via useradd --home-dir, created with mkdir + chown
R11: Credentials written only to mktemp /dev/shm path — never stdout
R12: All config paths exist within Quadlet Volume= mount points
R13: Image lifecycle via .build Quadlet unit — no bare podman build calls
R14: MOH stream uses HTTPS URL via HAProxy — not host.containers.internal
R15: PUBLIC_IP retrieved from keepalived VIP via ip(1) — never hardcoded
R16: All ephemeral secrets use mktemp /dev/shm — never /tmp or /root

Output: list each rule, PASS or FAIL, and a one-line reason.