Files
matthew da7eae3558 basement: rename image basement-ui -> basement; chown appdata for distroless
- template Repository now ghcr.io/mattjackson/basement:latest
- Registry URL and all "basement-ui" descriptive text updated
- install.sh chowns /mnt/user/appdata/basement to 65532:65532 so the
  nonroot distroless image can create its data subpaths
2026-05-22 16:07:06 -07:00

162 lines
6.7 KiB
Bash
Executable File

#!/bin/bash
# unraid-1 installer — sets up the unraid box with the standard fleet of
# containers (garage S3, basement UI, watchtower, host-agent).
#
# Usage on the unraid console:
# bash <(curl -sL https://git.docker.pq.io/pq/scripts/raw/branch/main/unraid-1/install.sh)
#
# What it does:
# 1. Validates this is an unraid host with the array started.
# 2. Confirms the "s3" share exists (parity-protected; you create it).
# 3. mkdir's /mnt/user/appdata/garage/{meta,} and /mnt/user/s3/data.
# 4. Generates 3 garage secrets (rpc, admin, metrics) → secrets.env (preserved).
# 5. Fetches garage.toml → /mnt/user/appdata/garage/garage.toml.
# 6. Fetches 4 unraid container templates (garage / basement / watchtower /
# host-agent), substitutes secrets where needed, writes them to
# /boot/config/plugins/dockerMan/templates-user/my-*.xml.
#
# After: Unraid UI → Docker → Add Container → Template dropdown → Apply each.
#
# Idempotent: re-running keeps existing secrets and garage.toml. FORCE=1
# rotates the garage secrets (you'll then need to re-Apply garage + basement).
set -euo pipefail
BASE="${UNRAID1_INSTALL_BASE:-https://git.docker.pq.io/pq/scripts/raw/branch/main/unraid-1}"
APPDATA=/mnt/user/appdata/garage
DATADIR=/mnt/user/s3/data
TEMPLATE_DIR=/boot/config/plugins/dockerMan/templates-user
TOML_DEST="$APPDATA/garage.toml"
SECRETS="$APPDATA/secrets.env"
say() { printf "install.sh: %s\n" "$*"; }
die() { printf "install.sh: %s\n" "$*" >&2; exit 1; }
# ── Sanity ──
[ -d /boot/config/plugins/dockerMan ] || die "no /boot/config/plugins/dockerMan — not an unraid host?"
[ -d /mnt/user ] || die "/mnt/user missing — is the array started?"
[ -d /mnt/user/s3 ] || die "share /mnt/user/s3 missing. Create it in unraid UI first (Shares → Add Share → 's3', cache:yes recommended), then re-run."
mkdir -p "$APPDATA/meta" "$DATADIR" "$TEMPLATE_DIR" /mnt/user/appdata/basement /mnt/user/appdata/host-agent
# basement image is distroless and runs as nonroot UID 65532 — it can't mkdir
# subpaths under a root-owned bind mount target. host-agent runs as root, so
# only the basement dir needs this.
chown 65532:65532 /mnt/user/appdata/basement
# ── garage.toml ──
if [ -f "$TOML_DEST" ] && [ "${FORCE:-}" != "1" ]; then
say "$TOML_DEST exists — keeping (FORCE=1 to overwrite)"
else
say "fetching $BASE/garage.toml"
curl -fsSL "$BASE/garage.toml" -o "$TOML_DEST"
fi
# ── Secrets ──
if [ -f "$SECRETS" ] && [ "${FORCE:-}" != "1" ]; then
say "reusing existing secrets at $SECRETS (FORCE=1 to rotate)"
# shellcheck disable=SC1090
. "$SECRETS"
else
GARAGE_RPC_SECRET=$(openssl rand -hex 32)
GARAGE_ADMIN_TOKEN=$(openssl rand -hex 32)
GARAGE_METRICS_TOKEN=$(openssl rand -hex 32)
BASEMENT_JWT_SECRET=$(openssl rand -hex 32)
umask 077
cat > "$SECRETS" <<EOF
GARAGE_RPC_SECRET=$GARAGE_RPC_SECRET
GARAGE_ADMIN_TOKEN=$GARAGE_ADMIN_TOKEN
GARAGE_METRICS_TOKEN=$GARAGE_METRICS_TOKEN
BASEMENT_JWT_SECRET=$BASEMENT_JWT_SECRET
EOF
say "generated secrets → $SECRETS"
fi
# Existing deployments predate BASEMENT_JWT_SECRET; backfill if missing
# so the basement template sub'n doesn't leave a literal placeholder.
if [ -z "${BASEMENT_JWT_SECRET:-}" ]; then
BASEMENT_JWT_SECRET=$(openssl rand -hex 32)
umask 077
printf "BASEMENT_JWT_SECRET=%s\n" "$BASEMENT_JWT_SECRET" >> "$SECRETS"
say "backfilled BASEMENT_JWT_SECRET into $SECRETS"
fi
# ── Templates ──
# Fetch each, optionally sed-substitute __PLACEHOLDER__ secrets, write to
# templates-user/. Secrets are hex chars only — safe in sed delimiters.
fetch_template() {
local src=$1 dest=$2 tmp
shift 2
tmp=$(mktemp)
say "fetching $src"
curl -fsSL "$src" -o "$tmp"
if [ "$#" -gt 0 ]; then
sed "$@" "$tmp" > "$dest"
else
cp "$tmp" "$dest"
fi
rm -f "$tmp"
say "wrote $dest"
}
fetch_template \
"$BASE/garage.template.xml" \
"$TEMPLATE_DIR/my-garage.xml" \
-e "s|__GARAGE_RPC_SECRET__|$GARAGE_RPC_SECRET|" \
-e "s|__GARAGE_ADMIN_TOKEN__|$GARAGE_ADMIN_TOKEN|" \
-e "s|__GARAGE_METRICS_TOKEN__|$GARAGE_METRICS_TOKEN|"
# Preserve user-set fields (admin user + password hash) across re-runs:
# pull whatever's currently in the existing my-basement.xml so we can
# sub it back into the freshly-fetched template. First-time installs
# substitute to empty (placeholders disappear) and the user fills them
# in via the Unraid UI; subsequent re-runs keep what they set.
PREV_BASEMENT_USER=""
PREV_BASEMENT_HASH=""
if [ -f "$TEMPLATE_DIR/my-basement.xml" ]; then
PREV_BASEMENT_USER=$(sed -n 's|.*<Config Name="BASEMENT_ADMIN_USER"[^>]*>\([^<]*\)</Config>.*|\1|p' "$TEMPLATE_DIR/my-basement.xml" | head -1)
PREV_BASEMENT_HASH=$(sed -n 's|.*<Config Name="BASEMENT_ADMIN_PASSWORD_HASH"[^>]*>\([^<]*\)</Config>.*|\1|p' "$TEMPLATE_DIR/my-basement.xml" | head -1)
# Don't carry forward the literal placeholder from an early-run template
[ "$PREV_BASEMENT_USER" = "__BASEMENT_ADMIN_USER__" ] && PREV_BASEMENT_USER=""
[ "$PREV_BASEMENT_HASH" = "__BASEMENT_ADMIN_PASSWORD_HASH__" ] && PREV_BASEMENT_HASH=""
[ -n "$PREV_BASEMENT_USER" ] && say "preserving BASEMENT_ADMIN_USER from existing template"
[ -n "$PREV_BASEMENT_HASH" ] && say "preserving BASEMENT_ADMIN_PASSWORD_HASH from existing template"
fi
fetch_template \
"$BASE/basement.template.xml" \
"$TEMPLATE_DIR/my-basement.xml" \
-e "s|__GARAGE_ADMIN_TOKEN__|$GARAGE_ADMIN_TOKEN|" \
-e "s|__BASEMENT_JWT_SECRET__|$BASEMENT_JWT_SECRET|" \
-e "s|__BASEMENT_ADMIN_USER__|$PREV_BASEMENT_USER|" \
-e "s|__BASEMENT_ADMIN_PASSWORD_HASH__|$PREV_BASEMENT_HASH|"
fetch_template \
"$BASE/watchtower.template.xml" \
"$TEMPLATE_DIR/my-watchtower.xml"
fetch_template \
"$BASE/host-agent.template.xml" \
"$TEMPLATE_DIR/my-host-agent.xml"
cat <<'EOF'
────────────────────────────────────────
Done. Apply each template in Unraid UI → Docker → Add Container → Template dropdown:
1. "my-garage" → Apply. Initialise cluster after first start:
docker exec garage /garage status # copy node id
docker exec garage /garage layout assign -z dc1 -c 1T <node-id>
docker exec garage /garage layout apply --version 1
Smoke test from your laptop:
aws --endpoint-url https://s3.pq.io --region garage s3 ls
2. "my-basement" → Apply. Reachable at https://basement.pq.io.
3. "my-watchtower" → Apply. Auto-updates the labelled containers above + below.
4. "my-host-agent" → Apply. Pushes node/cadvisor/smartctl metrics to your
Prometheus (defaults to docker-1.internal.pq.io:9090).
────────────────────────────────────────
EOF