Files
deploy-felhom-compose/scripts
admin 45cf527050 fix(docker-setup): yaml_get handles 4-space YAML indentation
Hub YAML is generated by Go's yaml.v3 which uses 4-space indentation.
The yaml_get helper was matching "  key:" (2 spaces) so all extractions
silently returned empty — BASE_DOMAIN stayed as homeserver.local and
CF_TUNNEL_TOKEN was never set from hub config.

Strip leading whitespace before key matching, making yaml_get
indentation-agnostic.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-22 11:27:44 +01:00
..
2026-02-22 11:18:38 +01:00
2026-02-22 11:18:38 +01:00

scripts/

Setup and maintenance scripts for Felhom homeserver deployments.


docker-setup.sh

Automated deployment script for Felhom customer nodes.

Takes a fresh Debian 13 server and deploys a complete Felhom homeserver stack: Docker, Traefik reverse proxy, Cloudflare Tunnel (optional), TLS certificates, FileBrowser, and the felhom-controller dashboard.

Version: 6.0.0

Quick start

# Minimal — interactive wizard for all settings
sudo ./docker-setup.sh --domain example.com --email admin@example.com

# Full — Cloudflare DNS + static IP + pre-seeded customer
sudo ./docker-setup.sh \
  --ip 192.168.0.50 \
  --domain example.com \
  --email admin@example.com \
  --cf-token "your-cloudflare-api-token" \
  --customer "customer-1"

# Hub mode — one-liner: all infra settings (domain, email, CF tokens) come from Hub
sudo ./docker-setup.sh \
  --hub-customer "customer-1" \
  --hub-password "retrieval-password-from-hub"

CLI flags

Flag Argument Description Default
--bootstrap Install sudo on fresh Debian (run as root first)
--ip ADDRESS Static IP address (e.g., 192.168.0.50) DHCP
--gateway ADDRESS Gateway address 192.168.0.1
--dns SERVERS DNS servers, comma-separated 1.1.1.1,8.8.8.8
--interface NAME Network interface name Auto-detect
--domain DOMAIN Base domain (pre-seeds wizard) homeserver.local
--email EMAIL ACME email for Let's Encrypt
--cf-token TOKEN Cloudflare API token for DNS-01 challenge
--customer ID Customer identifier (pre-seeds wizard)
--hub-customer ID Download config from Hub: customer ID
--hub-password PASSWORD Download config from Hub: retrieval password
--traefik-password PASSWORD Traefik dashboard basicAuth password Auto-generated
--self-signed-cert Generate self-signed wildcard certificate false
--skip-filebrowser Skip FileBrowser installation false
--dry-run Show plan without making changes false
--debug Enable bash debug tracing (set -x) false
-h, --help Show help message

Installation steps

The script runs these steps in order:

