#!/usr/bin/env bash
# Waveline self-host kit installer (Linux) - full-setup edition.
#
# One-liner from the Waveline site:
#   curl -fsSL https://waveline.maxi-gaming.com/selfhost/install.sh | sudo bash
# or save the file, read it, then:
#   sudo bash install.sh [target-dir]
#
# What it does:
#   0. makes sure the basic tools exist: curl/wget, python3 (unzip as a
#      fallback extractor), Docker ONLY when it is missing;
#   1. downloads the self-host kit (relay.py, accounts.py,
#      relay_server.py, the self-hosting guide, README), checks the zip
#      and extracts it;
#   2. ASKS first, then performs the guide's setup for you:
#        - the two service users (wrelay, walexa)
#        - the remote relay: code, admin key, permissions, systemd unit,
#          plus a live health check on 127.0.0.1:8640
#        - the Alexa relay: code, the two secrets, permissions, systemd
#          unit, plus a live wrong-token check on 127.0.0.1:8099
#        - the Apache HTTPS proxy + a Let's Encrypt certificate for the
#          domain you type in
#        - account e-mail (guide section 2.5): installs the Stalwart mail
#          server, finishes its one-time web setup for you (or walks you
#          through it and waits), then CREATES the waveline sending
#          account and its password itself and wires admin.env - or
#          configures an external SMTP provider you already have
#   3. prints the admin key, client token and mail credentials ONCE
#      and saves them to /root/wrelay-credentials.txt (chmod 600).
#
# All questions are asked on the real terminal (/dev/tty), so the piped
# "curl ... | sudo bash" form works. Answering "no" skips that stage;
# without a terminal the optional stages are skipped and the script
# behaves like the plain downloader. Re-running is SAFE: an existing
# admin key, the Alexa secrets, existing pairings and configured mail
# settings are never replaced - the kit and relay code are refreshed
# and the services restarted.
#
# Note: the relays themselves are plain Python standard-library programs
# run as systemd services - they do NOT need Docker. Docker is installed
# only because a server usually wants it for other things, and skipped
# entirely with WAVELINE_SKIP_DOCKER=1.
#
# Overrides (forwarded through the sudo re-exec; or set them with
#   curl -fsSL https://waveline.maxi-gaming.com/selfhost/install.sh \
#     | sudo env WAVELINE_DOMAIN=relay.example.com bash -s
# ):
#   WAVELINE_INSTALL_DIR   kit extraction target (default /opt/wrelay-kit)
#   WAVELINE_KIT_URL       kit zip URL (default: the official one)
#   WAVELINE_SKIP_DOCKER   1 = leave Docker alone
#   WAVELINE_SKIP_RELAYS   1 = do not offer the relay setup stage
#   WAVELINE_SKIP_APACHE   1 = do not offer the Apache/HTTPS stage
#   WAVELINE_DOMAIN        your domain (skips the domain question)
#   WAVELINE_EMAIL         e-mail for the certificate (non-interactive)
#   WAVELINE_SKIP_EMAIL    1 = do not offer the e-mail stage
#   WAVELINE_STALWART      1 = headless Stalwart setup (with WAVELINE_AUTO)
#   WAVELINE_SMTP_HOST/_PORT/_USER/_PASS/_FROM/_TLS
#                          configure an external SMTP provider headlessly
#                          (with WAVELINE_AUTO=1)
#   WAVELINE_AUTO          1 = run every stage without asking (the
#                          Apache stage still needs WAVELINE_DOMAIN)
# A first positional argument overrides the install directory.

if [ -z "${BASH_VERSION:-}" ]; then
  if [ -f "$0" ] && [ "$0" != "bash" ]; then exec bash "$0" "$@"; fi
  printf 'ERROR: run this installer with bash (curl -fsSL <url> | bash).\n' >&2
  exit 1
fi
set -euo pipefail

KIT_URL="${WAVELINE_KIT_URL:-https://waveline.maxi-gaming.com/selfhost/waveline-selfhost-kit.zip}"
SELF_URL="https://waveline.maxi-gaming.com/selfhost/install.sh"
SKIP_DOCKER="${WAVELINE_SKIP_DOCKER:-0}"
SKIP_RELAYS="${WAVELINE_SKIP_RELAYS:-0}"
SKIP_APACHE="${WAVELINE_SKIP_APACHE:-0}"
AUTO="${WAVELINE_AUTO:-0}"
DOMAIN="${WAVELINE_DOMAIN:-}"
CERT_EMAIL="${WAVELINE_EMAIL:-}"
SKIP_EMAIL="${WAVELINE_SKIP_EMAIL:-0}"
STALWART_HEADLESS="${WAVELINE_STALWART:-0}"
CRED_FILE=/root/wrelay-credentials.txt
STALWART_ADMIN_STATE=/root/.wrelay-stalwart-admin
NEW_ADMIN_KEY=0
NEW_SHARED=0
NEW_TOKEN=0
NEW_SMTP_PASS=0
NEW_STALWART_ADMIN=0
CLI_BIN=""
MAIL_HOST=""; MAIL_PORT=""; MAIL_TLS=""; MAIL_USER=""; MAIL_PASS=""; MAIL_FROM=""
MAIL_STATUS="not configured - guide section 2.5"
UNINSTALL=0
YES=0
KEEP_KIT="${WAVELINE_KEEP_KIT:-0}"

log()  { printf '\n==> %s\n' "$*"; }
info() { printf '    %s\n' "$*"; }
warn() { printf 'WARNING: %s\n' "$*" >&2; }
die()  { printf 'ERROR: %s\n' "$*" >&2; exit 1; }
have() { command -v "$1" >/dev/null 2>&1; }
fetch() { # fetch URL DEST
  if have curl; then curl -fSL --proto '=https' --tlsv1.2 -o "$2" "$1"
  elif have wget; then wget -q -O "$2" "$1"
  else return 127
  fi
}

# --- terminal access ----------------------------------------------------------
# Questions MUST come from the real terminal: with "curl | bash" stdin is the
# script itself. Without a terminal (ssh without -t, CI, cron) ask_yesno /
# ask_value fail and callers skip the stage gracefully.
TTY=""
if [ -c /dev/tty ] && { true < /dev/tty; } 2>/dev/null; then TTY=/dev/tty; fi

ask_yesno() { # ask_yesno QUESTION DEFAULT(yes|no) -> exit 0 = yes
  local ans
  if [ "$AUTO" = "1" ]; then return 0; fi
  if [ -z "$TTY" ]; then return 1; fi
  if [ "$2" = "yes" ]; then
    read -r -p "$1 [Y/n] " ans < "$TTY" || return 1
    case "$ans" in ""|y|Y|yes|YES|Yes) return 0 ;; *) return 1 ;; esac
  else
    read -r -p "$1 [y/N] " ans < "$TTY" || return 1
    case "$ans" in y|Y|yes|YES|Yes) return 0 ;; *) return 1 ;; esac
  fi
}

ask_value() { # ask_value PROMPT VARNAME  (empty answer -> exit 1)
  local ans __var="$2"
  if [ -z "$TTY" ]; then return 1; fi
  read -r -p "$1" ans < "$TTY" || return 1
  [ -n "$ans" ] || return 1
  printf -v "$__var" '%s' "$ans"
  return 0
}

valid_domain() { # basic hostname check: two labels, letters/digits/hyphens
  printf '%s' "$1" | grep -Eq '^[a-zA-Z0-9]([a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(\.[a-zA-Z0-9]([a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)+$'
}

