#!/usr/bin/env bash
# AETHER NODE INSTALLER
#
# What you do:
#   1. Download this file.
#   2. Run it with sudo.
#   3. Answer two questions (or skip them with flags).
#
# What it does for you:
#   - Detects your OS and installs Python, Caddy, git, curl, firewalld.
#   - Pulls the latest AETHER node source.
#   - Generates a node id from your domain.
#   - Writes .env, systemd unit, Caddyfile.
#   - Opens the OS firewall for 80/443.
#   - Starts everything. Acquires a real TLS cert from Let's Encrypt.
#   - Registers your node with the mesh — it appears on the network
#     automatically through peer gossip within ~30 seconds.
#
# Hands-free flags (skip the prompts):
#   sudo ./aether-node-install --domain my.example.com --token <mesh-token>
#
# Note about cloud firewalls: this script opens the *operating system*
# firewall. If you're on AWS/Azure/Oracle/GCP, you also have to open
# ports 80 and 443 in the cloud provider's security group / VCN. The
# script reminds you of this at the end if HTTPS doesn't come up.

set -euo pipefail

DOMAIN=""
TOKEN=""
BOOTSTRAP="https://aether-us.ai-ministries.com"
SOURCE_URL="https://aether-east.ai-ministries.com/install/aether-node.tar.gz"
NODE_ID=""
SERVE_CLIENTS=""   # y/n   (default y when interactive)
STORE_SHARDS=""    # y/n   (default y when interactive)
REGION=""          # smart-routing label, e.g. "EU-West", "US-East"
CITY=""            # smart-routing label, e.g. "Lauterbourg, DE"

while [ $# -gt 0 ]; do
  case "$1" in
    --domain)    DOMAIN="$2";    shift 2;;
    --token)     TOKEN="$2";     shift 2;;
    --bootstrap) BOOTSTRAP="$2"; shift 2;;
    --id)        NODE_ID="$2";   shift 2;;
    --source)    SOURCE_URL="$2";shift 2;;
    --region)    REGION="$2";    shift 2;;
    --city)      CITY="$2";      shift 2;;
    --no-clients) SERVE_CLIENTS=n; shift;;
    --no-storage) STORE_SHARDS=n;  shift;;
    --serve-clients) SERVE_CLIENTS=y; shift;;
    --store-shards)  STORE_SHARDS=y;  shift;;
    -h|--help)
      sed -n '1,28p' "$0" | sed 's/^# //;s/^#//'
      exit 0;;
    *) echo "unknown arg: $1" >&2; exit 1;;
  esac
done

if [ "$EUID" -ne 0 ]; then
  echo "This installer needs root. Run with: sudo $0"
  exit 1
fi

# --- prompt for anything not provided as a flag ----------------------------
echo ""
echo "  ╔══════════════════════════════════════════╗"
echo "  ║         AETHER NODE INSTALLER             ║"
echo "  ╚══════════════════════════════════════════╝"
echo ""

if [ -z "$DOMAIN" ]; then
  echo "  Your node needs a public DNS name pointing at this server's IP."
  echo "  Example: aether-myname.example.com"
  read -rp "  Domain: " DOMAIN
fi
[ -n "$DOMAIN" ] || { echo "domain required"; exit 1; }

if [ -z "$TOKEN" ]; then
  echo ""
  echo "  Mesh token (paste — input hidden):"
  read -rsp "  Token: " TOKEN
  echo ""
fi
[ -n "$TOKEN" ] || { echo "token required"; exit 1; }

if [ -z "$NODE_ID" ]; then
  NODE_ID="$(echo "$DOMAIN" | sed -E 's/\.[^.]+\.[^.]+$//;s/[^a-zA-Z0-9-]/-/g')"
fi

# Two role prompts. The same binary becomes:
#   y/y → full access node (serves chat clients AND holds shards)
#   y/n → router (serves clients, never stores)
#   n/y → storage holder (holds shards, no client API)
#   n/n → refused at startup (nothing to do)
if [ -z "$SERVE_CLIENTS" ]; then
  echo ""
  echo "  Serve clients on this box? (chat API for the AETHER app)"
  read -rp "  [Y/n]: " ans
  case "${ans:-y}" in n|N) SERVE_CLIENTS=n;; *) SERVE_CLIENTS=y;; esac
