Back up existing files before every service overwrites them

Confirmed live: install_frigate()'s fresh-install path overwrote a
working, hand-crafted docker-compose.yml (Frigate + mosquitto +
frigate-notify) with zero backup, because that file's shape didn't match
what frigate.sh's own "existing install" detection knew how to recognize.
Every service's own detection is a judgment call about what counts as
"already installed" and can miss a real setup built outside this repo's
conventions.

lib/common.sh gains backup_if_exists(FILE) — copies FILE to
FILE.bak.<timestamp> if it exists, no-ops otherwise (including DRY_RUN).
Applied before every service's own `cat > docker-compose.yml`/`cat > .env`
write across all 60 services that do one (115 call sites), plus a matching
standalone-mode stub added to every service's own bootstrap block, same
convention already used for port_in_use/find_free_port. This doesn't
replace a service's own update/fresh-reinstall detection — it's the safety
net underneath it, so a wrong detection costs a .bak file to restore from
instead of the original silently disappearing.

Also fixes the actual gap that surfaced this: services/frigate.sh's
Authelia offer only checked for Authelia installed locally on Frigate's
own box, which is never true for a dedicated NVR box with no local Caddy
either (the common shape — Caddy lives elsewhere, snippet-generation mode
already handles that). Now offers Authelia protection unconditionally and,
when Authelia isn't local, asks whether it lives on the same machine as
Caddy (still "import authelia", since that's local to wherever Caddy ends
up) or on a genuinely separate third machine (the explicit
header-pinned forward_auth form, per CLAUDE.md's "forward_auth to a remote
Authelia" note, needed because a bare authelia:9091 shortcut only works
one hop).
This commit is contained in:
Claude
2026-08-26 17:26:17 +00:00
parent 493ee30916
commit bec9228c55
61 changed files with 486 additions and 5 deletions
+26
View File
@@ -802,6 +802,32 @@ write_readme() {
chown "$ACTUAL_USER:$ACTUAL_USER" "$dir/README.md" 2>/dev/null || true chown "$ACTUAL_USER:$ACTUAL_USER" "$dir/README.md" 2>/dev/null || true
} }
# Copies FILE to FILE.bak.<timestamp> if it already exists, right before a
# caller is about to overwrite it with a fresh `cat > FILE` heredoc. No-ops
# in DRY_RUN and silently no-ops if FILE doesn't exist yet (first install,
# nothing to save) — safe to call unconditionally right before every such
# write, fresh install or not.
#
# Confirmed live: install_frigate()'s fresh-install path overwrote a
# working, hand-crafted multi-container docker-compose.yml (Frigate +
# mosquitto + frigate-notify) with zero backup, because that file's shape
# didn't match what the service's own "existing install" detection knew
# how to recognize. Every service's own detection logic is a judgment call
# about what counts as "already installed" and can miss a real setup built
# outside this repo's own conventions — this exists as the safety net
# underneath that judgment call, not a replacement for it: call it right
# before any `cat > FILE` that could clobber something a user already has,
# so a wrong detection costs a `.bak` file to restore from instead of the
# original silently disappearing.
backup_if_exists() {
local file="$1"
[ "$DRY_RUN" = true ] && return 0
[ -f "$file" ] || return 0
local backup="${file}.bak.$(date +%Y%m%d-%H%M%S)"
cp -p "$file" "$backup" 2>/dev/null \
&& log_info "Backed up existing $(basename "$file") to $(basename "$backup")"
}
# ── Host port collision avoidance (shared by every service that publishes a # ── Host port collision avoidance (shared by every service that publishes a
# fixed host port) ──────────────────────────────────────────────────────────── # fixed host port) ────────────────────────────────────────────────────────────
# With 70+ services in this repo, several ship the same default port (e.g. # With 70+ services in this repo, several ship the same default port (e.g.
+7
View File
@@ -180,6 +180,11 @@ CBLOCK
mkdir -p "$_dir" mkdir -p "$_dir"
cat > "$_dir/README.md" cat > "$_dir/README.md"
} }
backup_if_exists() {
local _file="$1"
[ -f "$_file" ] || return 0
cp -p "$_file" "${_file}.bak.$(date +%Y%m%d-%H%M%S)" 2>/dev/null
}
fi fi
ACTUAL_USER="${ACTUAL_USER:-${SUDO_USER:-$USER}}" ACTUAL_USER="${ACTUAL_USER:-${SUDO_USER:-$USER}}"
@@ -388,6 +393,7 @@ networks:
" "
fi fi
backup_if_exists docker-compose.yml
cat > docker-compose.yml << AB_COMPOSE cat > docker-compose.yml << AB_COMPOSE
name: $CONTAINER name: $CONTAINER
@@ -405,6 +411,7 @@ services:
${_CADDY_NET_BLOCK}${_CADDY_NET_SECTION} ${_CADDY_NET_BLOCK}${_CADDY_NET_SECTION}
AB_COMPOSE AB_COMPOSE
backup_if_exists .env
cat > .env << AB_ENV cat > .env << AB_ENV
TZ=$TZ_VAL TZ=$TZ_VAL
CADDY_NET=$SITE_CADDY_NET CADDY_NET=$SITE_CADDY_NET
+8
View File
@@ -170,6 +170,11 @@ CBLOCK
[[ "${DRY_RUN:-false}" == "true" ]] && return 0 [[ "${DRY_RUN:-false}" == "true" ]] && return 0
cat > "$_dir/README.md" cat > "$_dir/README.md"
} }
backup_if_exists() {
local _file="$1"
[ -f "$_file" ] || return 0
cp -p "$_file" "${_file}.bak.$(date +%Y%m%d-%H%M%S)" 2>/dev/null
}
generate_password() { generate_password() {
local _len="${1:-32}" local _len="${1:-32}"
@@ -368,6 +373,7 @@ install_ai-gpu() {
sed -i "s|America/New_York|$TZ_VAL|g" {} \; sed -i "s|America/New_York|$TZ_VAL|g" {} \;
fi fi
backup_if_exists "$IMAGE_GEN_DIR/.env"
cat > "$IMAGE_GEN_DIR/.env" << IMGENV cat > "$IMAGE_GEN_DIR/.env" << IMGENV
# InvokeAI — image generation # InvokeAI — image generation
TZ=${TZ_VAL} TZ=${TZ_VAL}
@@ -407,6 +413,7 @@ IMGENV
local WEBUI_SECRET local WEBUI_SECRET
WEBUI_SECRET="$(generate_password 32)" WEBUI_SECRET="$(generate_password 32)"
backup_if_exists "$LLM_DIR/.env"
cat > "$LLM_DIR/.env" << LLMENV cat > "$LLM_DIR/.env" << LLMENV
# Ollama + Open WebUI + SearXNG # Ollama + Open WebUI + SearXNG
TZ=${TZ_VAL} TZ=${TZ_VAL}
@@ -441,6 +448,7 @@ LLMENV
fi fi
fi fi
backup_if_exists "$PORTAL_DIR/.env"
cat > "$PORTAL_DIR/.env" << PORTALENV cat > "$PORTAL_DIR/.env" << PORTALENV
# AI Portal — GPU stack swap controller # AI Portal — GPU stack swap controller
TZ=${TZ_VAL} TZ=${TZ_VAL}
+7
View File
@@ -174,6 +174,11 @@ CBLOCK
mkdir -p "$_dir" mkdir -p "$_dir"
cat > "$_dir/README.md" cat > "$_dir/README.md"
} }
backup_if_exists() {
local _file="$1"
[ -f "$_file" ] || return 0
cp -p "$_file" "${_file}.bak.$(date +%Y%m%d-%H%M%S)" 2>/dev/null
}
fi fi
ACTUAL_USER="${ACTUAL_USER:-${SUDO_USER:-$USER}}" ACTUAL_USER="${ACTUAL_USER:-${SUDO_USER:-$USER}}"
@@ -240,6 +245,7 @@ networks:
" "
fi fi
backup_if_exists docker-compose.yml
cat > docker-compose.yml << ABCOMPOSE cat > docker-compose.yml << ABCOMPOSE
name: archivebox name: archivebox
@@ -262,6 +268,7 @@ services:
${_CADDY_NET_BLOCK}${_CADDY_NET_SECTION} ${_CADDY_NET_BLOCK}${_CADDY_NET_SECTION}
ABCOMPOSE ABCOMPOSE
backup_if_exists .env
cat > .env << ABENV cat > .env << ABENV
CADDY_NET=$SITE_CADDY_NET CADDY_NET=$SITE_CADDY_NET
ABENV ABENV
+7
View File
@@ -188,6 +188,11 @@ CBLOCK
mkdir -p "$_dir" mkdir -p "$_dir"
cat > "$_dir/README.md" cat > "$_dir/README.md"
} }
backup_if_exists() {
local _file="$1"
[ -f "$_file" ] || return 0
cp -p "$_file" "${_file}.bak.$(date +%Y%m%d-%H%M%S)" 2>/dev/null
}
fi fi
# Globals — ACTUAL_USER/ACTUAL_HOME must come before DOCKER_DIR # Globals — ACTUAL_USER/ACTUAL_HOME must come before DOCKER_DIR
@@ -278,6 +283,7 @@ networks:
" "
fi fi
backup_if_exists docker-compose.yml
cat > docker-compose.yml << ARM_COMPOSE cat > docker-compose.yml << ARM_COMPOSE
name: arm name: arm
@@ -306,6 +312,7 @@ services:
${_CADDY_NET_BLOCK}${_CADDY_NET_SECTION} ${_CADDY_NET_BLOCK}${_CADDY_NET_SECTION}
ARM_COMPOSE ARM_COMPOSE
backup_if_exists .env
cat > .env << ARM_ENV cat > .env << ARM_ENV
ARM_OUTPUT=$ARM_OUTPUT ARM_OUTPUT=$ARM_OUTPUT
CADDY_NET=$SITE_CADDY_NET CADDY_NET=$SITE_CADDY_NET
+7
View File
@@ -255,6 +255,11 @@ CBLOCK
[[ "${DRY_RUN:-false}" == "true" ]] && return 0 [[ "${DRY_RUN:-false}" == "true" ]] && return 0
cat > "$_dir/README.md" cat > "$_dir/README.md"
} }
backup_if_exists() {
local _file="$1"
[ -f "$_file" ] || return 0
cp -p "$_file" "${_file}.bak.$(date +%Y%m%d-%H%M%S)" 2>/dev/null
}
generate_password() { generate_password() {
local _len="${1:-32}" local _len="${1:-32}"
@@ -1588,6 +1593,7 @@ _asterisk_write_compose() {
" "
[[ "$USE_EMBEDDED_COTURN" != true ]] && _COTURN_DEPENDS="" && _COTURN_SERVICE="" [[ "$USE_EMBEDDED_COTURN" != true ]] && _COTURN_DEPENDS="" && _COTURN_SERVICE=""
backup_if_exists docker-compose.yml
cat > docker-compose.yml << EOF cat > docker-compose.yml << EOF
name: PROJECT_NAME_PLACEHOLDER name: PROJECT_NAME_PLACEHOLDER
@@ -2727,6 +2733,7 @@ install_asterisk() {
# WireGuard/Tailscale) on a subnet this box isn't directly attached to." # WireGuard/Tailscale) on a subnet this box isn't directly attached to."
fi fi
backup_if_exists .env
cat > .env << ENV cat > .env << ENV
# ── Domain ──────────────────────────────────────────────────── # ── Domain ────────────────────────────────────────────────────
# ${_domain_comment} # ${_domain_comment}
+7
View File
@@ -180,6 +180,11 @@ CBLOCK
mkdir -p "$_dir" mkdir -p "$_dir"
cat > "$_dir/README.md" cat > "$_dir/README.md"
} }
backup_if_exists() {
local _file="$1"
[ -f "$_file" ] || return 0
cp -p "$_file" "${_file}.bak.$(date +%Y%m%d-%H%M%S)" 2>/dev/null
}
fi fi
ACTUAL_USER="${ACTUAL_USER:-${SUDO_USER:-$USER}}" ACTUAL_USER="${ACTUAL_USER:-${SUDO_USER:-$USER}}"
@@ -379,6 +384,7 @@ networks:
" "
fi fi
backup_if_exists docker-compose.yml
cat > docker-compose.yml << ABS_COMPOSE cat > docker-compose.yml << ABS_COMPOSE
name: $CONTAINER name: $CONTAINER
@@ -400,6 +406,7 @@ services:
${_CADDY_NET_BLOCK}${_CADDY_NET_SECTION} ${_CADDY_NET_BLOCK}${_CADDY_NET_SECTION}
ABS_COMPOSE ABS_COMPOSE
backup_if_exists .env
cat > .env << ABS_ENV cat > .env << ABS_ENV
AUDIOBOOKS_PATH=$AUDIOBOOKS_PATH AUDIOBOOKS_PATH=$AUDIOBOOKS_PATH
PODCASTS_PATH=./podcasts PODCASTS_PATH=./podcasts
+7
View File
@@ -186,6 +186,11 @@ CBLOCK
mkdir -p "$_dir" mkdir -p "$_dir"
cat > "$_dir/README.md" cat > "$_dir/README.md"
} }
backup_if_exists() {
local _file="$1"
[ -f "$_file" ] || return 0
cp -p "$_file" "${_file}.bak.$(date +%Y%m%d-%H%M%S)" 2>/dev/null
}
fi fi
# Globals — ACTUAL_USER/ACTUAL_HOME must come before DOCKER_DIR # Globals — ACTUAL_USER/ACTUAL_HOME must come before DOCKER_DIR
@@ -408,6 +413,7 @@ install_authelia() {
cd "$AUTHELIA_DIR" || return 1 cd "$AUTHELIA_DIR" || return 1
# ── .env ───────────────────────────────────────────────────────────────── # ── .env ─────────────────────────────────────────────────────────────────
backup_if_exists .env
cat > .env << AUTHELIA_ENV cat > .env << AUTHELIA_ENV
MY_DOMAIN=${AUTHELIA_DOMAIN} MY_DOMAIN=${AUTHELIA_DOMAIN}
SMTP_USER=${AUTHELIA_SMTP_USER} SMTP_USER=${AUTHELIA_SMTP_USER}
@@ -416,6 +422,7 @@ TZ=${AUTHELIA_TZ}
AUTHELIA_ENV AUTHELIA_ENV
# ── docker-compose.yml (quoted heredoc: ${SMTP_USER} resolved by compose/.env) ── # ── docker-compose.yml (quoted heredoc: ${SMTP_USER} resolved by compose/.env) ──
backup_if_exists docker-compose.yml
cat > docker-compose.yml << 'AUTHELIA_COMPOSE' cat > docker-compose.yml << 'AUTHELIA_COMPOSE'
name: authelia name: authelia
+9
View File
@@ -120,6 +120,11 @@ if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
local _companion; _companion="$(dirname "${BASH_SOURCE[0]}")/beszel.md" local _companion; _companion="$(dirname "${BASH_SOURCE[0]}")/beszel.md"
[ -f "$_companion" ] && cat "$_companion" >> "$_dir/README.md" [ -f "$_companion" ] && cat "$_companion" >> "$_dir/README.md"
} }
backup_if_exists() {
local _file="$1"
[ -f "$_file" ] || return 0
cp -p "$_file" "${_file}.bak.$(date +%Y%m%d-%H%M%S)" 2>/dev/null
}
fi fi
ACTUAL_USER="${ACTUAL_USER:-${SUDO_USER:-$USER}}" ACTUAL_USER="${ACTUAL_USER:-${SUDO_USER:-$USER}}"
@@ -458,6 +463,7 @@ networks:
# profile recognizes. security_opt: apparmor:unconfined below is # profile recognizes. security_opt: apparmor:unconfined below is
# Beszel's own documented fix (beszel.dev/guide/systemd#apparmor-error) # Beszel's own documented fix (beszel.dev/guide/systemd#apparmor-error)
# — confirmed live, this exact error on a real box. # — confirmed live, this exact error on a real box.
backup_if_exists docker-compose.yml
cat > docker-compose.yml << BESZEL_COMPOSE cat > docker-compose.yml << BESZEL_COMPOSE
name: beszel name: beszel
@@ -504,6 +510,7 @@ BESZEL_COMPOSE
# README). Not threaded through automatically here because Caddy setup # README). Not threaded through automatically here because Caddy setup
# (below) happens after this file is written, same ordering every # (below) happens after this file is written, same ordering every
# other service in this repo uses for its own Caddy prompt. # other service in this repo uses for its own Caddy prompt.
backup_if_exists .env
cat > .env << BESZEL_ENV cat > .env << BESZEL_ENV
TZ=${SITE_TZ:-$(cat /etc/timezone 2>/dev/null || echo UTC)} TZ=${SITE_TZ:-$(cat /etc/timezone 2>/dev/null || echo UTC)}
CADDY_NET=$SITE_CADDY_NET CADDY_NET=$SITE_CADDY_NET
@@ -678,6 +685,7 @@ install_beszel-agent() {
# and there's no caddy_net to conditionally join since this box never # and there's no caddy_net to conditionally join since this box never
# runs a web UI of its own. See that function's own comment for why the # runs a web UI of its own. See that function's own comment for why the
# systemd/dbus/sensor mounts below matter (Services/Temp columns). # systemd/dbus/sensor mounts below matter (Services/Temp columns).
backup_if_exists docker-compose.yml
cat > docker-compose.yml << AGENT_COMPOSE cat > docker-compose.yml << AGENT_COMPOSE
name: beszel-agent name: beszel-agent
@@ -703,6 +711,7 @@ services:
- /sys/class/thermal:/sys/class/thermal:ro - /sys/class/thermal:/sys/class/thermal:ro
AGENT_COMPOSE AGENT_COMPOSE
backup_if_exists .env
cat > .env << AGENT_ENV cat > .env << AGENT_ENV
TZ=${SITE_TZ:-$(cat /etc/timezone 2>/dev/null || echo UTC)} TZ=${SITE_TZ:-$(cat /etc/timezone 2>/dev/null || echo UTC)}
HUB_URL=$HUB_URL HUB_URL=$HUB_URL
+6
View File
@@ -192,6 +192,11 @@ CBLOCK
mkdir -p "$_dir" mkdir -p "$_dir"
cat > "$_dir/README.md" cat > "$_dir/README.md"
} }
backup_if_exists() {
local _file="$1"
[ -f "$_file" ] || return 0
cp -p "$_file" "${_file}.bak.$(date +%Y%m%d-%H%M%S)" 2>/dev/null
}
fi fi
# Globals — ACTUAL_USER/ACTUAL_HOME must come before DOCKER_DIR # Globals — ACTUAL_USER/ACTUAL_HOME must come before DOCKER_DIR
@@ -279,6 +284,7 @@ install_caddy() {
cd "$CADDY_DIR" || return 1 cd "$CADDY_DIR" || return 1
backup_if_exists docker-compose.yml
cat > docker-compose.yml << 'CADDY_COMPOSE' cat > docker-compose.yml << 'CADDY_COMPOSE'
name: caddy name: caddy
+7
View File
@@ -177,6 +177,11 @@ CBLOCK
mkdir -p "$_dir" mkdir -p "$_dir"
cat > "$_dir/README.md" cat > "$_dir/README.md"
} }
backup_if_exists() {
local _file="$1"
[ -f "$_file" ] || return 0
cp -p "$_file" "${_file}.bak.$(date +%Y%m%d-%H%M%S)" 2>/dev/null
}
fi fi
ACTUAL_USER="${ACTUAL_USER:-${SUDO_USER:-$USER}}" ACTUAL_USER="${ACTUAL_USER:-${SUDO_USER:-$USER}}"
@@ -242,6 +247,7 @@ networks:
" "
fi fi
backup_if_exists docker-compose.yml
cat > docker-compose.yml << CW_COMPOSE cat > docker-compose.yml << CW_COMPOSE
name: calibre-web name: calibre-web
@@ -264,6 +270,7 @@ services:
${_CADDY_NET_BLOCK}${_CADDY_NET_SECTION} ${_CADDY_NET_BLOCK}${_CADDY_NET_SECTION}
CW_COMPOSE CW_COMPOSE
backup_if_exists .env
cat > .env << CW_ENV cat > .env << CW_ENV
CADDY_NET=$SITE_CADDY_NET CADDY_NET=$SITE_CADDY_NET
CW_ENV CW_ENV
+7
View File
@@ -177,6 +177,11 @@ CBLOCK
mkdir -p "$_dir" mkdir -p "$_dir"
cat > "$_dir/README.md" cat > "$_dir/README.md"
} }
backup_if_exists() {
local _file="$1"
[ -f "$_file" ] || return 0
cp -p "$_file" "${_file}.bak.$(date +%Y%m%d-%H%M%S)" 2>/dev/null
}
fi fi
ACTUAL_USER="${ACTUAL_USER:-${SUDO_USER:-$USER}}" ACTUAL_USER="${ACTUAL_USER:-${SUDO_USER:-$USER}}"
@@ -243,6 +248,7 @@ networks:
" "
fi fi
backup_if_exists docker-compose.yml
cat > docker-compose.yml << CD_COMPOSE cat > docker-compose.yml << CD_COMPOSE
name: changedetection name: changedetection
@@ -272,6 +278,7 @@ ${_CADDY_NET_BLOCK} depends_on:
${_CADDY_NET_BLOCK}${_CADDY_NET_SECTION} ${_CADDY_NET_BLOCK}${_CADDY_NET_SECTION}
CD_COMPOSE CD_COMPOSE
backup_if_exists .env
cat > .env << CD_ENV cat > .env << CD_ENV
# Changedetection.io environment — edit before starting if needed # Changedetection.io environment — edit before starting if needed
BASE_URL=https://changes.${SITE_DOMAIN} BASE_URL=https://changes.${SITE_DOMAIN}
+7
View File
@@ -174,6 +174,11 @@ CBLOCK
mkdir -p "$_dir" mkdir -p "$_dir"
cat > "$_dir/README.md" cat > "$_dir/README.md"
} }
backup_if_exists() {
local _file="$1"
[ -f "$_file" ] || return 0
cp -p "$_file" "${_file}.bak.$(date +%Y%m%d-%H%M%S)" 2>/dev/null
}
fi fi
# Globals — ACTUAL_USER/ACTUAL_HOME must come before DOCKER_DIR # Globals — ACTUAL_USER/ACTUAL_HOME must come before DOCKER_DIR
@@ -212,6 +217,7 @@ install_ddclient() {
local TZ_VAL; TZ_VAL="${SITE_TZ:-$(cat /etc/timezone 2>/dev/null || echo UTC)}" local TZ_VAL; TZ_VAL="${SITE_TZ:-$(cat /etc/timezone 2>/dev/null || echo UTC)}"
backup_if_exists docker-compose.yml
cat > docker-compose.yml << 'DDCLIENT_COMPOSE' cat > docker-compose.yml << 'DDCLIENT_COMPOSE'
name: ddclient name: ddclient
@@ -229,6 +235,7 @@ services:
- ./config:/config - ./config:/config
DDCLIENT_COMPOSE DDCLIENT_COMPOSE
backup_if_exists .env
cat > .env << DDCLIENT_ENV cat > .env << DDCLIENT_ENV
TZ=$TZ_VAL TZ=$TZ_VAL
DDCLIENT_ENV DDCLIENT_ENV
+7
View File
@@ -178,6 +178,11 @@ CBLOCK
mkdir -p "$_dir" mkdir -p "$_dir"
cat > "$_dir/README.md" cat > "$_dir/README.md"
} }
backup_if_exists() {
local _file="$1"
[ -f "$_file" ] || return 0
cp -p "$_file" "${_file}.bak.$(date +%Y%m%d-%H%M%S)" 2>/dev/null
}
fi fi
ACTUAL_USER="${ACTUAL_USER:-${SUDO_USER:-$USER}}" ACTUAL_USER="${ACTUAL_USER:-${SUDO_USER:-$USER}}"
@@ -477,6 +482,7 @@ networks:
" "
fi fi
backup_if_exists docker-compose.yml
cat > docker-compose.yml << DRUM_COMPOSE cat > docker-compose.yml << DRUM_COMPOSE
name: drum-rhythm-game name: drum-rhythm-game
@@ -492,6 +498,7 @@ services:
${_CADDY_NET_BLOCK}${_CADDY_NET_SECTION} ${_CADDY_NET_BLOCK}${_CADDY_NET_SECTION}
DRUM_COMPOSE DRUM_COMPOSE
backup_if_exists .env
cat > .env << DRUM_ENV cat > .env << DRUM_ENV
CADDY_NET=${SITE_CADDY_NET} CADDY_NET=${SITE_CADDY_NET}
DRUM_ENV DRUM_ENV
+7
View File
@@ -189,6 +189,11 @@ CBLOCK
mkdir -p "$_dir" mkdir -p "$_dir"
cat > "$_dir/README.md" cat > "$_dir/README.md"
} }
backup_if_exists() {
local _file="$1"
[ -f "$_file" ] || return 0
cp -p "$_file" "${_file}.bak.$(date +%Y%m%d-%H%M%S)" 2>/dev/null
}
fi fi
# Globals — ACTUAL_USER/ACTUAL_HOME must come before DOCKER_DIR # Globals — ACTUAL_USER/ACTUAL_HOME must come before DOCKER_DIR
@@ -352,6 +357,7 @@ networks:
" "
fi fi
backup_if_exists docker-compose.yml
cat > docker-compose.yml << EMBY_COMPOSE cat > docker-compose.yml << EMBY_COMPOSE
name: $CONTAINER name: $CONTAINER
@@ -377,6 +383,7 @@ services:
${_CADDY_NET_BLOCK}${_CADDY_NET_SECTION} ${_CADDY_NET_BLOCK}${_CADDY_NET_SECTION}
EMBY_COMPOSE EMBY_COMPOSE
backup_if_exists .env
cat > .env << EMBY_ENV cat > .env << EMBY_ENV
MEDIA_PATH=$MEDIA_PATH MEDIA_PATH=$MEDIA_PATH
CADDY_NET=$SITE_CADDY_NET CADDY_NET=$SITE_CADDY_NET
+7
View File
@@ -177,6 +177,11 @@ CBLOCK
mkdir -p "$_dir" mkdir -p "$_dir"
cat > "$_dir/README.md" cat > "$_dir/README.md"
} }
backup_if_exists() {
local _file="$1"
[ -f "$_file" ] || return 0
cp -p "$_file" "${_file}.bak.$(date +%Y%m%d-%H%M%S)" 2>/dev/null
}
fi fi
ACTUAL_USER="${ACTUAL_USER:-${SUDO_USER:-$USER}}" ACTUAL_USER="${ACTUAL_USER:-${SUDO_USER:-$USER}}"
@@ -315,6 +320,7 @@ networks:
" "
fi fi
backup_if_exists docker-compose.yml
cat > docker-compose.yml << FB_COMPOSE cat > docker-compose.yml << FB_COMPOSE
name: $CONTAINER name: $CONTAINER
@@ -334,6 +340,7 @@ services:
${_CADDY_NET_BLOCK}${_CADDY_NET_SECTION} ${_CADDY_NET_BLOCK}${_CADDY_NET_SECTION}
FB_COMPOSE FB_COMPOSE
backup_if_exists .env
cat > .env << FB_ENV cat > .env << FB_ENV
CADDY_NET=$SITE_CADDY_NET CADDY_NET=$SITE_CADDY_NET
FB_ENV FB_ENV
+7
View File
@@ -199,6 +199,11 @@ CBLOCK
mkdir -p "$_dir" mkdir -p "$_dir"
cat > "$_dir/README.md" cat > "$_dir/README.md"
} }
backup_if_exists() {
local _file="$1"
[ -f "$_file" ] || return 0
cp -p "$_file" "${_file}.bak.$(date +%Y%m%d-%H%M%S)" 2>/dev/null
}
fi fi
# Globals — ACTUAL_USER/ACTUAL_HOME must come before DOCKER_DIR # Globals — ACTUAL_USER/ACTUAL_HOME must come before DOCKER_DIR
@@ -339,6 +344,7 @@ networks:
" "
fi fi
backup_if_exists docker-compose.yml
cat > docker-compose.yml << FMD_COMPOSE cat > docker-compose.yml << FMD_COMPOSE
name: $CONTAINER name: $CONTAINER
@@ -357,6 +363,7 @@ services:
${_CADDY_NET_BLOCK}${_CADDY_NET_SECTION} ${_CADDY_NET_BLOCK}${_CADDY_NET_SECTION}
FMD_COMPOSE FMD_COMPOSE
backup_if_exists .env
cat > .env << FMD_ENV cat > .env << FMD_ENV
FMD_REGISTRATIONTOKEN=$FMD_TOKEN FMD_REGISTRATIONTOKEN=$FMD_TOKEN
CADDY_NET=$SITE_CADDY_NET CADDY_NET=$SITE_CADDY_NET
+7
View File
@@ -207,6 +207,11 @@ CBLOCK
mkdir -p "$_dir" mkdir -p "$_dir"
cat > "$_dir/README.md" cat > "$_dir/README.md"
} }
backup_if_exists() {
local _file="$1"
[ -f "$_file" ] || return 0
cp -p "$_file" "${_file}.bak.$(date +%Y%m%d-%H%M%S)" 2>/dev/null
}
fi fi
# Globals — ACTUAL_USER/ACTUAL_HOME must come before DOCKER_DIR # Globals — ACTUAL_USER/ACTUAL_HOME must come before DOCKER_DIR
@@ -365,6 +370,7 @@ install_frigate-audio() {
# ── .env ────────────────────────────────────────────────────────────────── # ── .env ──────────────────────────────────────────────────────────────────
log_info "Writing .env..." log_info "Writing .env..."
backup_if_exists "$DIR/.env"
cat > "$DIR/.env" << ENVEOF cat > "$DIR/.env" << ENVEOF
# Frigate audio stack — generated by setup.sh # Frigate audio stack — generated by setup.sh
# DO NOT commit this file — it contains credentials. # DO NOT commit this file — it contains credentials.
@@ -432,6 +438,7 @@ networks:
" "
fi fi
backup_if_exists "$DIR/docker-compose.yml"
cat > "$DIR/docker-compose.yml" << 'COMPOSEEOF' cat > "$DIR/docker-compose.yml" << 'COMPOSEEOF'
# Frigate NVR + Mosquitto MQTT + frigate-notify # Frigate NVR + Mosquitto MQTT + frigate-notify
# Generated by ubuntu-post-install setup.sh # Generated by ubuntu-post-install setup.sh
+6
View File
@@ -174,6 +174,11 @@ CBLOCK
mkdir -p "$_dir" mkdir -p "$_dir"
cat > "$_dir/README.md" cat > "$_dir/README.md"
} }
backup_if_exists() {
local _file="$1"
[ -f "$_file" ] || return 0
cp -p "$_file" "${_file}.bak.$(date +%Y%m%d-%H%M%S)" 2>/dev/null
}
fi fi
# Globals — ACTUAL_USER/ACTUAL_HOME must come before DOCKER_DIR # Globals — ACTUAL_USER/ACTUAL_HOME must come before DOCKER_DIR
@@ -234,6 +239,7 @@ networks:
" "
fi fi
backup_if_exists docker-compose.yml
cat > docker-compose.yml << FN_COMPOSE cat > docker-compose.yml << FN_COMPOSE
name: frigate-notify name: frigate-notify
+67 -5
View File
@@ -202,6 +202,11 @@ CBLOCK
mkdir -p "$_dir" mkdir -p "$_dir"
cat > "$_dir/README.md" cat > "$_dir/README.md"
} }
backup_if_exists() {
local _file="$1"
[ -f "$_file" ] || return 0
cp -p "$_file" "${_file}.bak.$(date +%Y%m%d-%H%M%S)" 2>/dev/null
}
fi fi
# Globals — ACTUAL_USER/ACTUAL_HOME must come before DOCKER_DIR # Globals — ACTUAL_USER/ACTUAL_HOME must come before DOCKER_DIR
@@ -626,6 +631,7 @@ networks:
" "
fi fi
backup_if_exists docker-compose.yml
cat > docker-compose.yml << FRIGATE_COMPOSE cat > docker-compose.yml << FRIGATE_COMPOSE
name: frigate name: frigate
@@ -673,16 +679,71 @@ FRIGATE_COMPOSE
# reverse_proxy block) stops that trust from being spoofed by a # reverse_proxy block) stops that trust from being spoofed by a
# request that reaches Frigate's published host port directly, # request that reaches Frigate's published host port directly,
# bypassing Caddy/Authelia entirely. # bypassing Caddy/Authelia entirely.
# Whether to even OFFER this can't just check "$DOCKER_DIR/authelia"
# locally — Frigate's own box very often has no local Caddy at all
# (configure_caddy_for_service falls back to writing a snippet for
# a remote Caddy machine to pick up, confirmed live: this is the
# normal shape for a dedicated NVR box), in which case Authelia, if
# it exists anywhere, lives on THAT remote Caddy machine instead —
# a box this script has no filesystem access to inspect. Default to
# "y" only when local Authelia is actually confirmed; otherwise still
# offer it (default "n") and sort out local-vs-remote below once the
# admin says yes, rather than silently refusing to ask at all.
local FRIGATE_USE_AUTHELIA="n" FRIGATE_PROXY_SECRET="" AUTH_CONFIG_BLOCK="" local FRIGATE_USE_AUTHELIA="n" FRIGATE_PROXY_SECRET="" AUTH_CONFIG_BLOCK=""
if [ -d "$DOCKER_DIR/authelia" ]; then local _frigate_local_authelia="n"
echo "" [ -d "$DOCKER_DIR/authelia" ] && _frigate_local_authelia="y"
prompt_yn "Protect Frigate with Authelia SSO (disables Frigate's own login)? (y/n):" "y" FRIGATE_USE_AUTHELIA echo ""
fi prompt_yn "Protect Frigate with Authelia SSO (disables Frigate's own login)? (y/n):" "$_frigate_local_authelia" FRIGATE_USE_AUTHELIA
if [[ "$FRIGATE_USE_AUTHELIA" =~ ^[Yy]$ ]]; then if [[ "$FRIGATE_USE_AUTHELIA" =~ ^[Yy]$ ]]; then
FRIGATE_PROXY_SECRET="${ENV_MAP[FRIGATE_PROXY_AUTH_SECRET]:-$(generate_password 32)}" FRIGATE_PROXY_SECRET="${ENV_MAP[FRIGATE_PROXY_AUTH_SECRET]:-$(generate_password 32)}"
# Local Authelia snippet (import authelia) only actually exists
# in the Caddyfile it's imported into if Authelia is on THAT
# same machine. When it's on this box, that's this box's own
# Caddy — safe to assume. When Caddy itself turns out to be
# remote (below), "local" instead means "local to wherever
# Caddy is", which this script can't see — so ask, rather than
# silently emit an import that would fail Caddy's own reload
# with "file to import not found" on that other machine.
local _frigate_auth_block=" import authelia"
if [ "$_frigate_local_authelia" != "y" ]; then
echo ""
log_info "No local Authelia on this box — Caddy for Frigate may end up on a"
log_info "different machine (decided next)."
local _authelia_with_caddy=""
prompt_yn " Does Authelia run on that SAME machine as Caddy? (y/n):" "y" _authelia_with_caddy
if [[ ! "$_authelia_with_caddy" =~ ^[Yy]$ ]]; then
# Genuinely cross-machine: Authelia is a third box,
# different from both this one and wherever Caddy ends
# up. Needs the explicit header-pinned forward_auth form
# — see CLAUDE.md's "forward_auth to a remote Authelia"
# note for why the bare "authelia:9091" shortcut can't
# be used here and X-Forwarded-Host must be pinned
# explicitly (a second Caddy hop otherwise silently
# evaluates every domain as if it were auth's own
# portal domain — confirmed live, a real incident this
# exact snippet shape was written to prevent).
local _remote_authelia_domain=""
prompt_text " Authelia's own portal domain (e.g. authelia.example.com):" "" _remote_authelia_domain
if [ -n "$_remote_authelia_domain" ]; then
_frigate_auth_block=" forward_auth https://${_remote_authelia_domain} {
uri /api/authz/forward-auth
copy_headers Remote-User Remote-Groups Remote-Name Remote-Email
header_up X-Forwarded-Method {method}
header_up X-Forwarded-Proto {scheme}
header_up X-Forwarded-Host {host}
header_up X-Forwarded-Uri {uri}
}"
else
log_warning "No domain entered — falling back to 'import authelia', which will fail"
log_warning "Caddy's reload unless Authelia is actually local to that Caddy machine."
fi
fi
fi
configure_caddy_for_service "Frigate" "frigate:5000" "frigate" \ configure_caddy_for_service "Frigate" "frigate:5000" "frigate" \
" import authelia" \ "$_frigate_auth_block" \
" header_up X-Proxy-Secret ${FRIGATE_PROXY_SECRET}" " header_up X-Proxy-Secret ${FRIGATE_PROXY_SECRET}"
if [ "${CADDY_SERVICE_CONFIGURED:-false}" = true ]; then if [ "${CADDY_SERVICE_CONFIGURED:-false}" = true ]; then
AUTH_CONFIG_BLOCK="auth: AUTH_CONFIG_BLOCK="auth:
@@ -780,6 +841,7 @@ snapshots:
FRIGATE_CONFIG FRIGATE_CONFIG
fi fi
backup_if_exists .env
cat > .env << FRIGATE_ENV cat > .env << FRIGATE_ENV
FRIGATE_MEDIA=$FRIGATE_MEDIA FRIGATE_MEDIA=$FRIGATE_MEDIA
CADDY_NET=$SITE_CADDY_NET CADDY_NET=$SITE_CADDY_NET
+7
View File
@@ -92,6 +92,11 @@ if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
mkdir -p "$_dir" mkdir -p "$_dir"
cat > "$_dir/README.md" cat > "$_dir/README.md"
} }
backup_if_exists() {
local _file="$1"
[ -f "$_file" ] || return 0
cp -p "$_file" "${_file}.bak.$(date +%Y%m%d-%H%M%S)" 2>/dev/null
}
fi fi
ACTUAL_USER="${ACTUAL_USER:-${SUDO_USER:-$USER}}" ACTUAL_USER="${ACTUAL_USER:-${SUDO_USER:-$USER}}"
@@ -199,6 +204,7 @@ install_garage-webui() {
ensure_docker_dir_ownership "$DIR" ensure_docker_dir_ownership "$DIR"
cd "$DIR" || return 1 cd "$DIR" || return 1
backup_if_exists docker-compose.yml
cat > docker-compose.yml << COMPOSE cat > docker-compose.yml << COMPOSE
name: garage-webui name: garage-webui
@@ -219,6 +225,7 @@ services:
- "${WEB_PORT}:3909" - "${WEB_PORT}:3909"
COMPOSE COMPOSE
backup_if_exists .env
cat > .env << ENV cat > .env << ENV
TZ=${SITE_TZ:-$(cat /etc/timezone 2>/dev/null || echo UTC)} TZ=${SITE_TZ:-$(cat /etc/timezone 2>/dev/null || echo UTC)}
+7
View File
@@ -108,6 +108,11 @@ if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
mkdir -p "$_dir" mkdir -p "$_dir"
cat > "$_dir/README.md" cat > "$_dir/README.md"
} }
backup_if_exists() {
local _file="$1"
[ -f "$_file" ] || return 0
cp -p "$_file" "${_file}.bak.$(date +%Y%m%d-%H%M%S)" 2>/dev/null
}
fi fi
# Globals — ACTUAL_USER/ACTUAL_HOME must come before DOCKER_DIR # Globals — ACTUAL_USER/ACTUAL_HOME must come before DOCKER_DIR
@@ -277,6 +282,7 @@ api_bind_addr = "[::]:${ADMIN_PORT}"
admin_token = "${ADMIN_TOKEN}" admin_token = "${ADMIN_TOKEN}"
TOML TOML
backup_if_exists docker-compose.yml
cat > docker-compose.yml << COMPOSE cat > docker-compose.yml << COMPOSE
name: garage name: garage
@@ -297,6 +303,7 @@ services:
- "${ADMIN_PORT}:${ADMIN_PORT}" - "${ADMIN_PORT}:${ADMIN_PORT}"
COMPOSE COMPOSE
backup_if_exists .env
cat > .env << ENV cat > .env << ENV
TZ=${SITE_TZ:-$(cat /etc/timezone 2>/dev/null || echo UTC)} TZ=${SITE_TZ:-$(cat /etc/timezone 2>/dev/null || echo UTC)}
+7
View File
@@ -187,6 +187,11 @@ CBLOCK
mkdir -p "$_dir" mkdir -p "$_dir"
cat > "$_dir/README.md" cat > "$_dir/README.md"
} }
backup_if_exists() {
local _file="$1"
[ -f "$_file" ] || return 0
cp -p "$_file" "${_file}.bak.$(date +%Y%m%d-%H%M%S)" 2>/dev/null
}
fi fi
# Globals — ACTUAL_USER/ACTUAL_HOME must come before DOCKER_DIR # Globals — ACTUAL_USER/ACTUAL_HOME must come before DOCKER_DIR
@@ -405,6 +410,7 @@ networks:
" "
fi fi
backup_if_exists docker-compose.yml
cat > docker-compose.yml << GATUS_COMPOSE cat > docker-compose.yml << GATUS_COMPOSE
name: gatus name: gatus
@@ -423,6 +429,7 @@ services:
${_CADDY_NET_BLOCK}${_CADDY_NET_SECTION} ${_CADDY_NET_BLOCK}${_CADDY_NET_SECTION}
GATUS_COMPOSE GATUS_COMPOSE
backup_if_exists .env
cat > .env << GATUS_ENV cat > .env << GATUS_ENV
TZ=$TZ_VAL TZ=$TZ_VAL
CADDY_NET=$SITE_CADDY_NET CADDY_NET=$SITE_CADDY_NET
+7
View File
@@ -104,6 +104,11 @@ if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
mkdir -p "$_dir" mkdir -p "$_dir"
cat > "$_dir/README.md" cat > "$_dir/README.md"
} }
backup_if_exists() {
local _file="$1"
[ -f "$_file" ] || return 0
cp -p "$_file" "${_file}.bak.$(date +%Y%m%d-%H%M%S)" 2>/dev/null
}
fi fi
ACTUAL_USER="${ACTUAL_USER:-${SUDO_USER:-$USER}}" ACTUAL_USER="${ACTUAL_USER:-${SUDO_USER:-$USER}}"
@@ -585,6 +590,7 @@ install_gitea() {
[[ "$WEB_PORT" != 3001 ]] && log_info "Port 3001 was taken — Gitea's web UI will use ${WEB_PORT}." [[ "$WEB_PORT" != 3001 ]] && log_info "Port 3001 was taken — Gitea's web UI will use ${WEB_PORT}."
[[ "$SSH_PORT" != 2222 ]] && log_info "Port 2222 was taken — Gitea's SSH clone port will use ${SSH_PORT}." [[ "$SSH_PORT" != 2222 ]] && log_info "Port 2222 was taken — Gitea's SSH clone port will use ${SSH_PORT}."
backup_if_exists docker-compose.yml
cat > docker-compose.yml << EOF cat > docker-compose.yml << EOF
name: gitea name: gitea
services: services:
@@ -699,6 +705,7 @@ EOF
cp -f "$SYNC_SRC" "$DIR/gitea-github-sync.sh" cp -f "$SYNC_SRC" "$DIR/gitea-github-sync.sh"
chmod +x "$DIR/gitea-github-sync.sh" chmod +x "$DIR/gitea-github-sync.sh"
backup_if_exists "$DIR/.env"
cat > "$DIR/.env" << ENV cat > "$DIR/.env" << ENV
# Written by services/gitea.sh — re-run that (update mode) to change any of this. # Written by services/gitea.sh — re-run that (update mode) to change any of this.
GITEA_URL='http://localhost:${WEB_PORT}' GITEA_URL='http://localhost:${WEB_PORT}'
+1
View File
@@ -274,6 +274,7 @@ networks:
" "
fi fi
backup_if_exists docker-compose.yml
cat > docker-compose.yml << HOMEASSISTANT_COMPOSE cat > docker-compose.yml << HOMEASSISTANT_COMPOSE
name: homeassistant name: homeassistant
+7
View File
@@ -177,6 +177,11 @@ CBLOCK
mkdir -p "$_dir" mkdir -p "$_dir"
cat > "$_dir/README.md" cat > "$_dir/README.md"
} }
backup_if_exists() {
local _file="$1"
[ -f "$_file" ] || return 0
cp -p "$_file" "${_file}.bak.$(date +%Y%m%d-%H%M%S)" 2>/dev/null
}
fi fi
ACTUAL_USER="${ACTUAL_USER:-${SUDO_USER:-$USER}}" ACTUAL_USER="${ACTUAL_USER:-${SUDO_USER:-$USER}}"
@@ -311,6 +316,7 @@ networks:
local HB_PEPPER local HB_PEPPER
HB_PEPPER="$(generate_password 48)" HB_PEPPER="$(generate_password 48)"
backup_if_exists docker-compose.yml
cat > docker-compose.yml << HB_COMPOSE cat > docker-compose.yml << HB_COMPOSE
name: $CONTAINER name: $CONTAINER
@@ -331,6 +337,7 @@ services:
${_CADDY_NET_BLOCK}${_CADDY_NET_SECTION} ${_CADDY_NET_BLOCK}${_CADDY_NET_SECTION}
HB_COMPOSE HB_COMPOSE
backup_if_exists .env
cat > .env << HB_ENV cat > .env << HB_ENV
CADDY_NET=$SITE_CADDY_NET CADDY_NET=$SITE_CADDY_NET
+8
View File
@@ -183,6 +183,11 @@ CBLOCK
mkdir -p "$_dir" mkdir -p "$_dir"
cat > "$_dir/README.md" cat > "$_dir/README.md"
} }
backup_if_exists() {
local _file="$1"
[ -f "$_file" ] || return 0
cp -p "$_file" "${_file}.bak.$(date +%Y%m%d-%H%M%S)" 2>/dev/null
}
fi fi
ACTUAL_USER="${ACTUAL_USER:-${SUDO_USER:-$USER}}" ACTUAL_USER="${ACTUAL_USER:-${SUDO_USER:-$USER}}"
@@ -606,6 +611,7 @@ networks:
[ -n "$EXTERNAL_LIBRARY" ] && _EXTERNAL_VOLUME_LINE=" - \${EXTERNAL_LIBRARY}:/usr/src/app/external:ro [ -n "$EXTERNAL_LIBRARY" ] && _EXTERNAL_VOLUME_LINE=" - \${EXTERNAL_LIBRARY}:/usr/src/app/external:ro
" "
backup_if_exists docker-compose.yml
cat > docker-compose.yml << IMMICH_COMPOSE cat > docker-compose.yml << IMMICH_COMPOSE
name: $PROJECT name: $PROJECT
@@ -686,6 +692,7 @@ S3_SECRET_ACCESS_KEY=$S3_SECRET_ACCESS_KEY
fi fi
if [ "$IMMICH_STRATEGY" = "2" ]; then if [ "$IMMICH_STRATEGY" = "2" ]; then
backup_if_exists .env
cat > .env << IMMICH_ENV cat > .env << IMMICH_ENV
# IMMICH CONFIGURATION — External Library Mode # IMMICH CONFIGURATION — External Library Mode
# #
@@ -713,6 +720,7 @@ TZ=$TZ_VAL
CADDY_NET=$SITE_CADDY_NET CADDY_NET=$SITE_CADDY_NET
IMMICH_ENV IMMICH_ENV
else else
backup_if_exists .env
cat > .env << IMMICH_ENV cat > .env << IMMICH_ENV
# IMMICH CONFIGURATION — Unified Library # IMMICH CONFIGURATION — Unified Library
# #
+8
View File
@@ -185,6 +185,11 @@ CBLOCK
[[ "${DRY_RUN:-false}" == "true" ]] && return 0 [[ "${DRY_RUN:-false}" == "true" ]] && return 0
cat > "$_dir/README.md" cat > "$_dir/README.md"
} }
backup_if_exists() {
local _file="$1"
[ -f "$_file" ] || return 0
cp -p "$_file" "${_file}.bak.$(date +%Y%m%d-%H%M%S)" 2>/dev/null
}
fi fi
ACTUAL_USER="${ACTUAL_USER:-${SUDO_USER:-$USER}}" ACTUAL_USER="${ACTUAL_USER:-${SUDO_USER:-$USER}}"
@@ -314,6 +319,7 @@ networks:
fi fi
if [[ "$USE_GPU" =~ ^[Yy]$ ]]; then if [[ "$USE_GPU" =~ ^[Yy]$ ]]; then
backup_if_exists docker-compose.yml
cat > docker-compose.yml << IOPAINT_GPU cat > docker-compose.yml << IOPAINT_GPU
name: iopaint name: iopaint
@@ -346,6 +352,7 @@ services:
${_CADDY_NET_BLOCK}${_CADDY_NET_SECTION} ${_CADDY_NET_BLOCK}${_CADDY_NET_SECTION}
IOPAINT_GPU IOPAINT_GPU
else else
backup_if_exists docker-compose.yml
cat > docker-compose.yml << IOPAINT_CPU cat > docker-compose.yml << IOPAINT_CPU
name: iopaint name: iopaint
@@ -373,6 +380,7 @@ IOPAINT_CPU
fi fi
# ── .env ───────────────────────────────────────────────────────────────── # ── .env ─────────────────────────────────────────────────────────────────
backup_if_exists .env
cat > .env << IOPAINT_ENV cat > .env << IOPAINT_ENV
# IOPaint — change MODEL and restart to switch (no need to edit docker-compose.yml) # IOPaint — change MODEL and restart to switch (no need to edit docker-compose.yml)
+7
View File
@@ -182,6 +182,11 @@ CBLOCK
mkdir -p "$_dir" mkdir -p "$_dir"
cat > "$_dir/README.md" cat > "$_dir/README.md"
} }
backup_if_exists() {
local _file="$1"
[ -f "$_file" ] || return 0
cp -p "$_file" "${_file}.bak.$(date +%Y%m%d-%H%M%S)" 2>/dev/null
}
fi fi
ACTUAL_USER="${ACTUAL_USER:-${SUDO_USER:-$USER}}" ACTUAL_USER="${ACTUAL_USER:-${SUDO_USER:-$USER}}"
@@ -347,6 +352,7 @@ networks:
' '
fi fi
backup_if_exists docker-compose.yml
cat > docker-compose.yml << JELLYFIN_COMPOSE cat > docker-compose.yml << JELLYFIN_COMPOSE
name: $CONTAINER name: $CONTAINER
@@ -368,6 +374,7 @@ $HWACCEL_BLOCK
${_DISCOVERY_PORTS}${_CADDY_NET_BLOCK}${_CADDY_NET_SECTION} ${_DISCOVERY_PORTS}${_CADDY_NET_BLOCK}${_CADDY_NET_SECTION}
JELLYFIN_COMPOSE JELLYFIN_COMPOSE
backup_if_exists .env
cat > .env << JELLYFIN_ENV cat > .env << JELLYFIN_ENV
MEDIA_PATH=$MEDIA_PATH MEDIA_PATH=$MEDIA_PATH
CADDY_NET=$SITE_CADDY_NET CADDY_NET=$SITE_CADDY_NET
+7
View File
@@ -177,6 +177,11 @@ CBLOCK
mkdir -p "$_dir" mkdir -p "$_dir"
cat > "$_dir/README.md" cat > "$_dir/README.md"
} }
backup_if_exists() {
local _file="$1"
[ -f "$_file" ] || return 0
cp -p "$_file" "${_file}.bak.$(date +%Y%m%d-%H%M%S)" 2>/dev/null
}
fi fi
ACTUAL_USER="${ACTUAL_USER:-${SUDO_USER:-$USER}}" ACTUAL_USER="${ACTUAL_USER:-${SUDO_USER:-$USER}}"
@@ -314,6 +319,7 @@ networks:
" "
fi fi
backup_if_exists docker-compose.yml
cat > docker-compose.yml << JOPLIN_COMPOSE cat > docker-compose.yml << JOPLIN_COMPOSE
name: $CONTAINER name: $CONTAINER
@@ -340,6 +346,7 @@ ${_CADDY_NET_BLOCK}
${_CADDY_NET_BLOCK}${_CADDY_NET_SECTION} ${_CADDY_NET_BLOCK}${_CADDY_NET_SECTION}
JOPLIN_COMPOSE JOPLIN_COMPOSE
backup_if_exists .env
cat > .env << JOPLIN_ENV cat > .env << JOPLIN_ENV
# Joplin Server configuration # Joplin Server configuration
APP_PORT=22300 APP_PORT=22300
+6
View File
@@ -174,6 +174,11 @@ CBLOCK
mkdir -p "$_dir" mkdir -p "$_dir"
cat > "$_dir/README.md" cat > "$_dir/README.md"
} }
backup_if_exists() {
local _file="$1"
[ -f "$_file" ] || return 0
cp -p "$_file" "${_file}.bak.$(date +%Y%m%d-%H%M%S)" 2>/dev/null
}
fi fi
# Globals — ACTUAL_USER/ACTUAL_HOME must come before DOCKER_DIR # Globals — ACTUAL_USER/ACTUAL_HOME must come before DOCKER_DIR
@@ -459,6 +464,7 @@ networks:
" "
fi fi
backup_if_exists "$JS99ER_DIR/docker-compose.yml"
cat > "$JS99ER_DIR/docker-compose.yml" << COMPOSE cat > "$JS99ER_DIR/docker-compose.yml" << COMPOSE
name: js99er name: js99er
+7
View File
@@ -188,6 +188,11 @@ CBLOCK
mkdir -p "$_dir" mkdir -p "$_dir"
cat > "$_dir/README.md" cat > "$_dir/README.md"
} }
backup_if_exists() {
local _file="$1"
[ -f "$_file" ] || return 0
cp -p "$_file" "${_file}.bak.$(date +%Y%m%d-%H%M%S)" 2>/dev/null
}
fi fi
ACTUAL_USER="${ACTUAL_USER:-${SUDO_USER:-$USER}}" ACTUAL_USER="${ACTUAL_USER:-${SUDO_USER:-$USER}}"
@@ -410,6 +415,7 @@ install_koha() {
fi fi
# ── docker-compose.yml ──────────────────────────────────────────────────── # ── docker-compose.yml ────────────────────────────────────────────────────
backup_if_exists docker-compose.yml
cat > docker-compose.yml << KOHA_COMPOSE cat > docker-compose.yml << KOHA_COMPOSE
name: koha name: koha
@@ -481,6 +487,7 @@ ${_CADDY_NET_SECTION}
KOHA_COMPOSE KOHA_COMPOSE
# ── config-main.env ─────────────────────────────────────────────────────── # ── config-main.env ───────────────────────────────────────────────────────
backup_if_exists config-main.env
cat > config-main.env << KOHA_ENV cat > config-main.env << KOHA_ENV
# Koha ILS configuration — generated at install time # Koha ILS configuration — generated at install time
MYSQL_SERVER=koha-db MYSQL_SERVER=koha-db
+7
View File
@@ -388,6 +388,7 @@ for a in data.get('assets', []):
mkdir -p "$DIR" mkdir -p "$DIR"
ensure_docker_dir_ownership "$DIR" ensure_docker_dir_ownership "$DIR"
backup_if_exists "$DIR/docker-compose.yml"
cat > "$DIR/docker-compose.yml" << EOF cat > "$DIR/docker-compose.yml" << EOF
name: kyber-server name: kyber-server
services: services:
@@ -411,6 +412,7 @@ EOF
# Write .env with restricted permissions # Write .env with restricted permissions
# Values are single-quoted so special characters ($, !, &, etc.) are safe. # Values are single-quoted so special characters ($, !, &, etc.) are safe.
# Exception: single quotes inside a value would still break — avoid them. # Exception: single quotes inside a value would still break — avoid them.
backup_if_exists "$DIR/.env"
cat > "$DIR/.env" << EOF cat > "$DIR/.env" << EOF
MAXIMA_CREDENTIALS='${EA_EMAIL}:${EA_PASSWORD}' MAXIMA_CREDENTIALS='${EA_EMAIL}:${EA_PASSWORD}'
KYBER_TOKEN='${KYBER_TOKEN}' KYBER_TOKEN='${KYBER_TOKEN}'
@@ -558,6 +560,11 @@ if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
mkdir -p "$_dir" mkdir -p "$_dir"
cat > "$_dir/README.md" cat > "$_dir/README.md"
} }
backup_if_exists() {
local _file="$1"
[ -f "$_file" ] || return 0
cp -p "$_file" "${_file}.bak.$(date +%Y%m%d-%H%M%S)" 2>/dev/null
}
ACTUAL_USER="${SUDO_USER:-$USER}" ACTUAL_USER="${SUDO_USER:-$USER}"
ACTUAL_HOME=$(eval echo "~$ACTUAL_USER") ACTUAL_HOME=$(eval echo "~$ACTUAL_USER")
+7
View File
@@ -188,6 +188,11 @@ CBLOCK
mkdir -p "$_dir" mkdir -p "$_dir"
cat > "$_dir/README.md" cat > "$_dir/README.md"
} }
backup_if_exists() {
local _file="$1"
[ -f "$_file" ] || return 0
cp -p "$_file" "${_file}.bak.$(date +%Y%m%d-%H%M%S)" 2>/dev/null
}
fi fi
# Globals — ACTUAL_USER/ACTUAL_HOME must come before DOCKER_DIR # Globals — ACTUAL_USER/ACTUAL_HOME must come before DOCKER_DIR
@@ -353,6 +358,7 @@ install_lyrion() {
_HTTP_PORT_INTERNAL="9000" _HTTP_PORT_INTERNAL="9000"
fi fi
backup_if_exists docker-compose.yml
cat > docker-compose.yml << LYRION_COMPOSE cat > docker-compose.yml << LYRION_COMPOSE
name: $CONTAINER name: $CONTAINER
@@ -375,6 +381,7 @@ ${_NETWORK_BLOCK}${_PORTS_BLOCK} environment:
LYRION_COMPOSE LYRION_COMPOSE
backup_if_exists .env
cat > .env << LYRION_ENV cat > .env << LYRION_ENV
MUSIC_PATH=$MUSIC_PATH MUSIC_PATH=$MUSIC_PATH
CADDY_NET=$SITE_CADDY_NET CADDY_NET=$SITE_CADDY_NET
+6
View File
@@ -174,6 +174,11 @@ CBLOCK
mkdir -p "$_dir" mkdir -p "$_dir"
cat > "$_dir/README.md" cat > "$_dir/README.md"
} }
backup_if_exists() {
local _file="$1"
[ -f "$_file" ] || return 0
cp -p "$_file" "${_file}.bak.$(date +%Y%m%d-%H%M%S)" 2>/dev/null
}
fi fi
# Globals — ACTUAL_USER/ACTUAL_HOME must come before DOCKER_DIR # Globals — ACTUAL_USER/ACTUAL_HOME must come before DOCKER_DIR
@@ -254,6 +259,7 @@ networks:
" "
fi fi
backup_if_exists docker-compose.yml
cat > docker-compose.yml << MM_COMPOSE cat > docker-compose.yml << MM_COMPOSE
name: mm-$MM_PORT name: mm-$MM_PORT
+7
View File
@@ -170,6 +170,11 @@ CBLOCK
mkdir -p "$_dir" mkdir -p "$_dir"
cat > "$_dir/README.md" cat > "$_dir/README.md"
} }
backup_if_exists() {
local _file="$1"
[ -f "$_file" ] || return 0
cp -p "$_file" "${_file}.bak.$(date +%Y%m%d-%H%M%S)" 2>/dev/null
}
fi fi
# Globals — ACTUAL_USER/ACTUAL_HOME must come before DOCKER_DIR # Globals — ACTUAL_USER/ACTUAL_HOME must come before DOCKER_DIR
@@ -244,6 +249,7 @@ networks:
" "
fi fi
backup_if_exists docker-compose.yml
cat > docker-compose.yml << MA_COMPOSE cat > docker-compose.yml << MA_COMPOSE
name: mail-archiver name: mail-archiver
@@ -279,6 +285,7 @@ ${_CADDY_NET_BLOCK}
${_CADDY_NET_SECTION} ${_CADDY_NET_SECTION}
MA_COMPOSE MA_COMPOSE
backup_if_exists .env
cat > .env << MA_ENV cat > .env << MA_ENV
# ── General ─────────────────────────────────────────────────────────────────── # ── General ───────────────────────────────────────────────────────────────────
TZ=$TZ_VAL TZ=$TZ_VAL
+7
View File
@@ -213,6 +213,11 @@ CBLOCK
[[ "${DRY_RUN:-false}" == "true" ]] && return 0 [[ "${DRY_RUN:-false}" == "true" ]] && return 0
cat > "$_dir/README.md" cat > "$_dir/README.md"
} }
backup_if_exists() {
local _file="$1"
[ -f "$_file" ] || return 0
cp -p "$_file" "${_file}.bak.$(date +%Y%m%d-%H%M%S)" 2>/dev/null
}
generate_password() { generate_password() {
local _len="${1:-32}" local _len="${1:-32}"
@@ -518,6 +523,7 @@ networks:
" "
fi fi
backup_if_exists docker-compose.yml
cat > docker-compose.yml << EOF cat > docker-compose.yml << EOF
name: ${PROJECT} name: ${PROJECT}
@@ -580,6 +586,7 @@ ${_CADDY_NET_BLOCK} healthcheck:
${_CADDY_NET_BLOCK}${_COTURN_SERVICE}${_CADDY_NET_SECTION} ${_CADDY_NET_BLOCK}${_COTURN_SERVICE}${_CADDY_NET_SECTION}
EOF EOF
backup_if_exists .env
cat > .env << EOF cat > .env << EOF
TZ=$TZ_VAL TZ=$TZ_VAL
CADDY_NET=$SITE_CADDY_NET CADDY_NET=$SITE_CADDY_NET
+7
View File
@@ -180,6 +180,11 @@ CBLOCK
mkdir -p "$_dir" mkdir -p "$_dir"
cat > "$_dir/README.md" cat > "$_dir/README.md"
} }
backup_if_exists() {
local _file="$1"
[ -f "$_file" ] || return 0
cp -p "$_file" "${_file}.bak.$(date +%Y%m%d-%H%M%S)" 2>/dev/null
}
fi fi
ACTUAL_USER="${ACTUAL_USER:-${SUDO_USER:-$USER}}" ACTUAL_USER="${ACTUAL_USER:-${SUDO_USER:-$USER}}"
@@ -459,6 +464,7 @@ networks:
" "
fi fi
backup_if_exists docker-compose.yml
cat > docker-compose.yml << MEALIE_COMPOSE cat > docker-compose.yml << MEALIE_COMPOSE
name: $CONTAINER name: $CONTAINER
@@ -483,6 +489,7 @@ services:
${_CADDY_NET_BLOCK}${_CADDY_NET_SECTION} ${_CADDY_NET_BLOCK}${_CADDY_NET_SECTION}
MEALIE_COMPOSE MEALIE_COMPOSE
backup_if_exists .env
cat > .env << MEALIE_ENV cat > .env << MEALIE_ENV
# Public URL Mealie is served on — used for email links and OAuth redirects. # Public URL Mealie is served on — used for email links and OAuth redirects.
# Update if you change your domain or switch from HTTP to HTTPS. # Update if you change your domain or switch from HTTP to HTTPS.
+7
View File
@@ -188,6 +188,11 @@ CBLOCK
mkdir -p "$_dir" mkdir -p "$_dir"
cat > "$_dir/README.md" cat > "$_dir/README.md"
} }
backup_if_exists() {
local _file="$1"
[ -f "$_file" ] || return 0
cp -p "$_file" "${_file}.bak.$(date +%Y%m%d-%H%M%S)" 2>/dev/null
}
fi fi
# Globals — ACTUAL_USER/ACTUAL_HOME must come before DOCKER_DIR # Globals — ACTUAL_USER/ACTUAL_HOME must come before DOCKER_DIR
@@ -323,6 +328,7 @@ networks:
" "
fi fi
backup_if_exists docker-compose.yml
cat > docker-compose.yml << MC_COMPOSE cat > docker-compose.yml << MC_COMPOSE
name: $CONTAINER name: $CONTAINER
@@ -350,6 +356,7 @@ services:
${_CADDY_NET_BLOCK}${_CADDY_NET_SECTION} ${_CADDY_NET_BLOCK}${_CADDY_NET_SECTION}
MC_COMPOSE MC_COMPOSE
backup_if_exists .env
cat > .env << MC_ENV cat > .env << MC_ENV
MC_HOSTNAME=$MC_HOSTNAME MC_HOSTNAME=$MC_HOSTNAME
MC_REVERSE_PROXY=false MC_REVERSE_PROXY=false
+2
View File
@@ -1519,6 +1519,7 @@ PREGENINFOEOF
- ./config:/data/config" - ./config:/data/config"
fi fi
backup_if_exists "$MC_DIR/docker-compose.yml"
cat > "$MC_DIR/docker-compose.yml" << COMPOSEEOF cat > "$MC_DIR/docker-compose.yml" << COMPOSEEOF
name: ${MC_NAME} name: ${MC_NAME}
@@ -1902,6 +1903,7 @@ EXPOSE 80
CLIENTDOCKEREOF CLIENTDOCKEREOF
# Standalone compose for the client-mods page (its own folder). # Standalone compose for the client-mods page (its own folder).
backup_if_exists "$CLIENT_MODS_DIR/docker-compose.yml"
cat > "$CLIENT_MODS_DIR/docker-compose.yml" << CMCOMPOSEEOF cat > "$CLIENT_MODS_DIR/docker-compose.yml" << CMCOMPOSEEOF
name: ${CM_NAME} name: ${CM_NAME}
+7
View File
@@ -177,6 +177,11 @@ CBLOCK
mkdir -p "$_dir" mkdir -p "$_dir"
cat > "$_dir/README.md" cat > "$_dir/README.md"
} }
backup_if_exists() {
local _file="$1"
[ -f "$_file" ] || return 0
cp -p "$_file" "${_file}.bak.$(date +%Y%m%d-%H%M%S)" 2>/dev/null
}
fi fi
ACTUAL_USER="${ACTUAL_USER:-${SUDO_USER:-$USER}}" ACTUAL_USER="${ACTUAL_USER:-${SUDO_USER:-$USER}}"
@@ -242,6 +247,7 @@ networks:
" "
fi fi
backup_if_exists docker-compose.yml
cat > docker-compose.yml << N8N_COMPOSE cat > docker-compose.yml << N8N_COMPOSE
name: n8n name: n8n
@@ -266,6 +272,7 @@ services:
${_CADDY_NET_BLOCK}${_CADDY_NET_SECTION} ${_CADDY_NET_BLOCK}${_CADDY_NET_SECTION}
N8N_COMPOSE N8N_COMPOSE
backup_if_exists .env
cat > .env << N8N_ENV cat > .env << N8N_ENV
# n8n environment — edit before starting if needed # n8n environment — edit before starting if needed
N8N_HOST=n8n N8N_HOST=n8n
+7
View File
@@ -70,6 +70,11 @@ if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
mkdir -p "$_dir" mkdir -p "$_dir"
cat > "$_dir/README.md" cat > "$_dir/README.md"
} }
backup_if_exists() {
local _file="$1"
[ -f "$_file" ] || return 0
cp -p "$_file" "${_file}.bak.$(date +%Y%m%d-%H%M%S)" 2>/dev/null
}
# Match common.sh's eval-based pattern so local vars in install_* are set correctly # Match common.sh's eval-based pattern so local vars in install_* are set correctly
prompt_text() { prompt_text() {
@@ -260,6 +265,7 @@ networks:
fi fi
# ── docker-compose.yml ────────────────────────────────────────────────── # ── docker-compose.yml ──────────────────────────────────────────────────
backup_if_exists docker-compose.yml
cat > docker-compose.yml << NCCOMPOSE cat > docker-compose.yml << NCCOMPOSE
name: nextcloud name: nextcloud
services: services:
@@ -290,6 +296,7 @@ ${_CADDY_NET_BLOCK}${_CADDY_NET_SECTION}
NCCOMPOSE NCCOMPOSE
# ── .env ──────────────────────────────────────────────────────────────── # ── .env ────────────────────────────────────────────────────────────────
backup_if_exists .env
cat > .env << NCENV cat > .env << NCENV
TZ=$TZ_VAL TZ=$TZ_VAL
CADDY_NET=$SITE_CADDY_NET CADDY_NET=$SITE_CADDY_NET
+7
View File
@@ -184,6 +184,11 @@ CBLOCK
mkdir -p "$_dir" mkdir -p "$_dir"
cat > "$_dir/README.md" cat > "$_dir/README.md"
} }
backup_if_exists() {
local _file="$1"
[ -f "$_file" ] || return 0
cp -p "$_file" "${_file}.bak.$(date +%Y%m%d-%H%M%S)" 2>/dev/null
}
fi fi
# Globals — ACTUAL_USER/ACTUAL_HOME must come before DOCKER_DIR # Globals — ACTUAL_USER/ACTUAL_HOME must come before DOCKER_DIR
@@ -309,6 +314,7 @@ networks:
" "
fi fi
backup_if_exists docker-compose.yml
cat > docker-compose.yml << NTFY_COMPOSE cat > docker-compose.yml << NTFY_COMPOSE
name: $CONTAINER name: $CONTAINER
@@ -329,6 +335,7 @@ services:
${_CADDY_NET_BLOCK}${_CADDY_NET_SECTION} ${_CADDY_NET_BLOCK}${_CADDY_NET_SECTION}
NTFY_COMPOSE NTFY_COMPOSE
backup_if_exists .env
cat > .env << NTFY_ENV cat > .env << NTFY_ENV
TZ=${SITE_TZ:-$(cat /etc/timezone 2>/dev/null || echo UTC)} TZ=${SITE_TZ:-$(cat /etc/timezone 2>/dev/null || echo UTC)}
CADDY_NET=$SITE_CADDY_NET CADDY_NET=$SITE_CADDY_NET
+7
View File
@@ -70,6 +70,11 @@ if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
mkdir -p "$_dir" mkdir -p "$_dir"
cat > "$_dir/README.md" cat > "$_dir/README.md"
} }
backup_if_exists() {
local _file="$1"
[ -f "$_file" ] || return 0
cp -p "$_file" "${_file}.bak.$(date +%Y%m%d-%H%M%S)" 2>/dev/null
}
# Match common.sh's eval-based pattern so local vars in install_* are set correctly # Match common.sh's eval-based pattern so local vars in install_* are set correctly
prompt_text() { prompt_text() {
@@ -280,6 +285,7 @@ networks:
fi fi
# ── docker-compose.yml ────────────────────────────────────────────────── # ── docker-compose.yml ──────────────────────────────────────────────────
backup_if_exists docker-compose.yml
cat > docker-compose.yml << OOCOMPOSE cat > docker-compose.yml << OOCOMPOSE
name: onlyoffice name: onlyoffice
services: services:
@@ -299,6 +305,7 @@ ${_CADDY_NET_BLOCK}${_CADDY_NET_SECTION}
OOCOMPOSE OOCOMPOSE
# ── .env ──────────────────────────────────────────────────────────────── # ── .env ────────────────────────────────────────────────────────────────
backup_if_exists .env
cat > .env << OOENV cat > .env << OOENV
CADDY_NET=$SITE_CADDY_NET CADDY_NET=$SITE_CADDY_NET
# JWT authentication — keep JWT_SECRET private # JWT authentication — keep JWT_SECRET private
+7
View File
@@ -210,6 +210,11 @@ CBLOCK
mkdir -p "$_dir" mkdir -p "$_dir"
cat > "$_dir/README.md" cat > "$_dir/README.md"
} }
backup_if_exists() {
local _file="$1"
[ -f "$_file" ] || return 0
cp -p "$_file" "${_file}.bak.$(date +%Y%m%d-%H%M%S)" 2>/dev/null
}
fi fi
# Globals — ACTUAL_USER/ACTUAL_HOME must come before DOCKER_DIR # Globals — ACTUAL_USER/ACTUAL_HOME must come before DOCKER_DIR
@@ -325,6 +330,7 @@ networks:
# example — NET_ADMIN specifically is only needed if this instance is # example — NET_ADMIN specifically is only needed if this instance is
# ever used as a DHCP server too, which it isn't here, but the other two # ever used as a DHCP server too, which it isn't here, but the other two
# (SYS_TIME, SYS_NICE) are part of that same documented baseline. # (SYS_TIME, SYS_NICE) are part of that same documented baseline.
backup_if_exists docker-compose.yml
cat > docker-compose.yml << PIHOLE_COMPOSE cat > docker-compose.yml << PIHOLE_COMPOSE
name: pihole name: pihole
@@ -351,6 +357,7 @@ services:
${_CADDY_NET_BLOCK}${_CADDY_NET_SECTION} ${_CADDY_NET_BLOCK}${_CADDY_NET_SECTION}
PIHOLE_COMPOSE PIHOLE_COMPOSE
backup_if_exists .env
cat > .env << PIHOLE_ENV cat > .env << PIHOLE_ENV
TZ=${SITE_TZ:-$(cat /etc/timezone 2>/dev/null || echo UTC)} TZ=${SITE_TZ:-$(cat /etc/timezone 2>/dev/null || echo UTC)}
# Admin web UI password (System Console / login screen). # Admin web UI password (System Console / login screen).
+6
View File
@@ -177,6 +177,11 @@ CBLOCK
mkdir -p "$_dir" mkdir -p "$_dir"
cat > "$_dir/README.md" cat > "$_dir/README.md"
} }
backup_if_exists() {
local _file="$1"
[ -f "$_file" ] || return 0
cp -p "$_file" "${_file}.bak.$(date +%Y%m%d-%H%M%S)" 2>/dev/null
}
fi fi
ACTUAL_USER="${ACTUAL_USER:-${SUDO_USER:-$USER}}" ACTUAL_USER="${ACTUAL_USER:-${SUDO_USER:-$USER}}"
@@ -242,6 +247,7 @@ networks:
" "
fi fi
backup_if_exists docker-compose.yml
cat > docker-compose.yml << PORTAINER_COMPOSE cat > docker-compose.yml << PORTAINER_COMPOSE
name: portainer name: portainer
+1
View File
@@ -1746,6 +1746,7 @@ _pstn_apply_settings() {
# Direct") — unquoted, bash's `source` would treat the second word of # Direct") — unquoted, bash's `source` would treat the second word of
# any such value as a command to run ("Direct: command not found"), # any such value as a command to run ("Direct: command not found"),
# confirmed live while testing the multi-IP change. # confirmed live while testing the multi-IP change.
backup_if_exists "$EA_DIR/.pstn-trunk.env"
cat > "$EA_DIR/.pstn-trunk.env" << ENV cat > "$EA_DIR/.pstn-trunk.env" << ENV
PROVIDER_NAME="${PROVIDER_NAME}" PROVIDER_NAME="${PROVIDER_NAME}"
TRUNK_SERVER="${SERVER}" TRUNK_SERVER="${SERVER}"
+7
View File
@@ -91,6 +91,11 @@ if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
mkdir -p "$_dir" mkdir -p "$_dir"
cat > "$_dir/README.md" cat > "$_dir/README.md"
} }
backup_if_exists() {
local _file="$1"
[ -f "$_file" ] || return 0
cp -p "$_file" "${_file}.bak.$(date +%Y%m%d-%H%M%S)" 2>/dev/null
}
fi fi
# Globals — ACTUAL_USER/ACTUAL_HOME must come before DOCKER_DIR # Globals — ACTUAL_USER/ACTUAL_HOME must come before DOCKER_DIR
@@ -230,6 +235,7 @@ install_rustdesk() {
prompt_yn "Require encrypted connections only? (recommended) (y/n):" "y" _enc prompt_yn "Require encrypted connections only? (recommended) (y/n):" "y" _enc
[ "$_enc" = "n" ] || [ "$_enc" = "N" ] && ENCRYPTED_ONLY="0" [ "$_enc" = "n" ] || [ "$_enc" = "N" ] && ENCRYPTED_ONLY="0"
backup_if_exists docker-compose.yml
cat > docker-compose.yml << RD_COMPOSE cat > docker-compose.yml << RD_COMPOSE
name: $CONTAINER name: $CONTAINER
@@ -251,6 +257,7 @@ services:
- ./rustdesk_data:/data - ./rustdesk_data:/data
RD_COMPOSE RD_COMPOSE
backup_if_exists .env
cat > .env << RD_ENV cat > .env << RD_ENV
# ── General ─────────────────────────────────────────────────────────────────── # ── General ───────────────────────────────────────────────────────────────────
TZ=$TZ_VAL TZ=$TZ_VAL
+6
View File
@@ -60,6 +60,11 @@ if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
mkdir -p "$_dir" mkdir -p "$_dir"
cat > "$_dir/README.md" cat > "$_dir/README.md"
} }
backup_if_exists() {
local _file="$1"
[ -f "$_file" ] || return 0
cp -p "$_file" "${_file}.bak.$(date +%Y%m%d-%H%M%S)" 2>/dev/null
}
fi fi
# Globals — ACTUAL_USER/ACTUAL_HOME must come before DOCKER_DIR # Globals — ACTUAL_USER/ACTUAL_HOME must come before DOCKER_DIR
@@ -176,6 +181,7 @@ EOF
prompt_text " WEB_EXT_API_KEY (e.g. user:12345678:901), blank to skip:" "" FF_KEY prompt_text " WEB_EXT_API_KEY (e.g. user:12345678:901), blank to skip:" "" FF_KEY
prompt_text " WEB_EXT_API_SECRET, blank to skip:" "" FF_SECRET prompt_text " WEB_EXT_API_SECRET, blank to skip:" "" FF_SECRET
if [ -n "$FF_KEY" ] && [ -n "$FF_SECRET" ]; then if [ -n "$FF_KEY" ] && [ -n "$FF_SECRET" ]; then
backup_if_exists "$SS_DIR/.env"
cat > "$SS_DIR/.env" << ENVEOF cat > "$SS_DIR/.env" << ENVEOF
WEB_EXT_API_KEY="$FF_KEY" WEB_EXT_API_KEY="$FF_KEY"
WEB_EXT_API_SECRET="$FF_SECRET" WEB_EXT_API_SECRET="$FF_SECRET"
+7
View File
@@ -177,6 +177,11 @@ CBLOCK
mkdir -p "$_dir" mkdir -p "$_dir"
cat > "$_dir/README.md" cat > "$_dir/README.md"
} }
backup_if_exists() {
local _file="$1"
[ -f "$_file" ] || return 0
cp -p "$_file" "${_file}.bak.$(date +%Y%m%d-%H%M%S)" 2>/dev/null
}
fi fi
ACTUAL_USER="${ACTUAL_USER:-${SUDO_USER:-$USER}}" ACTUAL_USER="${ACTUAL_USER:-${SUDO_USER:-$USER}}"
@@ -242,6 +247,7 @@ networks:
" "
fi fi
backup_if_exists docker-compose.yml
cat > docker-compose.yml << PDF_COMPOSE cat > docker-compose.yml << PDF_COMPOSE
name: stirling-pdf name: stirling-pdf
@@ -261,6 +267,7 @@ services:
${_CADDY_NET_BLOCK}${_CADDY_NET_SECTION} ${_CADDY_NET_BLOCK}${_CADDY_NET_SECTION}
PDF_COMPOSE PDF_COMPOSE
backup_if_exists .env
cat > .env << PDF_ENV cat > .env << PDF_ENV
# Stirling PDF configuration # Stirling PDF configuration
+7
View File
@@ -147,6 +147,11 @@ CBLOCK
mkdir -p "$_dir" mkdir -p "$_dir"
cat > "$_dir/README.md" cat > "$_dir/README.md"
} }
backup_if_exists() {
local _file="$1"
[ -f "$_file" ] || return 0
cp -p "$_file" "${_file}.bak.$(date +%Y%m%d-%H%M%S)" 2>/dev/null
}
fi fi
# Globals — ACTUAL_USER/ACTUAL_HOME must come before DOCKER_DIR # Globals — ACTUAL_USER/ACTUAL_HOME must come before DOCKER_DIR
@@ -225,6 +230,7 @@ networks:
" "
fi fi
backup_if_exists docker-compose.yml
cat > docker-compose.yml << EOF cat > docker-compose.yml << EOF
name: syncthing name: syncthing
@@ -249,6 +255,7 @@ services:
${_CADDY_NET_BLOCK}${_CADDY_NET_SECTION} ${_CADDY_NET_BLOCK}${_CADDY_NET_SECTION}
EOF EOF
backup_if_exists .env
cat > .env << ENV cat > .env << ENV
CADDY_NET=$SITE_CADDY_NET CADDY_NET=$SITE_CADDY_NET
PUID=$PUID PUID=$PUID
+7
View File
@@ -180,6 +180,11 @@ CBLOCK
mkdir -p "$_dir" mkdir -p "$_dir"
cat > "$_dir/README.md" cat > "$_dir/README.md"
} }
backup_if_exists() {
local _file="$1"
[ -f "$_file" ] || return 0
cp -p "$_file" "${_file}.bak.$(date +%Y%m%d-%H%M%S)" 2>/dev/null
}
fi fi
# Globals — ACTUAL_USER/ACTUAL_HOME must come before DOCKER_DIR # Globals — ACTUAL_USER/ACTUAL_HOME must come before DOCKER_DIR
@@ -474,6 +479,7 @@ networks:
- \"${PROTO_MIN}-${PROTO_MAX}:${PROTO_MIN}-${PROTO_MAX}/udp\"" - \"${PROTO_MIN}-${PROTO_MAX}:${PROTO_MIN}-${PROTO_MAX}/udp\""
fi fi
backup_if_exists docker-compose.yml
cat > docker-compose.yml << TRACCAR_COMPOSE cat > docker-compose.yml << TRACCAR_COMPOSE
name: $CONTAINER name: $CONTAINER
@@ -560,6 +566,7 @@ SMS_HTTP_PASSWORD=$SMS_HTTP_PASSWORD
fi fi
fi fi
backup_if_exists .env
cat > .env << TRACCAR_ENV cat > .env << TRACCAR_ENV
TZ=$TZ_VAL TZ=$TZ_VAL
CADDY_NET=$SITE_CADDY_NET CADDY_NET=$SITE_CADDY_NET
+7
View File
@@ -89,6 +89,11 @@ if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
mkdir -p "$_dir" mkdir -p "$_dir"
cat > "$_dir/README.md" cat > "$_dir/README.md"
} }
backup_if_exists() {
local _file="$1"
[ -f "$_file" ] || return 0
cp -p "$_file" "${_file}.bak.$(date +%Y%m%d-%H%M%S)" 2>/dev/null
}
fi fi
# Globals — ACTUAL_USER/ACTUAL_HOME must come before DOCKER_DIR # Globals — ACTUAL_USER/ACTUAL_HOME must come before DOCKER_DIR
@@ -238,6 +243,7 @@ networks:
fi fi
# Unquoted heredoc; ${...} used for caddy_net vars; all Docker Compose vars escaped with \$ # Unquoted heredoc; ${...} used for caddy_net vars; all Docker Compose vars escaped with \$
backup_if_exists docker-compose.yml
cat > docker-compose.yml << UNIFI_COMPOSE cat > docker-compose.yml << UNIFI_COMPOSE
name: $PROJECT name: $PROJECT
@@ -286,6 +292,7 @@ configs:
db.getSiblingDB("\${MONGO_DBNAME}_stat").createUser({user: "\${MONGO_USER}", pwd: "\${MONGO_PASS}", roles: [{role: "\${MONGO_ROLE}", db: "\${MONGO_DBNAME}_stat"}]}); db.getSiblingDB("\${MONGO_DBNAME}_stat").createUser({user: "\${MONGO_USER}", pwd: "\${MONGO_PASS}", roles: [{role: "\${MONGO_ROLE}", db: "\${MONGO_DBNAME}_stat"}]});
UNIFI_COMPOSE UNIFI_COMPOSE
backup_if_exists .env
cat > .env << UNIFI_ENV cat > .env << UNIFI_ENV
# ── General ─────────────────────────────────────────────────────────────────── # ── General ───────────────────────────────────────────────────────────────────
TZ=$TZ_VAL TZ=$TZ_VAL
+6
View File
@@ -177,6 +177,11 @@ CBLOCK
mkdir -p "$_dir" mkdir -p "$_dir"
cat > "$_dir/README.md" cat > "$_dir/README.md"
} }
backup_if_exists() {
local _file="$1"
[ -f "$_file" ] || return 0
cp -p "$_file" "${_file}.bak.$(date +%Y%m%d-%H%M%S)" 2>/dev/null
}
fi fi
# Globals — ACTUAL_USER/ACTUAL_HOME must come before DOCKER_DIR # Globals — ACTUAL_USER/ACTUAL_HOME must come before DOCKER_DIR
@@ -277,6 +282,7 @@ ${UPTIME_ENV_BLOCK}
" "
fi fi
backup_if_exists docker-compose.yml
cat > docker-compose.yml << UPTIME_COMPOSE cat > docker-compose.yml << UPTIME_COMPOSE
name: uptime-kuma name: uptime-kuma
+7
View File
@@ -194,6 +194,11 @@ CBLOCK
mkdir -p "$_dir" mkdir -p "$_dir"
cat > "$_dir/README.md" cat > "$_dir/README.md"
} }
backup_if_exists() {
local _file="$1"
[ -f "$_file" ] || return 0
cp -p "$_file" "${_file}.bak.$(date +%Y%m%d-%H%M%S)" 2>/dev/null
}
fi fi
# Globals — ACTUAL_USER/ACTUAL_HOME must come before DOCKER_DIR # Globals — ACTUAL_USER/ACTUAL_HOME must come before DOCKER_DIR
@@ -420,6 +425,7 @@ networks:
" "
fi fi
backup_if_exists docker-compose.yml
cat > docker-compose.yml << VW_COMPOSE cat > docker-compose.yml << VW_COMPOSE
name: $CONTAINER name: $CONTAINER
@@ -437,6 +443,7 @@ services:
${_CADDY_NET_BLOCK}${_CADDY_NET_SECTION} ${_CADDY_NET_BLOCK}${_CADDY_NET_SECTION}
VW_COMPOSE VW_COMPOSE
backup_if_exists .env
cat > .env << VW_ENV cat > .env << VW_ENV
# ── General ─────────────────────────────────────────────────────────────────── # ── General ───────────────────────────────────────────────────────────────────
TZ=$TZ_VAL TZ=$TZ_VAL
+7
View File
@@ -64,6 +64,11 @@ if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
mkdir -p "$_dir" mkdir -p "$_dir"
cat > "$_dir/README.md" cat > "$_dir/README.md"
} }
backup_if_exists() {
local _file="$1"
[ -f "$_file" ] || return 0
cp -p "$_file" "${_file}.bak.$(date +%Y%m%d-%H%M%S)" 2>/dev/null
}
fi fi
# Globals — ACTUAL_USER/ACTUAL_HOME must come before DOCKER_DIR # Globals — ACTUAL_USER/ACTUAL_HOME must come before DOCKER_DIR
@@ -151,6 +156,7 @@ networks:
" "
fi fi
backup_if_exists docker-compose.yml
cat > docker-compose.yml << WT_COMPOSE cat > docker-compose.yml << WT_COMPOSE
name: watchtower name: watchtower
@@ -179,6 +185,7 @@ ${_CADDY_NET_BLOCK}${_CADDY_NET_SECTION}
WT_COMPOSE WT_COMPOSE
# Create .env # Create .env
backup_if_exists .env
cat > .env << WT_ENV cat > .env << WT_ENV
# Watchtower Configuration # Watchtower Configuration
# ========================= # =========================
+7
View File
@@ -68,6 +68,11 @@ if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
mkdir -p "$_dir" mkdir -p "$_dir"
cat > "$_dir/README.md" cat > "$_dir/README.md"
} }
backup_if_exists() {
local _file="$1"
[ -f "$_file" ] || return 0
cp -p "$_file" "${_file}.bak.$(date +%Y%m%d-%H%M%S)" 2>/dev/null
}
fi fi
# Globals — ACTUAL_USER/ACTUAL_HOME must come before DOCKER_DIR # Globals — ACTUAL_USER/ACTUAL_HOME must come before DOCKER_DIR
@@ -126,6 +131,7 @@ install_watchyourlan() {
prompt_text "GUI port [8840]:" "8840" GUI_PORT prompt_text "GUI port [8840]:" "8840" GUI_PORT
[ -z "$GUI_PORT" ] && GUI_PORT="8840" [ -z "$GUI_PORT" ] && GUI_PORT="8840"
backup_if_exists docker-compose.yml
cat > docker-compose.yml << 'WYL_COMPOSE' cat > docker-compose.yml << 'WYL_COMPOSE'
name: watchyourlan name: watchyourlan
@@ -141,6 +147,7 @@ services:
- ./watchyourlan_data:/data - ./watchyourlan_data:/data
WYL_COMPOSE WYL_COMPOSE
backup_if_exists .env
cat > .env << WYL_ENV cat > .env << WYL_ENV
# ── General ─────────────────────────────────────────────────────────────────── # ── General ───────────────────────────────────────────────────────────────────
TZ=$TZ_VAL TZ=$TZ_VAL
+7
View File
@@ -199,6 +199,11 @@ CBLOCK
mkdir -p "$_dir" mkdir -p "$_dir"
cat > "$_dir/README.md" cat > "$_dir/README.md"
} }
backup_if_exists() {
local _file="$1"
[ -f "$_file" ] || return 0
cp -p "$_file" "${_file}.bak.$(date +%Y%m%d-%H%M%S)" 2>/dev/null
}
fi fi
# Globals — ACTUAL_USER/ACTUAL_HOME must come before DOCKER_DIR # Globals — ACTUAL_USER/ACTUAL_HOME must come before DOCKER_DIR
@@ -302,6 +307,7 @@ networks:
" "
fi fi
backup_if_exists docker-compose.yml
cat > docker-compose.yml << WGEASY_COMPOSE cat > docker-compose.yml << WGEASY_COMPOSE
name: wg-easy name: wg-easy
@@ -332,6 +338,7 @@ services:
${_CADDY_NET_BLOCK}${_CADDY_NET_SECTION} ${_CADDY_NET_BLOCK}${_CADDY_NET_SECTION}
WGEASY_COMPOSE WGEASY_COMPOSE
backup_if_exists .env
cat > .env << WGEASY_ENV cat > .env << WGEASY_ENV
WG_HOST=$WG_HOST WG_HOST=$WG_HOST
# Plain-text password — used only if PASSWORD_HASH could not be generated above # Plain-text password — used only if PASSWORD_HASH could not be generated above
+6
View File
@@ -169,6 +169,11 @@ CBLOCK
mkdir -p "$_dir" mkdir -p "$_dir"
cat > "$_dir/README.md" cat > "$_dir/README.md"
} }
backup_if_exists() {
local _file="$1"
[ -f "$_file" ] || return 0
cp -p "$_file" "${_file}.bak.$(date +%Y%m%d-%H%M%S)" 2>/dev/null
}
fi fi
# Globals — ACTUAL_USER/ACTUAL_HOME must come before DOCKER_DIR # Globals — ACTUAL_USER/ACTUAL_HOME must come before DOCKER_DIR
@@ -404,6 +409,7 @@ DOCKERFILE
# network_mode: host — server.py reaches Wolf at localhost:47989 directly. # network_mode: host — server.py reaches Wolf at localhost:47989 directly.
# Docker socket (ro) — server.py calls `docker logs wolf` to read secrets. # Docker socket (ro) — server.py calls `docker logs wolf` to read secrets.
log_info "Writing docker-compose.yml..." log_info "Writing docker-compose.yml..."
backup_if_exists "$WOLFPAIR_DIR/docker-compose.yml"
cat > "$WOLFPAIR_DIR/docker-compose.yml" << 'COMPOSE' cat > "$WOLFPAIR_DIR/docker-compose.yml" << 'COMPOSE'
name: wolf-pair name: wolf-pair
+7
View File
@@ -179,6 +179,11 @@ CBLOCK
mkdir -p "$_dir" mkdir -p "$_dir"
cat > "$_dir/README.md" cat > "$_dir/README.md"
} }
backup_if_exists() {
local _file="$1"
[ -f "$_file" ] || return 0
cp -p "$_file" "${_file}.bak.$(date +%Y%m%d-%H%M%S)" 2>/dev/null
}
fi fi
# Globals — ACTUAL_USER/ACTUAL_HOME must come before DOCKER_DIR # Globals — ACTUAL_USER/ACTUAL_HOME must come before DOCKER_DIR
@@ -875,6 +880,7 @@ UDEV
# if the drive is remounted at a different path. IP/MAC/render-node are # if the drive is remounted at a different path. IP/MAC/render-node are
# baked in at install time because they're hardware-specific and not stored # baked in at install time because they're hardware-specific and not stored
# in .env — use 'manage.sh update-network' to regenerate if they change. # in .env — use 'manage.sh update-network' to regenerate if they change.
backup_if_exists docker-compose.yml
cat > docker-compose.yml << EOF cat > docker-compose.yml << EOF
name: wolf name: wolf
@@ -922,6 +928,7 @@ EOF
log_success "docker-compose.yml created" log_success "docker-compose.yml created"
# Save the game storage path so it's visible and editable later # Save the game storage path so it's visible and editable later
backup_if_exists .env
cat > .env << EOF cat > .env << EOF
# Wolf game/ROM storage root — edit this and run ./manage.sh update-storage to apply # Wolf game/ROM storage root — edit this and run ./manage.sh update-storage to apply
GAME_STORAGE_DIR=${GAME_STORAGE_DIR} GAME_STORAGE_DIR=${GAME_STORAGE_DIR}
+7
View File
@@ -207,6 +207,11 @@ CBLOCK
cat > "$_dir/README.md" cat > "$_dir/README.md"
chown "$ACTUAL_USER:$ACTUAL_USER" "$_dir/README.md" 2>/dev/null || true chown "$ACTUAL_USER:$ACTUAL_USER" "$_dir/README.md" 2>/dev/null || true
} }
backup_if_exists() {
local _file="$1"
[ -f "$_file" ] || return 0
cp -p "$_file" "${_file}.bak.$(date +%Y%m%d-%H%M%S)" 2>/dev/null
}
fi fi
ACTUAL_USER="${ACTUAL_USER:-${SUDO_USER:-$USER}}" ACTUAL_USER="${ACTUAL_USER:-${SUDO_USER:-$USER}}"
@@ -361,6 +366,7 @@ PHPINI
" "
fi fi
backup_if_exists docker-compose.yml
cat > docker-compose.yml << WPCOMPOSE cat > docker-compose.yml << WPCOMPOSE
name: $CONTAINER name: $CONTAINER
@@ -398,6 +404,7 @@ networks:
${_CADDY_NET_SECTION} ${_CADDY_NET_SECTION}
WPCOMPOSE WPCOMPOSE
backup_if_exists .env
cat > .env << WPENV cat > .env << WPENV
TZ=$TZ_VAL TZ=$TZ_VAL
CADDY_NET=$SITE_CADDY_NET CADDY_NET=$SITE_CADDY_NET