Step Function Description
1 install_base_packages() Install system packages: curl, git, htop, jq, openssl, apache2-utils, etc.
2 configure_static_ip() Configure static IP (optional). Supports NetworkManager, systemd-networkd, ifupdown.
3 install_docker() Install Docker CE + Compose plugin. GPG key, repo setup, daemon.json with DNS fallback, traefik-public network.
4 install_traefik() Deploy Traefik v3.6.7 reverse proxy with configurable TLS (Let's Encrypt DNS-01/HTTP-01 or self-signed).
4b install_cloudflare_tunnel() Deploy Cloudflare Tunnel (optional, only if tunnel token provided in wizard).
5 generate_self_signed_cert() Generate self-signed CA + wildcard cert (optional, if --self-signed-cert flag set).
6 run_config_wizard() Interactive wizard or Hub download. Generates controller.yaml with customer settings.
7 install_filebrowser() Deploy FileBrowser Quantum with auto-discovered drive mounts (optional).
8 install_controller() Deploy felhom-controller (privileged container with system access).
9 install_tools_and_configure() Install ctop, lazydocker, Docker shell aliases.

TLS certificate modes

The script supports three mutually exclusive TLS modes:

  1. Let's Encrypt + Cloudflare DNS-01 (recommended)

    • Requires: --email + --cf-token
    • Works with Cloudflare Tunnel (no public port 80 needed)
    • Automatic renewal via Traefik
  2. Let's Encrypt + HTTP-01

    • Requires: --email (no --cf-token)
    • Requires port 80 publicly accessible
    • Does NOT work with Cloudflare Tunnel
  3. Self-signed certificate

    • Requires: --self-signed-cert
    • Generates 10-year wildcard cert with custom CA
    • CA cert copied to user home for manual device import

Hub mode

When both --hub-customer and --hub-password are provided, the script downloads a pre-configured controller.yaml from the Felhom Hub before any infra setup begins, then extracts the stored values to auto-configure everything — no additional flags needed:

GET https://hub.felhom.eu/api/v1/config/{customer_id}
Header: X-Retrieval-Password: {password}

The downloaded config is parsed early in the run and populates:

Extracted field Used for
customer.domain Traefik routing, TLS cert SANs, DNS display
customer.email Let's Encrypt ACME registration
infrastructure.cf_api_token Traefik DNS-01 TLS challenge
infrastructure.cf_tunnel_token Cloudflare Tunnel connector

CLI flags always take precedence — passing --domain overrides the hub value.

On failure (wrong credentials, network error):

  • Script exits immediately with the HTTP status code and the failing URL
  • Nothing is installed

Hub credentials are found in the Hub web UI under the customer's Credentials section.

Configuration wizard

When Hub download is not used, the interactive wizard prompts for:

Section Fields
Customer identity ID, display name, domain, email
Infrastructure secrets Cloudflare Tunnel token, CF API token
Paths System data partition mount point
Dashboard password bcrypt-hashed login password (optional)
Git sync App catalog repo URL, username, token
Monitoring 5x healthchecks.io ping UUIDs (heartbeat, system, DB dump, backup, integrity)

Validation:

  • Customer ID is required (cannot be empty or demo-felhom)
  • Domain is required (cannot be homeserver.local)

Output files

Path Description
/opt/docker/felhom-controller/controller.yaml Controller configuration (perms 600)
/opt/docker/felhom-controller/docker-compose.yml Controller container definition
/opt/docker/traefik/traefik.yml Traefik static configuration
/opt/docker/traefik/dynamic/dashboard.yml Traefik dashboard route
/opt/docker/traefik/docker-compose.yml Traefik container definition
/opt/docker/stacks/filebrowser/docker-compose.yml FileBrowser container definition
/opt/docker/cloudflared/docker-compose.yml Cloudflare Tunnel definition (optional)
/var/log/docker-setup.log Full installation log

Safety features

  • Error trapping: set -euo pipefail + ERR trap with diagnostic collection
  • Dry-run mode: Full preview without state changes
  • Input validation: IP format, required fields, credential checks
  • Atomic writes: Temp file + rename for sensitive config files
  • Permission hardening: 600 for secrets, 644 for certificates
  • Idempotency: Checks for existing installations, skips if already done
  • Docker fallback: Falls back from Debian 13 (trixie) to 12 (bookworm) repo if needed
  • DNS verification: Tests DNS resolution before proceeding
  • Health checks: Verifies Docker daemon ready, containers running after each deploy

Prerequisites

  • OS: Debian 13 (Trixie) — minimal support for other distros with warnings
  • Privileges: Must run with sudo
  • Network: Internet connection for package downloads and Docker image pulls
  • DNS: Must resolve before script runs (verified via getent hosts download.docker.com)

Bootstrap mode

On a fresh Debian install without sudo:

# As root — install sudo and configure user
./docker-setup.sh --bootstrap

# Then as regular user — full setup
sudo ./docker-setup.sh --domain example.com --email admin@example.com

Shell aliases installed

After setup, these aliases are added to the user's .bashrc:

dc='sudo docker compose'
dcu='sudo docker compose up -d'
dcd='sudo docker compose down'
dcl='sudo docker compose logs -f'
dps='sudo docker ps --format "table {{.Names}}\t{{.Status}}\t{{.Ports}}"'
dlogs='sudo docker logs -f'
dexec='sudo docker exec -it'
dprune='sudo docker system prune -af'

Helper tools installed

  • ctop — Top-like interface for container metrics
  • lazydocker — Terminal UI for Docker management

felhom-wipe.sh

Test node cleanup script with 4 wipe levels.

Removes felhom-managed data from a node in a controlled, repeatable way. Designed for test/demo nodes to reset state between testing cycles.

Quick start

# Preview what will be removed (dry run — default)
sudo ./felhom-wipe.sh --level full

# Execute the wipe
sudo ./felhom-wipe.sh --level full --yes

Wipe levels

Level What it removes
soft Controller state files only: settings.json, metrics.db, setup-state.json, update-state.json, session-data.json, snapshot-history.json
controller Soft + all non-infra Docker containers, all Docker volumes (except portainer_data), all stack directories (skips protected stacks by default)
full controller-level cleanup + felhom-data/ on all storage drives (appdata, backups). Also removes old-style appdata/ and backups/ directories for pre-v0.26.0 compatibility. Removes /mnt/.felhom-scan/ (stale DR scan dir). Infra containers (including felhom-controller) are preserved; controller is restarted after cleanup.
nuclear Full + all infra containers (controller, traefik, cloudflared, portainer), DR markers (.felhom-infra-backup/ on all drives), raw helper mounts (/mnt/.felhom-raw/ — unmount bind+raw, strip fstab entries), /mnt/.felhom-scan/, docker system prune -af --volumes, and all infra config directories (/opt/docker/felhom-controller/, /opt/docker/traefik/, /opt/docker/cloudflared/, /opt/docker/stacks/)

CLI options

Option Description
--level <level> Required. One of: soft, controller, full, nuclear
--yes Execute the wipe. Default is dry-run (preview only).
--include-protected Also remove protected stacks (controller level only).

Path auto-detection

  • Reads stacks_dir and data_dir from /opt/docker/felhom-controller/controller.yaml if present
  • Reads registered storage paths from settings.json
  • Also scans /mnt/*/ for felhom-data/ or legacy appdata/ directories not in the registry

Raw helper mounts

The attach wizard creates a two-level mount structure for pre-formatted drives:

/dev/sdb1 (physical partition)
    └─ /mnt/.felhom-raw/hdd_1/       ← raw mount (persists in fstab, backs the bind)
           └─ felhom_data/
               └─ /mnt/hdd_1/        ← bind mount (what apps actually use)

Both fstab entries survive reboots. On nuclear wipe, the script:

  1. Unmounts bind mounts (e.g. /mnt/hdd_1) first
  2. Unmounts raw mounts (e.g. /mnt/.felhom-raw/hdd_1)
  3. Strips both fstab entries
  4. Removes the now-empty /mnt/.felhom-raw/ directory

The physical data on the drive partition is not touched — only the mount point directories (empty after unmounting) are removed.

/mnt/.felhom-scan/ is a separate ephemeral directory used only during the DR setup wizard to temporarily inspect drives. It is cleaned up from full level onwards.

What is preserved

  • OS and system files
  • Infrastructure containers and config (unless nuclear)
  • User files: Dokumentumok/, media/, other non-felhom directories on drives
  • Drive data — raw mounts are unmounted but partition contents are untouched
  • DR markers on drives (unless nuclear)

Safety

  • Dry-run by default — shows plan without deleting anything
  • Interactive YES confirmation prompt required even with --yes
  • Must run as root (sudo)
  • Checks Docker is running before proceeding
  • Protected stacks skipped by default (use --include-protected to override)

Redeploy after nuclear wipe

curl -fsSL https://gitea.dooplex.hu/admin/deploy-felhom-compose/raw/branch/main/scripts/docker-setup.sh | bash