#!/bin/bash setupWizardTerminal() { isHeader "LibrePortal Setup Wizard" isNotice "Let's get your install configured. This only runs once." echo "" local install_name=$(generateInstallName) while true; do isNotice "Suggested Install Name: $install_name" isQuestion "Press Enter to accept, 'r' to roll a new one, or type your own : " read -p "" install_name_input if [[ -z "$install_name_input" ]]; then break elif [[ "$install_name_input" =~ ^[rR]$ ]]; then install_name=$(generateInstallName) continue elif [[ "$install_name_input" =~ ^[a-zA-Z0-9-]+$ ]]; then install_name="$install_name_input" break fi isNotice "Invalid input. Use letters, numbers, and hyphens only." done # Domains — optional, multi (CFG_DOMAIN_1..9). Empty input ends the loop. isHeader "Domains (optional)" isNotice "Add one or more domains pointed at this server." isNotice "Each domain unlocks https://app. routing via Traefik + a self-signed SSL cert." isNotice "Press Enter on a blank line to finish (or skip entirely for a local-only install)." echo "" local domains=() local domain_idx=1 while [[ $domain_idx -le 9 ]]; do isQuestion "Domain $domain_idx (or blank to finish) : " read -p "" d [[ -z "$d" ]] && break if [[ ! "$d" =~ ^([a-zA-Z0-9]+(-[a-zA-Z0-9]+)*\.)+[a-zA-Z]{2,}$ ]]; then isNotice "Invalid domain — try again." continue fi local dns_result=$(setupCheckDomainPointsHere "$d") local matches=$(echo "$dns_result" | jq -r '.matches') local server_ip=$(echo "$dns_result" | jq -r '.server_ip') local domain_ip=$(echo "$dns_result" | jq -r '.domain_ip') if [[ "$matches" == "true" ]]; then isSuccessful "✓ '$d' resolves to this server ($server_ip)." domains+=("$d") ((domain_idx++)) else isNotice "⚠ '$d' resolves to '$domain_ip', this server is '$server_ip'." isQuestion "Use it anyway? Traefik may not route this until DNS is fixed. (y/n) : " read -p "" use_anyway if [[ "$use_anyway" =~ ^[yY]$ ]]; then domains+=("$d") ((domain_idx++)) fi fi done local detected_tz="" if command -v timedatectl >/dev/null 2>&1; then detected_tz=$(timedatectl show -p Timezone --value 2>/dev/null) fi if [[ -z "$detected_tz" && -r /etc/timezone ]]; then detected_tz=$(cat /etc/timezone 2>/dev/null | tr -d '[:space:]') fi local timezone="" local common_timezones=("Europe/London" "Europe/Paris" "Europe/Berlin" "America/New_York" "America/Chicago" "America/Los_Angeles" "America/Denver" "America/Phoenix" "Asia/Tokyo" "Australia/Sydney") while true; do if [[ -n "$detected_tz" ]]; then isNotice "Detected OS timezone: $detected_tz" isQuestion "Press Enter to use it, or type a number / 'c' to choose differently : " read -p "" tz_choice if [[ -z "$tz_choice" ]]; then timezone="$detected_tz" break fi else isNotice "Select a timezone:" for ((i=0; i<${#common_timezones[@]}; i++)); do isOption "$((i+1)). ${common_timezones[i]}" done isOption "c. Custom (enter manually)" isQuestion "Choice : " read -p "" tz_choice fi if [[ "$tz_choice" =~ ^[cC]$ ]]; then isQuestion "Custom timezone (e.g. Pacific/Auckland) : " read -p "" timezone [[ -n "$timezone" ]] && break elif [[ "$tz_choice" =~ ^[0-9]+$ && "$tz_choice" -ge 1 && "$tz_choice" -le "${#common_timezones[@]}" ]]; then timezone="${common_timezones[tz_choice-1]}" break else isNotice "Invalid selection." fi done isSuccessful "Timezone set to '$timezone'" isHeader "Recommended Apps" local apps=() local crowdsec_dashboard="false" isOption " - traefik (reverse proxy, handles LetsEncrypt SSL)" isOption " - crowdsec (host-installed intrusion prevention)" echo "" if [[ ${#domains[@]} -eq 0 ]]; then isNotice "No domains configured — Traefik has nothing to route. Skipping by default." isQuestion "Install Traefik anyway? (y/n) : " read -p "" want_traefik [[ "$want_traefik" =~ ^[yY]$ ]] && apps+=("traefik") else isQuestion "Install Traefik? (Y/n) : " read -p "" want_traefik [[ ! "$want_traefik" =~ ^[nN]$ ]] && apps+=("traefik") fi isQuestion "Install CrowdSec? (Y/n) : " read -p "" want_crowdsec if [[ ! "$want_crowdsec" =~ ^[nN]$ ]]; then apps+=("crowdsec") isQuestion " └─ Install CrowdSec Management Console (Metabase web UI)? (Y/n) : " read -p "" want_console [[ ! "$want_console" =~ ^[nN]$ ]] && crowdsec_dashboard="true" fi isHeader "Optional Apps" isQuestion "Install Wireguard (VPN — secure remote access)? (y/N) : " read -p "" want_wireguard [[ "$want_wireguard" =~ ^[yY]$ ]] && apps+=("wireguard") # Traefik is the only app that needs an email — for LetsEncrypt cert # registration. We collect it now (before the install task fires) so the # Traefik installer doesn't have to prompt mid-task. local traefik_email="" if [[ " ${apps[*]} " == *" traefik "* ]]; then while true; do isQuestion "LetsEncrypt email for Traefik (cert-expiry notices) : " read -p "" traefik_email emailValidation "$traefik_email" [[ $? -eq 0 ]] && break isNotice "Please provide a valid email address." done fi isHeader "Confirm" isNotice "Install Name : $install_name" isNotice "Timezone : $timezone" if [[ ${#domains[@]} -gt 0 ]]; then isNotice "Domains : ${domains[*]}" else isNotice "Domains : (none — local install)" fi isNotice "Apps : ${apps[*]:-none}" [[ " ${apps[*]} " == *" crowdsec "* ]] && isNotice "CrowdSec UI : $crowdsec_dashboard" [[ -n "$traefik_email" ]] && isNotice "Traefik Email : $traefik_email" echo "" isQuestion "Apply these settings? (Y/n) : " read -p "" confirm if [[ "$confirm" =~ ^[nN]$ ]]; then isNotice "Setup cancelled. You can re-run from the menu." return 1 fi local app_options="{}" if [[ " ${apps[*]} " == *" crowdsec "* ]]; then app_options=$(jq -n --argjson d "$crowdsec_dashboard" '{crowdsec:{dashboard:$d}}') fi local payload=$(jq -n \ --arg n "$install_name" \ --arg t "$timezone" \ --arg te "$traefik_email" \ --argjson a "$(printf '%s\n' "${apps[@]}" | jq -R . | jq -s .)" \ --argjson dom "$(printf '%s\n' "${domains[@]}" | jq -R . | jq -s 'map(select(length > 0))')" \ --argjson opts "$app_options" \ '{install_name:$n, timezone:$t, domains:$dom, apps:$a, appOptions:$opts, traefik_email:$te}') local b64=$(echo -n "$payload" | base64 -w 0) setupApply "$b64" } checkConfigFirstInstall() { if isSetupWizardComplete; then return 0 fi if [[ "$unattended_setup" == "true" ]]; then return 0 fi setupWizardTerminal }