usage() {
  cat <<'USAGE'
Waveline self-host kit installer (Linux)

  curl -fsSL https://waveline.maxi-gaming.com/selfhost/install.sh | sudo bash
  sudo bash install.sh [target-dir]
  sudo bash install.sh --uninstall [--yes]   (test-run cleanup)

Installs missing tools (curl/wget, python3, Docker when missing), downloads
the self-host kit (relay.py, accounts.py, relay_server.py, selfhosting.html,
README.txt) and extracts it. Then it ASKS whether to perform the guide's
setup for you: the two service users, the remote relay (admin key + systemd
service + health check), the Alexa relay (two secrets + systemd service +
wrong-token check) and the Apache HTTPS proxy with a Let's Encrypt
certificate for your domain. It can also set up account e-mail: install the
Stalwart mail server, guide you through its one-time web setup (or finish
it for you), then create the waveline sending account with a generated
password. The relays run on python3; no Docker needed.

Newly generated secrets are printed once and saved to
/root/wrelay-credentials.txt (chmod 600). Re-running is safe: existing
admin keys, Alexa secrets and pairings are kept; the kit and relay code
are refreshed and the services restarted.

Overrides (forwarded through the sudo re-exec):
  --uninstall            remove what the installer created (asks first;
                         add --yes to skip the question); secrets are
                         backed up to /root first; production services,
                         mail servers and certificates are NOT touched
  WAVELINE_INSTALL_DIR   kit extraction target (default /opt/wrelay-kit)
  WAVELINE_KIT_URL       kit zip URL (default: the official one)
  WAVELINE_SKIP_DOCKER   1 = leave Docker alone
  WAVELINE_SKIP_RELAYS   1 = do not offer the relay setup stage
  WAVELINE_SKIP_APACHE   1 = do not offer the Apache/HTTPS stage
  WAVELINE_SKIP_EMAIL    1 = do not offer the e-mail stage
  WAVELINE_STALWART      1 = headless Stalwart setup (with WAVELINE_AUTO)
  WAVELINE_SMTP_HOST/_PORT/_USER/_PASS  headless external SMTP provider
  WAVELINE_DOMAIN        your domain (skips the domain question)
  WAVELINE_EMAIL         e-mail for the certificate (non-interactive)
  WAVELINE_AUTO          1 = run every stage without asking
Pass overrides through sudo, e.g.:
  curl -fsSL https://waveline.maxi-gaming.com/selfhost/install.sh \
    | sudo env WAVELINE_DOMAIN=relay.example.com bash -s
USAGE
}
while [ "$#" -gt 0 ]; do
  case "$1" in
    -h|--help) usage; exit 0 ;;
    --uninstall|--remove) UNINSTALL=1; shift ;;
    --yes) YES=1; shift ;;
    *) break ;;
  esac
done

# --- test-run cleanup: remove exactly what the installer created -------------------
do_uninstall() {
  log "Uninstalling what this installer created (test-run cleanup)"
  if [ ! -f /etc/wrelay-remote/admin.env ] && [ ! -f /etc/wrelay-alexa/client-token ]; then
    info "nothing to remove - the installer's relays are not installed."
    return 0
  fi
  if [ "$YES" != "1" ]; then
    [ -n "$TTY" ] || die "--uninstall needs a terminal for the confirmation (or add --yes)."
    warn "This REMOVES the installer's test relays: services, code, secrets, pairings, users."
    info "Your production services (any unit NOT named wrelay-remote/wrelay-alexa), any existing"
    info "mail server, Docker and certbot certificates are NOT touched. Secrets are backed up first."
    ask_yesno "Really remove them? " no || { info "aborted - nothing changed."; return 0; }
  fi
  local ts bk
  ts="$(date +%Y%m%d-%H%M%S)"
  bk="/root/wrelay-uninstall-backup-$ts.tar.gz"
  tar -czf "$bk" --ignore-failed-read \
    /etc/wrelay-remote /etc/wrelay-alexa /root/.wrelay-stalwart-admin 2>/dev/null || true
  if [ -f "$bk" ]; then
    chmod 600 "$bk"
    info "secrets backed up to $bk (root-only)"
  fi
  if have systemctl; then
    systemctl stop wrelay-remote wrelay-alexa >/dev/null 2>&1 || true
    systemctl disable wrelay-remote wrelay-alexa >/dev/null 2>&1 || true
    rm -f /etc/systemd/system/wrelay-remote.service /etc/systemd/system/wrelay-alexa.service
    systemctl daemon-reload >/dev/null 2>&1 || true
    info "relay services stopped and removed"
  fi
  if [ -f /etc/apache2/sites-available/waveline.conf ] && have apache2ctl; then
    a2dissite waveline >/dev/null 2>&1 || true
    rm -f /etc/apache2/sites-available/waveline.conf
    if apache2ctl configtest >/dev/null 2>&1; then
      systemctl reload apache2 2>/dev/null || true
      info "the installer's apache site removed and apache reloaded"
    else
      warn "apache configtest failed after removing the site - check apache by hand."
    fi
  fi
  local pairs=""
  [ -f /var/lib/wrelay-remote/pairs.json ] && pairs=" (pairing data - it is inside the backup)"
  rm -rf /opt/wrelay-remote /opt/wrelay-alexa /etc/wrelay-remote /etc/wrelay-alexa /var/lib/wrelay-remote
  rm -f /root/.wrelay-stalwart-admin
  [ "$KEEP_KIT" = "1" ] || rm -rf /opt/wrelay-kit
  local u
  for u in wrelay walexa; do
    if getent passwd "$u" >/dev/null 2>&1; then
      deluser --system "$u" >/dev/null 2>&1 || userdel "$u" >/dev/null 2>&1 \
        || warn "could not remove the $u service user (harmless to keep)."
    fi
  done
  log "Uninstall complete."
  info "Removed: both installer relay services, the vhost, code, secrets, pairings$pairs, users."
  info "NOT touched: production services, any mail server, Docker, certificates."
  info "The old credentials file stays: /root/wrelay-credentials.txt (delete by hand if you want)."
  info "Re-run the installer any time for a fresh test."
}

INSTALL_DIR="${1:-${WAVELINE_INSTALL_DIR:-/opt/wrelay-kit}}"

# --- root ----------------------------------------------------------------
# WAVELINE_* overrides are forwarded explicitly: a plain sudo re-exec
# would strip the environment.
SUDO_ENV=(env WAVELINE_KIT_URL="$KIT_URL" WAVELINE_SKIP_DOCKER="$SKIP_DOCKER"
  WAVELINE_SKIP_RELAYS="$SKIP_RELAYS" WAVELINE_SKIP_APACHE="$SKIP_APACHE"
  WAVELINE_SKIP_EMAIL="$SKIP_EMAIL" WAVELINE_STALWART="$STALWART_HEADLESS"
  WAVELINE_AUTO="$AUTO" WAVELINE_DOMAIN="$DOMAIN" WAVELINE_EMAIL="$CERT_EMAIL"
  WAVELINE_UNINSTALL="$UNINSTALL" WAVELINE_YES="$YES"
  WAVELINE_SMTP_HOST="${WAVELINE_SMTP_HOST:-}" WAVELINE_SMTP_PORT="${WAVELINE_SMTP_PORT:-}"
  WAVELINE_SMTP_USER="${WAVELINE_SMTP_USER:-}" WAVELINE_SMTP_PASS="${WAVELINE_SMTP_PASS:-}"
  WAVELINE_INSTALL_DIR="$INSTALL_DIR")

if [ "$(id -u)" -ne 0 ]; then
  log "Not root - re-running this installer with sudo"
  have sudo || die "Not root and sudo is missing. Run as root, e.g.: sudo bash install.sh"
  if [ -f "$0" ] && [ "$0" != "bash" ]; then
    exec "${SUDO_ENV[@]}" bash "$0" "$@"
  fi
  # Piped (curl | bash): stdin is the script itself and already consumed,
  # so fetch a fresh copy and run that under sudo.
  TMP_SELF="$(mktemp /tmp/waveline-install-XXXXXX.sh)"
  fetch "$SELF_URL" "$TMP_SELF" \
    || die "Could not fetch the installer for sudo. Save it to a file first: curl -fsSL $SELF_URL -o install.sh && sudo bash install.sh"
  exec "${SUDO_ENV[@]}" bash "$TMP_SELF" "$@"
fi

log "Waveline self-host kit installer"
info "Kit:    $KIT_URL"
info "Target: $INSTALL_DIR"

if [ "$UNINSTALL" = "1" ]; then
  do_uninstall
  exit 0
