Add Node-RED, Netdata, Homepage, ntfy, Portainer, and restic backups to container host setup
Extends the container-host script to cover the rest of the Phase 1/9 stack (README's outstanding checklist item) so a single Debian host setup gets Home Assistant plus the full companion stack: automation glue, monitoring, a dashboard, push notifications, container management, and optional scheduled backups. Also fixes a stale comment referencing a network-attached SLZB-06 coordinator when the plan and config target a USB CC2652P dongle. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>main
parent
0cc22f82b3
commit
0cd49f9604
22
README.md
22
README.md
|
|
@ -28,7 +28,8 @@ identity/ Face<->MAC<->name correlation logic (Node-RED flow
|
||||||
|
|
||||||
- [x] Project plan drafted
|
- [x] Project plan drafted
|
||||||
- [x] Container host setup script v1 (HA, Mosquitto, Zigbee2MQTT USB, Frigate, Grocy)
|
- [x] Container host setup script v1 (HA, Mosquitto, Zigbee2MQTT USB, Frigate, Grocy)
|
||||||
- [ ] Add Node-RED + monitoring (Netdata) + dashboard (Homepage) + ntfy + Portainer to compose stack
|
- [x] Node-RED + monitoring (Netdata) + dashboard (Homepage) + ntfy + Portainer added to compose stack
|
||||||
|
- [x] Backup (restic) setup — scripted, off by default until a backup target is picked (`ENABLE_BACKUPS`)
|
||||||
- [ ] Bermuda / ESPHome BLE proxy configs
|
- [ ] Bermuda / ESPHome BLE proxy configs
|
||||||
- [ ] RuView node configs
|
- [ ] RuView node configs
|
||||||
- [ ] Frigate peephole camera config (real RTSP details)
|
- [ ] Frigate peephole camera config (real RTSP details)
|
||||||
|
|
@ -36,7 +37,6 @@ identity/ Face<->MAC<->name correlation logic (Node-RED flow
|
||||||
- [ ] LLM host (Ollama) setup script
|
- [ ] LLM host (Ollama) setup script
|
||||||
- [ ] CalDAV / Nextcloud calendar integration notes
|
- [ ] CalDAV / Nextcloud calendar integration notes
|
||||||
- [ ] Identity correlation flow (Node-RED)
|
- [ ] Identity correlation flow (Node-RED)
|
||||||
- [ ] Backup (restic) setup
|
|
||||||
|
|
||||||
## Quick start
|
## Quick start
|
||||||
|
|
||||||
|
|
@ -46,6 +46,18 @@ sudo ./setup-container-host.sh
|
||||||
```
|
```
|
||||||
|
|
||||||
Edit the variables at the top of the script first (timezone, Zigbee USB device
|
Edit the variables at the top of the script first (timezone, Zigbee USB device
|
||||||
path, Mealie/hardware-accel toggles). See `docs/project-plan.md` for the full
|
path, Mealie/hardware-accel toggles, and whether to enable Node-RED/Netdata/
|
||||||
phased rollout order — don't skip straight to Phase 4+ automations before the
|
Homepage/ntfy/Portainer and restic backups — all but backups are on by
|
||||||
Phase 2 reactive baseline (presence -> light, no LLM in the loop) is working.
|
default). See `docs/project-plan.md` for the full phased rollout order — don't
|
||||||
|
skip straight to Phase 4+ automations before the Phase 2 reactive baseline
|
||||||
|
(presence -> light, no LLM in the loop) is working.
|
||||||
|
|
||||||
|
The script brings up everything that runs on this one Debian host: Home
|
||||||
|
Assistant, Mosquitto, Zigbee2MQTT, Node-RED, Frigate, Grocy, Netdata, a
|
||||||
|
Homepage dashboard, ntfy, and Portainer, plus an optional scheduled restic
|
||||||
|
backup timer. What it can't do for you, because they need separate hardware,
|
||||||
|
credentials, or physical setup: pairing Zigbee sensors, flashing
|
||||||
|
RuView/ESPHome/Bermuda BLE proxy boards, pointing Frigate at a real camera
|
||||||
|
RTSP URL, the Grocy kiosk touchscreen, the separate LLM/GPU host, and wiring
|
||||||
|
up the Nextcloud CalDAV integration — see the Status checklist above and
|
||||||
|
`docs/project-plan.md` for those.
|
||||||
|
|
|
||||||
|
|
@ -3,14 +3,23 @@
|
||||||
# Smart Home Container Host Setup
|
# Smart Home Container Host Setup
|
||||||
# Target: Debian 12 (Bookworm) or Raspberry Pi OS Lite (Bookworm-based)
|
# Target: Debian 12 (Bookworm) or Raspberry Pi OS Lite (Bookworm-based)
|
||||||
#
|
#
|
||||||
# Sets up Docker + a compose stack for:
|
# Sets up Docker + a compose stack for everything that lives on this single
|
||||||
|
# host per docs/project-plan.md Phase 1 and Phase 9 (the LLM/GPU host is a
|
||||||
|
# separate physical machine — see hosts/llm-host):
|
||||||
# - Home Assistant (Container install)
|
# - Home Assistant (Container install)
|
||||||
# - Mosquitto (MQTT broker)
|
# - Mosquitto (MQTT broker)
|
||||||
# - Zigbee2MQTT (talks to your network-attached SLZB-06 coordinator)
|
# - Zigbee2MQTT (talks to your USB-attached CC2652P/Haozee coordinator)
|
||||||
|
# - Node-RED (automation glue, identity-correlation flow home)
|
||||||
# - Frigate (NVR + face recognition, for the peephole cam)
|
# - Frigate (NVR + face recognition, for the peephole cam)
|
||||||
# - Grocy (self-hosted inventory / shopping list)
|
# - Grocy (self-hosted inventory / shopping list)
|
||||||
|
# - Mealie (optional meal planning)
|
||||||
|
# - Netdata (host + container monitoring)
|
||||||
|
# - Homepage (single dashboard landing page)
|
||||||
|
# - ntfy (self-hosted push notifications)
|
||||||
|
# - Portainer (Docker GUI)
|
||||||
|
# - restic scheduled backups (optional, off by default)
|
||||||
#
|
#
|
||||||
# Run as: sudo ./setup-smart-home-host.sh
|
# Run as: sudo ./setup-container-host.sh
|
||||||
#
|
#
|
||||||
# EDIT THE VARIABLES BELOW BEFORE RUNNING.
|
# EDIT THE VARIABLES BELOW BEFORE RUNNING.
|
||||||
|
|
||||||
|
|
@ -24,6 +33,21 @@ TIMEZONE="Europe/Vienna" # Adjust to your timezone
|
||||||
ENABLE_MEALIE="false" # Set to "true" to also deploy Mealie
|
ENABLE_MEALIE="false" # Set to "true" to also deploy Mealie
|
||||||
ENABLE_INTEL_HWACCEL="false" # Set to "true" if this host has an Intel iGPU for Frigate
|
ENABLE_INTEL_HWACCEL="false" # Set to "true" if this host has an Intel iGPU for Frigate
|
||||||
|
|
||||||
|
# --- Phase 1/9 add-ons — on by default, set to "false" to skip any of them ---
|
||||||
|
ENABLE_NODERED="true"
|
||||||
|
ENABLE_NETDATA="true"
|
||||||
|
ENABLE_HOMEPAGE="true"
|
||||||
|
ENABLE_NTFY="true"
|
||||||
|
ENABLE_PORTAINER="true"
|
||||||
|
|
||||||
|
# --- Scheduled backups (restic) — off by default until you pick a target ---
|
||||||
|
# Set ENABLE_BACKUPS=true and RESTIC_REPOSITORY to a local path (e.g. an
|
||||||
|
# external/USB drive mount, or a NAS mount), or a remote target restic
|
||||||
|
# supports (s3:..., sftp:..., b2:..., rest:...). See https://restic.net
|
||||||
|
ENABLE_BACKUPS="false"
|
||||||
|
RESTIC_REPOSITORY="/mnt/backup/smart-home-restic"
|
||||||
|
BACKUP_SCHEDULE="03:30" # systemd OnCalendar time, daily at this local time
|
||||||
|
|
||||||
# --- Zigbee adapter: USB (CC2652P/CH340C, e.g. Haozee/Sonoff Dongle-P style) ---
|
# --- Zigbee adapter: USB (CC2652P/CH340C, e.g. Haozee/Sonoff Dongle-P style) ---
|
||||||
# Run `ls -l /dev/serial/by-id/` AFTER plugging the adapter in, and paste the
|
# Run `ls -l /dev/serial/by-id/` AFTER plugging the adapter in, and paste the
|
||||||
# full path it shows here. This is more stable across reboots than /dev/ttyUSB0.
|
# full path it shows here. This is more stable across reboots than /dev/ttyUSB0.
|
||||||
|
|
@ -34,7 +58,7 @@ ZIGBEE_USB_DEVICE="/dev/serial/by-id/usb-1a86_USB_Serial-if00-port0"
|
||||||
# Sanity checks
|
# Sanity checks
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
if [[ $EUID -ne 0 ]]; then
|
if [[ $EUID -ne 0 ]]; then
|
||||||
echo "Please run as root (sudo ./setup-smart-home-host.sh)" >&2
|
echo "Please run as root (sudo ./setup-container-host.sh)" >&2
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
|
@ -49,9 +73,18 @@ if [[ ! -e "$ZIGBEE_USB_DEVICE" ]]; then
|
||||||
echo " Continuing anyway — the zigbee2mqtt container just won't start correctly until this is fixed."
|
echo " Continuing anyway — the zigbee2mqtt container just won't start correctly until this is fixed."
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
if [[ "$ENABLE_BACKUPS" == "true" && "$RESTIC_REPOSITORY" == "/mnt/backup/smart-home-restic" ]]; then
|
||||||
|
echo "Warning: ENABLE_BACKUPS=true but RESTIC_REPOSITORY is still the placeholder path."
|
||||||
|
echo " Edit RESTIC_REPOSITORY at the top of this script to point at real backup storage."
|
||||||
|
fi
|
||||||
|
|
||||||
|
HOST_IP="$(hostname -I 2>/dev/null | awk '{print $1}')"
|
||||||
|
HOST_IP="${HOST_IP:-<host-ip>}"
|
||||||
|
|
||||||
echo "=== Smart Home Container Host Setup ==="
|
echo "=== Smart Home Container Host Setup ==="
|
||||||
echo "Base directory: $BASE_DIR"
|
echo "Base directory: $BASE_DIR"
|
||||||
echo "Timezone: $TIMEZONE"
|
echo "Timezone: $TIMEZONE"
|
||||||
|
echo "Host IP (detected): $HOST_IP"
|
||||||
echo "Zigbee adapter (USB): ${ZIGBEE_USB_DEVICE}"
|
echo "Zigbee adapter (USB): ${ZIGBEE_USB_DEVICE}"
|
||||||
echo
|
echo
|
||||||
|
|
||||||
|
|
@ -66,7 +99,12 @@ apt-get install -y \
|
||||||
curl \
|
curl \
|
||||||
gnupg \
|
gnupg \
|
||||||
lsb-release \
|
lsb-release \
|
||||||
jq
|
jq \
|
||||||
|
openssl
|
||||||
|
|
||||||
|
if [[ "$ENABLE_BACKUPS" == "true" ]]; then
|
||||||
|
apt-get install -y restic
|
||||||
|
fi
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
# 2. Install Docker Engine + Compose plugin (official Docker repo)
|
# 2. Install Docker Engine + Compose plugin (official Docker repo)
|
||||||
|
|
@ -103,6 +141,21 @@ mkdir -p "$BASE_DIR"/{homeassistant,mosquitto/config,mosquitto/data,mosquitto/lo
|
||||||
if [[ "$ENABLE_MEALIE" == "true" ]]; then
|
if [[ "$ENABLE_MEALIE" == "true" ]]; then
|
||||||
mkdir -p "$BASE_DIR"/mealie/data
|
mkdir -p "$BASE_DIR"/mealie/data
|
||||||
fi
|
fi
|
||||||
|
if [[ "$ENABLE_NODERED" == "true" ]]; then
|
||||||
|
mkdir -p "$BASE_DIR"/nodered/data
|
||||||
|
fi
|
||||||
|
if [[ "$ENABLE_NETDATA" == "true" ]]; then
|
||||||
|
mkdir -p "$BASE_DIR"/netdata/{config,lib,cache}
|
||||||
|
fi
|
||||||
|
if [[ "$ENABLE_HOMEPAGE" == "true" ]]; then
|
||||||
|
mkdir -p "$BASE_DIR"/homepage/config
|
||||||
|
fi
|
||||||
|
if [[ "$ENABLE_NTFY" == "true" ]]; then
|
||||||
|
mkdir -p "$BASE_DIR"/ntfy/{data,config}
|
||||||
|
fi
|
||||||
|
if [[ "$ENABLE_PORTAINER" == "true" ]]; then
|
||||||
|
mkdir -p "$BASE_DIR"/portainer/data
|
||||||
|
fi
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
# 4. Mosquitto config
|
# 4. Mosquitto config
|
||||||
|
|
@ -120,7 +173,7 @@ log_dest file /mosquitto/log/mosquitto.log
|
||||||
EOF
|
EOF
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
# 5. Zigbee2MQTT config (pre-seeded for the SLZB-06 network coordinator)
|
# 5. Zigbee2MQTT config (pre-seeded for your USB CC2652P dongle)
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
echo "--- Writing Zigbee2MQTT config ---"
|
echo "--- Writing Zigbee2MQTT config ---"
|
||||||
cat > "$BASE_DIR/zigbee2mqtt/data/configuration.yaml" <<EOF
|
cat > "$BASE_DIR/zigbee2mqtt/data/configuration.yaml" <<EOF
|
||||||
|
|
@ -180,7 +233,62 @@ EOF
|
||||||
echo " NOTE: edit $BASE_DIR/frigate/config/config.yml with your real camera RTSP URL before starting Frigate."
|
echo " NOTE: edit $BASE_DIR/frigate/config/config.yml with your real camera RTSP URL before starting Frigate."
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
# 7. docker-compose.yml
|
# 7. Homepage dashboard config (single landing page over the whole stack)
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
if [[ "$ENABLE_HOMEPAGE" == "true" ]]; then
|
||||||
|
echo "--- Writing Homepage config ---"
|
||||||
|
cat > "$BASE_DIR/homepage/config/settings.yaml" <<'EOF'
|
||||||
|
title: Smart Home
|
||||||
|
theme: dark
|
||||||
|
color: slate
|
||||||
|
headerStyle: clean
|
||||||
|
EOF
|
||||||
|
|
||||||
|
cat > "$BASE_DIR/homepage/config/widgets.yaml" <<'EOF'
|
||||||
|
- resources:
|
||||||
|
cpu: true
|
||||||
|
memory: true
|
||||||
|
disk: /
|
||||||
|
EOF
|
||||||
|
|
||||||
|
cat > "$BASE_DIR/homepage/config/bookmarks.yaml" <<'EOF'
|
||||||
|
[]
|
||||||
|
EOF
|
||||||
|
|
||||||
|
cat > "$BASE_DIR/homepage/config/services.yaml" <<EOF
|
||||||
|
- Home:
|
||||||
|
- Home Assistant:
|
||||||
|
href: http://${HOST_IP}:8123
|
||||||
|
description: Home automation hub
|
||||||
|
- Node-RED:
|
||||||
|
href: http://${HOST_IP}:1880
|
||||||
|
description: Automation flows
|
||||||
|
- Devices:
|
||||||
|
- Zigbee2MQTT:
|
||||||
|
href: http://${HOST_IP}:8080
|
||||||
|
description: Zigbee network
|
||||||
|
- Frigate:
|
||||||
|
href: http://${HOST_IP}:5000
|
||||||
|
description: NVR / face recognition
|
||||||
|
- Household:
|
||||||
|
- Grocy:
|
||||||
|
href: http://${HOST_IP}:9283
|
||||||
|
description: Inventory & shopping list
|
||||||
|
- System:
|
||||||
|
- Portainer:
|
||||||
|
href: http://${HOST_IP}:9000
|
||||||
|
description: Container management
|
||||||
|
- Netdata:
|
||||||
|
href: http://${HOST_IP}:19999
|
||||||
|
description: Host/container monitoring
|
||||||
|
- ntfy:
|
||||||
|
href: http://${HOST_IP}:8090
|
||||||
|
description: Push notifications
|
||||||
|
EOF
|
||||||
|
fi
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# 8. docker-compose.yml
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
echo "--- Writing docker-compose.yml ---"
|
echo "--- Writing docker-compose.yml ---"
|
||||||
|
|
||||||
|
|
@ -207,6 +315,105 @@ if [[ "$ENABLE_MEALIE" == "true" ]]; then
|
||||||
"
|
"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
NODERED_BLOCK=""
|
||||||
|
if [[ "$ENABLE_NODERED" == "true" ]]; then
|
||||||
|
NODERED_BLOCK="
|
||||||
|
nodered:
|
||||||
|
image: nodered/node-red:latest
|
||||||
|
container_name: nodered
|
||||||
|
restart: unless-stopped
|
||||||
|
depends_on:
|
||||||
|
- mosquitto
|
||||||
|
ports:
|
||||||
|
- \"1880:1880\"
|
||||||
|
volumes:
|
||||||
|
- ${BASE_DIR}/nodered/data:/data
|
||||||
|
- /etc/localtime:/etc/localtime:ro
|
||||||
|
environment:
|
||||||
|
- TZ=${TIMEZONE}
|
||||||
|
"
|
||||||
|
fi
|
||||||
|
|
||||||
|
NETDATA_BLOCK=""
|
||||||
|
if [[ "$ENABLE_NETDATA" == "true" ]]; then
|
||||||
|
NETDATA_BLOCK="
|
||||||
|
netdata:
|
||||||
|
image: netdata/netdata:stable
|
||||||
|
container_name: netdata
|
||||||
|
restart: unless-stopped
|
||||||
|
hostname: smart-home-host
|
||||||
|
network_mode: host
|
||||||
|
pid: host
|
||||||
|
cap_add:
|
||||||
|
- SYS_PTRACE
|
||||||
|
- SYS_ADMIN
|
||||||
|
security_opt:
|
||||||
|
- apparmor:unconfined
|
||||||
|
volumes:
|
||||||
|
- ${BASE_DIR}/netdata/config:/etc/netdata
|
||||||
|
- ${BASE_DIR}/netdata/lib:/var/lib/netdata
|
||||||
|
- ${BASE_DIR}/netdata/cache:/var/cache/netdata
|
||||||
|
- /etc/passwd:/host/etc/passwd:ro
|
||||||
|
- /etc/group:/host/etc/group:ro
|
||||||
|
- /etc/os-release:/host/etc/os-release:ro
|
||||||
|
- /var/log:/host/var/log:ro
|
||||||
|
- /proc:/host/proc:ro
|
||||||
|
- /sys:/host/sys:ro
|
||||||
|
- /etc/localtime:/etc/localtime:ro
|
||||||
|
- /var/run/docker.sock:/var/run/docker.sock:ro
|
||||||
|
"
|
||||||
|
fi
|
||||||
|
|
||||||
|
HOMEPAGE_BLOCK=""
|
||||||
|
if [[ "$ENABLE_HOMEPAGE" == "true" ]]; then
|
||||||
|
HOMEPAGE_BLOCK="
|
||||||
|
homepage:
|
||||||
|
image: ghcr.io/gethomepage/homepage:latest
|
||||||
|
container_name: homepage
|
||||||
|
restart: unless-stopped
|
||||||
|
ports:
|
||||||
|
- \"3000:3000\"
|
||||||
|
volumes:
|
||||||
|
- ${BASE_DIR}/homepage/config:/app/config
|
||||||
|
environment:
|
||||||
|
- TZ=${TIMEZONE}
|
||||||
|
"
|
||||||
|
fi
|
||||||
|
|
||||||
|
NTFY_BLOCK=""
|
||||||
|
if [[ "$ENABLE_NTFY" == "true" ]]; then
|
||||||
|
NTFY_BLOCK="
|
||||||
|
ntfy:
|
||||||
|
image: binwiederhier/ntfy:latest
|
||||||
|
container_name: ntfy
|
||||||
|
restart: unless-stopped
|
||||||
|
command: serve
|
||||||
|
ports:
|
||||||
|
- \"8090:80\"
|
||||||
|
volumes:
|
||||||
|
- ${BASE_DIR}/ntfy/data:/var/lib/ntfy
|
||||||
|
- ${BASE_DIR}/ntfy/config:/etc/ntfy
|
||||||
|
environment:
|
||||||
|
- TZ=${TIMEZONE}
|
||||||
|
"
|
||||||
|
fi
|
||||||
|
|
||||||
|
PORTAINER_BLOCK=""
|
||||||
|
if [[ "$ENABLE_PORTAINER" == "true" ]]; then
|
||||||
|
PORTAINER_BLOCK="
|
||||||
|
portainer:
|
||||||
|
image: portainer/portainer-ce:lts
|
||||||
|
container_name: portainer
|
||||||
|
restart: unless-stopped
|
||||||
|
command: -H unix:///var/run/docker.sock
|
||||||
|
ports:
|
||||||
|
- \"9000:9000\"
|
||||||
|
volumes:
|
||||||
|
- /var/run/docker.sock:/var/run/docker.sock
|
||||||
|
- ${BASE_DIR}/portainer/data:/data
|
||||||
|
"
|
||||||
|
fi
|
||||||
|
|
||||||
cat > "$BASE_DIR/docker-compose.yml" <<EOF
|
cat > "$BASE_DIR/docker-compose.yml" <<EOF
|
||||||
name: smart-home
|
name: smart-home
|
||||||
|
|
||||||
|
|
@ -283,11 +490,91 @@ ${FRIGATE_DEVICES}
|
||||||
- PUID=1000
|
- PUID=1000
|
||||||
- PGID=1000
|
- PGID=1000
|
||||||
- TZ=${TIMEZONE}
|
- TZ=${TIMEZONE}
|
||||||
${MEALIE_BLOCK}
|
${MEALIE_BLOCK}${NODERED_BLOCK}${NETDATA_BLOCK}${HOMEPAGE_BLOCK}${NTFY_BLOCK}${PORTAINER_BLOCK}
|
||||||
EOF
|
EOF
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
# 8. Bring the stack up
|
# 9. Scheduled backups (restic) — optional
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
if [[ "$ENABLE_BACKUPS" == "true" ]]; then
|
||||||
|
echo "--- Setting up restic backups ---"
|
||||||
|
|
||||||
|
RESTIC_PASSWORD_FILE="$BASE_DIR/.restic-password"
|
||||||
|
if [[ ! -f "$RESTIC_PASSWORD_FILE" ]]; then
|
||||||
|
openssl rand -base64 32 > "$RESTIC_PASSWORD_FILE"
|
||||||
|
chmod 600 "$RESTIC_PASSWORD_FILE"
|
||||||
|
echo " Generated a new restic repository password at $RESTIC_PASSWORD_FILE."
|
||||||
|
echo " BACK THIS FILE UP SOMEWHERE ELSE — losing it makes the backup repo unreadable."
|
||||||
|
fi
|
||||||
|
|
||||||
|
cat > "$BASE_DIR/backup.env" <<EOF
|
||||||
|
RESTIC_REPOSITORY=${RESTIC_REPOSITORY}
|
||||||
|
RESTIC_PASSWORD_FILE=${RESTIC_PASSWORD_FILE}
|
||||||
|
EOF
|
||||||
|
chmod 600 "$BASE_DIR/backup.env"
|
||||||
|
|
||||||
|
cat > "$BASE_DIR/backup.sh" <<'BACKUP_EOF'
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
# Backs up all stateful smart-home volumes with restic.
|
||||||
|
# Stops the stack briefly for a consistent snapshot of SQLite-backed configs
|
||||||
|
# (HA, Zigbee2MQTT, Grocy, Node-RED), then restarts it.
|
||||||
|
# Frigate's recorded video is excluded — it's large and non-critical to keep;
|
||||||
|
# face-recognition embeddings live under frigate/config, which IS backed up.
|
||||||
|
set -euo pipefail
|
||||||
|
BASE_DIR="/opt/smart-home"
|
||||||
|
source "$BASE_DIR/backup.env"
|
||||||
|
|
||||||
|
cd "$BASE_DIR"
|
||||||
|
|
||||||
|
if ! restic snapshots --no-lock >/dev/null 2>&1; then
|
||||||
|
echo "Initializing new restic repository at $RESTIC_REPOSITORY"
|
||||||
|
restic init
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "Stopping stack for a consistent backup..."
|
||||||
|
docker compose stop
|
||||||
|
|
||||||
|
restic backup "$BASE_DIR" \
|
||||||
|
--exclude "$BASE_DIR/frigate/media" \
|
||||||
|
--exclude "$BASE_DIR/.restic-password" \
|
||||||
|
--exclude "$BASE_DIR/backup.env"
|
||||||
|
|
||||||
|
echo "Restarting stack..."
|
||||||
|
docker compose start
|
||||||
|
|
||||||
|
restic forget --keep-daily 7 --keep-weekly 4 --keep-monthly 6 --prune
|
||||||
|
BACKUP_EOF
|
||||||
|
chmod +x "$BASE_DIR/backup.sh"
|
||||||
|
|
||||||
|
cat > /etc/systemd/system/smart-home-backup.service <<EOF
|
||||||
|
[Unit]
|
||||||
|
Description=Smart home restic backup
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
Type=oneshot
|
||||||
|
ExecStart=${BASE_DIR}/backup.sh
|
||||||
|
EOF
|
||||||
|
|
||||||
|
cat > /etc/systemd/system/smart-home-backup.timer <<EOF
|
||||||
|
[Unit]
|
||||||
|
Description=Daily smart home restic backup
|
||||||
|
|
||||||
|
[Timer]
|
||||||
|
OnCalendar=*-*-* ${BACKUP_SCHEDULE}:00
|
||||||
|
Persistent=true
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=timers.target
|
||||||
|
EOF
|
||||||
|
|
||||||
|
systemctl daemon-reload
|
||||||
|
systemctl enable --now smart-home-backup.timer
|
||||||
|
echo " Backup timer installed: daily at ${BACKUP_SCHEDULE}, repository: ${RESTIC_REPOSITORY}"
|
||||||
|
echo " Run it manually any time with: sudo $BASE_DIR/backup.sh"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# 10. Bring the stack up
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
echo "--- Starting containers ---"
|
echo "--- Starting containers ---"
|
||||||
cd "$BASE_DIR"
|
cd "$BASE_DIR"
|
||||||
|
|
@ -296,12 +583,27 @@ docker compose up -d
|
||||||
echo
|
echo
|
||||||
echo "=== Done ==="
|
echo "=== Done ==="
|
||||||
echo "Services should be reachable at:"
|
echo "Services should be reachable at:"
|
||||||
echo " Home Assistant : http://<host-ip>:8123"
|
echo " Home Assistant : http://${HOST_IP}:8123"
|
||||||
echo " Zigbee2MQTT UI : http://<host-ip>:8080"
|
echo " Zigbee2MQTT UI : http://${HOST_IP}:8080"
|
||||||
echo " Frigate : http://<host-ip>:5000"
|
echo " Frigate : http://${HOST_IP}:5000"
|
||||||
echo " Grocy : http://<host-ip>:9283"
|
echo " Grocy : http://${HOST_IP}:9283"
|
||||||
if [[ "$ENABLE_MEALIE" == "true" ]]; then
|
if [[ "$ENABLE_MEALIE" == "true" ]]; then
|
||||||
echo " Mealie : http://<host-ip>:9925"
|
echo " Mealie : http://${HOST_IP}:9925"
|
||||||
|
fi
|
||||||
|
if [[ "$ENABLE_NODERED" == "true" ]]; then
|
||||||
|
echo " Node-RED : http://${HOST_IP}:1880"
|
||||||
|
fi
|
||||||
|
if [[ "$ENABLE_NETDATA" == "true" ]]; then
|
||||||
|
echo " Netdata : http://${HOST_IP}:19999"
|
||||||
|
fi
|
||||||
|
if [[ "$ENABLE_HOMEPAGE" == "true" ]]; then
|
||||||
|
echo " Homepage : http://${HOST_IP}:3000"
|
||||||
|
fi
|
||||||
|
if [[ "$ENABLE_NTFY" == "true" ]]; then
|
||||||
|
echo " ntfy : http://${HOST_IP}:8090"
|
||||||
|
fi
|
||||||
|
if [[ "$ENABLE_PORTAINER" == "true" ]]; then
|
||||||
|
echo " Portainer : http://${HOST_IP}:9000"
|
||||||
fi
|
fi
|
||||||
echo
|
echo
|
||||||
echo "Next steps:"
|
echo "Next steps:"
|
||||||
|
|
@ -312,8 +614,17 @@ echo " ZIGBEE_USB_DEVICE at the top of this script matches 'ls -l /dev/seria
|
||||||
echo " and that the dongle has Z-Stack coordinator firmware (not stock gateway firmware)."
|
echo " and that the dongle has Z-Stack coordinator firmware (not stock gateway firmware)."
|
||||||
echo " 3. Complete Home Assistant's onboarding wizard at :8123."
|
echo " 3. Complete Home Assistant's onboarding wizard at :8123."
|
||||||
echo " 4. In HA, add the MQTT integration (it should auto-discover Zigbee2MQTT devices)."
|
echo " 4. In HA, add the MQTT integration (it should auto-discover Zigbee2MQTT devices)."
|
||||||
echo " 5. Add the CalDAV integration pointing at your Nextcloud instance."
|
echo " 5. Portainer: open :9000 within a few minutes of first start to set the admin password"
|
||||||
echo " 6. Point HA's Ollama conversation integration at your separate GPU/LLM host."
|
echo " before anyone else on the LAN can claim that instance."
|
||||||
|
echo " 6. Node-RED runs in its own bridge network, but Home Assistant uses network_mode: host,"
|
||||||
|
echo " so Node-RED can't reach it by container name — use http://${HOST_IP}:8123 plus a"
|
||||||
|
echo " long-lived access token (HA profile page) when wiring up the HA nodes."
|
||||||
|
echo " 7. Add the CalDAV integration pointing at your Nextcloud instance."
|
||||||
|
echo " 8. Point HA's Ollama conversation integration at your separate GPU/LLM host."
|
||||||
|
if [[ "$ENABLE_BACKUPS" != "true" ]]; then
|
||||||
|
echo " 9. Backups are off. Set ENABLE_BACKUPS=true and RESTIC_REPOSITORY at the top of this"
|
||||||
|
echo " script and re-run once you have backup storage (external drive, NAS, or remote) ready."
|
||||||
|
fi
|
||||||
echo
|
echo
|
||||||
echo "Updating later: cd $BASE_DIR && docker compose pull && docker compose up -d"
|
echo "Updating later: cd $BASE_DIR && docker compose pull && docker compose up -d"
|
||||||
echo "Backing up: stop the stack (docker compose down), then tar/copy $BASE_DIR, then start it again."
|
echo "Backing up manually: sudo $BASE_DIR/backup.sh (requires ENABLE_BACKUPS=true was run once)"
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue