Three separate faults, reported together as "the old web admin made
extensions correctly and this doesn't".
1. Every write through ea_docker_write left pjsip.conf owned by root. `tee`
runs as root inside the container while Asterisk runs as `asterisk` and
expects to own its own config — the vendored admin's add_device() chowns
it back immediately after writing, and services/asterisk.sh's device
migration does too. This was the one writer in the project that didn't,
and is the most likely reason an extension created here behaves
differently from one created in the vendored admin. Now chowned after
every write, with a scoped sudoers entry for it, and a warning logged if
the chown itself fails rather than passing silently.
2. The dashboard's update path rewrote the systemd unit and then restarted
the service without daemon-reload, so systemd kept running the cached
unit. Any Environment= line added since the last FRESH install was written
to disk and ignored — which is exactly how a box with DOMAIN_NAME and TURN
both set in .env still reported "no domain set" and "TURN not configured".
3. The connection panel rendered at the top of the card, so on any table long
enough to scroll, the answer appeared off-screen above the row that was
clicked. It is now a table row injected directly beneath its own
extension, toggled by the same button, and it survives the transport and
password actions by reopening after the reload they trigger.
Also: LAN devices now get ice_support=yes when a TURN server is configured,
matching the vendored admin (a device given TURN credentials but no ICE can't
use the relay); the panel reports the device type, so a Mobile extension can
be confirmed as such; and an unreadable .env now says which of "path not
set", "file missing", "permission denied" or "TURN_SERVER empty" applies
instead of the flat "not configured" that covered all four.
Verified: the three .env failure states each produce their own message; the
detail row lands directly after its anchor, only one is ever open, it clears
on re-render and reopens rather than sticking closed; no duplicate or missing
element IDs and no JS errors.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NAddJGE1G6eGaPzmScG5Vh