fi
if [ -z "$TTY" ] && [ "$AUTO" != "1" ]; then
  info "No terminal found for questions - the optional setup stages will be skipped."
  info "To automate them, run again with e.g.: WAVELINE_AUTO=1 WAVELINE_DOMAIN=your-domain bash install.sh"
fi

# --- package manager -------------------------------------------------------
PM=""
if have apt-get; then PM=apt
elif have dnf; then PM=dnf
elif have yum; then PM=yum
elif have zypper; then PM=zypper
elif have pacman; then PM=pacman
fi

pkg_install() {
  [ "$#" -gt 0 ] || return 0
  case "$PM" in
    apt)    DEBIAN_FRONTEND=noninteractive apt-get -y install "$@" ;;
    dnf)    dnf -y install "$@" ;;
    yum)    yum -y install "$@" ;;
    zypper) zypper --non-interactive install "$@" ;;
    pacman) pacman -Sy --noconfirm --needed "$@" ;;
    *)      return 1 ;;
  esac
}

if [ "$PM" = "apt" ]; then
  log "Refreshing package lists (apt-get update)"
  apt-get update || warn "apt-get update failed - package installs below may fail"
fi

# --- downloader -------------------------------------------------------------
if ! have curl && ! have wget; then
  log "Installing curl (needed to download the kit)"
  pkg_install curl || die "No curl/wget available and curl could not be installed - install curl and re-run."
fi

# --- python3 -----------------------------------------------------------------
if ! have python3; then
  log "Installing python3 (runs the relays; extracts the kit)"
  pkg_install python3 || pkg_install python || die "python3 is required - install it and re-run."
fi

# --- Docker (only when missing) -----------------------------------------------
if [ "$SKIP_DOCKER" = "1" ]; then
  log "Skipping Docker (WAVELINE_SKIP_DOCKER=1)"
elif have docker; then
  log "Docker found: $(docker --version 2>/dev/null || echo docker)"
else
  log "Installing Docker (not detected; the relays do NOT need it)"
  if [ "$(uname -s)" = "Darwin" ]; then
    warn "macOS detected - install Docker Desktop manually; continuing with the kit."
  else
    GET_DOCKER_SH="$(mktemp /tmp/waveline-get-docker-XXXXXX.sh)"
    if fetch https://get.docker.com "$GET_DOCKER_SH" && sh "$GET_DOCKER_SH"; then
      info "Docker installed via get.docker.com"
    else
      warn "get.docker.com route failed - trying the distro package"
    fi
    rm -f "$GET_DOCKER_SH"
    if ! have docker; then
      case "$PM" in
        apt)               pkg_install docker.io || true ;;
        dnf)               pkg_install docker || pkg_install moby-engine || true ;;
        yum|zypper|pacman) pkg_install docker || true ;;
        *)                 : ;;
      esac
    fi
  fi
  if have docker && have systemctl; then
    systemctl enable --now docker >/dev/null 2>&1 \
      || warn "Could not enable the docker service here (container or WSL?) - start it manually later."
  fi
  have docker || warn "Docker could not be installed automatically. The relays do not need it; install it later if you want it."
fi

# docker group for the invoking user (standard docker practice)
if have docker && [ -n "${SUDO_USER:-}" ] && [ "${SUDO_USER:-root}" != "root" ]; then
  if id -nG "$SUDO_USER" 2>/dev/null | tr ' ' '\n' | grep -qx docker; then
    info "$SUDO_USER is already in the docker group."
  elif usermod -aG docker "$SUDO_USER" 2>/dev/null; then
    info "Added $SUDO_USER to the docker group - log out and back in to use docker without sudo."
  fi
fi

# --- kit ------------------------------------------------------------------------
log "Downloading the self-host kit"
TMPZIP="$(mktemp /tmp/waveline-kit-XXXXXX.zip)"
fetch "$KIT_URL" "$TMPZIP" || die "Could not download $KIT_URL - check the address or try again."
info "Downloaded $KIT_URL ($(wc -c < "$TMPZIP" | tr -d ' ') bytes)"

log "Verifying the zip and extracting to $INSTALL_DIR"
mkdir -p "$INSTALL_DIR"
if have python3; then
  python3 -m zipfile -t "$TMPZIP" >/dev/null \
    || die "The downloaded zip failed its integrity test - try again shortly."
  python3 -m zipfile -e "$TMPZIP" "$INSTALL_DIR/"
elif have unzip; then
  unzip -t "$TMPZIP" >/dev/null \
    || die "The downloaded zip failed its integrity test - try again shortly."
  unzip -o "$TMPZIP" -d "$INSTALL_DIR/" >/dev/null
else
  die "Nothing to extract with (python3 or unzip). Install one and re-run."
fi
rm -f "$TMPZIP"

for f in README.txt relay.py accounts.py relay_server.py selfhosting.html; do
  [ -f "$INSTALL_DIR/$f" ] || die "Kit incomplete: $f is missing after extraction."
done

if [ -n "${SUDO_USER:-}" ] && [ "${SUDO_USER:-root}" != "root" ]; then
  chown -R "$SUDO_USER" "$INSTALL_DIR" 2>/dev/null || true
  info "Ownership of $INSTALL_DIR given to $SUDO_USER."
fi

# --- guide section 1: the two service users --------------------------------------
sys_user_exists() { getent passwd "$1" >/dev/null 2>&1; }

create_sys_user() { # create_sys_user NAME  (locked service account, per guide)
  local u="$1" shell=""
  if sys_user_exists "$u"; then
    info "service user $u already exists"
    return 0
  fi
  for shell in /usr/sbin/nologin /sbin/nologin /bin/false; do
    if [ -x "$shell" ]; then break; fi
    shell=""
  done
  [ -n "$shell" ] || shell=/bin/false
  if have adduser; then
    adduser --system --group --home /nonexistent --shell "$shell" "$u" \
      || die "Could not create the $u service user - create it per guide section 1 and re-run."
  elif have useradd; then
    useradd --system --user-group --no-create-home --home-dir /nonexistent --shell "$shell" "$u" \
      || die "Could not create the $u service user - create it per guide section 1 and re-run."
  else
    die "Neither adduser nor useradd found - cannot create the $u service user."
  fi
  info "created service user $u (no login, no home)"
}

random_token() { # 64 hex chars, exactly the guide's generation recipe
  head -c 32 /dev/urandom | sha256sum | cut -d' ' -f1
}

