diff --git a/lib/common.sh b/lib/common.sh index 9f24c6c..a80ad63 100644 --- a/lib/common.sh +++ b/lib/common.sh @@ -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 diff --git a/services/actualbudget.sh b/services/actualbudget.sh index 8fa71f9..7da0e9c 100644 --- a/services/actualbudget.sh +++ b/services/actualbudget.sh @@ -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 diff --git a/services/arm.sh b/services/arm.sh index 3d84efa..7f5dd19 100644 --- a/services/arm.sh +++ b/services/arm.sh @@ -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 diff --git a/services/audiobookshelf.sh b/services/audiobookshelf.sh index 6149f6f..c230602 100644 --- a/services/audiobookshelf.sh +++ b/services/audiobookshelf.sh @@ -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 diff --git a/services/authelia.sh b/services/authelia.sh index 9a57e56..b11e138 100644 --- a/services/authelia.sh +++ b/services/authelia.sh @@ -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 ──────────────────────── diff --git a/services/ddclient.sh b/services/ddclient.sh index 49b9bd9..3d8aef5 100644 --- a/services/ddclient.sh +++ b/services/ddclient.sh @@ -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 diff --git a/services/emby.sh b/services/emby.sh index 280c31a..e8edbdc 100644 --- a/services/emby.sh +++ b/services/emby.sh @@ -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 diff --git a/services/filebrowser.sh b/services/filebrowser.sh index 0de8c4a..13ef4c3 100644 --- a/services/filebrowser.sh +++ b/services/filebrowser.sh @@ -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 diff --git a/services/frigate-audio.sh b/services/frigate-audio.sh index 9406975..4cad85f 100644 --- a/services/frigate-audio.sh +++ b/services/frigate-audio.sh @@ -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="" diff --git a/services/frigate.sh b/services/frigate.sh index 836961b..98557d9 100644 --- a/services/frigate.sh +++ b/services/frigate.sh @@ -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="" diff --git a/services/homeassistant.sh b/services/homeassistant.sh index 624ae25..060daf9 100644 --- a/services/homeassistant.sh +++ b/services/homeassistant.sh @@ -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 diff --git a/services/immich.sh b/services/immich.sh index 0bce477..80b60c3 100644 --- a/services/immich.sh +++ b/services/immich.sh @@ -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 diff --git a/services/jellyfin.sh b/services/jellyfin.sh index 8211150..a1d4d67 100644 --- a/services/jellyfin.sh +++ b/services/jellyfin.sh @@ -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. diff --git a/services/lyrion.sh b/services/lyrion.sh index ecdea8c..1844009 100644 --- a/services/lyrion.sh +++ b/services/lyrion.sh @@ -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 diff --git a/services/magicmirror.sh b/services/magicmirror.sh index e9d0851..e0bb4e5 100644 --- a/services/magicmirror.sh +++ b/services/magicmirror.sh @@ -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 diff --git a/services/mealie.sh b/services/mealie.sh index 1db9eb3..7f07d28 100644 --- a/services/mealie.sh +++ b/services/mealie.sh @@ -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 diff --git a/services/minecraft.sh b/services/minecraft.sh index ebd90b8..93a6daa 100644 --- a/services/minecraft.sh +++ b/services/minecraft.sh @@ -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="" diff --git a/services/ntfy.sh b/services/ntfy.sh index e5ecd46..fb004d4 100644 --- a/services/ntfy.sh +++ b/services/ntfy.sh @@ -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 diff --git a/services/sky-cam.sh b/services/sky-cam.sh index ef34630..985c0cd 100644 --- a/services/sky-cam.sh +++ b/services/sky-cam.sh @@ -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" diff --git a/setup.sh b/setup.sh index b57c78e..eaca02f 100755 --- a/setup.sh +++ b/setup.sh @@ -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:-}]:" "$_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