#!/usr/bin/env bash
set -Eeuo pipefail

DOWNLOAD_URL="https://download.sentralogic.id/filesystem-shell-relay-20260721-170522.tar.gz"
TMP_ARCHIVE="/tmp/filesystem-shell-relay-20260721-170522.tar.gz"
INSTALL_DIR="/opt/filesystem-shell-relay"
APP_DLL="$INSTALL_DIR/FileSystemShellMcp.Relay.Server.dll"
APP_PORT="5300"
SERVICE_USER="filesystem-shell"
PROGRAM="scryfer-relay"
SUPERVISOR_CONF="/etc/supervisor/conf.d/$PROGRAM.conf"
LOG_DIR="/var/log/$PROGRAM"

TRAEFIK_VERSION="${TRAEFIK_VERSION:-3.7.1}"
TRAEFIK_USER="traefik"
TRAEFIK_GROUP="traefik"
TRAEFIK_BINARY="/usr/local/bin/traefik"
TRAEFIK_CONFIG_DIR="/etc/traefik"
TRAEFIK_DYNAMIC_DIR="$TRAEFIK_CONFIG_DIR/dynamic"
TRAEFIK_STATIC_CONFIG="$TRAEFIK_CONFIG_DIR/traefik.yml"
TRAEFIK_ACME_DIR="$TRAEFIK_CONFIG_DIR/acme"
TRAEFIK_ACME_FILE="$TRAEFIK_ACME_DIR/acme.json"
TRAEFIK_SYSTEMD_UNIT="/etc/systemd/system/traefik.service"
TRAEFIK_LOG_DIR="/var/log/traefik"
TRAEFIK_HTTP_PORT="80"
TRAEFIK_HTTPS_PORT="443"
ACME_RESOLVER="letsencrypt"
ACME_EMAIL=""

info() { printf '\n[INFO] %s\n' "$*"; }
warn() { printf '\n[WARN] %s\n' "$*" >&2; }
die()  { printf '\n[ERROR] %s\n' "$*" >&2; exit 1; }

confirm() {
  local answer
  read -r -p "$1 [y/N]: " answer
  case "$answer" in y|Y|yes|YES|Yes) return 0 ;; *) return 1 ;; esac
}

must_confirm() { confirm "$1" || die "Cancelled by user."; }

run_step() {
  local prompt="$1"
  shift
  must_confirm "$prompt"
  "$@"
}

backup_file() {
  local source_file="$1"
  if [[ -e "$source_file" || -L "$source_file" ]]; then
    local backup_file_name="${source_file}.backup-$(date +%Y%m%d-%H%M%S)"
    run_step "Back up $source_file to $backup_file_name?" cp -a "$source_file" "$backup_file_name"
  fi
}

normalize_domain() {
  local domain="$1"
  domain="${domain,,}"
  domain="${domain#http://}"
  domain="${domain#https://}"
  domain="${domain%%/*}"
  domain="${domain%.}"
  printf '%s' "$domain"
}

validate_domain() {
  [[ "$1" =~ ^[a-z0-9]([a-z0-9.-]*[a-z0-9])?\.[a-z]{2,}$ ]]
}

validate_email() {
  [[ "$1" =~ ^[A-Za-z0-9.!#$%\'*+/=?^_\`\{\|\}~-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}$ ]]
}

get_architecture() {
  case "$(uname -m)" in
    x86_64|amd64) printf 'amd64' ;;
    aarch64|arm64) printf 'arm64' ;;
    armv7l|armv7) printf 'armv7' ;;
    armv6l|armv6) printf 'armv6' ;;
    i386|i486|i586|i686) printf '386' ;;
    *) die "Unsupported CPU architecture: $(uname -m)" ;;
  esac
}

download_file() {
  local url="$1" destination="$2"
  rm -f "$destination"
  if command -v curl >/dev/null 2>&1; then
    curl -fL --retry 3 --retry-delay 2 --connect-timeout 20 -o "$destination" "$url"
  elif command -v wget >/dev/null 2>&1; then
    wget --tries=3 --timeout=20 -O "$destination" "$url"
  else
    die "Neither curl nor wget is installed."
  fi
}

[[ $EUID -eq 0 ]] || die "Run as root: sudo $0"