# --- guide sections 2-3: both relays ---------------------------------------------
setup_relays() {
  log "Setting up the relays (guide sections 1-3)"

  log "Creating the service users"
  create_sys_user wrelay
  create_sys_user walexa

  log "Creating the directories"
  mkdir -p /opt/wrelay-remote /etc/wrelay-remote /var/lib/wrelay-remote
  mkdir -p /opt/wrelay-alexa /etc/wrelay-alexa

  log "Installing the relay code from the kit"
  install -m 0644 "$INSTALL_DIR/relay.py" /opt/wrelay-remote/relay.py \
    || die "Could not install relay.py - check disk space and permissions."
  install -m 0644 "$INSTALL_DIR/accounts.py" /opt/wrelay-remote/accounts.py \
    || die "Could not install accounts.py."
  install -m 0644 "$INSTALL_DIR/liveroom.py" /opt/wrelay-remote/liveroom.py \
    || die "Could not install liveroom.py (the relay imports it as a sibling module)."
  install -m 0644 "$INSTALL_DIR/vpsagent.py" /opt/wrelay-remote/vpsagent.py \
    || die "Could not install vpsagent.py (the relay imports it as a sibling module)."
  install -m 0644 "$INSTALL_DIR/relay_server.py" /opt/wrelay-alexa/server.py \
    || die "Could not install the Alexa relay server.py."

  log "Setting ownership and permissions"
  chown -R wrelay:wrelay /opt/wrelay-remote
  chown wrelay:wrelay /var/lib/wrelay-remote
  chown -R walexa:walexa /opt/wrelay-alexa

  # Remote relay admin key (guide 2.2) - an existing key is NEVER replaced.
  if [ -f /etc/wrelay-remote/admin.env ]; then
    info "/etc/wrelay-remote/admin.env already exists - keeping the existing admin key"
  else
    printf 'RELAY_ADMIN_KEY=%s\n' "$(random_token)" > /etc/wrelay-remote/admin.env
    NEW_ADMIN_KEY=1
  fi
  chown root:wrelay /etc/wrelay-remote/admin.env
  chmod 640 /etc/wrelay-remote/admin.env

  # Alexa relay secrets (guide 3.1) - existing secrets are NEVER replaced.
  if [ -f /etc/wrelay-alexa/shared-secret ] && [ -f /etc/wrelay-alexa/client-token ]; then
    info "/etc/wrelay-alexa secrets already exist - keeping them"
  else
    if [ ! -f /etc/wrelay-alexa/shared-secret ]; then
      random_token > /etc/wrelay-alexa/shared-secret
      NEW_SHARED=1
    fi
    if [ ! -f /etc/wrelay-alexa/client-token ]; then
      random_token > /etc/wrelay-alexa/client-token
      NEW_TOKEN=1
    fi
  fi
  chown -R root:walexa /etc/wrelay-alexa
  chmod 750 /etc/wrelay-alexa
  chmod 640 /etc/wrelay-alexa/shared-secret /etc/wrelay-alexa/client-token

  log "Writing the systemd units"
  cat > /etc/systemd/system/wrelay-remote.service <<'EOF'
[Unit]
Description=Waveline remote-access relay
After=network.target

[Service]
Type=simple
User=wrelay
Group=wrelay
ExecStart=/usr/bin/python3 /opt/wrelay-remote/relay.py
Environment=RELAY_PORT=8640
EnvironmentFile=/etc/wrelay-remote/admin.env
Environment=WAVELINE_RELAY_DB=/var/lib/wrelay-remote/pairs.json
Restart=on-failure
RestartSec=5
NoNewPrivileges=yes
ProtectSystem=strict
ProtectHome=yes
PrivateTmp=yes

[Install]
WantedBy=multi-user.target
EOF
  cat > /etc/systemd/system/wrelay-alexa.service <<'EOF'
[Unit]
Description=Waveline Alexa relay API
After=network.target

[Service]
Type=simple
User=walexa
Group=walexa
ExecStart=/usr/bin/python3 /opt/wrelay-alexa/server.py
Restart=on-failure
RestartSec=3
NoNewPrivileges=true
ProtectSystem=strict
ProtectHome=true
PrivateTmp=true
PrivateDevices=true
ReadWritePaths=

[Install]
WantedBy=multi-user.target
EOF
  chmod 644 /etc/systemd/system/wrelay-remote.service /etc/systemd/system/wrelay-alexa.service

  if have systemctl; then
    systemctl daemon-reload || true
    systemctl enable wrelay-remote wrelay-alexa >/dev/null 2>&1 \
      || warn "Could not enable the relay units - 'systemctl enable wrelay-remote wrelay-alexa' later."
    # restart (not start) so a re-run also picks up refreshed relay code
    systemctl restart wrelay-remote wrelay-alexa >/dev/null 2>&1 \
      || warn "Could not (re)start the relays - check 'journalctl -u wrelay-remote -u wrelay-alexa'."
  else
    warn "systemd is not available here - the units are written but not started."
  fi

  log "Checking the relays (live)"
  remote_ok=0
  if have curl; then
    i=0
    while [ "$i" -lt 15 ]; do
      if curl -s http://127.0.0.1:8640/healthz 2>/dev/null | grep -q '"ok"'; then remote_ok=1; break; fi
      i=$((i + 1)); sleep 1
    done
  fi
  if [ "$remote_ok" = "1" ]; then
    info "remote relay answers {\"ok\": true} on 127.0.0.1:8640/healthz"
  else
    warn "remote relay did not answer on 127.0.0.1:8640/healthz yet - check 'journalctl -u wrelay-remote' (in a container systemd may be absent; the unit is still installed)."
  fi
  alexa_ok=0
  if have curl; then
    i=0
    while [ "$i" -lt 15 ]; do
      if curl -s -X POST http://127.0.0.1:8099/command -H 'Content-Type: application/json' -d '{"token":"wrong"}' 2>/dev/null | grep -q unauthorized; then alexa_ok=1; break; fi
      i=$((i + 1)); sleep 1
    done
  fi
  if [ "$alexa_ok" = "1" ]; then
    info "Alexa relay is up and refuses wrong tokens (127.0.0.1:8099)"
  else
    warn "Alexa relay did not answer 'unauthorized' on 127.0.0.1:8099 yet - check 'journalctl -u wrelay-alexa'."
  fi
}

# --- guide section 4: Apache HTTPS proxy ------------------------------------------
setup_apache() {
  if [ -z "$DOMAIN" ]; then
    if ! ask_value "Your domain for the relays (e.g. waveline.example.com): " DOMAIN; then
      warn "No domain given - skipping the HTTPS proxy. Re-run this installer any time to configure it."
      return 0
    fi
  fi
  if ! valid_domain "$DOMAIN"; then
    warn "'$DOMAIN' does not look like a domain name (example: waveline.example.com) - skipping the HTTPS proxy."
    return 0
  fi
  if [ "$PM" != "apt" ]; then
    warn "The guide's HTTPS-proxy stage assumes Ubuntu/Debian (apache2 + certbot) - skipping here; do guide section 4 by hand."
    return 0
  fi
  if ! have apache2ctl; then
    log "Installing apache2 + certbot"
    pkg_install apache2 certbot python3-certbot-apache \
      || warn "apache2/certbot install failed - do guide section 4 by hand."
  fi
  if ! have apache2ctl; then
    warn "apache2 is still missing - skipping the proxy stage; do guide section 4 by hand."
    return 0
  fi

  log "Writing the Apache site for $DOMAIN"
  cat > /etc/apache2/sites-available/waveline.conf <<EOF
<VirtualHost *:80>
    ServerName $DOMAIN
    ProxyPreserveHost On
    ProxyPass        /remote http://127.0.0.1:8640/ retry=0
    ProxyPassReverse /remote http://127.0.0.1:8640/
    ProxyPass        /relay/ http://127.0.0.1:8099/ retry=0
    ProxyPassReverse /relay/ http://127.0.0.1:8099/
</VirtualHost>
EOF
  chmod 644 /etc/apache2/sites-available/waveline.conf
  a2enmod proxy proxy_http headers >/dev/null || warn "a2enmod failed - check the apache2 install."
  a2ensite waveline >/dev/null || warn "a2ensite failed - check the vhost file."
  if apache2ctl configtest; then
    systemctl reload apache2 2>/dev/null || systemctl restart apache2 2>/dev/null \
      || warn "Could not reload apache2 - check 'systemctl status apache2'."
    info "Apache now proxies /remote and /relay/ for $DOMAIN (plain HTTP for the moment)"
  else
    warn "apache2ctl configtest FAILED - Apache keeps running with its old config; fix /etc/apache2/sites-available/waveline.conf by hand."
    return 0
  fi

  log "HTTPS certificate (Let's Encrypt)"
  if ask_yesno "Request a Let's Encrypt certificate for $DOMAIN now (certbot)? " yes; then
    if [ -n "$TTY" ]; then
      info "certbot will ask its own questions in this terminal"
      certbot --apache -d "$DOMAIN" \
        || warn "certbot did not finish - run 'certbot --apache -d $DOMAIN' later."
    else
      if [ -n "$CERT_EMAIL" ]; then
        certbot --apache -d "$DOMAIN" --non-interactive --agree-tos --redirect -m "$CERT_EMAIL" \
          || warn "certbot did not finish - run 'certbot --apache -d $DOMAIN' later."
      else
        certbot --apache -d "$DOMAIN" --non-interactive --agree-tos --redirect --register-unsafely-without-email \
          || warn "certbot did not finish - run 'certbot --apache -d $DOMAIN' later."
      fi
    fi
  else
    info "Skipping certbot - run 'certbot --apache -d $DOMAIN' when the DNS record points here."
  fi

  info "Firewall reminder (guide section 4): only SSH + web should be open."
  if have ufw; then
    info "If you use ufw: ufw allow OpenSSH; ufw allow 'Apache Full'; then ufw enable if you want it (the installer never touches your firewall itself)."
  fi
}

