Make WebRTC config survive pod restart
The previous approach (http_custom.conf with [general] override) didn't actually take effect because Asterisk's INI parser doesn't honor multiple [general] sect...
Enable Asterisk built-in HTTP server on :8089 for WebRTC /ws
A chan_pjsip `protocol=ws` transport rides on top of res_http_websocket, which in turn requires Asterisk's built-in HTTP server to be running. The default FreeP...
Add WSS WebRTC transport for browser softphones
Asterisk's chan_pjsip now binds a plain WebSocket transport on 0.0.0.0:8089 and Traefik terminates TLS at **************** (port 443) so kamo-internal can regis...
Expose port 8088 directly on ClusterIP service
Service only had port 80→8088 mapping. Internal services using **************** (e.g. VOIPService API token requests) were timing out because port 8088 had no l...
Add missing fail2ban-config ConfigMap for k1m1
Deployment references fail2ban-config ConfigMap (jail.local + asterisk-security.conf) but it did not exist, blocking the pod at MountVolume.SetUp and preventing...
Allow /domain/ path prefix on theme IngressRoute for CORS
The theme config at theme.kamocrm.com/domain/... was not matched by the existing route rules (only /public/ and /internal/ were allowed), so Traefik returned a ...
Production fail2ban with PJSIP jails and persistent state
- Add fail2ban-configmap.yaml with [asterisk] and [freepbx-web] jails, incremental ban escalation (1h→24h→30d ceiling), LAN ignoreip, and a custom asterisk-...
Replace Coqui TTS with Piper TTS (en_US-norman-medium)
Coqui VCTK model has no named speakers — replace with Piper TTS which has the exact norman[medium] voice. Piper binary (~3 MB) and model (~60 MB) are downloaded...
Integrate Coqui TTS with norman voice (VCTK p267)
- Deploy coqui-tts server (VCTK VITS model, CPU-only) as ClusterIP service on port 5002 with model cache at /var/lib/coqui-tts on k1m1 - Inject coqui_tts.agi ...
Add LAN DNS pod for split-DNS on k1m1
Deploys a dnsmasq pod with hostNetwork on k1m1 that listens on 192.168.4.22:53. Overrides pbx.k1.kluster.kamocrm.com -> 192.168.4.22 so LAN phones reach the PBX...
Add local_net for split-DNS / LAN client RTP fix
Phones on the same LAN as k1m1 connect via split DNS (domain resolves to 192.168.4.22 internally). Without local_net, Asterisk still advertises the external dom...
Persist sorcery contact=memory fix and block SIP scanner in startup.sh
- Inject [res_pjsip] contact=memory into sorcery.conf before Asterisk starts; the default sorcery-astdb SQLite backend silently drops all REGISTER contacts - ...
Move hostPath from MinIO (rclone FUSE) to local /var/lib/pbx
MinIO via rclone FUSE does not support POSIX chmod or symlink() — both required by FreePBX's module system. Changed all FreePBX and MariaDB hostPath volumes fro...
Wrap fwconsole/amportal via php for MinIO FUSE hostPath
/var/lib/asterisk is backed by MinIO via rclone FUSE which doesn't support POSIX chmod, so fwconsole scripts can't be made executable on that volume. startup.sh...
Add preStop hook and terminationGracePeriodSeconds
Containers were getting stuck in Terminating because background Asterisk processes kept running after apache2ctl (PID 1) exited. preStop hook sends SIGTERM to A...
Add bootstrap.php require to regenerated /etc/freepbx.conf
The installer writes freepbx.conf with datasource='' and a require_once for bootstrap.php, which config.php depends on to load the FreePBX class. Our regenerate...
Regenerate /etc/freepbx.conf on restart
/etc is ephemeral (not a hostPath), so /etc/freepbx.conf written by the installer is lost on every pod restart. Added else branch to regenerate it from env vars...
Bypass start_asterisk, wait for control socket instead
start_asterisk uses `pidof asterisk` which returns a false positive on pod restarts, causing the wait-for-Asterisk loop to hang. Now startup.sh starts Asterisk ...
Start Asterisk in background, wait for it, then run installer
The entrypoint.sh does not run the FreePBX installer — it only starts services. startup.sh now replicates the entrypoint setup steps, starts Asterisk in backgro...
Let entrypoint handle install, not startup.sh
FreePBX installer (php install -n) requires Asterisk to already be running. Our startup.sh was calling it before Asterisk started, causing "Error communicating ...
Mount /etc/asterisk only, not /etc, to fix PHP simplexml error
Mounting the full /etc as a hostPath volume was corrupting PHP extension loading even after seeding from the image — simplexml_load_file undefined at install ti...
Reduce CPU limits to 2:1 ratio to eliminate overcommitment
Docs 4→1 core, jibri 4→2 cores, libretranslate 2→1 core, bergamot 2→1 core, coturn 1→0.5 core. Saves ~7.5 cores off node limits.
Switch to escomputers/freepbx:17 with correct architecture
tiredofit/freepbx has no FreePBX 17 image (abandoned since 2022). escomputers/freepbx:17 (updated Apr 2026, Asterisk 21 + FreePBX 17) requires a separate MariaD...
Drop public admin ingress; access via http://k[12]m1:8088
Admin UI is now internal-only over LAN/WireGuard on port 8088 (FreePBX's nginx bound directly on hostNetwork). No Traefik route, no Let's Encrypt cert, no publi...
Deploy FreePBX 17 + Jigasi SIP bridge on k1m1 and k2m1
FreePBX (tiredofit/freepbx:17-latest) runs with hostNetwork so SIP/RTP preserves real client IPs, pinned one-per-node via nodeSelector. SIP on 5060 UDP/TCP (506...
Use ECDSA P-256 for cert-manager Certificate specs and migrate RSA
Sync SUBDOMAINS with product DNS aliases (add apps, sign; remove media)
Remove nonexistent postfix-lmdb package, install lmdb library instead
postfix-lmdb is not a valid Alpine package; lmdb support is built into the base postfix package. The pod was CrashLoopBackOff due to apk failure.
Add weekly image prune and journal vacuum CronJob for k1m1
CronJob runs every Sunday 3am, privileged, pinned to k1m1. - crictl rmi --prune removes unused container images - nsenter journal vacuum enforces 500MB cap - CI...
Fix Docs image registry, add Recreate strategy to coturn/postfix, increase qdrant memory
- Docs: fix wrong image registry (redis.kamo.svc... → **************** add Recreate strategy - coturn: add Recreate strategy to prevent rolling update port conf...
Switch Postfix sasl_passwd from hash to lmdb format
Alpine postfix build does not include hash support — postmap and smtp_sasl_password_maps were silently failing, deferring every outbound relay attempt through s...
Set Traefik externalTrafficPolicy=Local to preserve client IP
With the default "Cluster" policy, kube-proxy SNATs external traffic to the node IP before forwarding to Traefik. That caused Traefik to see the node IP as the ...
Add geo-database-url to analytics-secret for CockroachDB geolite lookup
Kamo Analytics uses this URL to look up visitor IP→country/region/city from the **************** tables. Uses kamo user with password auth and sslmode=require (...
Clean up legacy analytics deployment on each CI run
Delete old analytics deployment, service, and ingressroute that conflict with the kamo-analytics deployment managed by its own CI/CD pipeline.
Strip X-Frame-Options header in iframe-compatible middleware
Set X-Frame-Options to empty string to remove the SAMEORIGIN value that was being inherited, allowing CSP frame-ancestors to control iframe embedding instead.
Add iframe-compatible middleware chain for kamo-analytics
Add security-headers-allow-iframe (no X-Frame-Options) and kamo-middlewares-allow-iframe chain so analytics can be embedded in iframes from internal.kamocrm.com...
Move kamo-analytics to own repo, keep only postgres in KlusterServices
- Remove analytics deployment, service, and nginx-configmap (now owned by kamo-analytics project with its own CI/CD pipeline) - Remove analytics-ingressroute ...
Reset document.title in rebrand() when React overwrites it
React re-applies RSC payload after hydration, resetting the title to "analytics". Add a title check inside the MutationObserver callback so it gets corrected ev...
Replace "analytics" text nodes with "Kamo Analytics" instead of clearing
Use TreeWalker to remove all "analytics" text nodes from rendered DOM
The workspace nav title "analytics" comes from JS bundles loaded after auth, rendered as a text node next to the logo SVG. Uses TreeWalker to find and clear all...
Add guarded MutationObserver for client-side rendered logo/text
Login page and dashboard content is rendered by Next.js JS bundles, not in the HTML body, so sub_filter can't reach it. Adds a script with a busy-flag-guarded M...
Redirect favicons at nginx level, fix RSC payload escaping
- Serve favicon via nginx 302 redirect instead of trying to rewrite HTML/RSC payload href strings (cleaner, always works) - Fix RSC title replacement with pro...
Use pure sub_filter replacements instead of MutationObserver
MutationObserver caused infinite loop (DOM change -> observer fires -> DOM change). Switch to direct nginx sub_filter string replacements targeting both static ...
Like what you see shipping?
Every one of these updates lands in your workspace automatically. Start free and watch it grow week after week.