fi
if [ -z "$STORE_SHARDS" ]; then
  echo ""
  echo "  Store encrypted shards on this box? (helps the network keep beings alive)"
  read -rp "  [Y/n]: " ans
  case "${ans:-y}" in n|N) STORE_SHARDS=n;; *) STORE_SHARDS=y;; esac
fi
if [ "$SERVE_CLIENTS" = "n" ] && [ "$STORE_SHARDS" = "n" ]; then
  echo ""
  echo "  ✗ A node that serves no clients AND stores no shards has nothing to do."
  echo "    Re-run and answer Y to at least one."
  exit 1
fi

# Smart-routing labels — used by the client picker so users can sort the
# server list by region/city. Optional; blank just means "no location shown".
if [ -z "$REGION" ]; then
  echo ""
  echo "  Region label for the client server picker (optional)."
  echo "  Examples: US-East, US-West, EU-West, EU-Central, Asia, Oceania"
  read -rp "  Region [blank to skip]: " REGION
fi
if [ -z "$CITY" ]; then
  echo ""
  echo "  City/datacenter label for the picker (optional)."
  echo "  Example: Lauterbourg, DE   /   Ashburn, VA   /   Sydney, AU"
  read -rp "  City [blank to skip]: " CITY
fi

# Translate answers to systemd ExecStart fragments
ACCEPT_FLAG="public,clients,mesh"
[ "$SERVE_CLIENTS" = "n" ] && ACCEPT_FLAG="public,mesh"
STORAGE_FLAG=""
[ "$STORE_SHARDS"  = "n" ] && STORAGE_FLAG="--no-storage"

# Friendly role label for the summary
if   [ "$SERVE_CLIENTS" = "y" ] && [ "$STORE_SHARDS" = "y" ]; then ROLE_LABEL="full access node"
elif [ "$SERVE_CLIENTS" = "y" ] && [ "$STORE_SHARDS" = "n" ]; then ROLE_LABEL="router (serves clients, no storage)"
else                                                                ROLE_LABEL="storage holder (no client API)"
fi

echo ""
echo "  domain:    $DOMAIN"
echo "  node id:   $NODE_ID"
echo "  role:      $ROLE_LABEL"
echo "  bootstrap: $BOOTSTRAP"
echo ""
echo "  ─── AETHER — DISCLAIMER OF WARRANTY ──────────────────────────────"
echo "  AETHER is provided AS IS, without warranty. The authors and"
echo "  AI-Ministries.com bear no liability for damage to your system,"
echo "  data, network, or hardware — including disk wear from storage-"
echo "  holder operations, network bandwidth use, or interruption of"
echo "  other services on the same machine. You assume all risk for"
echo "  content you create, host, or publish through the mesh, and for"
echo "  compliance with the laws of your jurisdiction."
echo "  ──────────────────────────────────────────────────────────────────"
read -rp "  Type 'y' to accept and continue, anything else to abort: " ACCEPT_DISCLAIMER
case "$ACCEPT_DISCLAIMER" in
  y|Y|yes|YES) ;;
  *) echo "  ✗ Disclaimer not accepted. Nothing was changed."; exit 1;;
esac
echo ""
echo "  Starting in 3 seconds. Ctrl-C to abort."
sleep 3

# --- detect package manager + install OS packages --------------------------
if   command -v dnf     >/dev/null 2>&1; then PM=dnf
elif command -v apt-get >/dev/null 2>&1; then PM=apt
else echo "[!] Unsupported OS — need dnf (RHEL/Oracle) or apt (Debian/Ubuntu)."; exit 1
fi

