diff --git a/services/security-dashboard.sh b/services/security-dashboard.sh
index 239f973..0834055 100644
--- a/services/security-dashboard.sh
+++ b/services/security-dashboard.sh
@@ -140,6 +140,7 @@ install_security-dashboard() {
_secdash_grant_asterisk_access "$SVC_USER" "$ASTERISK_LOG_DIR" "$ASTERISK_CONFIG_DIR" "$ASTERISK_EA_CONFIG_DIR"
_secdash_write_app "$APP_DIR"
_secdash_write_asn_helper "$APP_DIR"
+ _secdash_copy_kiosk_installer "$APP_DIR"
_secdash_write_sudoers "$SVC_USER" "$ASTERISK_EA_CONTAINER"
_secdash_write_systemd_unit "$APP_DIR" "$SVC_USER" "$DASHBOARD_PORT" "$ASTERISK_LOG_DIR" "$ASTERISK_CONFIG_DIR" "$ASTERISK_EA_CONFIG_DIR" "$ASTERISK_EA_CONTAINER"
# systemd caches unit files; a plain restart re-runs the OLD
@@ -182,6 +183,7 @@ install_security-dashboard() {
_secdash_write_app "$APP_DIR"
chown -R "$SVC_USER:$SVC_USER" "$APP_DIR"
_secdash_write_asn_helper "$APP_DIR"
+ _secdash_copy_kiosk_installer "$APP_DIR"
_secdash_write_sudoers "$SVC_USER" "$ASTERISK_EA_CONTAINER"
_secdash_write_systemd_unit "$APP_DIR" "$SVC_USER" "$DASHBOARD_PORT" "$ASTERISK_LOG_DIR" "$ASTERISK_CONFIG_DIR" "$ASTERISK_EA_CONFIG_DIR" "$ASTERISK_EA_CONTAINER"
@@ -824,6 +826,30 @@ ASNHELPER
chmod 700 "$_app_dir/set-asn-exempt.sh"
}
+# Copies the vendored Easy Asterisk installer alongside the app so the
+# dashboard's "Download kiosk client installer" link (Ring Groups card ->
+# "How this works") has a real file to serve -- see docs/kiosk-paging-setup.md
+# for what it's for. Copied at install time, not read live from vendor/ at
+# request time, since a standalone run of this one file (`sudo bash
+# security-dashboard.sh`, no full repo clone -- this file supports that mode,
+# see the _RUN_STANDALONE stub above) has no vendor/ directory to read from at
+# all. Missing source is a soft failure: the rest of the dashboard installs
+# fine either way, only that one download link won't work.
+_secdash_copy_kiosk_installer() {
+ local _app_dir="$1"
+ local _self_dir
+ _self_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
+ local _vendor_script="$_self_dir/../vendor/easy-asterisk/easy-asterisk-v0.10.0.sh"
+ if [[ -f "$_vendor_script" ]]; then
+ cp "$_vendor_script" "$_app_dir/kiosk-client-installer.sh"
+ chmod 644 "$_app_dir/kiosk-client-installer.sh"
+ else
+ log_warning "vendor/easy-asterisk/easy-asterisk-v0.10.0.sh not found (standalone run without"
+ log_warning "the full repo?) -- the dashboard's kiosk installer download link will 404 until"
+ log_warning "this exists. Re-run from a full 'ubuntu-post-install' checkout to fix."
+ fi
+}
+
# Writes the Python app. Separate function so "update" mode (refresh code,
# keep config) and fresh installs share one copy instead of drifting apart.
_secdash_write_app() {
@@ -859,6 +885,12 @@ ASN_SCENARIO_FILES = [
# goes through sudo instead of loosening those files' permissions.
ASN_HELPER_SCRIPT = "/opt/security-dashboard/set-asn-exempt.sh"
+# Copied in by _secdash_copy_kiosk_installer at install time (see that
+# function's own comment for why this reads a local copy, not vendor/ live).
+# May not exist on a standalone run without the full repo -- the download
+# route below 404s cleanly in that case rather than erroring.
+KIOSK_INSTALLER_SCRIPT = "/opt/security-dashboard/kiosk-client-installer.sh"
+
TS_RE = re.compile(r"^\[([^\]]+)\]")
KV_RE = re.compile(r'(\w+)="([^"]*)"')
ASN_FILTER_RE = re.compile(r"ASNNumber in \[([^\]]*)\]\)")
@@ -2315,6 +2347,33 @@ def ea_device_provisioning(extension):
re.sub(r"[^A-Za-z0-9._-]", "-", host), d["extension"]), "\n".join(lines) + "\n"
+def ea_device_sipnetic_string(extension):
+ """Sipnetic's own documented "account string" QR-scan format
+ (https://www.sipnetic.com/qr-codes): semicolon-separated key=value pairs,
+ n=display name, u=username, d=domain/IP (no port), p=password,
+ dt=default transport (0=UDP, 1=TCP, 2=TLS). Unlike
+ ea_device_provisioning()'s deliberately generic plain-text file, this one
+ IS a verified, documented format for one specific app, built from the
+ exact same ea_device_details() data.
+
+ A literal ';' in any field must be doubled per that same doc page --
+ generated passwords are alnum-only (_ea_generate_password) so this only
+ matters for a hand-typed device name, but escaping costs nothing."""
+ d = ea_device_details(extension)
+ if not d:
+ return None
+
+ def esc_field(v):
+ return str(v or "").replace(";", ";;")
+
+ host = d["server"] or ""
+ dt = "2" if d["transport"] == "TLS" else "0"
+ return "n=%s;u=%s;d=%s;p=%s;dt=%s;" % (
+ esc_field(d["name"] or d["extension"]), esc_field(d["extension"]),
+ esc_field(host), esc_field(d["password"]), dt,
+ )
+
+
def _ea_edit_device_block(extension, mutate):
"""Rewrite one device's endpoint stanza in place.
@@ -3250,8 +3309,9 @@ INDEX_HTML = """
Ring dials every member at once — first to answer gets the call, everyone else stops ringing. Page tells Asterisk to signal auto-answer to every member via SIP headers, for devices that honor it, turning the same simultaneous dial into a one-way intercom-style broadcast instead. You don't need Page just to mix an auto-answering device with normally-ringing phones in the same group, though — auto-answer is really a property of the device's own SIP client configuration, not something Asterisk enforces per member. A plain Ring group already dials everyone simultaneously, so a device configured to auto-answer (e.g. a dedicated intercom/kiosk running For a dedicated always-on auto-answer device (a wall-mounted intercom, a paging station), Easy Asterisk — the vendor project this installer builds on — has a built-in You don't need Page just to mix an auto-answering device with normally-ringing phones in the same group, though — auto-answer is really a property of the device's own SIP client configuration, not something Asterisk enforces per member. Confirmed against baresip's own source: it decides purely from its account's local For a dedicated always-on auto-answer device (a wall-mounted intercom, a paging station), Easy Asterisk — the vendor project this installer builds on — has a built-in For a phone or tablet running Sipnetic instead, each extension's own detail panel below (Extensions tab → click a row → "Sipnetic QR code") can generate a scan-to-configure code — no dedicated kiosk hardware needed for that one.Ring vs Page, and mixing auto-answer devices in one group
baresip in Answer Mode: Auto) picks up instantly, while ordinary phones in the same group just keep ringing until a person answers — no extra setting needed here for that mix.baresip-based kiosk client for exactly that. It installs on a separate small Linux machine (an old PC, a Raspberry Pi), not this Asterisk server itself. See docs/kiosk-paging-setup.md in this repo for the full walkthrough.answermode setting and never looks at any auto-answer signal on the incoming call, so a device configured to auto-answer (e.g. a dedicated intercom/kiosk running baresip in Answer Mode: Auto) picks up everything routed to it instantly and unconditionally, while ordinary phones in the same plain Ring group just keep ringing until a person answers — no extra setting needed here for that mix.baresip-based kiosk client for exactly that. It installs on a separate small Linux machine (an old PC, a Raspberry Pi), not this Asterisk server itself: download the installer script, then see docs/kiosk-paging-setup.md in this repo for the full walkthrough.