#!/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" <> "$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|.*]*>\([^<]*\).*|\1|p' "$TEMPLATE_DIR/my-basement.xml" | head -1) PREV_BASEMENT_HASH=$(sed -n 's|.*]*>\([^<]*\).*|\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 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