cat <<EOF
============================================================
 Scryfer Relay Installer with Traefik
============================================================
Archive          : $DOWNLOAD_URL
Application      : $INSTALL_DIR
Application port : $APP_PORT
Traefik version  : $TRAEFIK_VERSION
HTTP port        : $TRAEFIK_HTTP_PORT
HTTPS port       : $TRAEFIK_HTTPS_PORT

Every installation and configuration action requires approval.
============================================================
EOF

must_confirm "Begin installation checks?"

if command -v apt-get >/dev/null 2>&1; then
  PM="apt"
elif command -v dnf >/dev/null 2>&1; then
  PM="dnf"
elif command -v yum >/dev/null 2>&1; then
  PM="yum"
else
  die "Supported package manager not found: apt, dnf, or yum."
fi

install_packages() {
  if [[ "$PM" == "apt" ]]; then
    run_step "Refresh APT package metadata?" apt-get update
    run_step "Install packages: $* ?" apt-get install -y "$@"
  elif [[ "$PM" == "dnf" ]]; then
    run_step "Install packages: $* ?" dnf install -y "$@"
  else
    run_step "Install packages: $* ?" yum install -y "$@"
  fi
}

if ! command -v curl >/dev/null 2>&1 && ! command -v wget >/dev/null 2>&1; then
  install_packages curl
fi
command -v tar >/dev/null 2>&1 || install_packages tar gzip
command -v sha256sum >/dev/null 2>&1 || install_packages coreutils
command -v supervisorctl >/dev/null 2>&1 || install_packages supervisor

if command -v systemctl >/dev/null 2>&1; then
  run_step "Enable Supervisor at boot?" systemctl enable supervisor
  run_step "Start or restart Supervisor?" systemctl restart supervisor
fi

if ! command -v dotnet >/dev/null 2>&1; then
  must_confirm "Install ASP.NET Core runtime 8.0 from the configured repositories?"
  if [[ "$PM" == "apt" ]]; then
    apt-get install -y aspnetcore-runtime-8.0
  elif [[ "$PM" == "dnf" ]]; then
    dnf install -y aspnetcore-runtime-8.0
  else
    yum install -y aspnetcore-runtime-8.0
  fi
fi

must_confirm "Download the relay archive?"
download_file "$DOWNLOAD_URL" "$TMP_ARCHIVE"
run_step "Validate the downloaded application archive?" tar -tzf "$TMP_ARCHIVE"

if ! id "$SERVICE_USER" >/dev/null 2>&1; then
  must_confirm "Create restricted system user $SERVICE_USER?"
  useradd --system --no-create-home --shell /usr/sbin/nologin "$SERVICE_USER"
fi

if [[ -d "$INSTALL_DIR" ]]; then
  BACKUP="$INSTALL_DIR.backup-$(date +%Y%m%d-%H%M%S)"
  run_step "Move existing installation to $BACKUP?" mv "$INSTALL_DIR" "$BACKUP"
fi

run_step "Extract the application into /opt?" tar -xzf "$TMP_ARCHIVE" -C /opt
[[ -f "$APP_DLL" ]] || die "Application DLL not found: $APP_DLL"
run_step "Set application ownership to $SERVICE_USER?" chown -R "$SERVICE_USER:$SERVICE_USER" "$INSTALL_DIR"

must_confirm "Set application file permissions?"
find "$INSTALL_DIR" -type d -exec chmod 755 {} \;
find "$INSTALL_DIR" -type f -exec chmod 644 {} \;
[[ -f "$INSTALL_DIR/FileSystemShellMcp.Relay.Server" ]] && chmod 755 "$INSTALL_DIR/FileSystemShellMcp.Relay.Server"

must_confirm "Create Supervisor log directory $LOG_DIR?"
mkdir -p "$LOG_DIR"
chown "$SERVICE_USER:$SERVICE_USER" "$LOG_DIR"
chmod 750 "$LOG_DIR"

