initial: unraid-1 bootstrap (garage + basement + watchtower + host-agent)
This commit is contained in:
@@ -0,0 +1,34 @@
|
|||||||
|
# pq/scripts
|
||||||
|
|
||||||
|
Public bootstrap scripts. Only public repo on git.docker.pq.io — everything
|
||||||
|
else is private. This one is public so machines on the internal network
|
||||||
|
can `curl` raw URLs anonymously, without embedding tokens.
|
||||||
|
|
||||||
|
Distribution is over the LAN (gitea is not internet-reachable); "public"
|
||||||
|
here just means anonymous access from the internal network.
|
||||||
|
|
||||||
|
## Layout
|
||||||
|
|
||||||
|
```
|
||||||
|
unraid-1/
|
||||||
|
install.sh # bootstrap entrypoint
|
||||||
|
garage.toml # garage S3 config (single-node, lmdb, rf=1)
|
||||||
|
garage.template.xml # unraid CA template — garage container
|
||||||
|
basement.template.xml # unraid CA template — basement-ui (garage admin UI)
|
||||||
|
watchtower.template.xml # unraid CA template — image auto-updater
|
||||||
|
host-agent.template.xml # unraid CA template — per-host metrics + fan ctrl
|
||||||
|
```
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
On the unraid-1 console:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
bash <(curl -sL https://git.docker.pq.io/pq/scripts/raw/branch/main/unraid-1/install.sh)
|
||||||
|
```
|
||||||
|
|
||||||
|
The installer is idempotent. Re-run any time to re-fetch templates or pick
|
||||||
|
up new ones. `FORCE=1` rotates garage's secrets.
|
||||||
|
|
||||||
|
After install: Unraid UI → Docker → Add Container → Template dropdown →
|
||||||
|
Apply each of `my-garage`, `my-basement`, `my-watchtower`, `my-host-agent`.
|
||||||
@@ -0,0 +1,48 @@
|
|||||||
|
<?xml version="1.0"?>
|
||||||
|
<!--
|
||||||
|
Unraid Community Apps template for basement-ui (Garage admin UI).
|
||||||
|
Source of truth: pq/scripts on Gitea (https://git.docker.pq.io/pq/scripts).
|
||||||
|
Served via gitea raw URL: https://git.docker.pq.io/pq/scripts/raw/branch/main/unraid-1/basement.template.xml
|
||||||
|
|
||||||
|
This file is NOT installed directly. install.sh fetches it, substitutes the
|
||||||
|
__GARAGE_ADMIN_TOKEN__ placeholder with the same secret used by the garage
|
||||||
|
container, and writes the result to /boot/config/plugins/dockerMan/templates-user/my-basement.xml.
|
||||||
|
|
||||||
|
Caddy on classe reverse-proxies https://basement.pq.io to this container's
|
||||||
|
port 8080. DNS for basement.pq.io and the Caddy vhost are managed on classe,
|
||||||
|
not here.
|
||||||
|
-->
|
||||||
|
<Container version="2">
|
||||||
|
<Name>basement</Name>
|
||||||
|
<Repository>ghcr.io/mattjackson/basement-ui:latest</Repository>
|
||||||
|
<Registry>https://github.com/mattjackson/basement-ui</Registry>
|
||||||
|
<Network>bridge</Network>
|
||||||
|
<MyIP/>
|
||||||
|
<Shell>sh</Shell>
|
||||||
|
<Privileged>false</Privileged>
|
||||||
|
<Support/>
|
||||||
|
<Project/>
|
||||||
|
<Overview>basement-ui: admin UI for the local garage S3 container. Caddy on classe terminates TLS for basement.pq.io and reverse-proxies to this container's port 8080.</Overview>
|
||||||
|
<Category>Network:Web Cloud:</Category>
|
||||||
|
<WebUI>http://[IP]:[PORT:8080]/</WebUI>
|
||||||
|
<TemplateURL/>
|
||||||
|
<Icon>https://garagehq.deuxfleurs.fr/img/logo.svg</Icon>
|
||||||
|
<ExtraParams>--restart=unless-stopped --add-host=host.docker.internal:host-gateway --label com.centurylinklabs.watchtower.enable=true</ExtraParams>
|
||||||
|
<PostArgs/>
|
||||||
|
<CPUset/>
|
||||||
|
<DateInstalled/>
|
||||||
|
<DonateText/>
|
||||||
|
<DonateLink/>
|
||||||
|
<Description>
|
||||||
|
Admin UI for the garage container running on this host.
|
||||||
|
|
||||||
|
Reaches garage's admin API at http://host.docker.internal:3903 (the host-gateway alias resolves to unraid-1's docker bridge IP, where the garage container publishes port 3903). The admin token below is shared with the garage container — install.sh pre-fills both.
|
||||||
|
|
||||||
|
Publicly served at https://basement.pq.io via Caddy on classe (reverse proxy → unraid-1:8080).
|
||||||
|
</Description>
|
||||||
|
|
||||||
|
<Config Name="WebUI" Target="8080" Default="8080" Mode="tcp" Description="HTTP port. Caddy on classe proxies basement.pq.io here." Type="Port" Display="always" Required="true" Mask="false">8080</Config>
|
||||||
|
|
||||||
|
<Config Name="GARAGE_ADMIN_URL" Target="GARAGE_ADMIN_URL" Default="http://host.docker.internal:3903" Mode="" Description="Garage admin API base URL (reaches the sibling garage container via the host-gateway alias)." Type="Variable" Display="always" Required="true" Mask="false">http://host.docker.internal:3903</Config>
|
||||||
|
<Config Name="GARAGE_ADMIN_TOKEN" Target="GARAGE_ADMIN_TOKEN" Default="" Mode="" Description="Bearer token for Garage admin API. Pre-filled by install.sh (same value as the garage container's GARAGE_ADMIN_TOKEN)." Type="Variable" Display="always" Required="true" Mask="true">__GARAGE_ADMIN_TOKEN__</Config>
|
||||||
|
</Container>
|
||||||
@@ -0,0 +1,63 @@
|
|||||||
|
<?xml version="1.0"?>
|
||||||
|
<!--
|
||||||
|
Unraid Community Apps template for Garage (single-node S3 server).
|
||||||
|
Source of truth: pq/scripts on Gitea (https://git.docker.pq.io/pq/scripts).
|
||||||
|
Served via gitea raw URL: https://git.docker.pq.io/pq/scripts/raw/branch/main/unraid-1/garage.template.xml
|
||||||
|
|
||||||
|
This file is NOT installed directly. install.sh fetches this, substitutes
|
||||||
|
the three __PLACEHOLDER__ secrets it just generated, and writes the result
|
||||||
|
to /boot/config/plugins/dockerMan/templates-user/my-garage.xml.
|
||||||
|
|
||||||
|
Unraid then surfaces it in the "Template" dropdown on the Add Container page.
|
||||||
|
-->
|
||||||
|
<Container version="2">
|
||||||
|
<Name>garage</Name>
|
||||||
|
<Repository>dxflrs/garage:v1.0.1</Repository>
|
||||||
|
<Registry>https://hub.docker.com/r/dxflrs/garage</Registry>
|
||||||
|
<Network>bridge</Network>
|
||||||
|
<MyIP/>
|
||||||
|
<Shell>sh</Shell>
|
||||||
|
<Privileged>false</Privileged>
|
||||||
|
<Support/>
|
||||||
|
<Project>https://garagehq.deuxfleurs.fr/</Project>
|
||||||
|
<Overview>Single-node Garage S3 server. Caddy on classe terminates TLS for s3.pq.io and reverse-proxies to this container's port 3900.</Overview>
|
||||||
|
<Category>Backup: Cloud: Network:Other</Category>
|
||||||
|
<WebUI/>
|
||||||
|
<TemplateURL/>
|
||||||
|
<Icon>https://garagehq.deuxfleurs.fr/img/logo.svg</Icon>
|
||||||
|
<ExtraParams>--restart=unless-stopped --label com.centurylinklabs.watchtower.enable=true</ExtraParams>
|
||||||
|
<PostArgs/>
|
||||||
|
<CPUset/>
|
||||||
|
<DateInstalled/>
|
||||||
|
<DonateText/>
|
||||||
|
<DonateLink/>
|
||||||
|
<Description>
|
||||||
|
Single-node Garage S3 (lmdb engine, replication_factor=1). Path-style endpoint: https://s3.pq.io/<bucket>/<key>.
|
||||||
|
|
||||||
|
After the container starts the FIRST time only, initialise the cluster layout from the unraid console:
|
||||||
|
|
||||||
|
docker exec garage /garage status
|
||||||
|
# copy the node-id from the output
|
||||||
|
docker exec garage /garage layout assign -z dc1 -c 1T <node-id>
|
||||||
|
docker exec garage /garage layout apply --version 1
|
||||||
|
|
||||||
|
Then create a bucket and an access key:
|
||||||
|
|
||||||
|
docker exec garage /garage bucket create my-bucket
|
||||||
|
docker exec garage /garage key create my-key
|
||||||
|
docker exec garage /garage bucket allow --read --write --owner my-bucket --key my-key
|
||||||
|
docker exec garage /garage key info --show-secret my-key
|
||||||
|
</Description>
|
||||||
|
|
||||||
|
<Config Name="S3 API" Target="3900" Default="3900" Mode="tcp" Description="S3-compatible API. Caddy on classe proxies s3.pq.io here." Type="Port" Display="always" Required="true" Mask="false">3900</Config>
|
||||||
|
<Config Name="S3 Static Web" Target="3902" Default="3902" Mode="tcp" Description="Static site hosting (optional, not currently exposed via Caddy)." Type="Port" Display="advanced" Required="false" Mask="false">3902</Config>
|
||||||
|
<Config Name="Admin / Metrics" Target="3903" Default="3903" Mode="tcp" Description="Admin API + Prometheus metrics. Caddy proxies /-/health to this port." Type="Port" Display="always" Required="true" Mask="false">3903</Config>
|
||||||
|
|
||||||
|
<Config Name="Metadata (SSD)" Target="/var/lib/garage/meta" Default="/mnt/user/appdata/garage/meta" Mode="rw" Description="LMDB metadata. MUST live on SSD (appdata is cache-only by default — good)." Type="Path" Display="always" Required="true" Mask="false">/mnt/user/appdata/garage/meta</Config>
|
||||||
|
<Config Name="Data (parity)" Target="/var/lib/garage/data" Default="/mnt/user/s3/data" Mode="rw" Description="Object data chunks. Use a parity-protected share with cache:yes for SSD-fast writes + array durability." Type="Path" Display="always" Required="true" Mask="false">/mnt/user/s3/data</Config>
|
||||||
|
<Config Name="garage.toml" Target="/etc/garage.toml" Default="/mnt/user/appdata/garage/garage.toml" Mode="ro" Description="Garage config file. install.sh placed this for you." Type="Path" Display="always" Required="true" Mask="false">/mnt/user/appdata/garage/garage.toml</Config>
|
||||||
|
|
||||||
|
<Config Name="GARAGE_RPC_SECRET" Target="GARAGE_RPC_SECRET" Default="" Mode="" Description="32-byte hex inter-node secret. Pre-filled by install.sh." Type="Variable" Display="always" Required="true" Mask="true">__GARAGE_RPC_SECRET__</Config>
|
||||||
|
<Config Name="GARAGE_ADMIN_TOKEN" Target="GARAGE_ADMIN_TOKEN" Default="" Mode="" Description="32-byte hex bearer token for admin API. Pre-filled by install.sh." Type="Variable" Display="always" Required="true" Mask="true">__GARAGE_ADMIN_TOKEN__</Config>
|
||||||
|
<Config Name="GARAGE_METRICS_TOKEN" Target="GARAGE_METRICS_TOKEN" Default="" Mode="" Description="32-byte hex bearer token for Prometheus /metrics. Pre-filled by install.sh." Type="Variable" Display="always" Required="true" Mask="true">__GARAGE_METRICS_TOKEN__</Config>
|
||||||
|
</Container>
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
# Garage v1 single-node config. Lives on unraid-1.
|
||||||
|
# Secrets (rpc_secret, admin_token, metrics_token) come from env vars
|
||||||
|
# defined in .env — NOT in this file. See Garage docs:
|
||||||
|
# https://garagehq.deuxfleurs.fr/documentation/reference-manual/configuration/
|
||||||
|
|
||||||
|
metadata_dir = "/var/lib/garage/meta"
|
||||||
|
data_dir = "/var/lib/garage/data"
|
||||||
|
|
||||||
|
# lmdb is the v1 default; sled was deprecated. lmdb is more crash-resilient
|
||||||
|
# and the recommended engine for new deployments.
|
||||||
|
db_engine = "lmdb"
|
||||||
|
|
||||||
|
# Single-node: every object stored once. No HA.
|
||||||
|
replication_factor = 1
|
||||||
|
|
||||||
|
rpc_bind_addr = "[::]:3901"
|
||||||
|
rpc_public_addr = "127.0.0.1:3901"
|
||||||
|
# rpc_secret -> GARAGE_RPC_SECRET (env)
|
||||||
|
|
||||||
|
[s3_api]
|
||||||
|
s3_region = "garage"
|
||||||
|
api_bind_addr = "[::]:3900"
|
||||||
|
# Path-style: https://s3.pq.io/<bucket>/<key>
|
||||||
|
# Virtual-host style: https://<bucket>.s3.pq.io/<key> (requires DNS wildcard)
|
||||||
|
root_domain = ".s3.pq.io"
|
||||||
|
|
||||||
|
[s3_web]
|
||||||
|
bind_addr = "[::]:3902"
|
||||||
|
root_domain = ".web.s3.pq.io"
|
||||||
|
index = "index.html"
|
||||||
|
|
||||||
|
[admin]
|
||||||
|
api_bind_addr = "[::]:3903"
|
||||||
|
# admin_token -> GARAGE_ADMIN_TOKEN (env)
|
||||||
|
# metrics_token -> GARAGE_METRICS_TOKEN (env) — used by Prometheus scrape
|
||||||
@@ -0,0 +1,62 @@
|
|||||||
|
<?xml version="1.0"?>
|
||||||
|
<!--
|
||||||
|
Unraid Community Apps template for host-agent (per-host metrics + fan ctrl).
|
||||||
|
Source of truth: pq/scripts on Gitea (https://git.docker.pq.io/pq/scripts).
|
||||||
|
Served via gitea raw URL: https://git.docker.pq.io/pq/scripts/raw/branch/main/unraid-1/host-agent.template.xml
|
||||||
|
|
||||||
|
This file is NOT installed directly. install.sh fetches it and writes it to
|
||||||
|
/boot/config/plugins/dockerMan/templates-user/my-host-agent.xml. No secrets
|
||||||
|
to substitute.
|
||||||
|
|
||||||
|
host-agent is one container bundling: fan-controller (Dell PowerEdge only —
|
||||||
|
self-disables on consumer hardware like unraid), node_exporter, cadvisor,
|
||||||
|
ipmi_exporter, smartctl_exporter, nvidia_gpu_exporter, vmagent (remote_write
|
||||||
|
to Prometheus). Each sub-service probes its prerequisites on start and
|
||||||
|
sleep-infinity's if absent, so the same image runs on a Dell R730xd and an
|
||||||
|
unraid box on consumer hardware.
|
||||||
|
|
||||||
|
On unraid-1: fan-controller + ipmi_exporter + nvidia_gpu_exporter self-
|
||||||
|
disable (no Dell BMC, no nvidia GPU). node + cadvisor + smartctl + vmagent
|
||||||
|
run, ship metrics to monitoring stack on classe.
|
||||||
|
-->
|
||||||
|
<Container version="2">
|
||||||
|
<Name>host-agent</Name>
|
||||||
|
<Repository>ghcr.io/mattjackson/host-agent:latest</Repository>
|
||||||
|
<Registry>https://github.com/mattjackson/host-agent</Registry>
|
||||||
|
<Network>host</Network>
|
||||||
|
<MyIP/>
|
||||||
|
<Shell>sh</Shell>
|
||||||
|
<Privileged>true</Privileged>
|
||||||
|
<Support/>
|
||||||
|
<Project>https://github.com/mattjackson/host-agent</Project>
|
||||||
|
<Overview>Per-host Prometheus exporters (node, cadvisor, ipmi, smartctl, nvidia-gpu) plus Dell PowerEdge fan controller. Each sub-service self-disables if hardware prerequisites are missing — same image runs on Dell servers and consumer Unraid hardware.</Overview>
|
||||||
|
<Category>Tools: Status: Productivity:</Category>
|
||||||
|
<WebUI/>
|
||||||
|
<TemplateURL/>
|
||||||
|
<Icon>https://raw.githubusercontent.com/prometheus/prometheus/main/web/ui/static/img/prometheus_logo_grey.svg</Icon>
|
||||||
|
<ExtraParams>--restart=unless-stopped --cgroupns=host -v /:/host:ro,rslave --label com.centurylinklabs.watchtower.enable=true</ExtraParams>
|
||||||
|
<PostArgs/>
|
||||||
|
<CPUset/>
|
||||||
|
<DateInstalled/>
|
||||||
|
<DonateText/>
|
||||||
|
<DonateLink/>
|
||||||
|
<Description>
|
||||||
|
host-agent: single container, s6-supervised, bundles per-host metrics + Dell fan control.
|
||||||
|
|
||||||
|
On unraid-1 (consumer hardware): fan-controller + ipmi_exporter + nvidia_gpu_exporter self-disable. node_exporter (CPU/mem/disk/net), cadvisor (per-container), smartctl_exporter (drive SMART), and vmagent (remote_write) run and push to your Prometheus.
|
||||||
|
|
||||||
|
The root mount (/:/host:ro,rslave) is wired via ExtraParams since Unraid's Path config doesn't expose mount propagation flags; rslave is required for node_exporter's rootfs view to track later host mounts.
|
||||||
|
</Description>
|
||||||
|
|
||||||
|
<Config Name="/sys" Target="/sys" Default="/sys" Mode="ro" Description="Required: cadvisor reads /sys/fs/cgroup directly (its own cgroup ns is just /, even with --privileged)." Type="Path" Display="advanced" Required="true" Mask="false">/sys</Config>
|
||||||
|
<Config Name="docker.sock" Target="/run/docker.sock" Default="/var/run/docker.sock" Mode="rw" Description="Required: cadvisor opens /run/docker.sock at this hardcoded path for container metadata." Type="Path" Display="advanced" Required="true" Mask="false">/var/run/docker.sock</Config>
|
||||||
|
<Config Name="containerd" Target="/run/containerd" Default="/run/containerd" Mode="ro" Description="Required: cadvisor opens /run/containerd/containerd.sock at this hardcoded path." Type="Path" Display="advanced" Required="true" Mask="false">/run/containerd</Config>
|
||||||
|
<Config Name="/var/lib/docker" Target="/var/lib/docker" Default="/var/lib/docker" Mode="ro" Description="Required: cadvisor reads container metadata from here." Type="Path" Display="advanced" Required="true" Mask="false">/var/lib/docker</Config>
|
||||||
|
<Config Name="/dev" Target="/dev" Default="/dev" Mode="rw" Description="Required: ipmitool + smartctl hardcode /dev/* paths and Docker doesn't auto-import host devices." Type="Path" Display="advanced" Required="true" Mask="false">/dev</Config>
|
||||||
|
<Config Name="host-agent state" Target="/var/lib/host-agent" Default="/mnt/user/appdata/host-agent" Mode="rw" Description="Persistent state: fan-controller EWMA baseline + vmagent WAL." Type="Path" Display="always" Required="true" Mask="false">/mnt/user/appdata/host-agent</Config>
|
||||||
|
|
||||||
|
<Config Name="PROMETHEUS_REMOTE_WRITE_URL" Target="PROMETHEUS_REMOTE_WRITE_URL" Default="http://docker-1.internal.pq.io:9090/api/v1/write" Mode="" Description="Where vmagent pushes metrics. Defaults to monitoring stack on classe (docker-1). Prometheus must be started with --web.enable-remote-write-receiver (already is)." Type="Variable" Display="always" Required="true" Mask="false">http://docker-1.internal.pq.io:9090/api/v1/write</Config>
|
||||||
|
<Config Name="PROMETHEUS_REMOTE_WRITE_BEARER_TOKEN" Target="PROMETHEUS_REMOTE_WRITE_BEARER_TOKEN" Default="" Mode="" Description="Optional bearer token for remote_write auth (LAN-internal Prometheus typically needs none)." Type="Variable" Display="advanced" Required="false" Mask="true"/>
|
||||||
|
<Config Name="NVIDIA_VISIBLE_DEVICES" Target="NVIDIA_VISIBLE_DEVICES" Default="all" Mode="" Description="Harmless if no NVIDIA driver — nvidia_gpu_exporter sub-service self-disables." Type="Variable" Display="advanced" Required="false" Mask="false">all</Config>
|
||||||
|
<Config Name="NVIDIA_DRIVER_CAPABILITIES" Target="NVIDIA_DRIVER_CAPABILITIES" Default="utility" Mode="" Description="Harmless if no NVIDIA driver." Type="Variable" Display="advanced" Required="false" Mask="false">utility</Config>
|
||||||
|
</Container>
|
||||||
Executable
+126
@@ -0,0 +1,126 @@
|
|||||||
|
#!/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"
|
||||||
|
|
||||||
|
# ── 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)
|
||||||
|
umask 077
|
||||||
|
cat > "$SECRETS" <<EOF
|
||||||
|
GARAGE_RPC_SECRET=$GARAGE_RPC_SECRET
|
||||||
|
GARAGE_ADMIN_TOKEN=$GARAGE_ADMIN_TOKEN
|
||||||
|
GARAGE_METRICS_TOKEN=$GARAGE_METRICS_TOKEN
|
||||||
|
EOF
|
||||||
|
say "generated secrets → $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|"
|
||||||
|
|
||||||
|
fetch_template \
|
||||||
|
"$BASE/basement.template.xml" \
|
||||||
|
"$TEMPLATE_DIR/my-basement.xml" \
|
||||||
|
-e "s|__GARAGE_ADMIN_TOKEN__|$GARAGE_ADMIN_TOKEN|"
|
||||||
|
|
||||||
|
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
|
||||||
@@ -0,0 +1,54 @@
|
|||||||
|
<?xml version="1.0"?>
|
||||||
|
<!--
|
||||||
|
Unraid Community Apps template for Watchtower (auto-image-updater).
|
||||||
|
Source of truth: pq/scripts on Gitea (https://git.docker.pq.io/pq/scripts).
|
||||||
|
Served via gitea raw URL: https://git.docker.pq.io/pq/scripts/raw/branch/main/unraid-1/watchtower.template.xml
|
||||||
|
|
||||||
|
This file is NOT installed directly. install.sh fetches it and writes it to
|
||||||
|
/boot/config/plugins/dockerMan/templates-user/my-watchtower.xml. No secrets
|
||||||
|
to substitute — Watchtower needs none for these (public) images.
|
||||||
|
|
||||||
|
Mirrors the watchtower service on classe (docker-server/docker-compose.yml):
|
||||||
|
same nickfedor/watchtower:1.17.0 image, same fleet-wide 300s poll interval,
|
||||||
|
same opt-in via label. The other unraid-1 templates (garage, basement, host-
|
||||||
|
agent) all carry the enable label, so this container will pick them up.
|
||||||
|
-->
|
||||||
|
<Container version="2">
|
||||||
|
<Name>watchtower</Name>
|
||||||
|
<Repository>nickfedor/watchtower:1.17.0</Repository>
|
||||||
|
<Registry>https://hub.docker.com/r/nickfedor/watchtower</Registry>
|
||||||
|
<Network>bridge</Network>
|
||||||
|
<MyIP/>
|
||||||
|
<Shell>sh</Shell>
|
||||||
|
<Privileged>false</Privileged>
|
||||||
|
<Support/>
|
||||||
|
<Project>https://github.com/nicholas-fedor/watchtower</Project>
|
||||||
|
<Overview>Auto-updates running containers when newer images are published. Opt-in: only watches containers labelled com.centurylinklabs.watchtower.enable=true (garage + basement both carry this).</Overview>
|
||||||
|
<Category>Tools: Utilities:</Category>
|
||||||
|
<WebUI/>
|
||||||
|
<TemplateURL/>
|
||||||
|
<Icon>https://raw.githubusercontent.com/containrrr/watchtower/main/logo.png</Icon>
|
||||||
|
<ExtraParams>--restart=unless-stopped</ExtraParams>
|
||||||
|
<PostArgs/>
|
||||||
|
<CPUset/>
|
||||||
|
<DateInstalled/>
|
||||||
|
<DonateText/>
|
||||||
|
<DonateLink/>
|
||||||
|
<Description>
|
||||||
|
Watches all containers with the label `com.centurylinklabs.watchtower.enable=true` and pulls + restarts them when a newer image tag is published.
|
||||||
|
|
||||||
|
Configured identically to the watchtower service on classe (docker-server/docker-compose.yml) so unraid + classe behave the same way:
|
||||||
|
|
||||||
|
- polls every 300s (5min — fleet-wide default; gentle on ghcr/dockerhub rate limits)
|
||||||
|
- cleans up the old image after replacing a container
|
||||||
|
- opt-in only (WATCHTOWER_LABEL_ENABLE=true) — does NOT touch unlabelled containers
|
||||||
|
|
||||||
|
Does not watch itself: a new Watchtower version requires re-Applying this template (or manually pulling).
|
||||||
|
</Description>
|
||||||
|
|
||||||
|
<Config Name="docker.sock" Target="/var/run/docker.sock" Default="/var/run/docker.sock" Mode="rw" Description="Required: lets Watchtower talk to the Docker daemon to inspect and recreate containers." Type="Path" Display="advanced" Required="true" Mask="false">/var/run/docker.sock</Config>
|
||||||
|
|
||||||
|
<Config Name="WATCHTOWER_POLL_INTERVAL" Target="WATCHTOWER_POLL_INTERVAL" Default="300" Mode="" Description="Seconds between checks for new images. 300s (5min) is the fleet-wide default — gentle on ghcr/dockerhub rate limits, still effectively instant for new pushes." Type="Variable" Display="always" Required="true" Mask="false">300</Config>
|
||||||
|
<Config Name="WATCHTOWER_CLEANUP" Target="WATCHTOWER_CLEANUP" Default="true" Mode="" Description="Delete the previous image after a successful update (frees disk)." Type="Variable" Display="always" Required="true" Mask="false">true</Config>
|
||||||
|
<Config Name="WATCHTOWER_LABEL_ENABLE" Target="WATCHTOWER_LABEL_ENABLE" Default="true" Mode="" Description="Only watch containers with com.centurylinklabs.watchtower.enable=true. Leave true." Type="Variable" Display="always" Required="true" Mask="false">true</Config>
|
||||||
|
</Container>
|
||||||
Reference in New Issue
Block a user