echo ""
echo "  [1/7] installing system packages…"
case "$PM" in
  dnf)
    dnf install -y python3.11 python3.11-pip git tar curl firewalld policycoreutils \
      >/dev/null 2>&1 || dnf install -y python3.11 python3.11-pip git tar curl firewalld policycoreutils
    if ! command -v caddy >/dev/null 2>&1; then
      ARCH="$(uname -m)"; case "$ARCH" in aarch64) CADDY_ARCH=arm64;; x86_64) CADDY_ARCH=amd64;; *) CADDY_ARCH=$ARCH;; esac
      curl -fsSL "https://github.com/caddyserver/caddy/releases/download/v2.8.4/caddy_2.8.4_linux_${CADDY_ARCH}.tar.gz" \
        | tar -xz -C /usr/local/bin caddy
      chmod +x /usr/local/bin/caddy
    fi
    ;;
  apt)
    export DEBIAN_FRONTEND=noninteractive
    apt-get update -qq
    apt-get install -y -qq python3.11 python3.11-venv python3-pip git curl ca-certificates
    if ! command -v caddy >/dev/null 2>&1; then
      ARCH="$(dpkg --print-architecture)"
      curl -fsSL "https://github.com/caddyserver/caddy/releases/download/v2.8.4/caddy_2.8.4_linux_${ARCH}.tar.gz" \
        | tar -xz -C /usr/local/bin caddy
      chmod +x /usr/local/bin/caddy
    fi
    ;;
esac

# --- caddy systemd unit (we install Caddy as a binary, so we need our own) -
if [ ! -f /etc/systemd/system/caddy.service ]; then
  cat > /etc/systemd/system/caddy.service <<'EOF'
[Unit]
Description=Caddy reverse proxy (AETHER auto-HTTPS)
After=network.target

[Service]
ExecStart=/usr/local/bin/caddy run --config /etc/caddy/Caddyfile
ExecReload=/usr/local/bin/caddy reload --config /etc/caddy/Caddyfile
TimeoutStopSec=5s
LimitNOFILE=1048576
PrivateTmp=true
ProtectSystem=full
AmbientCapabilities=CAP_NET_BIND_SERVICE
Restart=on-failure

[Install]
WantedBy=multi-user.target
EOF
fi

echo "  [2/7] pulling AETHER node source (with checksum verify)…"
mkdir -p /opt /opt/aether/data
curl -fsSL "$SOURCE_URL" -o /tmp/aether-node.tar.gz
# Verify SHA256 against the published manifest. If aether-east is ever
# compromised the attacker would have to ALSO replace the checksum file —
# a separate write on the same host, but still raises the bar from "just
# replace the tarball" to "replace both atomically." Future: detached
# signature on a separate host for true supply-chain integrity.
EXPECTED_SHA="$(curl -fsSL "${SOURCE_URL}.sha256" 2>/dev/null | tr -d ' \r\n' || true)"
if [ -n "$EXPECTED_SHA" ]; then
  ACTUAL_SHA="$(sha256sum /tmp/aether-node.tar.gz | cut -d' ' -f1)"
  if [ "$EXPECTED_SHA" != "$ACTUAL_SHA" ]; then
    rm -f /tmp/aether-node.tar.gz
    echo "[fatal] checksum mismatch on source tarball — refusing to install."
    echo "        expected: $EXPECTED_SHA"
    echo "        got:      $ACTUAL_SHA"
    exit 1
  fi
  echo "    ✓ sha256 verified: $ACTUAL_SHA"
else
  echo "    ⚠ no checksum published at ${SOURCE_URL}.sha256 — proceeding without verify"
fi
# Extract with --no-same-owner and into a temp dir first so a malicious
# tarball can't write outside its intended path. After extract, move into
# place. The tarball is created from /opt/aether so the top-level dir is
# 'aether/' — anything else triggers the refusal below.
TMPX="$(mktemp -d)"
trap 'rm -rf "$TMPX"' EXIT
tar --no-same-owner --no-same-permissions -xzf /tmp/aether-node.tar.gz -C "$TMPX"
if [ ! -d "$TMPX/aether" ]; then
  echo "[fatal] tarball doesn't contain expected 'aether/' top dir — refusing."
  exit 1
fi
cp -a "$TMPX/aether/." /opt/aether/
rm -f /tmp/aether-node.tar.gz
chown -R root:root /opt/aether

echo "  [3/7] python venv + deps…"
python3.11 -m venv /opt/aether/venv
/opt/aether/venv/bin/pip install --quiet --upgrade pip
/opt/aether/venv/bin/pip install --quiet cryptography psutil