# --- guide section 2.5: account e-mail --------------------------------------------
ask_secret() { # ask_secret PROMPT VARNAME (hidden input)
  local ans __var="$2"
  if [ -z "$TTY" ]; then return 1; fi
  read -r -s -p "$1" ans < "$TTY" || return 1
  printf '\n' >&2
  [ -n "$ans" ] || return 1
  printf -v "$__var" '%s' "$ans"
  return 0
}

gen_password() { # 24 hex chars, no SIGPIPE recipe
  head -c 32 /dev/urandom | sha256sum | cut -c1-24
}

port_open() { # port_open HOST PORT -> exit 0 if connectable
  (exec 3<>"/dev/tcp/$1/$2") 2>/dev/null
}

local_submission_port() { # prints 587/465 when a mail server already answers locally
  if port_open 127.0.0.1 587; then printf '587'
  elif port_open 127.0.0.1 465; then printf '465'
  else return 1
  fi
}

smtp_from_addr() { # "Name <addr>" -> addr
  printf '%s' "$1" | sed -n 's/.*<\([^>]*\)>.*/\1/p'
}

STALWART_UNIT=""
stalwart_unit() {
  if [ -n "$STALWART_UNIT" ]; then printf '%s' "$STALWART_UNIT"; return 0; fi
  if have systemctl; then
    if systemctl list-unit-files 2>/dev/null | grep -q '^stalwart\.service'; then
      STALWART_UNIT=stalwart
    elif systemctl list-unit-files 2>/dev/null | grep -q '^stalwart-mail\.service'; then
      STALWART_UNIT=stalwart-mail
    fi
  fi
  [ -n "$STALWART_UNIT" ] || STALWART_UNIT=stalwart
  printf '%s' "$STALWART_UNIT"
}

stalwart_installed() {
  [ -f /etc/systemd/system/stalwart.service ] \
    || [ -f /etc/systemd/system/stalwart-mail.service ] \
    || have stalwart || [ -x /usr/local/bin/stalwart ]
}

stalwart_restart() {
  if have systemctl; then
    systemctl restart "$(stalwart_unit)" >/dev/null 2>&1 && return 0
  fi
  warn "could not restart the stalwart service automatically - run 'systemctl restart stalwart' (or stalwart-mail) yourself."
  return 1
}

api_account() { # api_account USER PASS -> prints the /api/account JSON
  curl -s -u "$1:$2" http://127.0.0.1:8080/api/account 2>/dev/null
}

auth_ok() { # 0 if these credentials authenticate at all
  [ -n "$1" ] && [ -n "$2" ] && api_account "$1" "$2" | grep -qv '"status":401'
}

bootstrap_pending() { # 0 if the server is still in first-time-setup mode
  api_account "$1" "$2" | grep -q 'sysBootstrapUpdate'
}

find_stalwart_cli() { # sets CLI_BIN (official installer; xz needed)
  if [ -z "$CLI_BIN" ]; then
    CLI_BIN="$(command -v stalwart-cli 2>/dev/null || true)"
  fi
  if [ -z "$CLI_BIN" ]; then
    local c
    for c in /usr/local/bin/stalwart-cli /root/.cargo/bin/stalwart-cli /root/.local/bin/stalwart-cli; do
      if [ -x "$c" ]; then CLI_BIN="$c"; break; fi
    done
  fi
  if [ -z "$CLI_BIN" ]; then
    if ! have xz; then pkg_install xz-utils || pkg_install xz || true; fi
    local sh_path
    sh_path="$(mktemp /tmp/waveline-cli-XXXXXX.sh)"
    if fetch https://github.com/stalwartlabs/cli/releases/latest/download/stalwart-cli-installer.sh "$sh_path" \
         && sh "$sh_path" < /dev/null 2>&1 | tail -3; then
      local c
      for c in /usr/local/bin/stalwart-cli /root/.cargo/bin/stalwart-cli /root/.local/bin/stalwart-cli; do
        if [ -x "$c" ]; then CLI_BIN="$c"; break; fi
      done
    fi
    rm -f "$sh_path"
    if [ -n "$CLI_BIN" ] && [ "$CLI_BIN" != "/usr/local/bin/stalwart-cli" ]; then
      cp "$CLI_BIN" /usr/local/bin/stalwart-cli 2>/dev/null || true
      CLI_BIN=/usr/local/bin/stalwart-cli
    fi
  fi
}

cli() { # cli <args...>  (uses the resolved admin credential)
  "$CLI_BIN" --url http://127.0.0.1:8080 --user "$STALWART_ADMIN_USER" --password "$STALWART_ADMIN_PASS" "$@"
}

write_smtp_env() { # write_smtp_env HOST PORT USER PASS FROM TLS
  mkdir -p /etc/wrelay-remote
  [ -f /etc/wrelay-remote/admin.env ] || : > /etc/wrelay-remote/admin.env
  local tmp
  tmp="$(mktemp /tmp/wrelay-admin-XXXXXX)"
  grep -v '^WAVELINE_SMTP_' /etc/wrelay-remote/admin.env > "$tmp" || true
  {
    cat "$tmp"
    printf 'WAVELINE_SMTP_HOST=%s\n' "$1"
    printf 'WAVELINE_SMTP_PORT=%s\n' "$2"
    printf 'WAVELINE_SMTP_USER=%s\n' "$3"
    printf 'WAVELINE_SMTP_PASS=%s\n' "$4"
    printf 'WAVELINE_SMTP_FROM=%s\n' "$5"
    printf 'WAVELINE_SMTP_TLS=%s\n' "$6"
  } > /etc/wrelay-remote/admin.env
  rm -f "$tmp"
  if chown root:wrelay /etc/wrelay-remote/admin.env 2>/dev/null; then :; else
    warn "group wrelay does not exist (relay stage skipped?) - fix admin.env ownership after installing the relays."
  fi
  chmod 640 /etc/wrelay-remote/admin.env
  if [ -f /etc/systemd/system/wrelay-remote.service ] && have systemctl; then
    systemctl restart wrelay-remote >/dev/null 2>&1 \
      || warn "could not restart wrelay-remote - do it once the setup is complete."
  else
    info "restart wrelay-remote once the relay is installed to pick up the mail settings."
  fi
}

admin_env_has_smtp() {
  [ -f /etc/wrelay-remote/admin.env ] \
    && grep -q '^WAVELINE_SMTP_PASS=' /etc/wrelay-remote/admin.env
}

setup_email_provider() { # interactive external-SMTP route
  local h="" p="" u="" pa="" f="" t=""
  ask_value "SMTP host: " h || { warn "no host - skipping."; return 0; }
  ask_value "SMTP port [587]: " p || p=""
  [ -n "$p" ] || p=587
  ask_value "SMTP user (e-mail address): " u || { warn "no user - skipping."; return 0; }
  ask_secret "SMTP password: " pa || { warn "no password - skipping."; return 0; }
  ask_value "From header, e.g. Waveline <waveline@your-domain> [Waveline <$u>]: " f || f=""
  [ -n "$f" ] || f="Waveline <$u>"
  ask_value "TLS mode starttls|implicit|none [starttls]: " t || t=""
  [ -n "$t" ] || t=starttls
  MAIL_HOST="$h"; MAIL_PORT="$p"; MAIL_USER="$u"; MAIL_PASS="$pa"; MAIL_FROM="$f"; MAIL_TLS="$t"
  if admin_env_has_smtp; then
    info "/etc/wrelay-remote/admin.env already carries SMTP settings - keeping them."
    MAIL_STATUS="kept from a previous run (/etc/wrelay-remote/admin.env)"
  else
    write_smtp_env "$h" "$p" "$u" "$pa" "$f" "$t"
    NEW_SMTP_PASS=1
    MAIL_STATUS="configured (SMTP $h:$p, $t)"
  fi
}

