Add site-wide defaults: timezone, domain, Caddy network

Introduces a one-time configuration wizard (sudo ./setup.sh configure)
that stores SITE_TZ, SITE_DOMAIN, and SITE_CADDY_NET in ~/docker/.config.
Every service now uses these as prompt defaults so the user types common
values once instead of re-answering the same questions for each service.

- lib/common.sh: load_site_config / save_site_config; auto-loads on source;
  backward-compat BASE_DOMAIN alias kept for old .config files
- setup.sh: run_site_configure wizard; first-run offer after base install;
  `sudo ./setup.sh configure` command to update defaults at any time
- 14 services: TZ_VAL now honours SITE_TZ, falling back to /etc/timezone
- 3 inline-heredoc services (filebrowser, homeassistant, ntfy): same fix
- authelia: SITE_TZ/SITE_DOMAIN as prompt defaults; SITE_CADDY_NET replaces
  hardcoded caddy_net throughout (env, compose patch, network creation)
- minecraft, frigate-audio: simplify BASE_DOMAIN read to use SITE_DOMAIN
- sky-cam: SITE_TZ as default for timezone prompt

https://claude.ai/code/session_01Y4dMKtkqkpvmgDKoRdzhTG
This commit is contained in:
Claude
2026-06-03 22:13:59 +00:00
parent cfad0ebd20
commit 56d2f9e85b
20 changed files with 121 additions and 29 deletions
+49
View File
@@ -47,6 +47,55 @@ register_service() {
SERVICE_ORDER+=("$name")
}
# ── Site-wide defaults ────────────────────────────────────────────────────────
# Stored in $DOCKER_DIR/.config (key=value, one per line).
# Service modules read these as prompt defaults so the user only types
# timezone, domain, and Caddy network once. Run: sudo ./setup.sh configure
SITE_TZ=""
SITE_DOMAIN=""
SITE_CADDY_NET="caddy_net"
SITE_PUID=""
SITE_PGID=""
load_site_config() {
local cfg="$DOCKER_DIR/.config"
[ -f "$cfg" ] || return 0
local key val
while IFS='=' read -r key val; do
[[ "$key" =~ ^[[:space:]]*# ]] && continue
[[ -z "${key// }" ]] && continue
case "$key" in
SITE_TZ) SITE_TZ="$val" ;;
SITE_DOMAIN) SITE_DOMAIN="$val" ;;
SITE_CADDY_NET) SITE_CADDY_NET="$val" ;;
SITE_PUID) SITE_PUID="$val" ;;
SITE_PGID) SITE_PGID="$val" ;;
BASE_DOMAIN) [ -z "$SITE_DOMAIN" ] && SITE_DOMAIN="$val" ;;
esac
done < "$cfg"
export SITE_TZ SITE_DOMAIN SITE_CADDY_NET SITE_PUID SITE_PGID
}
save_site_config() {
local cfg="$DOCKER_DIR/.config"
mkdir -p "$(dirname "$cfg")"
{
echo "# ubuntu-post-install site defaults"
echo "# Re-run wizard: sudo ./setup.sh configure"
[ -n "$SITE_TZ" ] && echo "SITE_TZ=$SITE_TZ"
[ -n "$SITE_DOMAIN" ] && echo "SITE_DOMAIN=$SITE_DOMAIN"
[ -n "$SITE_CADDY_NET" ] && echo "SITE_CADDY_NET=$SITE_CADDY_NET"
[ -n "$SITE_PUID" ] && echo "SITE_PUID=$SITE_PUID"
[ -n "$SITE_PGID" ] && echo "SITE_PGID=$SITE_PGID"
# Backward-compat alias for services that still read BASE_DOMAIN directly
[ -n "$SITE_DOMAIN" ] && echo "BASE_DOMAIN=$SITE_DOMAIN"
} > "$cfg"
chmod 600 "$cfg"
}
# Load immediately so all service modules inherit the values when sourced
load_site_config
# ── Pre-flight ───────────────────────────────────────────────────────────────
require_root() {
if [ "${EUID:-$(id -u)}" -ne 0 ]; then
+1 -1
View File
@@ -24,7 +24,7 @@ install_actualbudget() {
ensure_docker_dir_ownership "$AB_DIR"
cd "$AB_DIR" || return 1
local TZ_VAL; TZ_VAL=$(cat /etc/timezone 2>/dev/null || echo "UTC")
local TZ_VAL; TZ_VAL="${SITE_TZ:-$(cat /etc/timezone 2>/dev/null || echo UTC)}"
cat > docker-compose.yml << 'AB_COMPOSE'
name: actualbudget
+1 -1
View File
@@ -45,7 +45,7 @@ install_arm() {
cd "$ARM_DIR" || return 1
local TZ_VAL UID_VAL GID_VAL
TZ_VAL=$(cat /etc/timezone 2>/dev/null || echo "UTC")
TZ_VAL="${SITE_TZ:-$(cat /etc/timezone 2>/dev/null || echo UTC)}"
UID_VAL=$(id -u "$ACTUAL_USER"); GID_VAL=$(id -g "$ACTUAL_USER")
cat > docker-compose.yml << ARM_COMPOSE
+1 -1
View File
@@ -30,7 +30,7 @@ install_audiobookshelf() {
ensure_docker_dir_ownership "$ABS_DIR"
cd "$ABS_DIR" || return 1
local TZ_VAL; TZ_VAL=$(cat /etc/timezone 2>/dev/null || echo "UTC")
local TZ_VAL; TZ_VAL="${SITE_TZ:-$(cat /etc/timezone 2>/dev/null || echo UTC)}"
cat > docker-compose.yml << ABS_COMPOSE
name: audiobookshelf
+10 -8
View File
@@ -36,9 +36,10 @@ install_authelia() {
echo ""
echo " Authelia needs a few details to configure."
echo ""
local CADDY_NET="${SITE_CADDY_NET:-caddy_net}"
local AUTHELIA_DOMAIN AUTHELIA_ADMIN_USER AUTHELIA_ADMIN_DISPLAY AUTHELIA_ADMIN_EMAIL
local AUTHELIA_SMTP_HOST AUTHELIA_SMTP_PORT AUTHELIA_SMTP_USER AUTHELIA_SMTP_PASS AUTHELIA_TZ
prompt_text " Your domain (e.g., example.com):" "example.com" AUTHELIA_DOMAIN
prompt_text " Your domain (e.g., example.com):" "${SITE_DOMAIN:-example.com}" AUTHELIA_DOMAIN
prompt_text " Admin username:" "admin" AUTHELIA_ADMIN_USER
prompt_text " Admin display name:" "Administrator" AUTHELIA_ADMIN_DISPLAY
prompt_text " Admin email:" "admin@${AUTHELIA_DOMAIN}" AUTHELIA_ADMIN_EMAIL
@@ -46,7 +47,7 @@ install_authelia() {
prompt_text " SMTP port:" "587" AUTHELIA_SMTP_PORT
prompt_text " SMTP username (full email):" "authelia@${AUTHELIA_DOMAIN}" AUTHELIA_SMTP_USER
prompt_text " SMTP password:" "" AUTHELIA_SMTP_PASS
prompt_text " Timezone (e.g., America/New_York):" "America/New_York" AUTHELIA_TZ
prompt_text " Timezone (e.g., America/New_York):" "${SITE_TZ:-America/New_York}" AUTHELIA_TZ
# ── Secrets ──────────────────────────────────────────────────────────────
echo ""
@@ -81,7 +82,7 @@ install_authelia() {
cat > .env << AUTHELIA_ENV
MY_DOMAIN=${AUTHELIA_DOMAIN}
SMTP_USER=${AUTHELIA_SMTP_USER}
DOCKER_MY_NETWORK=caddy_net
DOCKER_MY_NETWORK=${CADDY_NET}
TZ=${AUTHELIA_TZ}
AUTHELIA_ENV
@@ -115,6 +116,7 @@ networks:
caddy_net:
external: true
AUTHELIA_COMPOSE
[ "$CADDY_NET" != "caddy_net" ] && sed -i "s/caddy_net/${CADDY_NET}/g" docker-compose.yml
# ── configuration.yml ────────────────────────────────────────────────────
cat > config/configuration.yml << AUTHELIA_CONFIG
@@ -199,12 +201,12 @@ AUTHELIA_USERS
chown -R 1000:1000 "$AUTHELIA_DIR/config" "$AUTHELIA_DIR/data"
log_success "Authelia configured at $AUTHELIA_DIR"
# ── caddy_net network ────────────────────────────────────────────────────
if ! docker network ls --format '{{.Name}}' | grep -q "^caddy_net$"; then
docker network create caddy_net >/dev/null 2>&1 && echo " ✓ Created docker network caddy_net" \
|| echo " ⚠ Failed to create caddy_net"
# ── Docker network ────────────────────────────────────────────────────────
if ! docker network ls --format '{{.Name}}' | grep -q "^${CADDY_NET}$"; then
docker network create "$CADDY_NET" >/dev/null 2>&1 && echo " ✓ Created docker network ${CADDY_NET}" \
|| echo " ⚠ Failed to create ${CADDY_NET}"
else
echo " ✓ Docker network caddy_net already exists"
echo " ✓ Docker network ${CADDY_NET} already exists"
fi
# ── Caddyfile forward-auth snippet + portal block ────────────────────────
+1 -1
View File
@@ -25,7 +25,7 @@ install_ddclient() {
ensure_docker_dir_ownership "$DDCLIENT_DIR"
cd "$DDCLIENT_DIR" || return 1
local TZ_VAL; TZ_VAL=$(cat /etc/timezone 2>/dev/null || echo "UTC")
local TZ_VAL; TZ_VAL="${SITE_TZ:-$(cat /etc/timezone 2>/dev/null || echo UTC)}"
cat > docker-compose.yml << 'DDCLIENT_COMPOSE'
name: ddclient
+1 -1
View File
@@ -34,7 +34,7 @@ install_emby() {
cd "$EMBY_DIR" || return 1
local TZ_VAL UID_VAL GID_VAL
TZ_VAL=$(cat /etc/timezone 2>/dev/null || echo "UTC")
TZ_VAL="${SITE_TZ:-$(cat /etc/timezone 2>/dev/null || echo UTC)}"
UID_VAL=$(id -u "$ACTUAL_USER"); GID_VAL=$(id -g "$ACTUAL_USER")
cat > docker-compose.yml << EMBY_COMPOSE
+1 -1
View File
@@ -33,7 +33,7 @@ services:
environment:
- PUID=$(id -u "$ACTUAL_USER")
- PGID=$(id -g "$ACTUAL_USER")
- TZ=$(cat /etc/timezone 2>/dev/null || echo "UTC")
- TZ=${SITE_TZ:-$(cat /etc/timezone 2>/dev/null || echo UTC)}
volumes:
- ${FB_PATH}:/srv
- ./database/filebrowser.db:/database/filebrowser.db
+1 -2
View File
@@ -98,8 +98,7 @@ install_frigate-audio() {
# ── Frigate public URL ─────────────────────────────────────────────────────
echo ""
local BASE_DOMAIN=""
[ -f "$DOCKER_DIR/.config" ] && BASE_DOMAIN=$(grep '^BASE_DOMAIN=' "$DOCKER_DIR/.config" 2>/dev/null | cut -d= -f2-)
local BASE_DOMAIN="${SITE_DOMAIN:-}"
local FRIGATE_PUBLIC_URL=""
if [ -n "$BASE_DOMAIN" ]; then
local _PFX=""
+1 -1
View File
@@ -33,7 +33,7 @@ install_frigate() {
ensure_docker_dir_ownership "$FRIGATE_DIR"
cd "$FRIGATE_DIR" || return 1
local TZ_VAL; TZ_VAL=$(cat /etc/timezone 2>/dev/null || echo "UTC")
local TZ_VAL; TZ_VAL="${SITE_TZ:-$(cat /etc/timezone 2>/dev/null || echo UTC)}"
# Hardware detection: include /dev/dri only when a render node exists
local DEVICE_BLOCK=""
+1 -1
View File
@@ -50,7 +50,7 @@ services:
restart: unless-stopped
privileged: true
environment:
- TZ=$(cat /etc/timezone 2>/dev/null || echo "UTC")
- TZ=${SITE_TZ:-$(cat /etc/timezone 2>/dev/null || echo UTC)}
volumes:
- ./config:/config
- /run/dbus:/run/dbus:ro
+1 -1
View File
@@ -112,7 +112,7 @@ install_immich() {
# ── Generate DB password ────────────────────────────────────────────────
local DB_PASS TZ_VAL
DB_PASS=$(openssl rand -base64 32 | tr -dc 'a-zA-Z0-9' | head -c 32)
TZ_VAL=$(cat /etc/timezone 2>/dev/null || echo "UTC")
TZ_VAL="${SITE_TZ:-$(cat /etc/timezone 2>/dev/null || echo UTC)}"
# ── Write docker-compose.yml ────────────────────────────────────────────
if [ -n "$EXTERNAL_LIBRARY" ]; then
+1 -1
View File
@@ -33,7 +33,7 @@ install_jellyfin() {
ensure_docker_dir_ownership "$JELLYFIN_DIR"
cd "$JELLYFIN_DIR" || return 1
local TZ_VAL; TZ_VAL=$(cat /etc/timezone 2>/dev/null || echo "UTC")
local TZ_VAL; TZ_VAL="${SITE_TZ:-$(cat /etc/timezone 2>/dev/null || echo UTC)}"
# Hardware acceleration: only wire /dev/dri through if a render node exists,
# otherwise the container would fail to start on a GPU-less host.
+1 -1
View File
@@ -33,7 +33,7 @@ install_lyrion() {
cd "$LYRION_DIR" || return 1
local TZ_VAL UID_VAL GID_VAL
TZ_VAL=$(cat /etc/timezone 2>/dev/null || echo "UTC")
TZ_VAL="${SITE_TZ:-$(cat /etc/timezone 2>/dev/null || echo UTC)}"
UID_VAL=$(id -u "$ACTUAL_USER"); GID_VAL=$(id -g "$ACTUAL_USER")
cat > docker-compose.yml << LYRION_COMPOSE
+1 -1
View File
@@ -33,7 +33,7 @@ install_magicmirror() {
mkdir -p "$MM_BASE"
chown "$ACTUAL_USER:$ACTUAL_USER" "$MM_BASE"
local TZ_VAL; TZ_VAL=$(cat /etc/timezone 2>/dev/null || echo "UTC")
local TZ_VAL; TZ_VAL="${SITE_TZ:-$(cat /etc/timezone 2>/dev/null || echo UTC)}"
local i MM_PORT MM_DIR
for i in $(seq 1 "$MM_COUNT"); do
+1 -1
View File
@@ -26,7 +26,7 @@ install_mealie() {
cd "$MEALIE_DIR" || return 1
local TZ_VAL UID_VAL GID_VAL
TZ_VAL=$(cat /etc/timezone 2>/dev/null || echo "UTC")
TZ_VAL="${SITE_TZ:-$(cat /etc/timezone 2>/dev/null || echo UTC)}"
UID_VAL=$(id -u "$ACTUAL_USER"); GID_VAL=$(id -g "$ACTUAL_USER")
cat > docker-compose.yml << MEALIE_COMPOSE
+1 -2
View File
@@ -953,8 +953,7 @@ except: pass
esac
local MC_DOMAIN=""
local BASE_DOMAIN=""
[ -f "$DOCKER_DIR/.config" ] && BASE_DOMAIN=$(grep '^BASE_DOMAIN=' "$DOCKER_DIR/.config" 2>/dev/null | cut -d= -f2-)
local BASE_DOMAIN="${SITE_DOMAIN:-}"
if [ "$USE_PLAYIT" = true ] || [ "$USE_PORTFORWARD" = true ]; then
if [ -n "$BASE_DOMAIN" ]; then
local _PREFIX=""
+1 -1
View File
@@ -38,7 +38,7 @@ services:
NTFY_COMPOSE
cat > .env << NTFY_ENV
TZ=$(cat /etc/timezone 2>/dev/null || echo "UTC")
TZ=${SITE_TZ:-$(cat /etc/timezone 2>/dev/null || echo UTC)}
NTFY_ENV
mkdir -p cache config
+1 -1
View File
@@ -72,7 +72,7 @@ install_sky-cam() {
local LATITUDE="" LONGITUDE="" TIMEZONE="" BASE_DIR="" CAMERAS_LIST="" SUNRISE_CAM=""
prompt_text " Latitude (decimal degrees) [0.0000]:" "0.0000" LATITUDE
prompt_text " Longitude (decimal degrees) [0.0000]:" "0.0000" LONGITUDE
prompt_text " Timezone [America/New_York]:" "America/New_York" TIMEZONE
prompt_text " Timezone [${SITE_TZ:-America/New_York}]:" "${SITE_TZ:-America/New_York}" TIMEZONE
echo ""
echo " Camera names are short identifiers, e.g.: east north south west"
+45 -2
View File
@@ -106,9 +106,42 @@ list_services() {
echo ""
}
# ── Site defaults wizard ──────────────────────────────────────────────────────
# Prompts for timezone, base domain, and Caddy network name; saves to .config.
# Run directly: sudo ./setup.sh configure
run_site_configure() {
local _sys_tz; _sys_tz=$(cat /etc/timezone 2>/dev/null || echo "UTC")
echo ""
echo "╔══════════════════════════════════════════════════════════════╗"
echo "║ Site defaults · pre-filled into every service prompt ║"
echo "╚══════════════════════════════════════════════════════════════╝"
echo ""
echo " These become the default answer each time a service asks for"
echo " timezone, domain, etc. Press Enter to keep the shown value."
echo ""
local _cur_tz="${SITE_TZ:-$_sys_tz}"
local _cur_dom="${SITE_DOMAIN:-}"
local _cur_net="${SITE_CADDY_NET:-caddy_net}"
prompt_text " Timezone [${_cur_tz}]:" "$_cur_tz" SITE_TZ
prompt_text " Base domain (e.g., example.com) [${_cur_dom:-<not set>}]:" "$_cur_dom" SITE_DOMAIN
prompt_text " Caddy Docker network [${_cur_net}]:" "$_cur_net" SITE_CADDY_NET
export SITE_TZ SITE_DOMAIN SITE_CADDY_NET
mkdir -p "$DOCKER_DIR"
save_site_config
log_success "Saved to $DOCKER_DIR/.config"
echo ""
}
# ── --list ───────────────────────────────────────────────────────────────────
if [ "$DO_LIST" = true ]; then list_services; exit 0; fi
# ── configure: show/update site-wide defaults ────────────────────────────────
if [ "${REQUESTED[*]:-}" = "configure" ]; then
require_root
run_site_configure
exit 0
fi
# ── Direct install: ./setup.sh caddy homeassistant ──────────────────────────
if [ "${#REQUESTED[@]}" -gt 0 ]; then
require_root
@@ -146,7 +179,17 @@ if ! command -v docker >/dev/null 2>&1; then
echo " Install with: curl -fsSL https://get.docker.com | sh"
fi
# 3) Offer Caddy first (most services proxy through it).
# 3) Offer site defaults wizard if .config has no SITE_TZ yet (first run).
if ! grep -q '^SITE_TZ=' "$DOCKER_DIR/.config" 2>/dev/null; then
echo ""
echo " No site defaults found. Setting them now pre-fills timezone, domain,"
echo " and Caddy network for every service — you type them once, not every time."
OFFER_CONFIG=""
prompt_yn "Configure site defaults now? (y/n):" "y" OFFER_CONFIG
[ "$OFFER_CONFIG" = "y" ] || [ "$OFFER_CONFIG" = "Y" ] && run_site_configure
fi
# 4) Offer Caddy first (most services proxy through it).
if [ -n "${SERVICE_GROUP[caddy]:-}" ] && ! is_installed caddy; then
echo ""
OFFER_CADDY=""
@@ -154,7 +197,7 @@ if [ -n "${SERVICE_GROUP[caddy]:-}" ] && ! is_installed caddy; then
[ "$OFFER_CADDY" = "y" ] || [ "$OFFER_CADDY" = "Y" ] && run_service caddy
fi
# 4) Category menu loop: pick a category → checklist → install → back to menu.
# 5) Category menu loop: pick a category → checklist → install → back to menu.
have_whiptail=false
command -v whiptail >/dev/null 2>&1 && have_whiptail=true