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: 5.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 — download pre-configured controller.yaml from Felhom Hub
sudo ./docker-setup.sh \
--domain example.com \
--email admin@example.com \
--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 |
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:
-
Let's Encrypt + Cloudflare DNS-01 (recommended)
- Requires:
--email+--cf-token - Works with Cloudflare Tunnel (no public port 80 needed)
- Automatic renewal via Traefik
- Requires:
-
Let's Encrypt + HTTP-01
- Requires:
--email(no--cf-token) - Requires port 80 publicly accessible
- Does NOT work with Cloudflare Tunnel
- Requires:
-
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
- Requires:
Hub download mode
When both --hub-customer and --hub-password are provided, the script downloads a
pre-configured controller.yaml from the Felhom Hub instead of running the interactive wizard:
GET https://hub.felhom.eu/api/v1/config/{customer_id}
Header: X-Retrieval-Password: {password}
On success:
- Saves downloaded YAML as
controller.yaml(permissions 600) - Extracts domain, email, CF tokens for use by subsequent setup steps (Traefik, Cloudflare Tunnel)
- Skips the interactive wizard entirely
On failure:
- Logs a warning with HTTP status code
- Falls back to the interactive wizard
Hub credentials are created in the Hub web UI at https://hub.felhom.eu/configs.
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