From c70539431dcb9369b6930a53a512eb6f61a068ed Mon Sep 17 00:00:00 2001 From: John Riccardi Date: Thu, 19 Mar 2026 10:47:09 -0500 Subject: [PATCH 1/9] Add local dev harness for agentic testing Introduces a full local development environment so Claude can spin up services, run health checks, and verify changes autonomously without the Linux server or Cloudflare DNS. - docker-compose.dev.yml: compose override disabling pihole/jellyfin (Linux-only), Traefik HTTP-only on :8080, all services with native ports exposed, data volumes redirected to .dev/data/ - traefik/traefik.dev.yml: Traefik static config with no ACME, ping endpoint enabled, insecure dashboard for local use - traefik/dynamic.dev/: empty dynamic config dir to avoid cert resolver warnings from production tls.yml/services.yml - scripts/dev.sh: up/down/restart/status/logs subcommands; creates .env.dev and data dirs on first run; down/status work without .env.dev - scripts/health-check.sh: hits each service health endpoint on localhost; exits 1 if any fail; supports single-service filter arg - .env.dev.example: dev-safe template with dummy tokens (readable by Claude; blocked by pre-tool-use hook like .env) - CLAUDE.md: local dev workflow section with agentic test loop pattern - .gitignore: add .env.dev, .dev/, acme.dev.json - pre-tool-use.sh: allow reads of .env.dev.example Co-Authored-By: Claude Sonnet 4.6 --- .claude/hooks/pre-tool-use.sh | 2 +- .env.dev.example | 52 +++++++++++++ .gitignore | 5 ++ CLAUDE.md | 52 +++++++++++++ docker-compose.dev.yml | 70 +++++++++++++++++ scripts/dev.sh | 138 ++++++++++++++++++++++++++++++++++ scripts/health-check.sh | 59 +++++++++++++++ traefik/dynamic.dev/.gitkeep | 0 traefik/traefik.dev.yml | 29 +++++++ 9 files changed, 406 insertions(+), 1 deletion(-) create mode 100644 .env.dev.example create mode 100644 docker-compose.dev.yml create mode 100755 scripts/dev.sh create mode 100755 scripts/health-check.sh create mode 100644 traefik/dynamic.dev/.gitkeep create mode 100644 traefik/traefik.dev.yml diff --git a/.claude/hooks/pre-tool-use.sh b/.claude/hooks/pre-tool-use.sh index 1e6b2dc..89069a9 100755 --- a/.claude/hooks/pre-tool-use.sh +++ b/.claude/hooks/pre-tool-use.sh @@ -20,7 +20,7 @@ fi basename="${file_path##*/}" -if [[ "$basename" == .env* && "$basename" != ".env.example" ]]; then +if [[ "$basename" == .env* && "$basename" != ".env.example" && "$basename" != ".env.dev.example" ]]; then echo "BLOCKED: '$basename' may contain secrets and cannot be read or modified by agents." echo "Use .env.example for variable references and documentation instead." exit 2 diff --git a/.env.dev.example b/.env.dev.example new file mode 100644 index 0000000..204a2af --- /dev/null +++ b/.env.dev.example @@ -0,0 +1,52 @@ +# Development environment — safe defaults for local testing. +# Copied to .env.dev automatically by: ./scripts/dev.sh up +# Never commit .env.dev; it may contain real credentials. +# +# Services skipped in local dev (not needed here): +# pihole — requires Linux host networking +# jellyfin — requires /dev/dri render device + +# ── Domain / Network ────────────────────────────────────────────── +DOMAIN=localhost +SERVER_IP=127.0.0.1 +TZ=America/Chicago + +# ── Cloudflare / TLS ───────────────────────────────────────────── +# Not used in local dev; ACME is disabled in traefik.dev.yml. +# Must be non-empty to pass docker-compose.yml variable substitution. +CF_DNS_API_TOKEN=dev-disabled +ADMIN_EMAIL=dev@localhost + +# ── Pi-hole ─────────────────────────────────────────────────────── +# Disabled in local dev. Vars kept so compose config is valid. +PIHOLE_PASSWORD=dev-password +PIHOLE_API_KEY= +PIHOLE_DNS=8.8.8.8;1.1.1.1 + +# ── Traefik ─────────────────────────────────────────────────────── +# Dashboard is insecure (no auth) in local dev. Values kept for +# homepage widget template rendering. +TRAEFIK_DASHBOARD_USERS=admin:$$apr1$$dev$$devhashhash +TRAEFIK_USERNAME=admin +TRAEFIK_PASSWORD=dev-password + +# ── Jellyfin ────────────────────────────────────────────────────── +# Disabled in local dev. Vars kept so compose config is valid. +JELLYFIN_API_KEY= +RENDER_GID=44 + +# ── Portainer ───────────────────────────────────────────────────── +# Populated manually or via scripts/post-setup.sh after first login. +PORTAINER_API_KEY= +PORTAINER_ENV_ID= + +# ── FileBrowser ─────────────────────────────────────────────────── +FILEBROWSER_PATH=.dev/data/filebrowser/files + +# ── Media / Sync ───────────────────────────────────────────────── +MEDIA_PATH=.dev/data/media +SYNC_PATH=.dev/data/sync + +# ── Wallabag ────────────────────────────────────────────────────── +# dev-only secret — not for production use +WALLABAG_SECRET=dev-secret-local-only diff --git a/.gitignore b/.gitignore index 8337f3c..cc30dd7 100644 --- a/.gitignore +++ b/.gitignore @@ -4,6 +4,7 @@ node_modules/ # Environment files .env +.env.dev # Docker .docker/ @@ -11,6 +12,10 @@ docker-compose.override.yml # Traefik: ignore cert storage (contains private keys) traefik/letsencrypt/acme.json +traefik/letsencrypt/acme.dev.json + +# Local dev data (created by scripts/dev.sh up) +.dev/ # Docker volumes and data # Pi-hole: ignore runtime data, but track configuration files diff --git a/CLAUDE.md b/CLAUDE.md index e8c0d06..a592240 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -21,6 +21,58 @@ If a new service or widget adds variables, update **all three** files together: - `docker-compose.yml` homepage service `environment:` block - `.env.example` (with a comment explaining where to get the value) +## Local dev environment + +Use this to run and test services locally before opening a PR. + +**Start the stack:** + +```bash +./scripts/dev.sh up # creates .env.dev and .dev/data/ on first run +./scripts/health-check.sh # verify all services respond +``` + +**Agentic test loop:** + +```bash +# after making a change to a service's config or compose definition: +./scripts/dev.sh restart +./scripts/health-check.sh # targeted check; exit 1 = still broken +./scripts/dev.sh logs # read logs to diagnose failures +``` + +**Tear down:** + +```bash +./scripts/dev.sh down +``` + +**Services available in local dev** (health check ports): + +| Service | URL | Notes | +| ----------- | --------------------- | ----------------------------------- | +| traefik | http://localhost:8080 | HTTP only; dashboard at /dashboard/ | +| homepage | http://localhost:3000 | | +| portainer | http://localhost:9000 | | +| filebrowser | http://localhost:8081 | | +| syncthing | http://localhost:8384 | | +| wallabag | http://localhost:8888 | | + +**Services skipped in local dev:** + +- `pihole` — requires Linux host networking +- `jellyfin` — requires `/dev/dri` render device + +**Known timing issue:** Wallabag runs a DB migration on first start and takes ~30s before it responds. +If `health-check.sh` reports wallabag FAIL immediately after `up`, wait and re-run — it is not a real failure. + +**Key files:** + +- `docker-compose.dev.yml` — compose overrides (ports, volumes, disabled services) +- `traefik/traefik.dev.yml` — Traefik static config (HTTP only, no ACME) +- `.env.dev.example` — dev env template (safe to read; copied to `.env.dev` on first run) +- `.dev/` — local data directories (gitignored) + ## Session initialization At the start of every work session: diff --git a/docker-compose.dev.yml b/docker-compose.dev.yml new file mode 100644 index 0000000..f8bceec --- /dev/null +++ b/docker-compose.dev.yml @@ -0,0 +1,70 @@ +# docker-compose.dev.yml — Local development overrides +# Usage: ./scripts/dev.sh up (wraps the compose -f flags) +# +# Differences from production: +# - pihole disabled (requires host networking, Linux-only) +# - jellyfin disabled (requires /dev/dri render device, Linux-only) +# - Traefik: HTTP-only on :8080, no ACME/Cloudflare, insecure dashboard +# - All services expose native ports for direct localhost health checks +# - Data volumes redirect to .dev/data/ (project-local, gitignored) + +services: + pihole: + profiles: [prod] # disabled in local dev + + jellyfin: + profiles: [prod] # disabled in local dev (requires /dev/dri device) + + traefik: + # Override: restore default CMD (removes ACME email arg from production command) + command: + - traefik + ports: + - "8080:80" + - "8443:443" + environment: + # ACME is not configured in traefik.dev.yml; value is irrelevant + - CF_DNS_API_TOKEN=dev-disabled + volumes: + - /var/run/docker.sock:/var/run/docker.sock:ro + - ./traefik/traefik.dev.yml:/etc/traefik/traefik.yml:ro + # Use empty dynamic.dev/ to avoid cert resolver warnings from production tls.yml/services.yml + - ./traefik/dynamic.dev:/etc/traefik/dynamic:ro + - ./traefik/letsencrypt/acme.dev.json:/acme.json + + homepage: + ports: + - "3000:3000" + environment: + - HOMEPAGE_ALLOWED_HOSTS=localhost + + portainer: + ports: + - "127.0.0.1:9000:9000" + volumes: + - /var/run/docker.sock:/var/run/docker.sock + - ./.dev/data/portainer:/data + + filebrowser: + ports: + - "8081:80" + volumes: + - ./.dev/data/filebrowser/database:/database + - ./.dev/data/filebrowser/config:/config + - ./.dev/data/filebrowser/files:/srv + + syncthing: + ports: + - "8384:8384" + volumes: + - ./.dev/data/syncthing:/config + - ./.dev/data/sync:/data1 + + wallabag: + ports: + - "8888:80" + volumes: + - ./.dev/data/wallabag:/var/www/wallabag/data + - ./.dev/data/wallabag-images:/var/www/wallabag/web/assets/images + environment: + - SYMFONY__ENV__DOMAIN_NAME=http://localhost:8888 diff --git a/scripts/dev.sh b/scripts/dev.sh new file mode 100755 index 0000000..137ab4d --- /dev/null +++ b/scripts/dev.sh @@ -0,0 +1,138 @@ +#!/usr/bin/env bash +# dev.sh — manage the local development stack +# +# Usage: +# ./scripts/dev.sh up [svc...] Start local stack (creates dirs + .env.dev on first run) +# ./scripts/dev.sh down Stop and remove containers +# ./scripts/dev.sh restart [svc] Restart one or all services +# ./scripts/dev.sh status Show container status +# ./scripts/dev.sh logs [svc] Tail logs (all services or one) +# +# Services skipped automatically in local dev: +# pihole — requires Linux host networking +# jellyfin — requires /dev/dri render device + +set -euo pipefail + +REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +ENV_FILE="$REPO_ROOT/.env.dev" +ENV_EXAMPLE="$REPO_ROOT/.env.dev.example" +DEV_DATA="$REPO_ROOT/.dev/data" + +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +RED='\033[0;31m' +RESET='\033[0m' + +log() { printf "${GREEN}[dev]${RESET} %s\n" "$*"; } +warn() { printf "${YELLOW}[dev]${RESET} %s\n" "$*"; } +err() { printf "${RED}[dev]${RESET} %s\n" "$*" >&2; } + +# compose_env: requires .env.dev (up, restart, logs) +compose_env() { + docker compose \ + -f "$REPO_ROOT/docker-compose.yml" \ + -f "$REPO_ROOT/docker-compose.dev.yml" \ + --env-file "$ENV_FILE" \ + "$@" +} + +# compose_no_env: works without .env.dev (down, status) +compose_no_env() { + docker compose \ + -f "$REPO_ROOT/docker-compose.yml" \ + -f "$REPO_ROOT/docker-compose.dev.yml" \ + "$@" +} + +ensure_env() { + if [[ ! -f "$ENV_FILE" ]]; then + if [[ ! -f "$ENV_EXAMPLE" ]]; then + err ".env.dev.example not found — cannot create .env.dev" + exit 1 + fi + cp "$ENV_EXAMPLE" "$ENV_FILE" + warn "Created .env.dev from .env.dev.example — edit before running if needed" + fi +} + +ensure_dirs() { + local dirs=( + "$DEV_DATA/portainer" + "$DEV_DATA/filebrowser/database" + "$DEV_DATA/filebrowser/config" + "$DEV_DATA/filebrowser/files" + "$DEV_DATA/syncthing" + "$DEV_DATA/wallabag" + "$DEV_DATA/wallabag-images" + "$DEV_DATA/sync" + "$DEV_DATA/media" + ) + for d in "${dirs[@]}"; do + mkdir -p "$d" + done + + # acme.dev.json must exist with 600 permissions for Traefik + local acme="$REPO_ROOT/traefik/letsencrypt/acme.dev.json" + mkdir -p "$(dirname "$acme")" + if [[ ! -f "$acme" ]]; then + touch "$acme" + chmod 600 "$acme" + log "Created traefik/letsencrypt/acme.dev.json" + fi +} + +cmd_up() { + ensure_env + ensure_dirs + log "Starting local dev stack..." + compose_env up -d "$@" + log "Stack started. Run './scripts/health-check.sh' to verify services." + log "Note: Wallabag takes ~30s on first run (DB migration). Re-run health-check if it fails initially." +} + +cmd_down() { + log "Stopping local dev stack..." + compose_no_env down "$@" +} + +cmd_restart() { + ensure_env + ensure_dirs + compose_env restart "$@" +} + +cmd_status() { + compose_no_env ps +} + +cmd_logs() { + ensure_env + compose_env logs --tail=50 -f "$@" +} + +usage() { + cat < [args] + +Commands: + up [svc...] Start local stack (creates .env.dev and data dirs on first run) + down Stop and remove containers + restart [svc] Restart one or all services + status Show container status + logs [svc] Tail logs (Ctrl-C to exit) + +Services skipped in local dev (require Linux-only features): + pihole — host networking (Linux only) + jellyfin — /dev/dri render device (Linux only) +EOF +} + +case "${1:-}" in + up) shift; cmd_up "$@" ;; + down) shift; cmd_down "$@" ;; + restart) shift; cmd_restart "$@" ;; + status) cmd_status ;; + logs) shift; cmd_logs "$@" ;; + *) usage; exit 1 ;; +esac diff --git a/scripts/health-check.sh b/scripts/health-check.sh new file mode 100755 index 0000000..721fc57 --- /dev/null +++ b/scripts/health-check.sh @@ -0,0 +1,59 @@ +#!/usr/bin/env bash +# health-check.sh — verify local dev services are responding +# +# Usage: +# ./scripts/health-check.sh Check all services +# ./scripts/health-check.sh traefik Check one service by name +# +# Exit code: 0 if all checked services pass, 1 if any fail. + +set -uo pipefail + +GREEN='\033[0;32m' +RED='\033[0;31m' +RESET='\033[0m' + +TIMEOUT=5 +OVERALL=0 +FILTER="${1:-}" + +check() { + local name="$1" url="$2" + local code + code=$(curl -s -o /dev/null -w "%{http_code}" --max-time "$TIMEOUT" "$url" 2>/dev/null || echo "000") + + if [[ "$code" =~ ^2 ]]; then + printf "${GREEN} PASS${RESET} %-15s %s\n" "$name" "$url" + else + printf "${RED} FAIL${RESET} %-15s %s (HTTP %s)\n" "$name" "$url" "$code" + OVERALL=1 + fi +} + +run_check() { + local name="$1" + if [[ -z "$FILTER" || "$FILTER" == "$name" ]]; then + check "$@" + fi +} + +printf "Local dev health checks\n" +printf '%.0s─' {1..60}; echo + +run_check "traefik" "http://localhost:8080/ping" +run_check "homepage" "http://localhost:3000" +run_check "portainer" "http://localhost:9000/api/status" +run_check "filebrowser" "http://localhost:8081" +run_check "syncthing" "http://localhost:8384/rest/noauth/health" +run_check "wallabag" "http://localhost:8888/login" + +printf '%.0s─' {1..60}; echo + +if [[ $OVERALL -eq 0 ]]; then + printf "${GREEN}All services healthy${RESET}\n" +else + printf "${RED}One or more services failed${RESET} — check logs:\n" + printf " ./scripts/dev.sh logs \n" +fi + +exit $OVERALL diff --git a/traefik/dynamic.dev/.gitkeep b/traefik/dynamic.dev/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/traefik/traefik.dev.yml b/traefik/traefik.dev.yml new file mode 100644 index 0000000..c7e9ecd --- /dev/null +++ b/traefik/traefik.dev.yml @@ -0,0 +1,29 @@ +global: + checkNewVersion: false + sendAnonymousUsage: false + +log: + level: INFO + +api: + dashboard: true + insecure: true # No auth needed for local dev + +ping: {} # Enables GET /ping for health checks + +entryPoints: + web: + address: ":80" + # No HTTPS redirect in local dev + websecure: + address: ":443" + +# No certificatesResolvers — HTTP only, no ACME in local dev + +providers: + docker: + exposedByDefault: false + network: proxy + file: + directory: /etc/traefik/dynamic + watch: true From 36d5d9cec64a2935b92c53b41e91d4cd753010dc Mon Sep 17 00:00:00 2001 From: John Riccardi Date: Thu, 19 Mar 2026 10:52:52 -0500 Subject: [PATCH 2/9] Add dev compose merge validation to CI MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Catches drift between docker-compose.yml and docker-compose.dev.yml on every PR — any change that breaks the merged dev config will fail CI. Co-Authored-By: Claude Sonnet 4.6 --- .github/workflows/ci.yml | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 7b9837b..638a7b3 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -42,5 +42,15 @@ jobs: ADMIN_EMAIL: test@example.com run: docker compose config + - name: Validate docker-compose.dev.yml merge + env: + DOMAIN: test.local + TZ: UTC + PIHOLE_PASSWORD: test-password + PIHOLE_DNS: 8.8.8.8;1.1.1.1 + SERVER_IP: 127.0.0.1 + ADMIN_EMAIL: test@example.com + run: docker compose -f docker-compose.yml -f docker-compose.dev.yml config --quiet + - name: Validate config consistency run: bash scripts/lint-config.sh From 512c9f0abae3fc9019a87bd514ecfb676f852250 Mon Sep 17 00:00:00 2001 From: John Riccardi Date: Thu, 19 Mar 2026 11:05:27 -0500 Subject: [PATCH 3/9] Disable syncthing in local dev, fix port binding strategy Docker Compose concatenates port lists rather than merging by target, so the production sync ports (22000, 21027) were always bound regardless of what the dev override specified. Since syncthing conflicts with a native install on the dev machine anyway, disable it via profiles like pihole and jellyfin. Also removes the dead .dev/data/syncthing directory from ensure_dirs. Co-Authored-By: Claude Sonnet 4.6 --- CLAUDE.md | 4 ++-- docker-compose.dev.yml | 8 ++------ scripts/dev.sh | 2 -- scripts/health-check.sh | 3 +-- 4 files changed, 5 insertions(+), 12 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index a592240..5626ccc 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -52,16 +52,16 @@ Use this to run and test services locally before opening a PR. | Service | URL | Notes | | ----------- | --------------------- | ----------------------------------- | | traefik | http://localhost:8080 | HTTP only; dashboard at /dashboard/ | -| homepage | http://localhost:3000 | | +| homepage | http://localhost:3001 | | | portainer | http://localhost:9000 | | | filebrowser | http://localhost:8081 | | -| syncthing | http://localhost:8384 | | | wallabag | http://localhost:8888 | | **Services skipped in local dev:** - `pihole` — requires Linux host networking - `jellyfin` — requires `/dev/dri` render device +- `syncthing` — sync ports (22000, 21027) conflict with a native Syncthing install **Known timing issue:** Wallabag runs a DB migration on first start and takes ~30s before it responds. If `health-check.sh` reports wallabag FAIL immediately after `up`, wait and re-run — it is not a real failure. diff --git a/docker-compose.dev.yml b/docker-compose.dev.yml index f8bceec..c8d74d4 100644 --- a/docker-compose.dev.yml +++ b/docker-compose.dev.yml @@ -34,7 +34,7 @@ services: homepage: ports: - - "3000:3000" + - "3001:3000" environment: - HOMEPAGE_ALLOWED_HOSTS=localhost @@ -54,11 +54,7 @@ services: - ./.dev/data/filebrowser/files:/srv syncthing: - ports: - - "8384:8384" - volumes: - - ./.dev/data/syncthing:/config - - ./.dev/data/sync:/data1 + profiles: [prod] # disabled in local dev — sync ports conflict with native Syncthing install wallabag: ports: diff --git a/scripts/dev.sh b/scripts/dev.sh index 137ab4d..8cec936 100755 --- a/scripts/dev.sh +++ b/scripts/dev.sh @@ -62,10 +62,8 @@ ensure_dirs() { "$DEV_DATA/filebrowser/database" "$DEV_DATA/filebrowser/config" "$DEV_DATA/filebrowser/files" - "$DEV_DATA/syncthing" "$DEV_DATA/wallabag" "$DEV_DATA/wallabag-images" - "$DEV_DATA/sync" "$DEV_DATA/media" ) for d in "${dirs[@]}"; do diff --git a/scripts/health-check.sh b/scripts/health-check.sh index 721fc57..6f0b1ea 100755 --- a/scripts/health-check.sh +++ b/scripts/health-check.sh @@ -41,10 +41,9 @@ printf "Local dev health checks\n" printf '%.0s─' {1..60}; echo run_check "traefik" "http://localhost:8080/ping" -run_check "homepage" "http://localhost:3000" +run_check "homepage" "http://localhost:3001" run_check "portainer" "http://localhost:9000/api/status" run_check "filebrowser" "http://localhost:8081" -run_check "syncthing" "http://localhost:8384/rest/noauth/health" run_check "wallabag" "http://localhost:8888/login" printf '%.0s─' {1..60}; echo From 882dcb9be7470dc32879a65cf2c73c9e23d4d3f9 Mon Sep 17 00:00:00 2001 From: John Riccardi Date: Thu, 19 Mar 2026 11:08:16 -0500 Subject: [PATCH 4/9] Fix traefik ping endpoint entry point for dev health check MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit api.insecure serves on an internal :8080 entry point, but the dev port mapping is 8080:80 (host→web entry point). Bind ping to the web entry point so localhost:8080/ping works as expected. Co-Authored-By: Claude Sonnet 4.6 --- traefik/traefik.dev.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/traefik/traefik.dev.yml b/traefik/traefik.dev.yml index c7e9ecd..3cdf46c 100644 --- a/traefik/traefik.dev.yml +++ b/traefik/traefik.dev.yml @@ -9,7 +9,8 @@ api: dashboard: true insecure: true # No auth needed for local dev -ping: {} # Enables GET /ping for health checks +ping: + entryPoint: web # respond on :80 (mapped to host :8080) for health checks entryPoints: web: From 7ca6f7e011d63a0373122ba076faf91c95e56bb5 Mon Sep 17 00:00:00 2001 From: John Riccardi Date: Thu, 19 Mar 2026 11:16:08 -0500 Subject: [PATCH 5/9] Show skipped services in health check output MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds SKIP lines for syncthing, jellyfin, and pihole so the full service list is always visible — makes it obvious they're intentionally absent rather than accidentally missing. Co-Authored-By: Claude Sonnet 4.6 --- CLAUDE.md | 14 +++++++------- scripts/health-check.sh | 11 +++++++++++ 2 files changed, 18 insertions(+), 7 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index 5626ccc..7cd27a0 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -49,13 +49,13 @@ Use this to run and test services locally before opening a PR. **Services available in local dev** (health check ports): -| Service | URL | Notes | -| ----------- | --------------------- | ----------------------------------- | -| traefik | http://localhost:8080 | HTTP only; dashboard at /dashboard/ | -| homepage | http://localhost:3001 | | -| portainer | http://localhost:9000 | | -| filebrowser | http://localhost:8081 | | -| wallabag | http://localhost:8888 | | +| Service | URL | Notes | +| ----------- | --------------------- | ---------------------------------- | +| traefik | http://localhost:8082 | API/ping/dashboard (internal port) | +| homepage | http://localhost:3001 | | +| portainer | http://localhost:9000 | | +| filebrowser | http://localhost:8081 | | +| wallabag | http://localhost:8888 | | **Services skipped in local dev:** diff --git a/scripts/health-check.sh b/scripts/health-check.sh index 6f0b1ea..8ec2eaf 100755 --- a/scripts/health-check.sh +++ b/scripts/health-check.sh @@ -11,6 +11,7 @@ set -uo pipefail GREEN='\033[0;32m' RED='\033[0;31m' +GRAY='\033[0;90m' RESET='\033[0m' TIMEOUT=5 @@ -30,6 +31,13 @@ check() { fi } +skip() { + local name="$1" reason="$2" + if [[ -z "$FILTER" || "$FILTER" == "$name" ]]; then + printf "${GRAY} SKIP${RESET} %-15s %s\n" "$name" "$reason" + fi +} + run_check() { local name="$1" if [[ -z "$FILTER" || "$FILTER" == "$name" ]]; then @@ -45,6 +53,9 @@ run_check "homepage" "http://localhost:3001" run_check "portainer" "http://localhost:9000/api/status" run_check "filebrowser" "http://localhost:8081" run_check "wallabag" "http://localhost:8888/login" +skip "syncthing" "disabled in dev (ports conflict with native install)" +skip "jellyfin" "disabled in dev (requires /dev/dri device)" +skip "pihole" "disabled in dev (requires Linux host networking)" printf '%.0s─' {1..60}; echo From 5cf23b93045df492303264e1726c874c32163ca9 Mon Sep 17 00:00:00 2001 From: John Riccardi Date: Thu, 19 Mar 2026 11:16:59 -0500 Subject: [PATCH 6/9] Fix traefik URL in CLAUDE.md (8080, not 8082) Co-Authored-By: Claude Sonnet 4.6 --- CLAUDE.md | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index 7cd27a0..e439617 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -49,13 +49,13 @@ Use this to run and test services locally before opening a PR. **Services available in local dev** (health check ports): -| Service | URL | Notes | -| ----------- | --------------------- | ---------------------------------- | -| traefik | http://localhost:8082 | API/ping/dashboard (internal port) | -| homepage | http://localhost:3001 | | -| portainer | http://localhost:9000 | | -| filebrowser | http://localhost:8081 | | -| wallabag | http://localhost:8888 | | +| Service | URL | Notes | +| ----------- | --------------------- | --------------------------- | +| traefik | http://localhost:8080 | HTTP routing; ping at /ping | +| homepage | http://localhost:3001 | | +| portainer | http://localhost:9000 | | +| filebrowser | http://localhost:8081 | | +| wallabag | http://localhost:8888 | | **Services skipped in local dev:** From a915c70149454962433bfbae6627e5b475086a59 Mon Sep 17 00:00:00 2001 From: John Riccardi Date: Thu, 19 Mar 2026 11:30:26 -0500 Subject: [PATCH 7/9] Replace local dev harness with collect-logs.sh MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The local stack was too brittle: 3 of 9 services couldn't run on macOS (host networking, GPU devices, port conflicts), and the real failure modes for this repo happen on the Linux server after deployment. Replace with scripts/collect-logs.sh — captures container status, recent errors, and per-service log tails in a format ready to paste into Claude for diagnosis. Removes: - docker-compose.dev.yml - traefik/traefik.dev.yml - traefik/dynamic.dev/ - .env.dev.example - scripts/dev.sh - scripts/health-check.sh - CI step: Validate docker-compose.dev.yml merge - .gitignore entries for .dev/, .env.dev, acme.dev.json - pre-tool-use.sh exception for .env.dev.example Co-Authored-By: Claude Sonnet 4.6 --- .claude/hooks/pre-tool-use.sh | 2 +- .dev/data/filebrowser/config/settings.json | 8 ++ .dev/data/filebrowser/database/filebrowser.db | Bin 0 -> 65536 bytes .dev/data/portainer/certs/cert.pem | 10 ++ .dev/data/portainer/certs/key.pem | 5 + .dev/data/portainer/chisel/private-key.pem | 5 + .dev/data/portainer/docker_config/config.json | 7 + .dev/data/portainer/portainer.db | Bin 0 -> 32768 bytes .dev/data/portainer/portainer.key | 5 + .dev/data/portainer/portainer.pub | 4 + .dev/data/wallabag/db/wallabag.sqlite | Bin 0 -> 221184 bytes .env.dev.example => .env.dev | 0 .github/workflows/ci.yml | 10 -- .gitignore | 5 - CLAUDE.md | 51 +------ docker-compose.dev.yml | 66 --------- scripts/collect-logs.sh | 32 +++++ scripts/dev.sh | 136 ------------------ scripts/health-check.sh | 69 --------- .../.gitkeep => letsencrypt/acme.dev.json} | 0 traefik/traefik.dev.yml | 30 ---- 21 files changed, 82 insertions(+), 363 deletions(-) create mode 100644 .dev/data/filebrowser/config/settings.json create mode 100644 .dev/data/filebrowser/database/filebrowser.db create mode 100644 .dev/data/portainer/certs/cert.pem create mode 100644 .dev/data/portainer/certs/key.pem create mode 100644 .dev/data/portainer/chisel/private-key.pem create mode 100644 .dev/data/portainer/docker_config/config.json create mode 100644 .dev/data/portainer/portainer.db create mode 100644 .dev/data/portainer/portainer.key create mode 100644 .dev/data/portainer/portainer.pub create mode 100644 .dev/data/wallabag/db/wallabag.sqlite rename .env.dev.example => .env.dev (100%) delete mode 100644 docker-compose.dev.yml create mode 100755 scripts/collect-logs.sh delete mode 100755 scripts/dev.sh delete mode 100755 scripts/health-check.sh rename traefik/{dynamic.dev/.gitkeep => letsencrypt/acme.dev.json} (100%) delete mode 100644 traefik/traefik.dev.yml diff --git a/.claude/hooks/pre-tool-use.sh b/.claude/hooks/pre-tool-use.sh index 89069a9..1e6b2dc 100755 --- a/.claude/hooks/pre-tool-use.sh +++ b/.claude/hooks/pre-tool-use.sh @@ -20,7 +20,7 @@ fi basename="${file_path##*/}" -if [[ "$basename" == .env* && "$basename" != ".env.example" && "$basename" != ".env.dev.example" ]]; then +if [[ "$basename" == .env* && "$basename" != ".env.example" ]]; then echo "BLOCKED: '$basename' may contain secrets and cannot be read or modified by agents." echo "Use .env.example for variable references and documentation instead." exit 2 diff --git a/.dev/data/filebrowser/config/settings.json b/.dev/data/filebrowser/config/settings.json new file mode 100644 index 0000000..cf7fb4e --- /dev/null +++ b/.dev/data/filebrowser/config/settings.json @@ -0,0 +1,8 @@ +{ + "port": 80, + "baseURL": "", + "address": "", + "log": "stdout", + "database": "/database/filebrowser.db", + "root": "/srv" +} diff --git a/.dev/data/filebrowser/database/filebrowser.db b/.dev/data/filebrowser/database/filebrowser.db new file mode 100644 index 0000000000000000000000000000000000000000..cec2fa99324c60cf6eb2eda58bd8e6d344ef981b GIT binary patch literal 65536 zcmeI*O>f*p7{Ku~lvi;m95{p3142==32h3IilE7sR3z!9B(y0)k=LHhI1y?Sn(;iFsi|f;>$dm5 zfByco?w@Nio^knp^Q_x>&TakiS3mpi>94-{c*~b#ivR)$AbIvOKhf91qUVhv(;7=d<>;3*D8?-ciyjw%Zps-r3u{ z72Ze|I(MSgt<~9ug+V*JwU*zA_qR9Kmv8j!t@XX3)Ko@xpfz8R{nSQbstKc?A*ub& z_;Y$;TAG>QATft)Hr7I!EyEc30yv&5?=fYFs8NOgKpurU7W)pV>I+4Sj8~NzJ5W*$<0xC9yrsQyU7C zzVY5n5ZNpX^SBHaKm15e+!mFbyPir{stZP+F%yJkBQPin%#rsKYmTlEbMmg?6 zV>!!fw#dS2eC=Um+Hq3ZVzX~XcP+TLvr|es+ez}+9PM0R_BZ+Tc6!u0np|x!+J^l6 z5ZU!ZO=ljgZ_721=6%0?JzeXQuGUd&a>d70W~K_|qQc)+)8tG`L-)(evZKRT{uE!4 zeXU%-2-lY*Tjz53WnY&wku;lVue5oS<`n@15I_I{1Q0*~0R#|u>;(Mt{pqP@PdjNX zS+x_SeK<{DlvU^Z`}A9~|CCE>?`_hhl1y2A|1R!M#uS<(k+}8>-RFPq(l59!{+F`VNz}i%$pi;dsg4!s;lFLXAgrstDUJh~mmdzQs2>K4d7Y;B zWd4B+B}l%tGKoEyEsF!0)!z+EvvqS-GIWZ+!~z&SDEn1fzGrm0e6zn~3U^>2)9EGq zjg}k;||^XseOz%0v@|EQADkE_EK6UL_S<&!MjH8)LZ(^?NNg=ti$q3-$;%CKUs#43!g z!0wy8eKbglQK8LbG-H3h9|%jO(E;Ugus^!oYuD$tSLQmm*Y=Z}d%aFJc=M9E*qXiF zK3wZvPH)<^!C=^4xO8`YVtXa(~rQX=|!aA))`1m1#9)C+r$LUxK`Sb_kC{D$ExRd`Efmw0Et&4A5y1dYm*cDS1!zGQv z2o^6r5d9*tHyW6;Bf&rNP1abLChcQsCOJOxW|C8e8p$(B*Mla+MsvERXA5(pT9;(=R!zRE-+{ULnLzQHSS7KLaHR46 z?=*pXZZLIT{=sEI009ILKmY**5J2D|0@|!1t$|7Jjd{(tj- z1g{7nfB*srAb zKmY**5I_I{1Q0*~0R#|0009ILKmY**5I_I{1Q0*~0R#|0009ILKmY**5I_I{1Q0*~ z0R#|0009ILKmY**5I_I{1Q0*~0R#|0009ILKmY**5I_I{1Q0*~0R#|0009ILKmY** m5I_I{1Q0*~0R#|0009ILKmY**5I_I{1Q0*~0R;Y^0>1+3JdeWw literal 0 HcmV?d00001 diff --git a/.dev/data/portainer/certs/cert.pem b/.dev/data/portainer/certs/cert.pem new file mode 100644 index 0000000..333c94b --- /dev/null +++ b/.dev/data/portainer/certs/cert.pem @@ -0,0 +1,10 @@ +-----BEGIN CERTIFICATE----- +MIIBUjCB+aADAgECAhEAkeff7ttnfA/zNg4HzuP9KzAKBggqhkjOPQQDAjAAMB4X +DTI2MDMxOTE1NTgzMVoXDTMxMDMxOTE1NTgzMVowADBZMBMGByqGSM49AgEGCCqG +SM49AwEHA0IABAcwnKX6htEDpc6T7jNM5/wC1YQ2eVlBfwA/1wNg4hFSroopaQvf +Z6lwBlVgyP5aW4ahMZDy7WILcWrqB+3IvuCjVDBSMA4GA1UdDwEB/wQEAwIFoDAT +BgNVHSUEDDAKBggrBgEFBQcDATAMBgNVHRMBAf8EAjAAMB0GA1UdEQEB/wQTMBGC +CWxvY2FsaG9zdIcEAAAAADAKBggqhkjOPQQDAgNIADBFAiBgXikaMfaXBSlsJkFC +wy4UyQMoHhj3Wqq7wW098aObtwIhANAZHVkg46eZ0ZOIh0veHntgwJWeLfh/k9Lt +GIiAn3xU +-----END CERTIFICATE----- diff --git a/.dev/data/portainer/certs/key.pem b/.dev/data/portainer/certs/key.pem new file mode 100644 index 0000000..349c7b4 --- /dev/null +++ b/.dev/data/portainer/certs/key.pem @@ -0,0 +1,5 @@ +-----BEGIN EC PRIVATE KEY----- +MHcCAQEEIORpppw4HeFLqihV6yMQGLdPjT0I2Ws0B8ZScbQ2u2VwoAoGCCqGSM49 +AwEHoUQDQgAEBzCcpfqG0QOlzpPuM0zn/ALVhDZ5WUF/AD/XA2DiEVKuiilpC99n +qXAGVWDI/lpbhqExkPLtYgtxauoH7ci+4A== +-----END EC PRIVATE KEY----- diff --git a/.dev/data/portainer/chisel/private-key.pem b/.dev/data/portainer/chisel/private-key.pem new file mode 100644 index 0000000..91e757b --- /dev/null +++ b/.dev/data/portainer/chisel/private-key.pem @@ -0,0 +1,5 @@ +-----BEGIN EC PRIVATE KEY----- +MHcCAQEEIAfo7pzDrKMyufS7wwQZG6WYSXAe3ExDZZ41t5MiSsI2oAoGCCqGSM49 +AwEHoUQDQgAEbkSGUoMx9ySCoMmfn68o8s1qIhlMafoNbaYBj3wRw8mCHMlt1tMu +AubVqE7c7AOUCDdiPFjokyY8Ll5JWthHkg== +-----END EC PRIVATE KEY----- diff --git a/.dev/data/portainer/docker_config/config.json b/.dev/data/portainer/docker_config/config.json new file mode 100644 index 0000000..96193e1 --- /dev/null +++ b/.dev/data/portainer/docker_config/config.json @@ -0,0 +1,7 @@ +{ + "HttpHeaders": { + "X-PortainerAgent-ManagerOperation": "1", + "X-PortainerAgent-PublicKey": "3059301306072a8648ce3d020106082a8648ce3d030107034200048a7e08de329e37226321b155bc30166a98cf2b5a82acbc2fd6997cd9c696ac2b2d27975323f1b2df2445b9707ddf8b145f4599584eb3a8e323eb86c0c1a6ebfc", + "X-PortainerAgent-Signature": "NHUP8KCXDIbBz8oFEcdDpOMlhH6gmd0thpKqMQt17KKa47Rzfatk01Pi3qoDme53QYyVnRsOS6ZE8YHMdItmCg" + } +} diff --git a/.dev/data/portainer/portainer.db b/.dev/data/portainer/portainer.db new file mode 100644 index 0000000000000000000000000000000000000000..f4ce4220ad91c4f2992711f5468ec8c35819a5cb GIT binary patch literal 32768 zcmeI3&u=8f701o;BS3;6C^rzYTDe5=?`4D65=h1#F9Wl-Ji31$DaNr8x*IhN!GwU$sw!mxcO!upL{p!`HUd{Tw zo>^L|kH_5kn=gL%%?-MKu5NhTwfoce?s&pA?7QNfC)T21Rim&{J;GF(K%P=^Z%CX|M=6b@YN^&``bBn7MTDOU;<2l z2`~XBzyz286JP>NfC(^xhk!u!tRH^dSn`#C`qBTBXnPkA|HD?d+djM#UIZOI)Tbw% zU?&Jxg4YufjXSEaGVX}JOp0LVoxiRGyQLk;%&JIOrL()zj&vOCY_0_D%u17qgcL$) zt;&YD-ve2c8WTPR*@$9EdWpT`5xXZ3EcDBfpRoU*&Y5x5~Z@Wn`p% zOJbdZx3~Um&f4jjzPH&KYz(qDl2-Th-eGzy?{qf?r)Qg4uHyaHdq;1cz6w7oBKADN zT5Nz29L#p_cI%KRO*LzZ44H|3A`zp`!!$2KY3}=(L8J-SQ-k2cbka~kt))mBizSp4 z39~I2)q>b;igCeUa?TJkjSVJUGf}TUHxyq+*GfkH*D8nMKqr5@j13q7@J2?hrTajbt=d z*>tp9D?zKmMk;$c%@Og=U6^(t30sJ$gW;}9B~})0Zjaz5Eo@Y6&DICGGPJfEXnsQ` z>50sBp{zFN^%O?d=EctXdNji3EY|wUW+GKVgQ(+{$oGR zsi9?XtrS?`l8P*zQbVvG<5O>@;lY*MxV(G_{!?HVm;e)C0!)AjFaajO1b(pu{PX=A zOD|S)q$O`bG$vqC`uiBZ+|cyzCCK#i25TacUn#gOrD(ti%&nTGuLx&D7c zruF$JUgS>;gL$4JSG}=3N9&abNLNRM^hc-GXN*_sqS33zIvPteD*KBRC6c0EL3!o% zHA&x=#dU5NQbFZfn(ylVV!bTRwaRR7)+`}y9#@sd{xem0C2tXV zdD<)CYtKxw|EbpOmA?nYoEW)uPgI4Ag}xX$M5WSz>8!b_DvmO_5M)5{a35>=q2hd@O2&v(wBy)wn?LW{8T;~QXhMd%ZqmhY@}61I;H zFM<$plL{27$qJTcQoOpWhNxg_v!_d{Dx$K~iWO~NS8507EUIc(f!O}b=4}y))mOHn zo2xHx$G2CvZ^dGDAY&;u2HV@WUbz`uzAyVDt;Y*zG>?O2Ccp%k025#WOyFlpfbvp0 zIRBsK-<?IVWs z|H+qUesccr8G5dq|2q;M4Cnv+{NHa5J~II(zyz286JP>NfC(@GCcp%k025#WOn?b6 p0Vco%m;e)C0!)AjFaajO1egF5U;<2l2`~XBzyz286ZjPp_yHnrISl{+ literal 0 HcmV?d00001 diff --git a/.dev/data/portainer/portainer.key b/.dev/data/portainer/portainer.key new file mode 100644 index 0000000..6d42ebd --- /dev/null +++ b/.dev/data/portainer/portainer.key @@ -0,0 +1,5 @@ +-----BEGIN EC PRIVATE KEY----- +MHcCAQEEIBGdymbu/YpwNyXLpaQq7cbD+hf9Fx8opgyIfMTEg0V1oAoGCCqGSM49 +AwEHoUQDQgAEin4I3jKeNyJjIbFVvDAWapjPK1qCrLwv1pl82caWrCstJ5dTI/Gy +3yRFuXB934sUX0WZWE6zqOMj64bAwabr/A== +-----END EC PRIVATE KEY----- diff --git a/.dev/data/portainer/portainer.pub b/.dev/data/portainer/portainer.pub new file mode 100644 index 0000000..0e14de4 --- /dev/null +++ b/.dev/data/portainer/portainer.pub @@ -0,0 +1,4 @@ +-----BEGIN ECDSA PUBLIC KEY----- +MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEin4I3jKeNyJjIbFVvDAWapjPK1qC +rLwv1pl82caWrCstJ5dTI/Gy3yRFuXB934sUX0WZWE6zqOMj64bAwabr/A== +-----END ECDSA PUBLIC KEY----- diff --git a/.dev/data/wallabag/db/wallabag.sqlite b/.dev/data/wallabag/db/wallabag.sqlite new file mode 100644 index 0000000000000000000000000000000000000000..bb12906ccb69c3c2b0c59dad615417fb8d247f13 GIT binary patch literal 221184 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 -./scripts/health-check.sh # targeted check; exit 1 = still broken -./scripts/dev.sh logs # read logs to diagnose failures +./scripts/collect-logs.sh # all services +./scripts/collect-logs.sh jellyfin # one service ``` -**Tear down:** - -```bash -./scripts/dev.sh down -``` - -**Services available in local dev** (health check ports): - -| Service | URL | Notes | -| ----------- | --------------------- | --------------------------- | -| traefik | http://localhost:8080 | HTTP routing; ping at /ping | -| homepage | http://localhost:3001 | | -| portainer | http://localhost:9000 | | -| filebrowser | http://localhost:8081 | | -| wallabag | http://localhost:8888 | | - -**Services skipped in local dev:** - -- `pihole` — requires Linux host networking -- `jellyfin` — requires `/dev/dri` render device -- `syncthing` — sync ports (22000, 21027) conflict with a native Syncthing install - -**Known timing issue:** Wallabag runs a DB migration on first start and takes ~30s before it responds. -If `health-check.sh` reports wallabag FAIL immediately after `up`, wait and re-run — it is not a real failure. - -**Key files:** - -- `docker-compose.dev.yml` — compose overrides (ports, volumes, disabled services) -- `traefik/traefik.dev.yml` — Traefik static config (HTTP only, no ACME) -- `.env.dev.example` — dev env template (safe to read; copied to `.env.dev` on first run) -- `.dev/` — local data directories (gitignored) +The script outputs: container status, recent errors across all services, and the last 75 lines per service. Paste the full output as context and describe the symptom. ## Session initialization diff --git a/docker-compose.dev.yml b/docker-compose.dev.yml deleted file mode 100644 index c8d74d4..0000000 --- a/docker-compose.dev.yml +++ /dev/null @@ -1,66 +0,0 @@ -# docker-compose.dev.yml — Local development overrides -# Usage: ./scripts/dev.sh up (wraps the compose -f flags) -# -# Differences from production: -# - pihole disabled (requires host networking, Linux-only) -# - jellyfin disabled (requires /dev/dri render device, Linux-only) -# - Traefik: HTTP-only on :8080, no ACME/Cloudflare, insecure dashboard -# - All services expose native ports for direct localhost health checks -# - Data volumes redirect to .dev/data/ (project-local, gitignored) - -services: - pihole: - profiles: [prod] # disabled in local dev - - jellyfin: - profiles: [prod] # disabled in local dev (requires /dev/dri device) - - traefik: - # Override: restore default CMD (removes ACME email arg from production command) - command: - - traefik - ports: - - "8080:80" - - "8443:443" - environment: - # ACME is not configured in traefik.dev.yml; value is irrelevant - - CF_DNS_API_TOKEN=dev-disabled - volumes: - - /var/run/docker.sock:/var/run/docker.sock:ro - - ./traefik/traefik.dev.yml:/etc/traefik/traefik.yml:ro - # Use empty dynamic.dev/ to avoid cert resolver warnings from production tls.yml/services.yml - - ./traefik/dynamic.dev:/etc/traefik/dynamic:ro - - ./traefik/letsencrypt/acme.dev.json:/acme.json - - homepage: - ports: - - "3001:3000" - environment: - - HOMEPAGE_ALLOWED_HOSTS=localhost - - portainer: - ports: - - "127.0.0.1:9000:9000" - volumes: - - /var/run/docker.sock:/var/run/docker.sock - - ./.dev/data/portainer:/data - - filebrowser: - ports: - - "8081:80" - volumes: - - ./.dev/data/filebrowser/database:/database - - ./.dev/data/filebrowser/config:/config - - ./.dev/data/filebrowser/files:/srv - - syncthing: - profiles: [prod] # disabled in local dev — sync ports conflict with native Syncthing install - - wallabag: - ports: - - "8888:80" - volumes: - - ./.dev/data/wallabag:/var/www/wallabag/data - - ./.dev/data/wallabag-images:/var/www/wallabag/web/assets/images - environment: - - SYMFONY__ENV__DOMAIN_NAME=http://localhost:8888 diff --git a/scripts/collect-logs.sh b/scripts/collect-logs.sh new file mode 100755 index 0000000..99cc07d --- /dev/null +++ b/scripts/collect-logs.sh @@ -0,0 +1,32 @@ +#!/usr/bin/env bash +# collect-logs.sh — capture diagnostic info from the running stack +# +# Paste the output into Claude to diagnose deployment issues. +# +# Usage: +# ./scripts/collect-logs.sh # all services +# ./scripts/collect-logs.sh jellyfin # one service + +set -uo pipefail + +LINES=75 +FILTER="${1:-}" + +section() { printf "\n\n### %s\n\n" "$1"; } + +section "Stack status" +docker compose ps + +section "Recent errors (all services)" +docker compose logs --tail=300 --no-color 2>&1 \ + | grep -iE '\b(err(or)?|warn(ing)?|fatal|panic|exception|failed)\b' \ + | tail -60 \ + || echo "(none found)" + +section "Service logs" +services=(traefik homepage jellyfin portainer filebrowser syncthing wallabag pihole) +for svc in "${services[@]}"; do + [[ -n "$FILTER" && "$FILTER" != "$svc" ]] && continue + printf "\n#### %s (last %d lines)\n\n" "$svc" "$LINES" + docker compose logs --tail="$LINES" --no-color "$svc" 2>&1 || true +done diff --git a/scripts/dev.sh b/scripts/dev.sh deleted file mode 100755 index 8cec936..0000000 --- a/scripts/dev.sh +++ /dev/null @@ -1,136 +0,0 @@ -#!/usr/bin/env bash -# dev.sh — manage the local development stack -# -# Usage: -# ./scripts/dev.sh up [svc...] Start local stack (creates dirs + .env.dev on first run) -# ./scripts/dev.sh down Stop and remove containers -# ./scripts/dev.sh restart [svc] Restart one or all services -# ./scripts/dev.sh status Show container status -# ./scripts/dev.sh logs [svc] Tail logs (all services or one) -# -# Services skipped automatically in local dev: -# pihole — requires Linux host networking -# jellyfin — requires /dev/dri render device - -set -euo pipefail - -REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" -ENV_FILE="$REPO_ROOT/.env.dev" -ENV_EXAMPLE="$REPO_ROOT/.env.dev.example" -DEV_DATA="$REPO_ROOT/.dev/data" - -GREEN='\033[0;32m' -YELLOW='\033[1;33m' -RED='\033[0;31m' -RESET='\033[0m' - -log() { printf "${GREEN}[dev]${RESET} %s\n" "$*"; } -warn() { printf "${YELLOW}[dev]${RESET} %s\n" "$*"; } -err() { printf "${RED}[dev]${RESET} %s\n" "$*" >&2; } - -# compose_env: requires .env.dev (up, restart, logs) -compose_env() { - docker compose \ - -f "$REPO_ROOT/docker-compose.yml" \ - -f "$REPO_ROOT/docker-compose.dev.yml" \ - --env-file "$ENV_FILE" \ - "$@" -} - -# compose_no_env: works without .env.dev (down, status) -compose_no_env() { - docker compose \ - -f "$REPO_ROOT/docker-compose.yml" \ - -f "$REPO_ROOT/docker-compose.dev.yml" \ - "$@" -} - -ensure_env() { - if [[ ! -f "$ENV_FILE" ]]; then - if [[ ! -f "$ENV_EXAMPLE" ]]; then - err ".env.dev.example not found — cannot create .env.dev" - exit 1 - fi - cp "$ENV_EXAMPLE" "$ENV_FILE" - warn "Created .env.dev from .env.dev.example — edit before running if needed" - fi -} - -ensure_dirs() { - local dirs=( - "$DEV_DATA/portainer" - "$DEV_DATA/filebrowser/database" - "$DEV_DATA/filebrowser/config" - "$DEV_DATA/filebrowser/files" - "$DEV_DATA/wallabag" - "$DEV_DATA/wallabag-images" - "$DEV_DATA/media" - ) - for d in "${dirs[@]}"; do - mkdir -p "$d" - done - - # acme.dev.json must exist with 600 permissions for Traefik - local acme="$REPO_ROOT/traefik/letsencrypt/acme.dev.json" - mkdir -p "$(dirname "$acme")" - if [[ ! -f "$acme" ]]; then - touch "$acme" - chmod 600 "$acme" - log "Created traefik/letsencrypt/acme.dev.json" - fi -} - -cmd_up() { - ensure_env - ensure_dirs - log "Starting local dev stack..." - compose_env up -d "$@" - log "Stack started. Run './scripts/health-check.sh' to verify services." - log "Note: Wallabag takes ~30s on first run (DB migration). Re-run health-check if it fails initially." -} - -cmd_down() { - log "Stopping local dev stack..." - compose_no_env down "$@" -} - -cmd_restart() { - ensure_env - ensure_dirs - compose_env restart "$@" -} - -cmd_status() { - compose_no_env ps -} - -cmd_logs() { - ensure_env - compose_env logs --tail=50 -f "$@" -} - -usage() { - cat < [args] - -Commands: - up [svc...] Start local stack (creates .env.dev and data dirs on first run) - down Stop and remove containers - restart [svc] Restart one or all services - status Show container status - logs [svc] Tail logs (Ctrl-C to exit) - -Services skipped in local dev (require Linux-only features): - pihole — host networking (Linux only) - jellyfin — /dev/dri render device (Linux only) -EOF -} - -case "${1:-}" in - up) shift; cmd_up "$@" ;; - down) shift; cmd_down "$@" ;; - restart) shift; cmd_restart "$@" ;; - status) cmd_status ;; - logs) shift; cmd_logs "$@" ;; - *) usage; exit 1 ;; -esac diff --git a/scripts/health-check.sh b/scripts/health-check.sh deleted file mode 100755 index 8ec2eaf..0000000 --- a/scripts/health-check.sh +++ /dev/null @@ -1,69 +0,0 @@ -#!/usr/bin/env bash -# health-check.sh — verify local dev services are responding -# -# Usage: -# ./scripts/health-check.sh Check all services -# ./scripts/health-check.sh traefik Check one service by name -# -# Exit code: 0 if all checked services pass, 1 if any fail. - -set -uo pipefail - -GREEN='\033[0;32m' -RED='\033[0;31m' -GRAY='\033[0;90m' -RESET='\033[0m' - -TIMEOUT=5 -OVERALL=0 -FILTER="${1:-}" - -check() { - local name="$1" url="$2" - local code - code=$(curl -s -o /dev/null -w "%{http_code}" --max-time "$TIMEOUT" "$url" 2>/dev/null || echo "000") - - if [[ "$code" =~ ^2 ]]; then - printf "${GREEN} PASS${RESET} %-15s %s\n" "$name" "$url" - else - printf "${RED} FAIL${RESET} %-15s %s (HTTP %s)\n" "$name" "$url" "$code" - OVERALL=1 - fi -} - -skip() { - local name="$1" reason="$2" - if [[ -z "$FILTER" || "$FILTER" == "$name" ]]; then - printf "${GRAY} SKIP${RESET} %-15s %s\n" "$name" "$reason" - fi -} - -run_check() { - local name="$1" - if [[ -z "$FILTER" || "$FILTER" == "$name" ]]; then - check "$@" - fi -} - -printf "Local dev health checks\n" -printf '%.0s─' {1..60}; echo - -run_check "traefik" "http://localhost:8080/ping" -run_check "homepage" "http://localhost:3001" -run_check "portainer" "http://localhost:9000/api/status" -run_check "filebrowser" "http://localhost:8081" -run_check "wallabag" "http://localhost:8888/login" -skip "syncthing" "disabled in dev (ports conflict with native install)" -skip "jellyfin" "disabled in dev (requires /dev/dri device)" -skip "pihole" "disabled in dev (requires Linux host networking)" - -printf '%.0s─' {1..60}; echo - -if [[ $OVERALL -eq 0 ]]; then - printf "${GREEN}All services healthy${RESET}\n" -else - printf "${RED}One or more services failed${RESET} — check logs:\n" - printf " ./scripts/dev.sh logs \n" -fi - -exit $OVERALL diff --git a/traefik/dynamic.dev/.gitkeep b/traefik/letsencrypt/acme.dev.json similarity index 100% rename from traefik/dynamic.dev/.gitkeep rename to traefik/letsencrypt/acme.dev.json diff --git a/traefik/traefik.dev.yml b/traefik/traefik.dev.yml deleted file mode 100644 index 3cdf46c..0000000 --- a/traefik/traefik.dev.yml +++ /dev/null @@ -1,30 +0,0 @@ -global: - checkNewVersion: false - sendAnonymousUsage: false - -log: - level: INFO - -api: - dashboard: true - insecure: true # No auth needed for local dev - -ping: - entryPoint: web # respond on :80 (mapped to host :8080) for health checks - -entryPoints: - web: - address: ":80" - # No HTTPS redirect in local dev - websecure: - address: ":443" - -# No certificatesResolvers — HTTP only, no ACME in local dev - -providers: - docker: - exposedByDefault: false - network: proxy - file: - directory: /etc/traefik/dynamic - watch: true From 200ad7d6329004eda0eb4285b2d40c5362c6a0bb Mon Sep 17 00:00:00 2001 From: John Riccardi Date: Thu, 19 Mar 2026 11:31:01 -0500 Subject: [PATCH 8/9] Remove accidentally committed dev data files .dev/ and .env.dev were created by the local docker stack during testing and not gitignored after removing them from the harness cleanup. Untrack and re-add to .gitignore. Co-Authored-By: Claude Sonnet 4.6 --- .dev/data/filebrowser/config/settings.json | 8 --- .dev/data/filebrowser/database/filebrowser.db | Bin 65536 -> 0 bytes .dev/data/portainer/certs/cert.pem | 10 ---- .dev/data/portainer/certs/key.pem | 5 -- .dev/data/portainer/chisel/private-key.pem | 5 -- .dev/data/portainer/docker_config/config.json | 7 --- .dev/data/portainer/portainer.db | Bin 32768 -> 0 bytes .dev/data/portainer/portainer.key | 5 -- .dev/data/portainer/portainer.pub | 4 -- .dev/data/wallabag/db/wallabag.sqlite | Bin 221184 -> 0 bytes .env.dev | 52 ------------------ .gitignore | 2 + 12 files changed, 2 insertions(+), 96 deletions(-) delete mode 100644 .dev/data/filebrowser/config/settings.json delete mode 100644 .dev/data/filebrowser/database/filebrowser.db delete mode 100644 .dev/data/portainer/certs/cert.pem delete mode 100644 .dev/data/portainer/certs/key.pem delete mode 100644 .dev/data/portainer/chisel/private-key.pem delete mode 100644 .dev/data/portainer/docker_config/config.json delete mode 100644 .dev/data/portainer/portainer.db delete mode 100644 .dev/data/portainer/portainer.key delete mode 100644 .dev/data/portainer/portainer.pub delete mode 100644 .dev/data/wallabag/db/wallabag.sqlite delete mode 100644 .env.dev diff --git a/.dev/data/filebrowser/config/settings.json b/.dev/data/filebrowser/config/settings.json deleted file mode 100644 index cf7fb4e..0000000 --- a/.dev/data/filebrowser/config/settings.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "port": 80, - "baseURL": "", - "address": "", - "log": "stdout", - "database": "/database/filebrowser.db", - "root": "/srv" -} diff --git a/.dev/data/filebrowser/database/filebrowser.db b/.dev/data/filebrowser/database/filebrowser.db deleted file mode 100644 index cec2fa99324c60cf6eb2eda58bd8e6d344ef981b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 65536 zcmeI*O>f*p7{Ku~lvi;m95{p3142==32h3IilE7sR3z!9B(y0)k=LHhI1y?Sn(;iFsi|f;>$dm5 zfByco?w@Nio^knp^Q_x>&TakiS3mpi>94-{c*~b#ivR)$AbIvOKhf91qUVhv(;7=d<>;3*D8?-ciyjw%Zps-r3u{ z72Ze|I(MSgt<~9ug+V*JwU*zA_qR9Kmv8j!t@XX3)Ko@xpfz8R{nSQbstKc?A*ub& z_;Y$;TAG>QATft)Hr7I!EyEc30yv&5?=fYFs8NOgKpurU7W)pV>I+4Sj8~NzJ5W*$<0xC9yrsQyU7C zzVY5n5ZNpX^SBHaKm15e+!mFbyPir{stZP+F%yJkBQPin%#rsKYmTlEbMmg?6 zV>!!fw#dS2eC=Um+Hq3ZVzX~XcP+TLvr|es+ez}+9PM0R_BZ+Tc6!u0np|x!+J^l6 z5ZU!ZO=ljgZ_721=6%0?JzeXQuGUd&a>d70W~K_|qQc)+)8tG`L-)(evZKRT{uE!4 zeXU%-2-lY*Tjz53WnY&wku;lVue5oS<`n@15I_I{1Q0*~0R#|u>;(Mt{pqP@PdjNX zS+x_SeK<{DlvU^Z`}A9~|CCE>?`_hhl1y2A|1R!M#uS<(k+}8>-RFPq(l59!{+F`VNz}i%$pi;dsg4!s;lFLXAgrstDUJh~mmdzQs2>K4d7Y;B zWd4B+B}l%tGKoEyEsF!0)!z+EvvqS-GIWZ+!~z&SDEn1fzGrm0e6zn~3U^>2)9EGq zjg}k;||^XseOz%0v@|EQADkE_EK6UL_S<&!MjH8)LZ(^?NNg=ti$q3-$;%CKUs#43!g z!0wy8eKbglQK8LbG-H3h9|%jO(E;Ugus^!oYuD$tSLQmm*Y=Z}d%aFJc=M9E*qXiF zK3wZvPH)<^!C=^4xO8`YVtXa(~rQX=|!aA))`1m1#9)C+r$LUxK`Sb_kC{D$ExRd`Efmw0Et&4A5y1dYm*cDS1!zGQv z2o^6r5d9*tHyW6;Bf&rNP1abLChcQsCOJOxW|C8e8p$(B*Mla+MsvERXA5(pT9;(=R!zRE-+{ULnLzQHSS7KLaHR46 z?=*pXZZLIT{=sEI009ILKmY**5J2D|0@|!1t$|7Jjd{(tj- z1g{7nfB*srAb zKmY**5I_I{1Q0*~0R#|0009ILKmY**5I_I{1Q0*~0R#|0009ILKmY**5I_I{1Q0*~ z0R#|0009ILKmY**5I_I{1Q0*~0R#|0009ILKmY**5I_I{1Q0*~0R#|0009ILKmY** m5I_I{1Q0*~0R#|0009ILKmY**5I_I{1Q0*~0R;Y^0>1+3JdeWw diff --git a/.dev/data/portainer/certs/cert.pem b/.dev/data/portainer/certs/cert.pem deleted file mode 100644 index 333c94b..0000000 --- a/.dev/data/portainer/certs/cert.pem +++ /dev/null @@ -1,10 +0,0 @@ ------BEGIN CERTIFICATE----- -MIIBUjCB+aADAgECAhEAkeff7ttnfA/zNg4HzuP9KzAKBggqhkjOPQQDAjAAMB4X -DTI2MDMxOTE1NTgzMVoXDTMxMDMxOTE1NTgzMVowADBZMBMGByqGSM49AgEGCCqG -SM49AwEHA0IABAcwnKX6htEDpc6T7jNM5/wC1YQ2eVlBfwA/1wNg4hFSroopaQvf -Z6lwBlVgyP5aW4ahMZDy7WILcWrqB+3IvuCjVDBSMA4GA1UdDwEB/wQEAwIFoDAT -BgNVHSUEDDAKBggrBgEFBQcDATAMBgNVHRMBAf8EAjAAMB0GA1UdEQEB/wQTMBGC -CWxvY2FsaG9zdIcEAAAAADAKBggqhkjOPQQDAgNIADBFAiBgXikaMfaXBSlsJkFC -wy4UyQMoHhj3Wqq7wW098aObtwIhANAZHVkg46eZ0ZOIh0veHntgwJWeLfh/k9Lt -GIiAn3xU ------END CERTIFICATE----- diff --git a/.dev/data/portainer/certs/key.pem b/.dev/data/portainer/certs/key.pem deleted file mode 100644 index 349c7b4..0000000 --- a/.dev/data/portainer/certs/key.pem +++ /dev/null @@ -1,5 +0,0 @@ ------BEGIN EC PRIVATE KEY----- -MHcCAQEEIORpppw4HeFLqihV6yMQGLdPjT0I2Ws0B8ZScbQ2u2VwoAoGCCqGSM49 -AwEHoUQDQgAEBzCcpfqG0QOlzpPuM0zn/ALVhDZ5WUF/AD/XA2DiEVKuiilpC99n -qXAGVWDI/lpbhqExkPLtYgtxauoH7ci+4A== ------END EC PRIVATE KEY----- diff --git a/.dev/data/portainer/chisel/private-key.pem b/.dev/data/portainer/chisel/private-key.pem deleted file mode 100644 index 91e757b..0000000 --- a/.dev/data/portainer/chisel/private-key.pem +++ /dev/null @@ -1,5 +0,0 @@ ------BEGIN EC PRIVATE KEY----- -MHcCAQEEIAfo7pzDrKMyufS7wwQZG6WYSXAe3ExDZZ41t5MiSsI2oAoGCCqGSM49 -AwEHoUQDQgAEbkSGUoMx9ySCoMmfn68o8s1qIhlMafoNbaYBj3wRw8mCHMlt1tMu -AubVqE7c7AOUCDdiPFjokyY8Ll5JWthHkg== ------END EC PRIVATE KEY----- diff --git a/.dev/data/portainer/docker_config/config.json b/.dev/data/portainer/docker_config/config.json deleted file mode 100644 index 96193e1..0000000 --- a/.dev/data/portainer/docker_config/config.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "HttpHeaders": { - "X-PortainerAgent-ManagerOperation": "1", - "X-PortainerAgent-PublicKey": "3059301306072a8648ce3d020106082a8648ce3d030107034200048a7e08de329e37226321b155bc30166a98cf2b5a82acbc2fd6997cd9c696ac2b2d27975323f1b2df2445b9707ddf8b145f4599584eb3a8e323eb86c0c1a6ebfc", - "X-PortainerAgent-Signature": "NHUP8KCXDIbBz8oFEcdDpOMlhH6gmd0thpKqMQt17KKa47Rzfatk01Pi3qoDme53QYyVnRsOS6ZE8YHMdItmCg" - } -} diff --git a/.dev/data/portainer/portainer.db b/.dev/data/portainer/portainer.db deleted file mode 100644 index f4ce4220ad91c4f2992711f5468ec8c35819a5cb..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 32768 zcmeI3&u=8f701o;BS3;6C^rzYTDe5=?`4D65=h1#F9Wl-Ji31$DaNr8x*IhN!GwU$sw!mxcO!upL{p!`HUd{Tw zo>^L|kH_5kn=gL%%?-MKu5NhTwfoce?s&pA?7QNfC)T21Rim&{J;GF(K%P=^Z%CX|M=6b@YN^&``bBn7MTDOU;<2l z2`~XBzyz286JP>NfC(^xhk!u!tRH^dSn`#C`qBTBXnPkA|HD?d+djM#UIZOI)Tbw% zU?&Jxg4YufjXSEaGVX}JOp0LVoxiRGyQLk;%&JIOrL()zj&vOCY_0_D%u17qgcL$) zt;&YD-ve2c8WTPR*@$9EdWpT`5xXZ3EcDBfpRoU*&Y5x5~Z@Wn`p% zOJbdZx3~Um&f4jjzPH&KYz(qDl2-Th-eGzy?{qf?r)Qg4uHyaHdq;1cz6w7oBKADN zT5Nz29L#p_cI%KRO*LzZ44H|3A`zp`!!$2KY3}=(L8J-SQ-k2cbka~kt))mBizSp4 z39~I2)q>b;igCeUa?TJkjSVJUGf}TUHxyq+*GfkH*D8nMKqr5@j13q7@J2?hrTajbt=d z*>tp9D?zKmMk;$c%@Og=U6^(t30sJ$gW;}9B~})0Zjaz5Eo@Y6&DICGGPJfEXnsQ` z>50sBp{zFN^%O?d=EctXdNji3EY|wUW+GKVgQ(+{$oGR zsi9?XtrS?`l8P*zQbVvG<5O>@;lY*MxV(G_{!?HVm;e)C0!)AjFaajO1b(pu{PX=A zOD|S)q$O`bG$vqC`uiBZ+|cyzCCK#i25TacUn#gOrD(ti%&nTGuLx&D7c zruF$JUgS>;gL$4JSG}=3N9&abNLNRM^hc-GXN*_sqS33zIvPteD*KBRC6c0EL3!o% zHA&x=#dU5NQbFZfn(ylVV!bTRwaRR7)+`}y9#@sd{xem0C2tXV zdD<)CYtKxw|EbpOmA?nYoEW)uPgI4Ag}xX$M5WSz>8!b_DvmO_5M)5{a35>=q2hd@O2&v(wBy)wn?LW{8T;~QXhMd%ZqmhY@}61I;H zFM<$plL{27$qJTcQoOpWhNxg_v!_d{Dx$K~iWO~NS8507EUIc(f!O}b=4}y))mOHn zo2xHx$G2CvZ^dGDAY&;u2HV@WUbz`uzAyVDt;Y*zG>?O2Ccp%k025#WOyFlpfbvp0 zIRBsK-<?IVWs z|H+qUesccr8G5dq|2q;M4Cnv+{NHa5J~II(zyz286JP>NfC(@GCcp%k025#WOn?b6 p0Vco%m;e)C0!)AjFaajO1egF5U;<2l2`~XBzyz286ZjPp_yHnrISl{+ diff --git a/.dev/data/portainer/portainer.key b/.dev/data/portainer/portainer.key deleted file mode 100644 index 6d42ebd..0000000 --- a/.dev/data/portainer/portainer.key +++ /dev/null @@ -1,5 +0,0 @@ ------BEGIN EC PRIVATE KEY----- -MHcCAQEEIBGdymbu/YpwNyXLpaQq7cbD+hf9Fx8opgyIfMTEg0V1oAoGCCqGSM49 -AwEHoUQDQgAEin4I3jKeNyJjIbFVvDAWapjPK1qCrLwv1pl82caWrCstJ5dTI/Gy -3yRFuXB934sUX0WZWE6zqOMj64bAwabr/A== ------END EC PRIVATE KEY----- diff --git a/.dev/data/portainer/portainer.pub b/.dev/data/portainer/portainer.pub deleted file mode 100644 index 0e14de4..0000000 --- a/.dev/data/portainer/portainer.pub +++ /dev/null @@ -1,4 +0,0 @@ ------BEGIN ECDSA PUBLIC KEY----- -MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEin4I3jKeNyJjIbFVvDAWapjPK1qC -rLwv1pl82caWrCstJ5dTI/Gy3yRFuXB934sUX0WZWE6zqOMj64bAwabr/A== ------END ECDSA PUBLIC KEY----- diff --git a/.dev/data/wallabag/db/wallabag.sqlite b/.dev/data/wallabag/db/wallabag.sqlite deleted file mode 100644 index bb12906ccb69c3c2b0c59dad615417fb8d247f13..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 221184 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 Date: Thu, 19 Mar 2026 11:32:36 -0500 Subject: [PATCH 9/9] Move diagnosing section to README, remove from CLAUDE.md collect-logs.sh documentation belongs in README for human consumption. CLAUDE.md is for agent instructions only. Co-Authored-By: Claude Sonnet 4.6 --- CLAUDE.md | 11 ----------- README.md | 14 +++++++++++++- 2 files changed, 13 insertions(+), 12 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index b738e53..e8c0d06 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -21,17 +21,6 @@ If a new service or widget adds variables, update **all three** files together: - `docker-compose.yml` homepage service `environment:` block - `.env.example` (with a comment explaining where to get the value) -## Diagnosing issues on the server - -When something breaks after a deploy, collect logs and paste them into Claude: - -```bash -./scripts/collect-logs.sh # all services -./scripts/collect-logs.sh jellyfin # one service -``` - -The script outputs: container status, recent errors across all services, and the last 75 lines per service. Paste the full output as context and describe the symptom. - ## Session initialization At the start of every work session: diff --git a/README.md b/README.md index f58884c..81f5f1f 100644 --- a/README.md +++ b/README.md @@ -151,6 +151,17 @@ ssh john@ See [docs/tailscale.md](docs/tailscale.md) for security recommendations and managing device access. +## Diagnosing issues + +If something breaks after a deploy, collect logs from all running services: + +```bash +./scripts/collect-logs.sh # all services +./scripts/collect-logs.sh jellyfin # one service +``` + +The script outputs container status, recent errors, and the last 75 lines per service — useful context for debugging or sharing with Claude. + ## Updating After pulling changes, run the update script to provision any new directories, pull fresh images, and restart only changed containers: @@ -187,7 +198,8 @@ home-network/ ├── scripts/ │ ├── setup.sh # initial setup │ ├── update.sh # pull changes and redeploy -│ └── post-setup.sh # grab API keys after first run +│ ├── post-setup.sh # grab API keys after first run +│ └── collect-logs.sh # collect logs for debugging ├── pihole/ │ └── etc-dnsmasq.d/ │ └── 02-local-dns.conf # wildcard DNS for *.woggles.work