setup_email_auto_env() { # headless provider route from WAVELINE_SMTP_* env
  MAIL_HOST="$WAVELINE_SMTP_HOST"
  MAIL_PORT="${WAVELINE_SMTP_PORT:-587}"
  MAIL_USER="$WAVELINE_SMTP_USER"
  MAIL_PASS="$WAVELINE_SMTP_PASS"
  MAIL_FROM="${WAVELINE_SMTP_FROM:-Waveline <$MAIL_USER>}"
  MAIL_TLS="${WAVELINE_SMTP_TLS:-starttls}"
  if admin_env_has_smtp; then
    info "/etc/wrelay-remote/admin.env already carries SMTP settings - keeping them."
    MAIL_STATUS="kept from a previous run (/etc/wrelay-remote/admin.env)"
  else
    write_smtp_env "$MAIL_HOST" "$MAIL_PORT" "$MAIL_USER" "$MAIL_PASS" "$MAIL_FROM" "$MAIL_TLS"
    NEW_SMTP_PASS=1
    MAIL_STATUS="configured (SMTP $MAIL_HOST:$MAIL_PORT, $MAIL_TLS)"
  fi
}

setup_email_menu() {
  local r="" lport="" def="1"
  info "Two routes (guide 2.5) - or skip if your mail already works:"
  info "  1) Stalwart mail server on THIS machine (full self-host; ~1 GB RAM, port 25)"
  info "  2) An SMTP provider you already have (fastest)"
  info "  3) Skip - keep my current mail setup, or decide later (guide 2.5)"
  lport="$(local_submission_port || true)"
  if [ -n "$lport" ]; then
    info "  (a mail server is already listening on 127.0.0.1:$lport - option 3 keeps it)"
    def=3
  fi
  ask_value "Choose 1, 2 or 3 [$def]: " r || r="$def"
  [ -n "$r" ] || r="$def"
  case "$r" in
    2) setup_email_provider ;;
    3) info "e-mail setup skipped - your current mail setup stays as it is (guide 2.5 covers the manual path)." ;;
    *) setup_email_stalwart ;;
  esac
}

