Merge pull request #290 from outis1one/claude/ionos-script-integration-x32ofw

Claude/ionos script integration x32ofw
This commit is contained in:
Outis
2026-08-10 22:46:10 -04:00
committed by GitHub
4 changed files with 131 additions and 9 deletions
+10 -1
View File
@@ -425,7 +425,16 @@ AUTHELIA_USERS
local CADDY_FILE="$DOCKER_DIR/caddy/Caddyfile" local CADDY_FILE="$DOCKER_DIR/caddy/Caddyfile"
if [ -f "$CADDY_FILE" ]; then if [ -f "$CADDY_FILE" ]; then
echo " Configuring Caddy for Authelia..." echo " Configuring Caddy for Authelia..."
if ! grep -q "(authelia)" "$CADDY_FILE"; then # Anchored to an actual, uncommented snippet definition — a bare
# `grep -q "(authelia)"` also matches the commented-out example
# block caddy.sh's starter Caddyfile ships ("# (authelia) {" as
# documentation). Confirmed live: that false match made this skip
# writing the real snippet entirely, leaving any later `import
# authelia` reference elsewhere in the file dangling — Caddy then
# refuses to start at all ("File to import not found: authelia"),
# taking down every site it fronts, not just the Authelia-protected
# one.
if ! grep -qE '^\(authelia\)[[:space:]]*\{' "$CADDY_FILE"; then
cp "$CADDY_FILE" "$CADDY_FILE.backup.$(date +%Y%m%d-%H%M%S)" cp "$CADDY_FILE" "$CADDY_FILE.backup.$(date +%Y%m%d-%H%M%S)"
{ cat << 'SNIPPET_EOF' { cat << 'SNIPPET_EOF'
# ── Authelia forward auth snippet ───────────────────────────────────────────── # ── Authelia forward auth snippet ─────────────────────────────────────────────
+19
View File
@@ -232,6 +232,7 @@ install_caddy() {
echo "[DRY-RUN] Would write $CADDY_DIR/docker-compose.yml (ports 80/443 + HTTP/3)" echo "[DRY-RUN] Would write $CADDY_DIR/docker-compose.yml (ports 80/443 + HTTP/3)"
echo "[DRY-RUN] Would write a starter $CADDY_DIR/Caddyfile (if none exists)" echo "[DRY-RUN] Would write a starter $CADDY_DIR/Caddyfile (if none exists)"
echo "[DRY-RUN] Would write $CADDY_DIR/README.md" echo "[DRY-RUN] Would write $CADDY_DIR/README.md"
echo "[DRY-RUN] Would open 80/tcp, 443/tcp, 443/udp in UFW (Docker's own iptables rules let this traffic through either way, but ufw status should actually reflect it)"
echo "[DRY-RUN] Would optionally start Caddy (docker compose up -d)" echo "[DRY-RUN] Would optionally start Caddy (docker compose up -d)"
return 0 return 0
fi fi
@@ -384,6 +385,24 @@ CADDYFILE
chown -R "$ACTUAL_USER:$ACTUAL_USER" "$CADDY_DIR" chown -R "$ACTUAL_USER:$ACTUAL_USER" "$CADDY_DIR"
echo " ✓ Caddy configured at $CADDY_DIR" echo " ✓ Caddy configured at $CADDY_DIR"
# Every other service in this repo opens its own UFW rule; this file
# never did — Docker manipulates iptables directly for published
# container ports (the ports: mapping above), which bypasses UFW's own
# filtering entirely for 80/tcp, 443/tcp, and 443/udp regardless of
# what `ufw status` shows. Confirmed live: sites were reachable over
# HTTPS with zero matching UFW rule for either port. That's not a
# meaningful protection gap on its own — 80/443 are supposed to be open
# to everyone, that's the whole point of a reverse proxy — but it means
# `ufw status` actively misrepresents this box's real exposure on its
# two most externally-facing ports, which is confusing and worth fixing
# even though nothing was actually unprotected as a result.
if command -v ufw &>/dev/null; then
ufw allow 80/tcp comment "Caddy HTTP" >/dev/null 2>&1
ufw allow 443/tcp comment "Caddy HTTPS" >/dev/null 2>&1
ufw allow 443/udp comment "Caddy HTTP/3" >/dev/null 2>&1
declare -F ensure_ufw_enabled >/dev/null 2>&1 && ensure_ufw_enabled
fi
write_readme "$CADDY_DIR" << 'CADDY_README' write_readme "$CADDY_DIR" << 'CADDY_README'
# Caddy — reverse proxy + automatic HTTPS # Caddy — reverse proxy + automatic HTTPS
+42 -8
View File
@@ -231,7 +231,8 @@ install_traccar() {
echo " - Point Traccar at it via env vars (CONFIG_USE_ENVIRONMENT_VARIABLES) — no secrets in a config file" echo " - Point Traccar at it via env vars (CONFIG_USE_ENVIRONMENT_VARIABLES) — no secrets in a config file"
echo " - Deploy an autoheal container that restarts Traccar if its healthcheck fails" echo " - Deploy an autoheal container that restarts Traccar if its healthcheck fails"
echo " - Expose port 8082 (web) and 5000-5150 (device protocols; 5038/5060/5061 skipped — Asterisk keeps" echo " - Expose port 8082 (web) and 5000-5150 (device protocols; 5038/5060/5061 skipped — Asterisk keeps"
echo " priority on those); an additional instance's device-protocol range shifts by 1000 instead" echo " priority on those); shifts by 1000 instead, whether for an additional instance or because"
echo " something else already has a port in that range"
echo " - No default login — register the first account at the web UI, it becomes admin" echo " - No default login — register the first account at the web UI, it becomes admin"
echo " - Offer optional ntfy push notifications (self-hosted anywhere, or ntfy.sh)" echo " - Offer optional ntfy push notifications (self-hosted anywhere, or ntfy.sh)"
echo " - Offer a Caddy reverse proxy and to start the container" echo " - Offer a Caddy reverse proxy and to start the container"
@@ -310,6 +311,36 @@ install_traccar() {
# directory-count-based mechanism (not an ss scan) and is unaffected. # directory-count-based mechanism (not an ss scan) and is unaffected.
find_free_port WEB_PORT "$WEB_PORT" find_free_port WEB_PORT "$WEB_PORT"
# Live-check the whole device-protocol range too — the directory-count
# offset above only avoids colliding with an *earlier Traccar instance*,
# not an unrelated single-port service. Confirmed live: this range sat
# unclaimed at the OS level while this Traccar instance's own container
# had never actually started (still `docker compose up`'d for the first
# time), so a completely unrelated service's own find_free_port scan
# found 5007 "free" (nothing was listening there yet) and took it —
# invisible to any check until Traccar itself actually tried to bind
# its declared range. Shift by 1000 (reusing the same offset step the
# multi-instance path uses) until the whole range is genuinely clear.
_traccar_range_conflict() {
local min="$1" max="$2" p
for ((p = min; p <= max; p++)); do
# The base 5000-5150 range's own carve-outs for Asterisk are
# expected occupants, not a conflict — only relevant at the
# unshifted range; a shifted range never overlaps them anyway.
if [ "$min" -eq 5000 ] && { [ "$p" -eq 5038 ] || [ "$p" -eq 5060 ] || [ "$p" -eq 5061 ]; }; then
continue
fi
port_in_use "$p" tcp && return 0
port_in_use "$p" udp && return 0
done
return 1
}
while _traccar_range_conflict "$PROTO_MIN" "$PROTO_MAX"; do
log_warning "Device-protocol range $PROTO_MIN-$PROTO_MAX collides with something already running — shifting to $((PROTO_MIN + 1000))-$((PROTO_MAX + 1000))."
PROTO_MIN=$((PROTO_MIN + 1000))
PROTO_MAX=$((PROTO_MAX + 1000))
done
mkdir -p "$TRACCAR_DIR" mkdir -p "$TRACCAR_DIR"
# Non-recursive on purpose — a rerun already has a `db/` full of Postgres's # Non-recursive on purpose — a rerun already has a `db/` full of Postgres's
# own data files, owned by whatever uid the postgres container runs as # own data files, owned by whatever uid the postgres container runs as
@@ -406,13 +437,16 @@ networks:
" "
fi fi
# First instance keeps the exact existing Asterisk-exclusion port block # Keyed on whether the range is still the unshifted default (5000), not
# (5000-5150 with 5038/5060/5061 carved out). An additional instance's # on INSTANCE_SUFFIX — a first instance can also end up shifted now, if
# range is shifted by 1000 per instance (computed above), which never # the live-collision check above moved it off 5000 (see that check's
# lands on Asterisk's fixed ports, so it just publishes the plain range # comment). Only the unshifted base range needs Asterisk's ports carved
# with no exclusions needed. # out; any shifted range (whether from a genuine additional instance or
# a first instance that got bumped for a live collision) never lands on
# Asterisk's fixed ports, so it just publishes the plain range with no
# exclusions needed.
local _PROTO_PORT_BLOCK local _PROTO_PORT_BLOCK
if [ -z "$INSTANCE_SUFFIX" ]; then if [ "$PROTO_MIN" -eq 5000 ]; then
_PROTO_PORT_BLOCK=" # 5038 (AMI), 5060 (SIP, tcp+udp), and 5061 (SIP TLS, tcp) are skipped: _PROTO_PORT_BLOCK=" # 5038 (AMI), 5060 (SIP, tcp+udp), and 5061 (SIP TLS, tcp) are skipped:
# they're Asterisk's ports (services/asterisk.sh runs Asterisk with # they're Asterisk's ports (services/asterisk.sh runs Asterisk with
# network_mode: host, so it binds them directly on the host, not # network_mode: host, so it binds them directly on the host, not
@@ -584,7 +618,7 @@ Traccar instance.")
stays open to anyone who reaches this server until you turn it off, so do stays open to anyone who reaches this server until you turn it off, so do
this right away, then go to Settings → Server → Permissions and uncheck this right away, then go to Settings → Server → Permissions and uncheck
Registration. Registration.
- Device protocols: ports ${PROTO_MIN}-${PROTO_MAX} (TCP + UDP$( [ -z "$INSTANCE_SUFFIX" ] && echo "; 5038/tcp, 5060/tcp+udp, and 5061/tcp are skipped — reserved for Asterisk's AMI and SIP if this box also runs Asterisk from this repo, which gets priority on those ports")) - Device protocols: ports ${PROTO_MIN}-${PROTO_MAX} (TCP + UDP$( [ "$PROTO_MIN" -eq 5000 ] && echo "; 5038/tcp, 5060/tcp+udp, and 5061/tcp are skipped — reserved for Asterisk's AMI and SIP if this box also runs Asterisk from this repo, which gets priority on those ports"))
- App data: \`data/\` and \`logs/\` - App data: \`data/\` and \`logs/\`
- Database: PostgreSQL (\`$DB_CONTAINER\` container, data in \`db/\`) - Database: PostgreSQL (\`$DB_CONTAINER\` container, data in \`db/\`)
- All database settings (name, user, password) live in \`.env\` — Traccar - All database settings (name, user, password) live in \`.env\` — Traccar
+60
View File
@@ -372,6 +372,61 @@ _vdm_remove_mount() {
log_success "Removed the existing mount for '$label' (fstab backup: $(basename "$bk"))" log_success "Removed the existing mount for '$label' (fstab backup: $(basename "$bk"))"
} }
# ── Every configured mount, for the removal menu below ─────────────────────
# Prints "label|host:share|mount_point" one per line. Unlike
# _vdm_find_existing_mount, not scoped to a particular host+share.
_vdm_list_all_mounts() {
grep -E "^${_VDM_TAG_PREFIX} " /etc/fstab 2>/dev/null \
| sed -E "s/^${_VDM_TAG_PREFIX} ([^ ]+) — ([^ ]+) -> (.*)\$/\1|\2|\3/"
}
# ── Pick one or more configured mounts by number and remove them ──────────
_vdm_remove_mount_interactive() {
local entries=()
while IFS= read -r line; do
[ -z "$line" ] && continue
entries+=("$line")
done < <(_vdm_list_all_mounts)
if [ "${#entries[@]}" -eq 0 ]; then
log_info "No vpn-data-mount mounts configured — nothing to remove."
return 0
fi
while true; do
echo ""
echo " Configured mounts:"
local i label hostshare point
for i in "${!entries[@]}"; do
IFS='|' read -r label hostshare point <<< "${entries[$i]}"
printf " %d) %-15s %-28s -> %s\n" "$((i + 1))" "$label" "$hostshare" "$point"
done
echo ""
local CHOICE=""
prompt_text " Remove which one? (number, or blank to stop):" "" CHOICE
[ -z "$CHOICE" ] && break
if ! [[ "$CHOICE" =~ ^[0-9]+$ ]] || [ "$CHOICE" -lt 1 ] || [ "$CHOICE" -gt "${#entries[@]}" ]; then
log_warning "'$CHOICE' isn't one of the listed mounts."
continue
fi
IFS='|' read -r label hostshare point <<< "${entries[$((CHOICE - 1))]}"
local CONFIRM=""
prompt_yn " Remove '$label' ($point)? This unmounts it, removes its credentials, and removes it from /etc/fstab (backed up first). (y/n):" "n" CONFIRM
if [[ "$CONFIRM" =~ ^[Yy]$ ]]; then
_vdm_remove_mount "$label" "$point"
unset "entries[$((CHOICE - 1))]"
entries=("${entries[@]}")
fi
[ "${#entries[@]}" -eq 0 ] && break
local AGAIN=""
prompt_yn " Remove another? (y/n):" "n" AGAIN
[[ "$AGAIN" =~ ^[Yy]$ ]] || break
done
}
# ── Prompt for a password twice, hidden, matching ────────────────────────── # ── Prompt for a password twice, hidden, matching ──────────────────────────
# Prints the password on success. No log_* calls — same reason as above; # Prints the password on success. No log_* calls — same reason as above;
# uses plain stderr output instead so it's visible without corrupting a # uses plain stderr output instead so it's visible without corrupting a
@@ -771,6 +826,7 @@ install_vpn-data-mount() {
echo "[DRY-RUN] Would let you pick one or more by number and mount them locally over CIFS" echo "[DRY-RUN] Would let you pick one or more by number and mount them locally over CIFS"
echo "[DRY-RUN] Would add each to /etc/fstab with a root-only credentials file (not guest)" echo "[DRY-RUN] Would add each to /etc/fstab with a root-only credentials file (not guest)"
echo "[DRY-RUN] Would offer a gocryptfs decrypt layer per share (opt-in, requires the home box already set up via tools/gocryptfs-setup-home.sh)" echo "[DRY-RUN] Would offer a gocryptfs decrypt layer per share (opt-in, requires the home box already set up via tools/gocryptfs-setup-home.sh)"
echo "[DRY-RUN] Would offer to remove any existing mount (unmount, drop its credentials + fstab entry)"
echo "[DRY-RUN] Repeatable — can be run again for additional home boxes" echo "[DRY-RUN] Repeatable — can be run again for additional home boxes"
return 0 return 0
fi fi
@@ -786,6 +842,10 @@ install_vpn-data-mount() {
_vdm_list_existing _vdm_list_existing
local REMOVE=""
prompt_yn "Remove any existing VPN data mounts? (y/n):" "n" REMOVE
[[ "$REMOVE" =~ ^[Yy]$ ]] && _vdm_remove_mount_interactive
while true; do while true; do
local ADD="" local ADD=""
prompt_yn "Connect to a home box and mount some of its shares now? (y/n):" "y" ADD prompt_yn "Connect to a home box and mount some of its shares now? (y/n):" "y" ADD