updated scripts

This commit is contained in:
2026-02-22 11:18:38 +01:00
parent 1fb2ff0516
commit c085de45dd
3 changed files with 233 additions and 36 deletions
+44 -15
View File
@@ -28,10 +28,8 @@ sudo ./docker-setup.sh \
--cf-token "your-cloudflare-api-token" \
--customer "customer-1"
# Hub mode — download pre-configured controller.yaml from Felhom Hub
# Hub mode — one-liner: all infra settings (domain, email, CF tokens) come from Hub
sudo ./docker-setup.sh \
--domain example.com \
--email admin@example.com \
--hub-customer "customer-1" \
--hub-password "retrieval-password-from-hub"
```
@@ -94,26 +92,33 @@ The script supports three mutually exclusive TLS modes:
- Generates 10-year wildcard cert with custom CA
- CA cert copied to user home for manual device import
### Hub download mode
### Hub 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:
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}
```
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
The downloaded config is parsed early in the run and populates:
On failure:
- Logs a warning with HTTP status code
- Falls back to the interactive wizard
| 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 |
Hub credentials are created in the Hub web UI at `https://hub.felhom.eu/configs`.
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
@@ -220,8 +225,8 @@ sudo ./felhom-wipe.sh --level full --yes
|-------|-----------------|
| `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. 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), `docker system prune -af --volumes`, and all infra config directories (`/opt/docker/felhom-controller/`, `/opt/docker/traefik/`, `/opt/docker/cloudflared/`, `/opt/docker/stacks/`) |
| `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
@@ -237,11 +242,35 @@ sudo ./felhom-wipe.sh --level full --yes
- 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
+106 -21
View File
@@ -144,6 +144,11 @@ CUSTOMER_ID=""
CF_TUNNEL_TOKEN=""
HUB_CUSTOMER=""
HUB_PASSWORD=""
HUB_CONFIG_TMP="" # path to downloaded hub config temp file (set by apply_hub_config)
DOMAIN_FROM_CLI=false
EMAIL_FROM_CLI=false
CF_TOKEN_FROM_CLI=false
CF_TUNNEL_FROM_CLI=false
# Directories
DOCKER_DATA_DIR="/opt/docker"
@@ -302,13 +307,13 @@ parse_args() {
INTERFACE="$2"; shift 2 ;;
--domain)
require_arg "$1" "${2:-}"
BASE_DOMAIN="$2"; shift 2 ;;
BASE_DOMAIN="$2"; DOMAIN_FROM_CLI=true; shift 2 ;;
--email)
require_arg "$1" "${2:-}"
ACME_EMAIL="$2"; shift 2 ;;
ACME_EMAIL="$2"; EMAIL_FROM_CLI=true; shift 2 ;;
--cf-token)
require_arg "$1" "${2:-}"
CF_DNS_API_TOKEN="$2"; shift 2 ;;
CF_DNS_API_TOKEN="$2"; CF_TOKEN_FROM_CLI=true; shift 2 ;;
--traefik-password)
require_arg "$1" "${2:-}"
TRAEFIK_PASSWORD="$2"; shift 2 ;;
@@ -317,7 +322,7 @@ parse_args() {
CUSTOMER_ID="$2"; shift 2 ;;
--cf-tunnel-token)
require_arg "$1" "${2:-}"
CF_TUNNEL_TOKEN="$2"; shift 2 ;;
CF_TUNNEL_TOKEN="$2"; CF_TUNNEL_FROM_CLI=true; shift 2 ;;
--hub-customer)
require_arg "$1" "${2:-}"
HUB_CUSTOMER="$2"; shift 2 ;;
@@ -1461,6 +1466,88 @@ EOF
}
#-------------------------------------------------------------------------------
# YAML helper: extract a single string value from a section+key
# Usage: yaml_get <file> <top-level-section> <key>
# Handles both quoted ("value") and unquoted values.
#-------------------------------------------------------------------------------
yaml_get() {
local file="$1" section="$2" key="$3"
awk -v s="${section}:" -v k=" ${key}:" '
/^[[:alpha:]]/ { in_s = ($0 == s) }
in_s && index($0, k) == 1 {
sub(/^[^:]*: */, ""); gsub(/^"|"$/, ""); print; exit
}
' "$file"
}
#-------------------------------------------------------------------------------
# Hub mode: download controller.yaml early and extract infra vars
# Called from main() before Traefik/infra setup so BASE_DOMAIN etc. are ready.
#-------------------------------------------------------------------------------
apply_hub_config() {
[[ -z "$HUB_CUSTOMER" ]] && return
log_info "Fetching configuration from Felhom Hub (customer: ${HUB_CUSTOMER})..."
if [[ "$DRY_RUN" == true ]]; then
echo -e "${CYAN}[DRY-RUN]${NC} Would fetch: https://hub.felhom.eu/api/v1/config/${HUB_CUSTOMER}"
echo -e "${CYAN}[DRY-RUN]${NC} Would apply domain, email, CF tokens from hub config"
# Set plausible placeholders so the plan display is meaningful
[[ "$DOMAIN_FROM_CLI" == false ]] && BASE_DOMAIN="<hub-domain>"
[[ "$EMAIL_FROM_CLI" == false ]] && ACME_EMAIL="<hub-email>"
[[ "$CF_TOKEN_FROM_CLI" == false ]] && CF_DNS_API_TOKEN="<hub-cf-token>"
[[ "$CF_TUNNEL_FROM_CLI" == false ]] && CF_TUNNEL_TOKEN="<hub-cf-tunnel-token>"
return
fi
HUB_CONFIG_TMP=$(mktemp /tmp/felhom-hub-config-XXXXXX.yaml)
local hub_url="https://hub.felhom.eu/api/v1/config/${HUB_CUSTOMER}"
local http_code
http_code=$(curl -fsSL \
-H "X-Retrieval-Password: ${HUB_PASSWORD}" \
-o "${HUB_CONFIG_TMP}" \
-w "%{http_code}" \
"${hub_url}" 2>&1) || true
if [[ "$http_code" != "200" ]]; then
rm -f "${HUB_CONFIG_TMP}"
HUB_CONFIG_TMP=""
log_error "Failed to fetch config from Felhom Hub (HTTP ${http_code})"
log_error "URL: ${hub_url}"
log_error "Check the customer ID and retrieval password, then re-run."
exit 1
fi
log_success "Hub config fetched successfully"
# Extract values from hub YAML
local hub_domain hub_email hub_cf_token hub_tunnel_token
hub_domain=$(yaml_get "${HUB_CONFIG_TMP}" "customer" "domain")
hub_email=$(yaml_get "${HUB_CONFIG_TMP}" "customer" "email")
hub_cf_token=$(yaml_get "${HUB_CONFIG_TMP}" "infrastructure" "cf_api_token")
hub_tunnel_token=$(yaml_get "${HUB_CONFIG_TMP}" "infrastructure" "cf_tunnel_token")
# Apply to script vars — CLI flags always take precedence
if [[ "$DOMAIN_FROM_CLI" == false && -n "$hub_domain" ]]; then
BASE_DOMAIN="$hub_domain"
log_info " domain: ${BASE_DOMAIN} (from Hub)"
fi
if [[ "$EMAIL_FROM_CLI" == false && -n "$hub_email" ]]; then
ACME_EMAIL="$hub_email"
log_info " email: ${ACME_EMAIL} (from Hub)"
fi
if [[ "$CF_TOKEN_FROM_CLI" == false && -n "$hub_cf_token" ]]; then
CF_DNS_API_TOKEN="$hub_cf_token"
log_info " cf_api_token: ${CF_DNS_API_TOKEN:0:6}... (from Hub)"
fi
if [[ "$CF_TUNNEL_FROM_CLI" == false && -n "$hub_tunnel_token" ]]; then
CF_TUNNEL_TOKEN="$hub_tunnel_token"
log_info " cf_tunnel_token: ${CF_TUNNEL_TOKEN:0:6}... (from Hub)"
fi
}
#-------------------------------------------------------------------------------
# Generate minimal controller.yaml — full configuration via web UI setup wizard
#-------------------------------------------------------------------------------
@@ -1474,30 +1561,24 @@ generate_minimal_config() {
mkdir -p "${CONTROLLER_DIR}"
if [[ -n "$HUB_CUSTOMER" ]]; then
log_step "${step_num}/$(get_total_steps) - Downloading controller.yaml from Felhom Hub..."
log_step "${step_num}/$(get_total_steps) - Installing controller.yaml from Felhom Hub..."
if [[ "$DRY_RUN" == true ]]; then
echo -e "${CYAN}[DRY-RUN]${NC} Would download controller.yaml from https://hub.felhom.eu/api/v1/config/${HUB_CUSTOMER}"
echo -e "${CYAN}[DRY-RUN]${NC} Would install hub controller.yaml to ${CONTROLLER_DIR}/controller.yaml"
return
fi
local hub_url="https://hub.felhom.eu/api/v1/config/${HUB_CUSTOMER}"
local http_code
http_code=$(curl -fsSL \
-H "X-Retrieval-Password: ${HUB_PASSWORD}" \
-o "${CONTROLLER_DIR}/controller.yaml" \
-w "%{http_code}" \
"${hub_url}" 2>&1) || true
if [[ "$http_code" == "200" ]]; then
chmod 600 "${CONTROLLER_DIR}/controller.yaml"
log_success "controller.yaml downloaded from Felhom Hub (customer: ${HUB_CUSTOMER})"
# Config was already downloaded by apply_hub_config() early in main()
if [[ -n "$HUB_CONFIG_TMP" && -f "$HUB_CONFIG_TMP" ]]; then
mv "${HUB_CONFIG_TMP}" "${CONTROLLER_DIR}/controller.yaml"
HUB_CONFIG_TMP=""
else
log_error "Failed to download controller.yaml from Hub (HTTP ${http_code})"
log_error "URL: ${hub_url}"
log_error "Check the customer ID and retrieval password, then re-run."
log_error "Hub config temp file not found — apply_hub_config() may not have run"
exit 1
fi
chmod 600 "${CONTROLLER_DIR}/controller.yaml"
log_success "controller.yaml installed from Felhom Hub (customer: ${HUB_CUSTOMER})"
return
fi
@@ -1734,7 +1815,11 @@ main() {
if [[ "$DEBUG_MODE" == true ]]; then
set -x
fi
# Hub mode: download config early so BASE_DOMAIN, ACME_EMAIL, CF tokens are
# available before Traefik and other infra steps run
apply_hub_config
print_banner
check_debian
+83
View File
@@ -209,6 +209,16 @@ print_plan() {
fi
fi
if [[ "$LEVEL" == "full" || "$LEVEL" == "nuclear" ]]; then
echo ""
echo -e "${CYAN}Mount cleanup:${NC}"
if [ -d /mnt/.felhom-scan ]; then
echo -e " ${YELLOW}DELETE${NC} /mnt/.felhom-scan/ (stale scan dir)"
else
echo -e " ${GREEN}(no .felhom-scan dir)${NC}"
fi
fi
if [[ "$LEVEL" == "full" || "$LEVEL" == "nuclear" ]]; then
echo ""
echo -e "${CYAN}Storage data:${NC}"
@@ -239,6 +249,21 @@ print_plan() {
echo -e " ${RED}DELETE${NC} Cloudflared container"
echo -e " ${RED}DELETE${NC} Portainer container + volume"
echo -e " ${RED}DELETE${NC} .felhom-infra-backup/ (DR markers on all drives)"
if [ -d /mnt/.felhom-raw ]; then
echo -e " ${RED}UNMOUNT+DELETE${NC} /mnt/.felhom-raw/ (raw helper mounts + fstab entries)"
# Show each raw mount and its bind target
for rmp in /mnt/.felhom-raw/*/; do
[ -d "$rmp" ] || continue
local label; label=$(basename "$rmp")
local bind_target
bind_target=$(grep -E "^/mnt/\.felhom-raw/${label}/" /etc/fstab 2>/dev/null | awk '{print $2}' | head -1 || true)
if [ -n "$bind_target" ]; then
echo -e " ${RED}umount${NC} ${bind_target} (bind) → ${rmp} (raw)"
else
echo -e " ${RED}umount${NC} ${rmp} (raw, no bind found)"
fi
done
fi
echo -e " ${RED}DELETE${NC} All Docker data (docker system prune -af --volumes)"
echo -e " ${RED}DELETE${NC} $COMPOSE_DIR/ (controller compose + .env)"
local infra_root; infra_root=$(dirname "$COMPOSE_DIR")
@@ -264,6 +289,57 @@ print_plan() {
echo ""
}
# --- Mount Cleanup Helpers ---
# cleanup_scan_dir: remove /mnt/.felhom-scan/ (ephemeral DR scan staging dir).
# Always empty after normal operation; safe to rm -rf unconditionally.
cleanup_scan_dir() {
if [ -d /mnt/.felhom-scan ]; then
rm -rf /mnt/.felhom-scan && info " Removed: /mnt/.felhom-scan/"
fi
}
# cleanup_raw_mounts: unmount bind mounts, unmount raw helper mounts, strip
# /etc/fstab entries, then remove the now-empty /mnt/.felhom-raw/ directory.
#
# Raw mounts are created by the attach wizard (two-level: raw partition mount +
# bind mount from subfolder). Both fstab entries must be removed so they don't
# cause errors on next boot. Order: bind umount first, then raw umount.
cleanup_raw_mounts() {
[ -d /mnt/.felhom-raw ] || return
info "Cleaning up raw helper mounts (/mnt/.felhom-raw/)..."
# 1. Unmount bind mounts whose source is inside .felhom-raw (field 1 matches)
if [ -f /etc/fstab ]; then
local bind_targets
bind_targets=$(grep -E '^/mnt/\.felhom-raw/' /etc/fstab | awk '{print $2}' || true)
for mp in $bind_targets; do
if mountpoint -q "$mp" 2>/dev/null; then
umount -l "$mp" 2>/dev/null && info " Unmounted bind: $mp" \
|| warn " Could not unmount bind: $mp"
fi
done
fi
# 2. Unmount raw partition mounts (field 2 matches /mnt/.felhom-raw/*)
for mp in /mnt/.felhom-raw/*/; do
[ -d "$mp" ] || continue
if mountpoint -q "$mp" 2>/dev/null; then
umount -l "$mp" 2>/dev/null && info " Unmounted raw: $mp" \
|| warn " Could not unmount raw: $mp"
fi
done
# 3. Strip all .felhom-raw entries from fstab (both raw and bind lines)
if [ -f /etc/fstab ] && grep -q '\.felhom-raw' /etc/fstab 2>/dev/null; then
sed -i '\|\.felhom-raw|d' /etc/fstab && info " Removed .felhom-raw entries from /etc/fstab"
fi
# 4. Remove directory — safe now that mounts are gone
rm -rf /mnt/.felhom-raw && info " Removed: /mnt/.felhom-raw/"
}
# --- Wipe Functions ---
do_soft_wipe() {
@@ -336,6 +412,9 @@ do_full_wipe() {
fi
done
# Remove stale scan dir (ephemeral DR staging — always safe to remove)
cleanup_scan_dir
# Restart controller after all cleanup is done
info "Restarting controller..."
docker restart felhom-controller 2>/dev/null || warn "Could not restart controller"
@@ -358,6 +437,10 @@ do_nuclear_wipe() {
fi
done
# Unmount raw helper mounts, strip fstab entries, remove dirs
# (scan dir already removed by do_full_wipe above)
cleanup_raw_mounts
# Remove all Docker data
warn "Pruning all Docker data..."
docker system prune -af --volumes 2>/dev/null || warn "Docker prune failed"