Restore this manifest's CRLF line endings
The previous commit rewrote the file with a script that normalised it to LF, which is a whole-file diff for a change that touched 28 lines. No content change.
Stop running the platform's entire edge on one pod
Traefik ran a single replica, so every restart of it — a rollout, an OOM, an eviction — took the whole platform's TLS down. 192.168.4.22:443 has no local endpoi...
Careers joins the documented alias list
kamo-careers serves the org's public job board at careers.<apex>. The list is informational — the log-scan mechanism is what actually mints certificates — but t...
The AI hand-off raced tmux, so nothing was typed and nothing named
Caught by running it on the machine: the terminal opened at the right path, with no command in it and no title. One race, both symptoms. `bootstrap` runs strai...
Pin the session id, and link the terminal session into VS Code
`--session-id <uuid>` is chosen at prepare time rather than left to the CLI, which is what makes the editor hand-off deterministic: the id is known before the s...
Host a Send-to-AI dispatch in a Remote Terminal
composed prompt — and answers with an opaque TOKEN. A terminal that connects command typed into it. THE TOKEN IS THE WHOLE SECURITY DESIGN. `bootstrap=<command...
The terminal list was always empty — tmux escapes control chars
The field separator was a unit separator (0x1f), which is the obvious choice and silently wrong: tmux OCTAL-ESCAPES control characters in `-F` format output, so...
List, rename and end a member's terminals
GET /terminals?user=X, POST /terminals/<id>/kill, POST /terminals/<id>/title. THERE IS NO DATABASE BEHIND THIS, and that is the design rather than a shortcut. ...
The reaper used `su` too — runuser, and no shell anywhere
Dropping su from the exec path left it in reap_idle, which runs on every terminal connect. Same three objections, and the last one is the one that bites: `su - ...
The terminal's IngressRoute was in a directory nothing applies
It was written into securityservice/k8s/ beside the deployment, which reads as the obvious home and is not one: that repo's workflow applies exactly configmap.y...
Drop `su` — it swallowed SIGWINCH, so no terminal ever resized
Caught by running the protocol against the live machine, not by reading it: after a resize control frame, `tput cols` in the shell still reported the size the t...
Pin the no-pipelining invariant on the PTY upgrade
self.rfile is a BUFFERED reader over the socket and pump reads the RAW socket, so a caller that sent bytes behind its request line would strand them in that buf...
Drive a real PTY and a real socket through the terminal pump
The existing tests pin argv and framing in isolation; these four prove the pieces actually move bytes. A shell is forked under a genuine pty.fork and pumped ove...
Give the agent a PTY endpoint for Remote Terminal
GET /pty upgrades the connection to `kamo-pty` and becomes a shell: one PTY per browser tool window, `su - <user>` into `tmux new-session -A`, frames pumped bot...
Add 8777 to the WebRTC endpoint roster
Aiden Perry's extension was created from KamoCRM, which cannot write this file — so it registered over WSS with no DTLS or ICE and its calls would have carried ...
64G swap on the dev machine, and catch access that fails intermittently
The blank white page was the VM out of memory. 16Gi of guest RAM with NO swap means "out of memory" goes straight to the OOM killer: applications die mid-work, ...
Probe the bridge's real health, not a port that is always open
kamo-meet let one person into a meeting and then ended it the moment a second joined. The bridge had been hard-unhealthy for 41 hours: an in-place container res...
Open Send To AI in a VS Code session tab, not a Konsole window
A dispatch now starts a BACKGROUND session and hands VS Code its session id, instead of running the CLI in a terminal window: Passing the prompt to the URI in...
Swap the voicemail greeting to KamoCRM-LeaveVM
24.9s, down from 27.9s. Uploaded alongside the previous recording rather than over it, so rolling back is a one-line change to KAMO_VOICEMAIL_GREETING plus a di...
Set the VM's MTU to the pod network's, and let the desktop apply updates
Two separate reasons the "Code" update could not be installed. THE UPDATE COULD NOT BE DOWNLOADED. enp1s0 came up at MTU 1500 while every other workload on thi...
Give every kamocrm extension WebRTC, and gate outbound calling
All 21 kamocrm team members now hold an 8XXX extension on the k1m1 PBX (Chris renumbered off 7255 to 8134). Two changes here back that. WebRTC endpoint blocks ...
Stop unattended upgrades restarting xrdp under a live session
The black screen was apt. apt-daily-upgrade.service upgraded xrdp at 06:49 UTC on 2026-08-27 and restarted it while a desktop from 2026-08-25 was running. xrdp-...
Keep FreeSWITCH's log readable while it is crash-looping
FreeSWITCH runs with -nc, so mod_logfile is the only record of what it did, and on the container filesystem that record dies with the container — precisely when...
Stop offering 8 unroutable ICE candidates on every call
Callers hear several seconds of one-way audio at the start of a call — the other party says "hi" three or four times before the softphone user hears anything. ...
Recorded-call notice on extension-to-extension calls
Completes the third call path. Staff-to-staff calls never touch the attendant — they go from-internal -> ext-local, where the Dial belongs to FreePBX — so [from...
Match the SIP status code, not the reason phrase
sip-probe.py required the literal string "200 OK". 2600Hz's Kamailio config answers OPTIONS with "200 Rawr!!", so the probe reported a healthy, correctly config...
SIP registration works end to end
An endpoint can now REGISTER from the public internet and Kazoo's API reports it: **************** 200 OK via 47.181.8.87:5060. Four separate faults, each of w...
Recorded-call notice on direct-dialled extensions too
A caller who dials an extension over the greeting now hears the notice as well. That path went through from-did-direct, where the Dial belongs to FreePBX's ext-...
Recorded-call notice to both parties at once, then a bridge tone
When an agent answers a queued call, the caller hears the client notice and the agent hears the agent notice at the same time, then a shared 1000Hz tone marks t...
Zone must be named 'local' to match the node
The config.ini was loading correctly and the AMQP URI was right, but every node logged 'no local zone configured, adding default AMQP' and then retried ********...
Probe SIP for real, and assert the white-label holds on the wire
A TCP connect to 5060 proves almost nothing — it succeeds against anything listening, and on UDP it cannot fail at all. That ambiguity is exactly what led me to...
Distinct Erlang node names for apps and ecallmgr
One image runs as two nodes. They need different names or they refuse to coexist, and both need the shared cookie that FreeSWITCH's mod_kazoo also uses.
Run the OTP release, not make release
The image now ships a relx release rather than the source tree, so passing 'make release' to the entrypoint failed with 'No rule to make target release'. foreg...
Bootstrap script and operational README
bootstrap.sh creates the master account, promotes it to reseller and writes the non-secret platform configuration. Idempotent: a second run detects the existing...
Swap the inbound greeting to KamoCRM-Main-Intro-3
Third take, 40s (down from 60s). Uploaded alongside the previous two rather than over them, so rolling back to either is a one-line change to KAMO_ATTENDANT_INT...
Scale edge to 0 until its config is rewritten
Kamailio cannot start on 2600Hz's vendored configs — they need 2600Hz's patched build (spec 16). Leaving it at replicas:1 restart-storms k3m1 indefinitely for n...
The NAT is in place; a closed port means no listener
The router forwards for 5060/5061 TCP+UDP and 20001-29999/UDP to 192.168.4.36 are confirmed. My gate reported PHASE 0 BLOCKED against them while Kamailio was cr...
Apps, media, edge manifests and network policy
- config.ini carries a [zone] block from day one so a second region is a config edit, not surgery. Secrets are substituted by an entrypoint at container start...
K3m1 is 47.181.8.87 (pbx.k3.kluster.kamocrm.com)
Reverts an incorrect change of mine. I had propagated 42.181.8.87 and edited coturn/deployment.yaml's comment, which was right all along. Verified by DNS: pbx...
CouchDB and RabbitMQ on k3m1
CouchDB q=8 / n=1, verified live. q is fixed at database creation and KamoPBX creates a database per account plus monthly MODBs, so q is sized now for the event...
Phase 0 infrastructure gate
- TURN over TLS on 5349 with a real cert (turn-kamocrm-com-cert). Without turns:, a customer behind a 443-only egress firewall has no media path. Verified e...
Drop dead rate.loans and priceturbo.com from static provisioning
Both have had ACME challenges stuck pending for 47-54 days. The ratestack estate they proxy to has been scaled to 0 since 2026-08-04, so nothing can answer HTTP...
Bound the Go heap with GOMEMLIMIT and declare a real memory request
Root cause of the crash-loop was not a leak. The 485Mi steady state that kept tripping the old 512Mi limit was two normal things stacked: ~350Mi Go heap at ...
Swap the inbound greeting to KamoCRM-Main-Intro-2
Uploaded as a new file rather than overwriting the old one, so rolling back is a one-line change to KAMO_ATTENDANT_INTRO plus a dialplan reload — no re-upload n...
Raise memory limit to 1Gi and relax liveness probe timeout
Traefik sat at ~485Mi against a 512Mi limit (95%). The cgroup recorded 47836 memory.max hits with oom_kill 0 — never OOM-killed, because enough of the footprint...
Queue timeout and "press 0" both fall through to voicemail
Replaces the hand-rolled Dial()+Goto queue loop with app_queue. The deciding factor is DTMF: digits are NOT matched against the dialplan while Dial() is running...
Hold-music queue ringing 8000 and 8080 simultaneously
Eight hold tracks uploaded as the `kamocrm-hold` MOH class, shuffled. The random ordering comes from `random=1` on the row in FreePBX's `music` table, which is ...
Recorded greeting, dial-by-extension, 1/2 to operator
Replaces the music-on-hold hold pattern with the recorded intro: - **************** plays the greeting AND listens at the same time, so a caller can dial ove...
Inbound auto-attendant — music on hold + dial-by-extension
Inbound calls had nowhere to go: the `incoming` table was completely empty, so even a delivered call would have dead-ended. Adds the attendant context plus the ...
Like what you see shipping?
All of it arrives in your workspace on its own. Start on the free plan and read this page again in a month.