setup_email_stalwart() { # the full self-host mail route (guide 2.5, Stalwart)
  local mdomain="" mhostname="" out

  if port_open gmail-smtp-in.l.google.com 25; then
    info "outbound port 25 is open - other mail servers can be reached."
  else
    warn "outbound port 25 looks BLOCKED - many VPS providers block it on new servers; account mail cannot leave until they unblock it (guide 2.5.4)."
  fi
  local hname
  hname="$(hostname 2>/dev/null || true)"
  if [ -n "$hname" ] && ! printf '%s' "$hname" | grep -q '\.'; then
    warn "hostname '$hname' is not a domain name - Stalwart rejects such EHLO greetings; fix with: hostnamectl set-hostname mail.your-domain"
  fi
  if have free; then
    local avail
    avail="$(free -m | awk '/^Mem:/{print $7}' || true)"
    if [ -n "$avail" ] && [ "${avail:-0}" -lt 900 ] 2>/dev/null; then
      warn "only ~${avail} MB RAM available - Stalwart wants about 1 GB (guide 2.5.1)."
    fi
  fi

  # which mail domain?
  if [ -n "$DOMAIN" ]; then
    mdomain="$DOMAIN"
    if [ -n "$TTY" ] && [ "$AUTO" != "1" ]; then
      ask_yesno "Use $DOMAIN as the mail domain (addresses like waveline@$DOMAIN)? " yes \
        || { ask_value "Mail domain: " mdomain || { warn "no mail domain - skipping."; return 0; }; }
    fi
  elif [ -n "$TTY" ]; then
    ask_value "Mail domain for account e-mail (e.g. example.com): " mdomain \
      || { warn "no mail domain given - skipping the e-mail stage."; return 0; }
  else
    warn "no domain known (set WAVELINE_DOMAIN) - skipping the e-mail stage."
    return 0
  fi
  valid_domain "$mdomain" || { warn "'$mdomain' does not look like a domain name - skipping."; return 0; }
  mhostname="mail.$mdomain"

  log "Installing Stalwart (guide 2.5.1)"
  if stalwart_installed; then
    info "Stalwart is already installed - skipping the download."
  else
    STALWART_SH="$(mktemp /tmp/waveline-stalwart-XXXXXX.sh)"
    if fetch https://get.stalw.art/install.sh "$STALWART_SH" \
         && sh "$STALWART_SH" < /dev/null 2>&1 | tee /tmp/waveline-stalwart-install.log; then
      info "Stalwart installed."
    else
      warn "the Stalwart installer failed - see the output above; do guide section 2.5.1 by hand."
      return 0
    fi
    rm -f "$STALWART_SH"
    if have systemctl; then
      systemctl enable --now "$(stalwart_unit)" >/dev/null 2>&1 \
        || warn "could not enable the stalwart service - check 'systemctl list-units | grep -i stalwart'."
    fi
    info "The installer's own highlights (the one-time admin password is there):"
    grep -iE 'password|admin|http' /tmp/waveline-stalwart-install.log 2>/dev/null \
      | tail -6 | sed 's/^/    /' || true
  fi

  log "Installing the stalwart-cli tool (drives the mail server's JMAP API)"
  find_stalwart_cli
  if [ -z "$CLI_BIN" ]; then
    warn "stalwart-cli could not be installed - create the account per guide 2.5.3, or re-run this installer."
    return 0
  fi
  info "stalwart-cli: $CLI_BIN ($("$CLI_BIN" --version 2>/dev/null || echo '?'))"

  log "Resolving the Stalwart admin credential"
  STALWART_ADMIN_USER=""; STALWART_ADMIN_PASS=""
  if [ -f "$STALWART_ADMIN_STATE" ]; then
    # shellcheck disable=SC1090
    . "$STALWART_ADMIN_STATE"
    STALWART_ADMIN_USER="${STALWART_ADMIN_USER:-}"
    STALWART_ADMIN_PASS="${STALWART_ADMIN_PASS:-}"
    auth_ok "$STALWART_ADMIN_USER" "$STALWART_ADMIN_PASS" \
      && info "using the saved admin credential ($STALWART_ADMIN_USER)."
  fi
  if ! auth_ok "$STALWART_ADMIN_USER" "$STALWART_ADMIN_PASS"; then
    STALWART_ADMIN_USER=""; STALWART_ADMIN_PASS=""
    local pinval=""
    pinval="$(grep '^STALWART_RECOVERY_ADMIN=' /etc/stalwart/stalwart.env 2>/dev/null | head -1 | cut -d= -f2- || true)"
    if [ -n "$pinval" ]; then
      # value is "user:password" - split on the FIRST colon only
      STALWART_ADMIN_USER="${pinval%%:*}"
      STALWART_ADMIN_PASS="${pinval#*:}"
      info "using the pinned admin credential ($STALWART_ADMIN_USER)."
    else
      pinval="$(gen_password)"
      mkdir -p /etc/stalwart
      touch /etc/stalwart/stalwart.env
      printf 'STALWART_RECOVERY_ADMIN=admin:%s\n' "$pinval" >> /etc/stalwart/stalwart.env
      chmod 640 /etc/stalwart/stalwart.env
      STALWART_ADMIN_USER="admin"; STALWART_ADMIN_PASS="$pinval"
      info "pinned a generated admin credential for the first-time setup."
      stalwart_restart || true
      sleep 3
    fi
    auth_ok "$STALWART_ADMIN_USER" "$STALWART_ADMIN_PASS" || {
      STALWART_ADMIN_USER=""; STALWART_ADMIN_PASS=""
      warn "the pinned admin credential did not authenticate."
    }
  fi
  if [ -z "$STALWART_ADMIN_USER" ] && [ -n "$TTY" ]; then
    ask_value "Stalwart web-admin user [admin]: " STALWART_ADMIN_USER || STALWART_ADMIN_USER="admin"
    [ -n "$STALWART_ADMIN_USER" ] || STALWART_ADMIN_USER="admin"
    ask_secret "Stalwart web-admin password: " STALWART_ADMIN_PASS || true
    auth_ok "$STALWART_ADMIN_USER" "$STALWART_ADMIN_PASS" || {
      warn "those credentials did not authenticate - finish the web setup per guide 2.5 and re-run this installer."
      return 0
    }
  fi
  [ -n "$STALWART_ADMIN_USER" ] || {
    warn "no admin credential available - re-run this installer with a terminal, or after the web setup."
    return 0
  }

  if bootstrap_pending "$STALWART_ADMIN_USER" "$STALWART_ADMIN_PASS"; then
    log "Stalwart first-time setup (one-time)"
    if ask_yesno "Finish the first-time setup for you now (hostname $mhostname, mail domain $mdomain, TLS + DKIM on)? " yes; then
      out="$(cli update bootstrap \
              --field "serverHostname=$mhostname" \
              --field "defaultDomain=$mdomain" 2>&1)" || {
        warn "the automated first-time setup failed:"
        printf '%s\n' "$out" | sed 's/^/    /'
        warn "do it in the web admin instead (steps below), then re-run this installer."
      }
      if printf '%s' "$out" | grep -q 'username:'; then
        STALWART_ADMIN_USER="$(printf '%s' "$out" | sed -n 's/.*username:[[:space:]]*"\([^"]*\)".*/\1/p')"
        STALWART_ADMIN_PASS="$(printf '%s' "$out" | sed -n 's/.*secret:[[:space:]]*"\([^"]*\)".*/\1/p')"
        NEW_STALWART_ADMIN=1
        STALWART_ADMIN_USER_OUT="$STALWART_ADMIN_USER"
        STALWART_ADMIN_PASS_OUT="$STALWART_ADMIN_PASS"
        info "the mail server's admin login is now: $STALWART_ADMIN_USER (password saved to the credentials file)."
        umask 077
        printf 'STALWART_ADMIN_USER=%s\nSTALWART_ADMIN_PASS=%s\n' \
          "$STALWART_ADMIN_USER" "$STALWART_ADMIN_PASS" > "$STALWART_ADMIN_STATE"
        umask 022
      fi
      log "Restarting stalwart to apply the first-time setup"
      stalwart_restart || true
      local i
      for i in 1 2 3 4 5 6; do
        port_open 127.0.0.1 8080 && break
        sleep 2
      done
      sleep 2
      auth_ok "$STALWART_ADMIN_USER" "$STALWART_ADMIN_PASS" \
        || warn "the new admin credential did not authenticate yet (a container without systemd cannot restart mid-run - re-running this installer completes the setup)."
    else
      log "YOUR PART - the one-time web setup"
      info "  1. From your PC open a tunnel:  ssh -L 8080:127.0.0.1:8080 you@this-server"
      info "  2. Browse:  http://localhost:8080/admin"
      if [ -n "$STALWART_ADMIN_PASS" ]; then
        info "     (admin login: $STALWART_ADMIN_USER  /  password: $STALWART_ADMIN_PASS)"
      else
        info "     (the one-time admin password was printed above / is in the service logs)"
      fi
      info "  3. Set the server hostname to $mhostname and the mail domain to $mdomain."
      info "  4. Leave 'obtain TLS certificate' and 'generate DKIM keys' ON."
      info "  5. DNS (any time): publish the MX/SPF/DKIM/DMARC records the admin"
      info "     shows, and set reverse DNS (PTR) to $mhostname at your VPS provider."
      if [ -n "$TTY" ]; then
        read -r -p "Press ENTER when the web setup is finished... " _ < "$TTY" || true
      fi
    fi
  else
    info "Stalwart is already set up - skipping the first-time setup."
  fi

  if ! auth_ok "$STALWART_ADMIN_USER" "$STALWART_ADMIN_PASS"; then
    if [ -n "$TTY" ]; then
      ask_value "Stalwart web-admin user [admin]: " STALWART_ADMIN_USER || STALWART_ADMIN_USER="admin"
      [ -n "$STALWART_ADMIN_USER" ] || STALWART_ADMIN_USER="admin"
      ask_secret "Stalwart web-admin password: " STALWART_ADMIN_PASS || true
      auth_ok "$STALWART_ADMIN_USER" "$STALWART_ADMIN_PASS" || {
        warn "no working admin credential - re-run this installer after the web setup."
        return 0
      }
    else
      warn "no working admin credential - re-running this installer completes the remaining steps."
      return 0
    fi
  fi
  if bootstrap_pending "$STALWART_ADMIN_USER" "$STALWART_ADMIN_PASS"; then
    warn "the server is still in first-time-setup mode - finish it, then re-run to create the account."
    return 0
  fi

  log "Creating the sending account (waveline)"
  local smtp_pass="" did=""
  if cli query Account --fields id,name --json 2>/dev/null | grep -q '"waveline"'; then
    info "account 'waveline' already exists - keeping it."
  else
    smtp_pass="$(gen_password)"
    did="$(cli query Domain --fields id,name --json 2>/dev/null \
            | grep -F "\"$mdomain\"" | grep -oE '"id":"[^"]+"' | head -1 | cut -d'"' -f4 || true)"
    out="$(cli create account/user \
            --field name=waveline \
            --field "domainId=$did" \
            --field "credentials={\"0\":{\"@type\":\"Password\",\"secret\":\"$smtp_pass\"}}" 2>&1)" \
      && { printf '%s\n' "$out"; NEW_SMTP_PASS=1; } \
      || { printf '%s\n' "$out" 2>/dev/null | sed 's/^/    /' || true;
           out="$(cli create account/user \
                   --field name=waveline \
                   --field "credentials={\"0\":{\"@type\":\"Password\",\"secret\":\"$smtp_pass\"}}" 2>&1)" \
             && { printf '%s\n' "$out"; NEW_SMTP_PASS=1; } \
             || warn "account creation failed - create 'waveline@$mdomain' per guide 2.5.3, then re-run for the wiring."; }
  fi
  if [ "$NEW_SMTP_PASS" = "1" ]; then
    MAIL_HOST="127.0.0.1"
    MAIL_USER="waveline@$mdomain"
    MAIL_PASS="$smtp_pass"
    MAIL_FROM="Waveline <waveline@$mdomain>"
  fi

  log "Detecting the mail server's submission port"
  MAIL_HOST="127.0.0.1"
  if port_open 127.0.0.1 587; then
    MAIL_PORT=587; MAIL_TLS=starttls
  elif port_open 127.0.0.1 465; then
    MAIL_PORT=465; MAIL_TLS=implicit
  else
    MAIL_PORT=465; MAIL_TLS=implicit
    warn "no submission port (587/465) is open yet - wiring the standard settings; sends stay fail-quiet (logged, never fatal) until it is."
  fi

  if admin_env_has_smtp; then
    info "/etc/wrelay-remote/admin.env already carries SMTP settings - keeping them."
    MAIL_STATUS="kept from a previous run (/etc/wrelay-remote/admin.env)"
  elif [ -n "$MAIL_PASS" ]; then
    write_smtp_env "$MAIL_HOST" "$MAIL_PORT" "$MAIL_USER" "$MAIL_PASS" "$MAIL_FROM" "$MAIL_TLS"
    MAIL_STATUS="configured (SMTP $MAIL_HOST:$MAIL_PORT, $MAIL_TLS)"
    info "until the TLS certificate is issued (DNS must point here), sends fail quietly - the relay's fail-safe design."
  else
    warn "the waveline account existed but its password is unknown - set WAVELINE_SMTP_PASS in /etc/wrelay-remote/admin.env by hand, or reset the account in the web admin."
  fi

  if ask_yesno "Send a test e-mail now (installs swaks)? " no; then
    local to="" from_addr="" tls_args=""
    ask_value "Send the test to which address? " to || to=""
    if [ -n "$to" ]; then
      if ! have swaks; then pkg_install swaks || warn "could not install swaks - test skipped."; fi
      if have swaks; then
        from_addr="$(smtp_from_addr "$MAIL_FROM")"
        [ -n "$from_addr" ] || from_addr="$MAIL_USER"
        case "$MAIL_TLS" in
          implicit) tls_args="-tlsc" ;;
          none)     tls_args="" ;;
          *)        tls_args="--tls" ;;
        esac
        # shellcheck disable=SC2086
        if swaks --to "$to" --from "$from_addr" --server "$MAIL_HOST" --port "$MAIL_PORT" \
              $tls_args --helo "$mhostname" \
              --auth-user "$MAIL_USER" --auth-password "$MAIL_PASS" --timeout 40; then
          info "test send succeeded - check the inbox (and the spam folder)."
        else
          warn "the test send failed - see the port-25 probe above, 'journalctl -u wrelay-remote', and guide 2.5.4."
        fi
      fi
    fi
  fi
}
if [ "$SKIP_RELAYS" = "1" ]; then
  log "Skipping the relay setup stage (WAVELINE_SKIP_RELAYS=1)"