backup_file "$SUPERVISOR_CONF"
must_confirm "Write Supervisor configuration $SUPERVISOR_CONF?"
cat > "$SUPERVISOR_CONF" <<EOF
[program:$PROGRAM]
command=/usr/bin/dotnet $APP_DLL
directory=$INSTALL_DIR
user=$SERVICE_USER
environment=ASPNETCORE_ENVIRONMENT="Production",ASPNETCORE_URLS="http://127.0.0.1:$APP_PORT"
autostart=true
autorestart=true
startsecs=5
startretries=5
stopsignal=TERM
stopwaitsecs=30
stopasgroup=true
killasgroup=true
stdout_logfile=$LOG_DIR/out.log
stderr_logfile=$LOG_DIR/err.log
stdout_logfile_maxbytes=10MB
stderr_logfile_maxbytes=10MB
stdout_logfile_backups=5
stderr_logfile_backups=5
EOF

run_step "Run supervisorctl reread?" supervisorctl reread
run_step "Run supervisorctl update?" supervisorctl update
if supervisorctl status "$PROGRAM" 2>/dev/null | grep -q RUNNING; then
  run_step "Restart $PROGRAM?" supervisorctl restart "$PROGRAM"
else
  run_step "Start $PROGRAM?" supervisorctl start "$PROGRAM"
fi
run_step "Display Supervisor status?" supervisorctl status "$PROGRAM"

for web_service in nginx apache2 httpd; do
  if systemctl list-unit-files "$web_service.service" >/dev/null 2>&1 && systemctl is-active --quiet "$web_service"; then
    warn "$web_service is running and may occupy ports 80 or 443."
    if confirm "Stop and disable $web_service so Traefik can use ports 80 and 443?"; then
      systemctl stop "$web_service"
      systemctl disable "$web_service"
    else
      die "Traefik requires ports 80 and 443."
    fi
  fi
done

TRAEFIK_ARCH="$(get_architecture)"
TRAEFIK_ARCHIVE_NAME="traefik_v${TRAEFIK_VERSION}_linux_${TRAEFIK_ARCH}.tar.gz"
TRAEFIK_ARCHIVE="/tmp/$TRAEFIK_ARCHIVE_NAME"
TRAEFIK_CHECKSUMS="/tmp/traefik_v${TRAEFIK_VERSION}_checksums.txt"
TRAEFIK_RELEASE_BASE_URL="https://github.com/traefik/traefik/releases/download/v${TRAEFIK_VERSION}"

CURRENT_TRAEFIK_VERSION=""
if [[ -x "$TRAEFIK_BINARY" ]]; then
  CURRENT_TRAEFIK_VERSION="$("$TRAEFIK_BINARY" version 2>/dev/null | awk '/Version:/ {print $2; exit}' || true)"
fi

if [[ "$CURRENT_TRAEFIK_VERSION" != "$TRAEFIK_VERSION" ]]; then
  must_confirm "Download Traefik v$TRAEFIK_VERSION for linux/$TRAEFIK_ARCH?"
  download_file "$TRAEFIK_RELEASE_BASE_URL/$TRAEFIK_ARCHIVE_NAME" "$TRAEFIK_ARCHIVE"
  download_file "$TRAEFIK_RELEASE_BASE_URL/traefik_v${TRAEFIK_VERSION}_checksums.txt" "$TRAEFIK_CHECKSUMS"

  must_confirm "Verify the Traefik archive checksum?"
  EXPECTED_CHECKSUM="$(awk -v file="$TRAEFIK_ARCHIVE_NAME" '$2 == file || $2 == "*" file {print $1; exit}' "$TRAEFIK_CHECKSUMS")"
  [[ -n "$EXPECTED_CHECKSUM" ]] || die "Checksum entry not found for $TRAEFIK_ARCHIVE_NAME."
  ACTUAL_CHECKSUM="$(sha256sum "$TRAEFIK_ARCHIVE" | awk '{print $1}')"
  [[ "$ACTUAL_CHECKSUM" == "$EXPECTED_CHECKSUM" ]] || die "Traefik checksum validation failed."

  TRAEFIK_EXTRACT_DIR="$(mktemp -d /tmp/traefik-install.XXXXXX)"
  tar -xzf "$TRAEFIK_ARCHIVE" -C "$TRAEFIK_EXTRACT_DIR"
  [[ -f "$TRAEFIK_EXTRACT_DIR/traefik" ]] || die "Traefik binary not found in archive."
  [[ -e "$TRAEFIK_BINARY" ]] && backup_file "$TRAEFIK_BINARY"
  run_step "Install Traefik to $TRAEFIK_BINARY?" install -m 0755 "$TRAEFIK_EXTRACT_DIR/traefik" "$TRAEFIK_BINARY"
  rm -rf "$TRAEFIK_EXTRACT_DIR"
