"pjsip reload" was never a valid Asterisk CLI command — confirmed
live: `asterisk -rx "pjsip reload"` returns "No such command 'pjsip
reload'". The real command is "module reload res_pjsip.so".
Every reload-after-change call in the script used the invalid form,
both in the interactive CLI (add/edit/delete device, transport setup,
TLS cert sync) and in every web admin mutation (add_device,
delete_device, rename_device, change_device_category) — all silently
no-op'd, since `asterisk -rx` just prints its own "no such command"
error to a discarded/redirected output and returns normally either
way. Endpoints only ever picked up new pjsip.conf entries after a
full container restart (which re-reads config from scratch at
startup) or a manual `module reload res_pjsip.so` — never from the
web admin's own reload call, live-confirmed: a device stayed
Unavailable with zero registration attempts logged until a manual
reload picked it up immediately.
Global replace across all 11 occurrences.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015X1jRGHwrvovz2qkhKfDZi
Both defaulted to whatever sorted/listed first (kiosks category,
LAN/VPN UDP transport) — reasonable for a fixed intercom install, but
the common case here is adding a phone over the internet. Default the
category select to "mobile" specifically (not just first-in-list, so
it survives category reordering) and make FQDN/Internet (TLS) the
default transport option instead of LAN/VPN (UDP).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015X1jRGHwrvovz2qkhKfDZi
The web admin script (/usr/local/bin/easy-asterisk-webadmin) is
generated on demand by the interactive CLI, but only ever lived in
the container's writable layer — not baked into the image, not
bind-mounted. Every docker compose down/up wiped it, and the
entrypoint's start logic only ran "if the file already exists", so
it silently never started again until someone manually ran the CLI's
Web Admin menu once per recreate.
Added a --write-web-admin-script non-interactive entry point
(same pattern as --rebuild-dialplan) and call it unconditionally
before the existence check, so the web admin comes back on its own
every time the container starts.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015X1jRGHwrvovz2qkhKfDZi
The "device created" modal only ever showed extension/password/name,
so setting up a client (e.g. Sipnetic) meant hunting down the server
domain, port, and transport separately — and the password is only
ever shown this once, so re-checking it later isn't an option.
Now shows everything a SIP client needs in one place: display name,
server, port, transport, username, password, plus TURN/STUN details
when enabled. The backend reports the actual transport/port used
(the container always forces TLS/FQDN mode regardless of what's
selected in the form, so the frontend no longer has to guess).
Added "Copy All" and "Copy Password" buttons, with a document.
execCommand fallback for contexts where the Clipboard API isn't
available (e.g. plain-HTTP self-signed-cert access).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015X1jRGHwrvovz2qkhKfDZi
Devices/rooms trigger a dialplan rebuild themselves via the web admin
now, but that only fixes the problem going forward — endpoints added
before that fix (or by any future path that misses the call) stay
registrable-but-uncallable with no obvious cause until someone thinks
to run --rebuild-dialplan by hand.
Call it unconditionally once Asterisk is up, before the PJSIP
transport check. Cheap and idempotent — it just regenerates
extensions.conf from the current pjsip.conf/rooms.conf state.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015X1jRGHwrvovz2qkhKfDZi
The web admin's device functions (add_device, delete_device,
rename_device, change_device_category) only ever called
"asterisk -rx pjsip reload" — they never regenerated
extensions.conf's [intercom] context, so newly added SIP endpoints
could register but could never call each other or dial into rooms
("extension not found in context 'intercom'").
The room functions (create_room, delete_room, rename_room,
update_room_members) already tried to fix this correctly by shelling
out to `easy-asterisk --rebuild-dialplan`, but that flag was never
actually wired up — main() at the bottom of the script ignores all
arguments and always launches the interactive menu, so every one of
those calls was a silent no-op too.
Fixed both: added real --rebuild-dialplan argument handling that
calls the existing rebuild_dialplan() bash function non-interactively,
and added the same subprocess call to the four device functions that
were missing it entirely.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015X1jRGHwrvovz2qkhKfDZi
pjsip show transports was checked once, immediately after "core show
version" first responded — but res_pjsip can take a moment longer to
finish binding its transports, so the check would sometimes read an
empty transport list and print "NOT LOADED" even though transport-tls
came up correctly a second later (confirmed live: TLS SIP traffic on
5061 in the container logs right after the misleading banner).
Poll for up to 10s instead of checking once, matching the existing
core-show-version wait pattern further up the same script.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015X1jRGHwrvovz2qkhKfDZi
Defaults install_asterisk() to FQDN networking mode and prompts for VLAN/VPN
subnets (with host-network auto-detection to filter out noise like Docker
bridges) so phones on other networks get correct NAT/SDP handling from the
first boot.
The container now mounts Caddy's cert store read-only when Caddy is
installed, and the entrypoint syncs a matching Let's Encrypt cert for
DOMAIN_NAME automatically, re-checking every 12h to pick up renewals without
a restart. Falls back to self-signed only when no matching cert is found.
Also fixes a real bug hit in the field: a preserved/migrated pjsip.conf could
be missing the transport-udp/transport-tcp sections entirely, with no bind
error logged, silently blocking any device that registers without TLS. Adds
the same migration-injection already used for transport-tls.
vendor/easy-asterisk/: All source files from outis1one/easy-asterisk v0.10.0
vendored so the repo is self-contained — no internet required at install time.
Includes the real Dockerfile (FROM ubuntu:24.04 + full Asterisk stack),
entrypoint.sh (IP detection, TLS cert gen, pjsip/rtp config, web admin),
coturn-entrypoint.sh (robust IP detection wrapper), and the management
script + diagnostic utilities.
services/asterisk.sh: Rewritten to copy from vendor/ instead of downloading
at runtime. Uses the upstream Dockerfile verbatim. Symlinks
easy-asterisk-v0.10.0.sh → easy-asterisk.sh for build context compatibility.
services/onlyoffice.sh: Complete rewrite with correct standalone bootstrap.
_ensure_yq() installs yq v4 automatically (arch-aware). JWT secret is
preserved across re-runs so rotating is explicit. _wire_nextcloud() and
_wire_filebrowser() run on every install invocation (idempotent), skipping
gracefully when containers aren't running rather than failing.
https://claude.ai/code/session_014CCYqVwW6d6f5dw1qRokYt