elif [ -z "$TTY" ] && [ "$AUTO" != "1" ]; then
  log "Skipping the relay setup stage (no terminal to ask questions)"
  info "Re-run with WAVELINE_AUTO=1 to accept the defaults non-interactively."
elif ask_yesno "Set up the two relays now (service users, admin key, secrets, systemd services - guide sections 1-3)? " yes; then
  setup_relays
else
  info "Relay setup skipped - follow guide sections 1-3 by hand ($INSTALL_DIR/selfhosting.html)."
fi

if [ "$SKIP_APACHE" = "1" ]; then
  log "Skipping the Apache/HTTPS stage (WAVELINE_SKIP_APACHE=1)"
elif [ -z "$TTY" ] && [ "$AUTO" != "1" ] && [ -z "$DOMAIN" ]; then
  log "Skipping the Apache/HTTPS stage (no terminal to ask questions)"
  info "Re-run with WAVELINE_DOMAIN=your-domain to configure it non-interactively."
elif ask_yesno "Configure the Apache HTTPS proxy now (guide section 4)? " yes; then
  setup_apache
else
  info "Apache/HTTPS skipped - re-run this installer later, or follow guide section 4 by hand."
fi

if [ "$SKIP_EMAIL" = "1" ]; then
  log "Skipping the e-mail stage (WAVELINE_SKIP_EMAIL=1)"
elif admin_env_has_smtp; then
  log "E-mail stage: /etc/wrelay-remote/admin.env already carries mail settings - keeping them"
  MAIL_STATUS="kept from a previous run (/etc/wrelay-remote/admin.env)"
elif [ -n "${WAVELINE_SMTP_HOST:-}" ] && [ -n "${WAVELINE_SMTP_USER:-}" ] && [ -n "${WAVELINE_SMTP_PASS:-}" ]; then
  log "E-mail stage: configuring the SMTP provider from environment variables"
  setup_email_auto_env
elif [ "$AUTO" = "1" ] && [ "$STALWART_HEADLESS" = "1" ]; then
  log "E-mail stage: Stalwart setup (headless, WAVELINE_STALWART=1)"
  setup_email_stalwart
elif [ -z "$TTY" ] && [ "$AUTO" != "1" ]; then
  log "Skipping the e-mail stage (no terminal to ask questions)"
  info "Re-run with WAVELINE_AUTO=1 WAVELINE_STALWART=1 WAVELINE_DOMAIN=your-domain for a headless setup."
elif ask_yesno "Set up account e-mail now (guide section 2.5 - Stalwart on this machine or your own SMTP)? " yes; then
  setup_email_menu
else
  info "E-mail skipped - account mail stays fail-quiet until a mail server is wired (guide 2.5)."
fi

# --- credentials: shown once, saved root-only --------------------------------------
if [ "$NEW_ADMIN_KEY" = "1" ] || [ "$NEW_TOKEN" = "1" ] || [ "$NEW_SHARED" = "1" ] \
   || [ "$NEW_SMTP_PASS" = "1" ] || [ "$NEW_STALWART_ADMIN" = "1" ]; then
  umask 077
  {
    echo "Waveline self-host credentials - generated $(date -u '+%Y-%m-%d %H:%M UTC')"
    echo
    if [ "$NEW_ADMIN_KEY" = "1" ]; then
      echo "Admin key (unlocks https://your-domain/remote/admin):"
      sed 's/^/  /' /etc/wrelay-remote/admin.env
      echo
    fi
    if [ "$NEW_TOKEN" = "1" ]; then
      echo "Alexa client token (paste into Waveline's Alexa settings):"
      sed 's/^/  /' /etc/wrelay-alexa/client-token
      echo
    fi
    if [ "$NEW_SHARED" = "1" ]; then
      echo "Alexa shared secret (Alexa/admin side of the relay):"
      sed 's/^/  /' /etc/wrelay-alexa/shared-secret
      echo
    fi
    if [ "$NEW_STALWART_ADMIN" = "1" ]; then
      echo "Stalwart web-admin login (your mail server's admin):"
      echo "  user: $STALWART_ADMIN_USER_OUT"
      echo "  password: $STALWART_ADMIN_PASS_OUT"
      echo
    fi
    if [ "$NEW_SMTP_PASS" = "1" ]; then
      echo "Sending account (the relay uses it to send account e-mail):"
      echo "  user: ${MAIL_USER:-waveline@<your-domain>}"
      echo "  password: ${MAIL_PASS:-<see /etc/wrelay-remote/admin.env>}"
      echo
    fi
    echo "Re-run the installer any time; these secrets are never regenerated."
  } > "$CRED_FILE"
  chmod 600 "$CRED_FILE"
  umask 022
fi

log "Done - the Waveline self-host kit is in $INSTALL_DIR"
ls -1 "$INSTALL_DIR" | sed 's/^/    /'

if [ -n "$DOMAIN" ]; then
  RELAY_URL="https://$DOMAIN/remote"
  ALEXA_URL="https://$DOMAIN/relay"
else
  RELAY_URL="https://your-domain/remote"
  ALEXA_URL="https://your-domain/relay"
fi

echo
echo "============================================================"
echo " Waveline self-hosting - summary"
echo "============================================================"
if [ "$NEW_ADMIN_KEY" = "1" ]; then
  echo " Admin key (shown once; also saved to $CRED_FILE):"
  sed 's/^/   /' /etc/wrelay-remote/admin.env
elif [ -f /etc/wrelay-remote/admin.env ]; then
  echo " Admin key: kept - read it with: cat /etc/wrelay-remote/admin.env"
fi
if [ "$NEW_TOKEN" = "1" ]; then
  echo " Alexa client token (shown once; also saved to $CRED_FILE):"
  sed 's/^/   /' /etc/wrelay-alexa/client-token
elif [ -f /etc/wrelay-alexa/client-token ]; then
  echo " Alexa client token: kept - read it with: cat /etc/wrelay-alexa/client-token"
fi
if [ "$NEW_SMTP_PASS" = "1" ] && [ -n "$MAIL_PASS" ]; then
  echo " Sending-account password (shown once; also saved to $CRED_FILE):"
  echo "   ${MAIL_USER:-waveline}: $MAIL_PASS"
fi
cat <<SUMMARY

 Point Waveline at your relays:
   - Remote access URL:  $RELAY_URL
   - Alexa relay URL:    $ALEXA_URL   (+ the client token above)

 Next steps:
   1. If you skipped a stage, re-run this installer - re-running is
      safe and only asks about the pieces that are still missing.
   2. Account e-mail: $MAIL_STATUS
   3. Test from your PC:  curl $RELAY_URL/healthz
      should say {"ok": true}.
   4. The full guide: $INSTALL_DIR/selfhosting.html

 Notes:
   - The relays run as plain systemd services on python3 - no Docker
     needed. Docker (if it was just installed) is for anything else
     you host on this machine.
   - Re-running refreshes the kit and the relay code and restarts the
     services; your admin key, Alexa secrets and pairings are kept.
SUMMARY
