Pi stack behind Nginx Proxy Manager: remove Caddy and cert-sync (D-120)
The server publishes 8080 on the Kvitt Pi's LAN interfaces and trusts X-Forwarded-For only from HA_HOST, NPM's LAN address (an IPv4 literal, checked by install.sh). KVITT_PUBLIC_HOST (owner-chosen, not icehack.asuscomm.com, which is NPM's proxy host for Home Assistant) builds KVITT_PUBLIC_URL. Only fcm-service-account.json and rclone.conf are installed; install.sh removes the Caddyfile, the Home Assistant SSH key, known_hosts and the certs and caddy_data volumes left by the earlier stack. Health checks: port 8080, then through NPM. The cert-sync image leaves the workflow and build-images.sh. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TQd3pEP9UeN2uifbHknQ7B
This commit is contained in:
+42
-20
@@ -1,15 +1,17 @@
|
||||
#!/usr/bin/env bash
|
||||
# Kvitt Pi install (infra/README.md "Pi: what the owner does"). Run ON the Kvitt Pi, as the owner's user, from the
|
||||
# bundle folder copied from the Mac (infra/pi/): cd ~/kvitt-pi && ./install.sh
|
||||
# Idempotent: safe to run again after changing .env, the secrets, compose.yaml or the Caddyfile.
|
||||
# Idempotent: safe to run again after changing .env, the secrets or compose.yaml.
|
||||
# 1. checks Docker Engine and the compose plugin
|
||||
# 2. checks .env (no CHANGE_ME left in the keys the stack reads)
|
||||
# 3. creates /opt/kvitt/{secrets,import,apk} and installs the secret files from ./secrets/ with their modes
|
||||
# 2. checks .env (no CHANGE_ME left in the keys the stack reads; KVITT_PUBLIC_HOST a host name, HA_HOST an IPv4
|
||||
# address)
|
||||
# 3. creates /opt/kvitt/{secrets,import,apk}, installs the secret files from ./secrets/ with their modes and
|
||||
# removes what the Caddy and cert-sync stack of before D-120 left in /opt/kvitt
|
||||
# 4. trusts the plain-HTTP registry in /etc/docker/daemon.json (insecure-registries) and logs in when credentials are set
|
||||
# 5. copies compose.yaml, Caddyfile, .env and the operation scripts to /opt/kvitt; a KVITT_TAG that deploy.sh set
|
||||
# 5. copies compose.yaml, .env and the operation scripts to /opt/kvitt; a KVITT_TAG that deploy.sh set
|
||||
# in /opt/kvitt/.env is kept, so re-running an older bundle never rolls the server back to the bundle's tag
|
||||
# 6. pulls the images (digests recorded in /opt/kvitt/deployed-digests), gives the named volumes their owners,
|
||||
# starts the stack, waits for /health
|
||||
# starts the stack, waits for /health on port 8080, then tries /health through Nginx Proxy Manager
|
||||
# Uses sudo for /opt/kvitt, the secret files and the Docker daemon configuration.
|
||||
set -euo pipefail
|
||||
bundle="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
@@ -26,26 +28,33 @@ command -v python3 >/dev/null || die "python3 is missing (step 4 edits daemon.js
|
||||
# 2. .env
|
||||
env_src="$bundle/.env"
|
||||
[[ -f "$env_src" ]] || die "no .env next to install.sh: cp .env.example .env and fill it (README step 4)"
|
||||
for key in KVITT_REGISTRY KVITT_TAG HA_HOST HA_PORT HA_USER HA_CERT_DIR RCLONE_REMOTE RCLONE_FOLDER_ID; do
|
||||
for key in KVITT_REGISTRY KVITT_TAG KVITT_PUBLIC_HOST HA_HOST RCLONE_REMOTE RCLONE_FOLDER_ID; do
|
||||
value=$(env_value "$key" "$env_src")
|
||||
[[ -n "$value" ]] || die ".env: $key is empty"
|
||||
[[ "$value" != CHANGE_ME* ]] || die ".env: $key is still $value"
|
||||
done
|
||||
# The server compares the TCP peer with KVITT_TRUSTED_PROXY (= HA_HOST). A host name would have to resolve inside
|
||||
# the container to exactly NPM's LAN address (mDNS names such as homeassistant.local do not), so require the address.
|
||||
ha_host=$(env_value HA_HOST "$env_src")
|
||||
[[ "$ha_host" =~ ^([0-9]{1,3}\.){3}[0-9]{1,3}$ ]] \
|
||||
|| die ".env: HA_HOST must be the Home Assistant Pi's LAN IPv4 address (for example 192.168.1.20), not $ha_host"
|
||||
public_host=$(env_value KVITT_PUBLIC_HOST "$env_src")
|
||||
[[ "$public_host" =~ ^[A-Za-z0-9]([A-Za-z0-9.-]*[A-Za-z0-9])?\.[A-Za-z]{2,}$ ]] \
|
||||
|| die ".env: KVITT_PUBLIC_HOST must be a host name such as kvitt.example.org (no scheme, no port), not $public_host"
|
||||
[[ "$public_host" != icehack.asuscomm.com ]] \
|
||||
|| die ".env: KVITT_PUBLIC_HOST cannot be icehack.asuscomm.com, NPM's proxy host for Home Assistant (D-120)"
|
||||
registry=$(env_value KVITT_REGISTRY "$env_src")
|
||||
registry_host=${registry%%/*}
|
||||
registry_user=$(env_value KVITT_REGISTRY_USER "$env_src")
|
||||
registry_token=$(env_value KVITT_REGISTRY_TOKEN "$env_src")
|
||||
|
||||
# 3. Directories and secrets. uid 1000 = cert-sync, uid 10001 = server and backup, uid 10002 = caddy (SPEC §10.2,
|
||||
# §10.7, D-023, D-105, D-117).
|
||||
# 3. Directories and secrets. uid 10001 = server and backup (SPEC §10.2, §10.7, D-105).
|
||||
say "Creating $KVITT_DIR"
|
||||
sudo install -d -m 0755 -o "$(id -u)" -g "$(id -g)" "$KVITT_DIR" "$KVITT_DIR/import" "$KVITT_DIR/apk"
|
||||
sudo install -d -m 0700 -o root -g root "$KVITT_DIR/secrets"
|
||||
# name : owner uid : group : mode
|
||||
secret_specs=(
|
||||
"fcm-service-account.json:0:10001:0440"
|
||||
"ha_ssh_key:1000:1000:0600"
|
||||
"ha_known_hosts:1000:1000:0600"
|
||||
"rclone.conf:10001:10001:0600"
|
||||
)
|
||||
missing=()
|
||||
@@ -71,6 +80,13 @@ done
|
||||
if [[ -d "$bundle/secrets" ]]; then
|
||||
say " The copies in $bundle/secrets/ are no longer needed: rm -rf $bundle/secrets"
|
||||
fi
|
||||
# Left by the Caddy and cert-sync stack of before D-120: the Home Assistant SSH key must not stay on this Pi.
|
||||
for old in "$KVITT_DIR/secrets/ha_ssh_key" "$KVITT_DIR/secrets/ha_known_hosts" "$KVITT_DIR/Caddyfile"; do
|
||||
if sudo test -e "$old"; then
|
||||
sudo rm -rf "$old"
|
||||
say " removed $old (not used since D-120)"
|
||||
fi
|
||||
done
|
||||
|
||||
# 4. Registry trust (plain HTTP on the home Wi-Fi, SPEC §10.3) and optional login.
|
||||
daemon_json=/etc/docker/daemon.json
|
||||
@@ -113,7 +129,6 @@ installed_tag=""
|
||||
[[ -f "$KVITT_DIR/.env" ]] && installed_tag=$(env_value KVITT_TAG)
|
||||
bundle_tag=$(env_value KVITT_TAG "$env_src")
|
||||
install -m 0644 "$bundle/compose.yaml" "$KVITT_DIR/compose.yaml"
|
||||
install -m 0644 "$bundle/Caddyfile" "$KVITT_DIR/Caddyfile"
|
||||
install -m 0600 "$env_src" "$KVITT_DIR/.env"
|
||||
if [[ -n "$installed_tag" && "$installed_tag" != "$bundle_tag" ]]; then
|
||||
env_set KVITT_TAG "$installed_tag"
|
||||
@@ -122,7 +137,7 @@ fi
|
||||
for script in lib.sh deploy.sh bootstrap.sh backup-now.sh install-apk.sh; do
|
||||
install -m 0755 "$bundle/$script" "$KVITT_DIR/$script"
|
||||
done
|
||||
say "Installed compose.yaml, Caddyfile, .env and the scripts in $KVITT_DIR"
|
||||
say "Installed compose.yaml, .env and the scripts in $KVITT_DIR"
|
||||
|
||||
# 6. Pull, volume owners, start.
|
||||
compose config --quiet
|
||||
@@ -133,17 +148,24 @@ compose create
|
||||
# backup image, which mounts the same data volume, sets /data). Runs as root once per install, then exits.
|
||||
compose run --rm --no-deps -T --user 0:0 --cap-add CHOWN --entrypoint /bin/sh backup \
|
||||
-c 'chown 10001:10001 /data /backups && mkdir -p /data/apk && chown 10001:10001 /data/apk'
|
||||
# /certs: cert-sync (1000) writes, Caddy (10002, supplementary group 1000) reads; /data of Caddy: 10002 (D-117).
|
||||
compose run --rm --no-deps -T --user 0:0 --cap-add CHOWN --cap-add FOWNER --entrypoint /bin/sh cert-sync \
|
||||
-c 'chown 1000:1000 /certs && chmod 0750 /certs'
|
||||
compose run --rm --no-deps -T --user 0:0 --cap-add CHOWN --entrypoint /bin/sh caddy \
|
||||
-c 'chown -R 10002:10002 /data'
|
||||
# --remove-orphans also removes the caddy and cert-sync containers of a stack installed before D-120.
|
||||
compose up -d --remove-orphans
|
||||
# Their volumes: certs held a copy of the certificate's private key.
|
||||
for volume in kvitt_certs kvitt_caddy_data; do
|
||||
if docker volume inspect "$volume" >/dev/null 2>&1; then
|
||||
if docker volume rm "$volume" >/dev/null; then
|
||||
say "Removed the volume $volume (not used since D-120)"
|
||||
else
|
||||
say "Could not remove the volume $volume (still in use?): docker volume rm $volume"
|
||||
fi
|
||||
fi
|
||||
done
|
||||
ok=1
|
||||
wait_healthy server 180 || ok=0
|
||||
wait_healthy cert-sync 120 || { ok=0; say "cert-sync has no certificate yet: docker compose logs cert-sync (README, Troubleshooting)"; }
|
||||
wait_healthy caddy 120 || ok=0
|
||||
compose ps
|
||||
print_health || ok=0
|
||||
(( ok == 1 )) || die "the stack is not fully up yet; see the lines above"
|
||||
say "Kvitt is running. Next: $KVITT_DIR/bootstrap.sh (first install only)."
|
||||
if ! print_proxy_health; then
|
||||
say "Not reachable through Nginx Proxy Manager yet: expected until its proxy host is set up (README step 7)."
|
||||
fi
|
||||
say "Kvitt is running. Next: README step 7 (NPM proxy host), then $KVITT_DIR/bootstrap.sh (first install only)."
|
||||
|
||||
Reference in New Issue
Block a user