echo "  [4/7] writing /opt/aether/.env…"
# Default moderation endpoint is the AI-Ministries-hosted network service.
# OpenAI's moderations API is free, so this costs the host nothing per call —
# the token is only here as a soft anti-spam gate. Operators running their
# own private moderation service can override MODERATION_URL + MODERATION_TOKEN
# in /opt/aether/.env before first start.
# AETHER_REGION / AETHER_CITY are smart-routing labels shown to clients in
# the server picker — blank values just mean "no location shown".
cat > /opt/aether/.env <<EOF
MESH_TOKEN=$TOKEN
AETHER_PEER_NODES=$BOOTSTRAP
MODERATION_URL=https://aether-us.ai-ministries.com/_moderate/moderate
MODERATION_TOKEN=__MODERATION_TOKEN_PLACEHOLDER__
AETHER_REGION=$REGION
AETHER_CITY=$CITY
EOF
chmod 600 /opt/aether/.env

echo "  [5/7] writing systemd unit…"
cat > /etc/systemd/system/aether-node.service <<EOF
[Unit]
Description=AETHER mesh node
After=network.target

[Service]
EnvironmentFile=/opt/aether/.env
WorkingDirectory=/opt/aether
ExecStart=/opt/aether/venv/bin/python /opt/aether/node_server.py \\
  --host 127.0.0.1 --port 9494 \\
  --id $NODE_ID --role docker \\
  --caps files,network,image \\
  --accept $ACCEPT_FLAG \\
  --public-url https://$DOMAIN \\
  --bootstrap $BOOTSTRAP $STORAGE_FLAG
Restart=on-failure
User=root

[Install]
WantedBy=multi-user.target
EOF

command -v restorecon >/dev/null 2>&1 && restorecon -R /opt/aether 2>/dev/null || true
command -v setsebool   >/dev/null 2>&1 && setsebool -P httpd_can_network_connect 1 2>/dev/null || true

echo "  [6/7] writing Caddyfile (auto-HTTPS via Let's Encrypt)…"
mkdir -p /etc/caddy /var/lib/caddy
cat > /etc/caddy/Caddyfile <<EOF
$DOMAIN {
    reverse_proxy 127.0.0.1:9494
}
EOF

if command -v firewall-cmd >/dev/null 2>&1; then
  systemctl enable --now firewalld >/dev/null 2>&1 || true
  firewall-cmd --permanent --add-service=http  >/dev/null 2>&1 || true
  firewall-cmd --permanent --add-service=https >/dev/null 2>&1 || true
  firewall-cmd --reload >/dev/null 2>&1 || true
fi

echo "  [7/7] starting services…"
systemctl daemon-reload
systemctl enable --now aether-node caddy >/dev/null 2>&1 || true

# Wait for Caddy to acquire cert + Aether to register
sleep 8
LOCAL_OK="$(curl -sS -m 5 http://127.0.0.1:9494/health 2>/dev/null || echo FAIL)"
PUBLIC_OK="$(curl -sS -m 20 https://$DOMAIN/health 2>/dev/null || echo FAIL)"

echo ""
echo "  ════════════════════════════════════════════════"
echo "    loopback /health : $LOCAL_OK"
echo "    public   /health : $PUBLIC_OK"
echo "  ════════════════════════════════════════════════"

if [ "$PUBLIC_OK" = "ok" ]; then
  echo ""
  echo "  ✓ NODE IS LIVE at https://$DOMAIN"
  echo ""
  echo "  Your node has joined the mesh. Peer gossip will propagate it"
  echo "  across the network within ~30 seconds. You don't need to do"
  echo "  anything else. The systemd service will restart automatically"
  echo "  on reboot or failure."
  echo ""
  echo "  Useful commands:"
  echo "    systemctl status aether-node"
  echo "    journalctl -u aether-node -f"
elif [ "$LOCAL_OK" = "ok" ]; then
  echo ""
  echo "  ⚠ Node is running locally but public HTTPS isn't reachable."
  echo "    Most likely cause: your CLOUD firewall blocks 80/443."
  echo "    Open ports 80 and 443 in your cloud provider's security"
  echo "    group / VCN / network ACL, then retry:"
  echo "      curl https://$DOMAIN/health"
else
  echo ""
  echo "  ✗ Node failed to start. Check logs:"
  echo "    journalctl -u aether-node -n 50 --no-pager"
fi