fi

if ! getent group "$TRAEFIK_GROUP" >/dev/null 2>&1; then
  must_confirm "Create system group $TRAEFIK_GROUP?"
  groupadd --system "$TRAEFIK_GROUP"
fi

if ! id "$TRAEFIK_USER" >/dev/null 2>&1; then
  must_confirm "Create restricted Traefik system user?"
  useradd --system --gid "$TRAEFIK_GROUP" --home-dir "$TRAEFIK_CONFIG_DIR" --no-create-home --shell /usr/sbin/nologin "$TRAEFIK_USER"
fi

must_confirm "Create Traefik configuration and log directories?"
mkdir -p "$TRAEFIK_DYNAMIC_DIR" "$TRAEFIK_ACME_DIR" "$TRAEFIK_LOG_DIR"
chown root:"$TRAEFIK_GROUP" "$TRAEFIK_CONFIG_DIR" "$TRAEFIK_DYNAMIC_DIR"
chown "$TRAEFIK_USER:$TRAEFIK_GROUP" "$TRAEFIK_ACME_DIR" "$TRAEFIK_LOG_DIR"
chmod 750 "$TRAEFIK_CONFIG_DIR" "$TRAEFIK_DYNAMIC_DIR" "$TRAEFIK_LOG_DIR"
chmod 700 "$TRAEFIK_ACME_DIR"
touch "$TRAEFIK_ACME_FILE"
chown "$TRAEFIK_USER:$TRAEFIK_GROUP" "$TRAEFIK_ACME_FILE"
chmod 600 "$TRAEFIK_ACME_FILE"

printf '\nEnter an email address for Let\'s Encrypt notices.\n'
while true; do
  read -r -p "ACME email: " ACME_EMAIL
  ACME_EMAIL="${ACME_EMAIL//[[:space:]]/}"
  validate_email "$ACME_EMAIL" && break
  warn "Invalid email address: $ACME_EMAIL"
done

backup_file "$TRAEFIK_STATIC_CONFIG"
must_confirm "Write Traefik static configuration $TRAEFIK_STATIC_CONFIG?"
cat > "$TRAEFIK_STATIC_CONFIG" <<EOF
global:
  checkNewVersion: false
  sendAnonymousUsage: false

entryPoints:
  web:
    address: ":$TRAEFIK_HTTP_PORT"
    http:
      redirections:
        entryPoint:
          to: websecure
          scheme: https
          permanent: true

  websecure:
    address: ":$TRAEFIK_HTTPS_PORT"
    transport:
      respondingTimeouts:
        readTimeout: 3600s
        writeTimeout: 3600s
        idleTimeout: 3600s

providers:
  file:
    directory: "$TRAEFIK_DYNAMIC_DIR"
    watch: true

certificatesResolvers:
  $ACME_RESOLVER:
    acme:
      email: "$ACME_EMAIL"
      storage: "$TRAEFIK_ACME_FILE"
      httpChallenge:
        entryPoint: web

api:
  dashboard: false

log:
  level: INFO
  filePath: "$TRAEFIK_LOG_DIR/traefik.log"

accessLog:
  filePath: "$TRAEFIK_LOG_DIR/access.log"
EOF
chown root:"$TRAEFIK_GROUP" "$TRAEFIK_STATIC_CONFIG"
chmod 640 "$TRAEFIK_STATIC_CONFIG"

backup_file "$TRAEFIK_SYSTEMD_UNIT"
must_confirm "Write Traefik systemd service $TRAEFIK_SYSTEMD_UNIT?"
cat > "$TRAEFIK_SYSTEMD_UNIT" <<EOF
[Unit]
Description=Traefik Edge Router
After=network-online.target
Wants=network-online.target

