# 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

```bash
# 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 no drive volumes (optional). Drive volumes are managed by the controller via `SyncFileBrowserMounts()` after storage is registered. |
| 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 the
customer's config from the Felhom Hub **before any infra setup begins** to extract
infrastructure variables (domain, email, CF tokens), then generates a **minimal**
`controller.yaml` without `customer.id` — triggering the setup wizard on first run.

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

The downloaded config is parsed early and populates infrastructure variables:

| 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.

The hub credentials are passed to the controller via environment variables
(`FELHOM_SETUP_CUSTOMER_ID`, `FELHOM_SETUP_PASSWORD`) so the setup wizard auto-fills
them. On first access, the wizard offers three choices:

1. **Restore from Hub** — downloads infra backup (settings, encryption keys, restic
   passwords, disk layout) and restores everything. Credentials are auto-processed.
2. **Restore from local drive** — scans connected drives for `.felhom-infra-backup/`.
3. **Fresh install** — downloads config only, starts with clean settings.

```
docker-setup.sh --hub-customer demo-felhom --hub-password xxx
  → downloads config for infra vars (domain, CF tokens)
  → generates minimal controller.yaml (no customer.id)
  → passes hub credentials via env vars
  → controller starts in setup mode
  → user opens http://<ip>:8081
  → setup wizard: restore / local scan / fresh install
```

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:

```bash
# 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`:

```bash
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

```bash
# 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

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