Merge pull request #188 from outis1one/claude/asterisk-digital-ocean-w22kk8

Claude/asterisk digital ocean w22kk8
This commit is contained in:
Outis
2026-07-20 14:31:39 -04:00
committed by GitHub
5 changed files with 147 additions and 22 deletions
+46
View File
@@ -159,6 +159,52 @@ reloads Caddy. No-ops silently if Caddy isn't installed. The fourth argument
is an optional string inserted verbatim inside the Caddy site block (use it is an optional string inserted verbatim inside the Caddy site block (use it
for `import authelia` or custom matchers). for `import authelia` or custom matchers).
Sets two out-params (not `local` — read them after the call returns) so the
caller can tell whether Caddy actually ended up fronting the service:
```bash
CADDY_SERVICE_CONFIGURED # true/false
CADDY_SERVICE_MODE # "local" or "remote" (only meaningful if configured)
```
Use this to skip opening a host firewall port for a service Caddy already
fronts *locally* (it reaches the service over `host.docker.internal`, not
the network) — but still open it when `CADDY_SERVICE_MODE` is `"remote"`,
since a remote Caddy machine needs to reach this host over the network
instead. See `services/asterisk.sh` and `services/asterisk-digital-ocean.sh`
for the reference pattern: call `configure_caddy_for_service` *before*
building firewall rules, not after, so the decision is known in time.
### UFW enable
```bash
ensure_ufw_enabled
```
Call this **after** your service has already added its own `ufw allow`
rules — it only flips UFW from inactive to active, it doesn't add rules for
you. No-ops if UFW is already active or not installed. Always allows SSH
first (reading the real port from `sshd_config` in case it's non-default)
before enabling, so this can't lock out the session running the installer.
### Closing a port to the internet without also closing it to Caddy
```bash
ufw_allow_from_caddy_net PORT [PROTO] # PROTO defaults to tcp
```
When `CADDY_SERVICE_MODE` is `"local"` (see above) and you `ufw delete
allow` a port because Caddy fronts it now, don't stop there — UFW rules
apply to *all* interfaces unless scoped, and Caddy's own request to
`host.docker.internal:PORT` is ordinary INPUT-chain traffic arriving over
the `caddy_net` bridge, not the public internet. A bare `ufw delete allow`
blocks that too and silently breaks the service (confirmed live: closing
the web admin port outright took Caddy down with it). Call
`ufw_allow_from_caddy_net` right after the `delete` to re-open the port
scoped to just `caddy_net`'s subnet — reachable from Caddy, not from the
internet. See `services/asterisk-digital-ocean.sh` and
`services/asterisk.sh` for the pattern.
### README generation ### README generation
```bash ```bash
+67 -1
View File
@@ -244,6 +244,55 @@ ensure_caddy_network() {
&& log_info "Created Docker network ${_net} (needed by Caddy-fronted services)" && log_info "Created Docker network ${_net} (needed by Caddy-fronted services)"
} }
# Enables UFW if it isn't already active. Call this AFTER the caller has
# already added its own `ufw allow` rules for whatever it needs — this only
# flips UFW from inactive to active, it doesn't add rules for the calling
# service itself.
#
# Always allows SSH first, using the sshd_config port if it's non-default —
# getting this wrong and then enabling UFW would lock out the very SSH
# session most people are running this script from. If UFW is already
# active, this is a no-op (assumed already handled correctly).
ensure_ufw_enabled() {
command -v ufw &>/dev/null || return 0
[ "$DRY_RUN" = true ] && return 0
ufw status 2>/dev/null | grep -q "Status: active" && return 0
local _ssh_port
_ssh_port="$(grep -iE '^[[:space:]]*Port[[:space:]]+[0-9]+' /etc/ssh/sshd_config 2>/dev/null \
| tail -1 | awk '{print $2}')"
_ssh_port="${_ssh_port:-22}"
ufw allow "${_ssh_port}/tcp" comment 'SSH' >/dev/null 2>&1
ufw --force enable >/dev/null 2>&1
log_success "UFW enabled (SSH on port ${_ssh_port} allowed first, so this won't lock you out)."
}
# Scopes a UFW allow rule to just the caddy_net bridge subnet instead of
# every interface. Needed for any port that only needs to be reachable from
# a *locally* Caddy-fronted service (via host.docker.internal) — a plain
# `ufw delete allow <port>` closes it everywhere, but Caddy's own request to
# host.docker.internal is still ordinary INPUT-chain traffic as far as UFW
# is concerned, arriving over the caddy_net bridge, not the internet. UFW
# rules apply to all interfaces unless scoped like this, so closing the
# port outright also silently breaks Caddy.
ufw_allow_from_caddy_net() {
local _port="$1" _proto="${2:-tcp}"
command -v ufw &>/dev/null || return 0
[ "$DRY_RUN" = true ] && return 0
local _subnet
_subnet="$(docker network inspect "${SITE_CADDY_NET:-caddy_net}" \
--format '{{range .IPAM.Config}}{{.Subnet}}{{end}}' 2>/dev/null)"
if [ -n "$_subnet" ]; then
ufw allow from "$_subnet" to any port "$_port" proto "$_proto" comment 'Caddy internal only' >/dev/null 2>&1
log_info "Port ${_port}/${_proto} reachable only from Caddy's internal network (${_subnet}), not the public internet."
else
log_warning "Could not determine ${SITE_CADDY_NET:-caddy_net}'s subnet — port ${_port}/${_proto} stays closed."
log_warning "If Caddy can't reach it: ufw allow from <caddy_net-subnet> to any port ${_port} proto ${_proto}"
fi
}
# ── SSH client config (~/.ssh/config) Host aliases ──────────────────────────── # ── SSH client config (~/.ssh/config) Host aliases ────────────────────────────
# Lets "ssh <alias>" connect directly to user@host without typing it out each # Lets "ssh <alias>" connect directly to user@host without typing it out each
# time — handy for VPN/NetBird peers with unmemorable IPs. Operates on the # time — handy for VPN/NetBird peers with unmemorable IPs. Operates on the
@@ -435,6 +484,16 @@ write_readme() {
configure_caddy_for_service() { configure_caddy_for_service() {
local SERVICE_NAME="$1" SERVICE_UPSTREAM="$2" DEFAULT_SUBDOMAIN="$3" EXTRA_CONFIG="${4:-}" local SERVICE_NAME="$1" SERVICE_UPSTREAM="$2" DEFAULT_SUBDOMAIN="$3" EXTRA_CONFIG="${4:-}"
# Out-params (not `local` — callers read these after the call returns) so
# a caller can tell whether Caddy actually ended up fronting the service
# and, if so, whether that's a local container (reachable only over the
# host's internal network) or a remote machine (needs to reach this host
# over the network — usually its public IP). Services that also open a
# host firewall for the same port use this to skip that when Caddy is
# already the only intended way in, instead of leaving both routes open.
CADDY_SERVICE_CONFIGURED=false
CADDY_SERVICE_MODE=""
# Derive the proxy upstream and a port number for display messages. # Derive the proxy upstream and a port number for display messages.
# Plain number → host.docker.internal:PORT (host-network or legacy # Plain number → host.docker.internal:PORT (host-network or legacy
# services — Caddy itself runs in its own container on # services — Caddy itself runs in its own container on
@@ -552,11 +611,16 @@ CADDY_BLOCK
local OVERWRITE="" local OVERWRITE=""
prompt_yn "Overwrite existing configuration? (y/n):" "n" OVERWRITE prompt_yn "Overwrite existing configuration? (y/n):" "n" OVERWRITE
if [ "$OVERWRITE" != "y" ] && [ "$OVERWRITE" != "Y" ]; then if [ "$OVERWRITE" != "y" ] && [ "$OVERWRITE" != "Y" ]; then
echo " Keeping existing configuration."; return 0 echo " Keeping existing configuration."
CADDY_SERVICE_CONFIGURED=true
CADDY_SERVICE_MODE="local"
return 0
fi fi
sed -i "/^${SERVICE_DOMAIN}/,/^}/d" "$CADDYFILE" sed -i "/^${SERVICE_DOMAIN}/,/^}/d" "$CADDYFILE"
fi fi
CADDY_SERVICE_CONFIGURED=true
CADDY_SERVICE_MODE="local"
echo " Adding $SERVICE_NAME configuration to Caddyfile..." echo " Adding $SERVICE_NAME configuration to Caddyfile..."
printf '%s\n' "$_SITE_BLOCK" >> "$CADDYFILE" printf '%s\n' "$_SITE_BLOCK" >> "$CADDYFILE"
@@ -582,6 +646,8 @@ CADDY_BLOCK
# ── Remote Caddy: write snippet file ───────────────────────────────────── # ── Remote Caddy: write snippet file ─────────────────────────────────────
else else
CADDY_SERVICE_CONFIGURED=true
CADDY_SERVICE_MODE="remote"
local SNIPPET_DIR="$DOCKER_DIR/caddy-snippets" local SNIPPET_DIR="$DOCKER_DIR/caddy-snippets"
local SNIPPET_FILE="$SNIPPET_DIR/${DEFAULT_SUBDOMAIN}.caddy" local SNIPPET_FILE="$SNIPPET_DIR/${DEFAULT_SUBDOMAIN}.caddy"
mkdir -p "$SNIPPET_DIR" mkdir -p "$SNIPPET_DIR"
+2 -1
View File
@@ -699,7 +699,7 @@ CADDY_BLOCK
ufw allow "${WEB_ADMIN_PORT_VAL}/tcp" ufw allow "${WEB_ADMIN_PORT_VAL}/tcp"
else else
ufw delete allow "${WEB_ADMIN_PORT_VAL}/tcp" 2>/dev/null || true ufw delete allow "${WEB_ADMIN_PORT_VAL}/tcp" 2>/dev/null || true
log_info "Web admin port ${WEB_ADMIN_PORT_VAL} kept closed to the internet — Caddy fronts it locally." ufw_allow_from_caddy_net "${WEB_ADMIN_PORT_VAL}"
fi fi
ufw allow 8088/tcp ufw allow 8088/tcp
ufw allow 8089/tcp ufw allow 8089/tcp
@@ -707,6 +707,7 @@ CADDY_BLOCK
ufw allow 3478/tcp ufw allow 3478/tcp
ufw allow 10000:20000/udp ufw allow 10000:20000/udp
ufw allow 49152:49252/udp ufw allow 49152:49252/udp
ensure_ufw_enabled
log_success "UFW rules added." log_success "UFW rules added."
fi fi
+26 -16
View File
@@ -503,23 +503,11 @@ WEB_ADMIN_AUTH_DISABLED=false
ENV ENV
chmod 600 .env chmod 600 .env
# ── UFW firewall rules ────────────────────────────────────────────────────
if command -v ufw &>/dev/null; then
log_info "Opening UFW ports for Asterisk + coturn..."
ufw allow 5060/udp
ufw allow 5060/tcp
ufw allow 5061/tcp
ufw allow "${WEB_ADMIN_PORT_VAL}/tcp"
ufw allow 8088/tcp
ufw allow 8089/tcp
ufw allow 3478/udp
ufw allow 3478/tcp
ufw allow 10000:20000/udp
ufw allow 49152:49252/udp
log_success "UFW rules added."
fi
# ── Caddy reverse proxy for web admin ───────────────────────────────────── # ── Caddy reverse proxy for web admin ─────────────────────────────────────
# Decided before the firewall rules below so they can be scoped
# correctly: if a local Caddy ends up fronting the web admin, there's no
# reason to also expose it on the LAN — Caddy already reaches it over
# the host's internal network (host.docker.internal).
local EXTRA_BLOCK="" local EXTRA_BLOCK=""
if [ -d "$DOCKER_DIR/authelia" ]; then if [ -d "$DOCKER_DIR/authelia" ]; then
local _use_auth="" local _use_auth=""
@@ -532,6 +520,28 @@ ENV
fi fi
configure_caddy_for_service "Asterisk Web Admin" "${WEB_ADMIN_PORT_VAL}" "asterisk" "$EXTRA_BLOCK" configure_caddy_for_service "Asterisk Web Admin" "${WEB_ADMIN_PORT_VAL}" "asterisk" "$EXTRA_BLOCK"
# ── UFW firewall rules ────────────────────────────────────────────────────
if command -v ufw &>/dev/null; then
log_info "Opening UFW ports for Asterisk + coturn..."
ufw allow 5060/udp
ufw allow 5060/tcp
ufw allow 5061/tcp
if [[ "$CADDY_SERVICE_CONFIGURED" == true && "$CADDY_SERVICE_MODE" == "local" ]]; then
ufw delete allow "${WEB_ADMIN_PORT_VAL}/tcp" 2>/dev/null || true
ufw_allow_from_caddy_net "${WEB_ADMIN_PORT_VAL}"
else
ufw allow "${WEB_ADMIN_PORT_VAL}/tcp"
fi
ufw allow 8088/tcp
ufw allow 8089/tcp
ufw allow 3478/udp
ufw allow 3478/tcp
ufw allow 10000:20000/udp
ufw allow 49152:49252/udp
ensure_ufw_enabled
log_success "UFW rules added."
fi
# ── README ──────────────────────────────────────────────────────────────── # ── README ────────────────────────────────────────────────────────────────
write_readme "$EA_DIR" << 'MD' write_readme "$EA_DIR" << 'MD'
# Easy Asterisk PBX + coturn # Easy Asterisk PBX + coturn
+6 -4
View File
@@ -5076,8 +5076,8 @@ HTML_TEMPLATE = '''<!DOCTYPE html>
<div class="form-group"> <div class="form-group">
<label>Connection Type</label> <label>Connection Type</label>
<select name="conn_type" class="form-control"> <select name="conn_type" class="form-control">
<option value="fqdn" selected>FQDN/Internet (TLS)</option>
<option value="lan">LAN/VPN (UDP)</option> <option value="lan">LAN/VPN (UDP)</option>
<option value="fqdn">FQDN/Internet (TLS)</option>
</select> </select>
</div> </div>
<div class="form-group"> <div class="form-group">
@@ -5314,9 +5314,11 @@ HTML_TEMPLATE = '''<!DOCTYPE html>
devicesCache = devices; devicesCache = devices;
renderDevices(); renderDevices();
// Update category select in add device form // Update category select in add device form. Most devices
// added day-to-day are phones, not fixed kiosks, so default
// to the "mobile" category instead of whichever sorts first.
document.getElementById('category-select').innerHTML = categoriesCache.map(c => document.getElementById('category-select').innerHTML = categoriesCache.map(c =>
`<option value="${c.id}">${c.name}</option>` `<option value="${c.id}" ${c.id === 'mobile' ? 'selected' : ''}>${c.name}</option>`
).join(''); ).join('');
} catch (e) { } catch (e) {
showAlert('Failed to load devices', 'error'); showAlert('Failed to load devices', 'error');
@@ -5455,7 +5457,7 @@ HTML_TEMPLATE = '''<!DOCTYPE html>
}).join(''); }).join('');
document.getElementById('category-select').innerHTML = categories.map(c => document.getElementById('category-select').innerHTML = categories.map(c =>
`<option value="${c.id}">${c.name}</option>` `<option value="${c.id}" ${c.id === 'mobile' ? 'selected' : ''}>${c.name}</option>`
).join(''); ).join('');
} catch (e) { } catch (e) {
showAlert('Failed to load categories', 'error'); showAlert('Failed to load categories', 'error');