[Service]
Type=notify
User=$TRAEFIK_USER
Group=$TRAEFIK_GROUP
ExecStart=$TRAEFIK_BINARY --configFile=$TRAEFIK_STATIC_CONFIG
Restart=always
RestartSec=5s
TimeoutStopSec=30s
AmbientCapabilities=CAP_NET_BIND_SERVICE
CapabilityBoundingSet=CAP_NET_BIND_SERVICE
NoNewPrivileges=true
PrivateTmp=true
ProtectSystem=strict
ProtectHome=true
ReadOnlyPaths=$TRAEFIK_CONFIG_DIR
ReadWritePaths=$TRAEFIK_ACME_DIR $TRAEFIK_LOG_DIR
LimitNOFILE=1048576

[Install]
WantedBy=multi-user.target
EOF

run_step "Validate the Traefik static configuration?" "$TRAEFIK_BINARY" check-config --configFile="$TRAEFIK_STATIC_CONFIG"
run_step "Reload systemd configuration?" systemctl daemon-reload
run_step "Enable Traefik at boot?" systemctl enable traefik
run_step "Start or restart Traefik?" systemctl restart traefik

sleep 2
systemctl is-active --quiet traefik || {
  systemctl status traefik --no-pager || true
  journalctl -u traefik -n 100 --no-pager || true
  die "Traefik failed to start."
}

printf '\nEnter the base domain, for example example.com.\n'
read -r -p "Base domain: " BASE_DOMAIN
BASE_DOMAIN="$(normalize_domain "$BASE_DOMAIN")"
validate_domain "$BASE_DOMAIN" || die "Invalid base domain: $BASE_DOMAIN"
DOMAIN="scryfer.$BASE_DOMAIN"
printf '\nProposed hostname: %s\n' "$DOMAIN"
must_confirm "Use $DOMAIN?"

TRAEFIK_ROUTE_FILE="$TRAEFIK_DYNAMIC_DIR/scryfer-relay.yml"
backup_file "$TRAEFIK_ROUTE_FILE"
must_confirm "Write Traefik route configuration for $DOMAIN?"
cat > "$TRAEFIK_ROUTE_FILE" <<EOF
http:
  routers:
    scryfer-relay:
      rule: "Host(\`$DOMAIN\`)"
      entryPoints:
        - websecure
      service: scryfer-relay
      tls:
        certResolver: $ACME_RESOLVER

  services:
    scryfer-relay:
      loadBalancer:
        passHostHeader: true
        servers:
          - url: "http://127.0.0.1:$APP_PORT"
EOF
chown root:"$TRAEFIK_GROUP" "$TRAEFIK_ROUTE_FILE"
chmod 640 "$TRAEFIK_ROUTE_FILE"

must_confirm "Check DNS resolution for $DOMAIN?"
if command -v getent >/dev/null 2>&1; then
  getent ahosts "$DOMAIN" || warn "DNS does not resolve yet for $DOMAIN."
elif command -v host >/dev/null 2>&1; then
  host "$DOMAIN" || warn "DNS does not resolve yet for $DOMAIN."
else
  warn "No DNS lookup utility is available."
fi

if command -v ufw >/dev/null 2>&1 && ufw status 2>/dev/null | grep -q '^Status: active'; then
  if confirm "Allow HTTP and HTTPS through UFW?"; then
    ufw allow 80/tcp
    ufw allow 443/tcp
  fi
fi

if confirm "Run final validation checks?"; then
  supervisorctl status "$PROGRAM" || true
  systemctl status traefik --no-pager || true
  command -v ss >/dev/null 2>&1 && ss -ltnp | grep -E ":($APP_PORT|80|443)[[:space:]]" || true
  curl -I --max-time 15 "http://127.0.0.1:$APP_PORT" || true
  curl -I --max-time 20 "http://$DOMAIN" || true
  curl -I --max-time 30 "https://$DOMAIN" || true
  journalctl -u traefik -n 50 --no-pager || true
fi

cat <<EOF

============================================================
Installation workflow completed.
Domain          : https://$DOMAIN
Application     : $APP_DLL
Supervisor      : $PROGRAM
Traefik config  : $TRAEFIK_STATIC_CONFIG
Traefik route   : $TRAEFIK_ROUTE_FILE
Logs            : $LOG_DIR and $TRAEFIK_LOG_DIR
============================================================
EOF
