#!/usr/bin/env sh
set -eu

cd /

INSTALL_DIR="${FORGE_HOME:-/opt/forgecloud}"
APP_DIR="${FORGE_APP_DIR:-$INSTALL_DIR}"
DATA_DIR="$APP_DIR/data"
INSTALL_MODE="${FORGE_INSTALL_MODE:-image}"
REPO_URL="${FORGE_REPO_URL:-https://github.com/adeyomilawal/forge.git}"
REPO_BRANCH="${FORGE_REPO_BRANCH:-master}"
FORGE_IMAGE="${FORGE_IMAGE:-ghcr.io/adeyomilawal/forge:latest}"
RAILPACK_VERSION="${RAILPACK_VERSION:-0.30.0}"
AUTO_UPDATE_ENABLED="${FORGE_AUTO_UPDATE_ENABLED:-false}"
AUTO_UPDATE_INTERVAL_MINUTES="${FORGE_AUTO_UPDATE_INTERVAL_MINUTES:-60}"
PORT="${FORGE_PORT:-4310}"
EMAIL_ENABLED="${FORGE_EMAIL_ENABLED:-false}"
EMAIL_INBOUND_ENABLED="${FORGE_EMAIL_INBOUND_ENABLED:-false}"
WEBSITE_HOSTNAMES="${FORGE_WEBSITE_HOSTNAMES:-}"
PRODUCT_DOMAIN="${FORGE_PRODUCT_DOMAIN:-}"
ADMIN_HOSTNAME="${FORGE_ADMIN_HOSTNAME:-}"
if [ -n "${FORGE_WEBSITE_STATIC_ENABLED:-}" ]; then
  WEBSITE_STATIC_ENABLED="$FORGE_WEBSITE_STATIC_ENABLED"
elif [ -n "$WEBSITE_HOSTNAMES$PRODUCT_DOMAIN" ]; then
  WEBSITE_STATIC_ENABLED="true"
else
  WEBSITE_STATIC_ENABLED="false"
fi
if [ -n "${FORGE_WEBSITE_AUTO_UPDATE_ENABLED:-}" ]; then
  WEBSITE_AUTO_UPDATE_ENABLED="$FORGE_WEBSITE_AUTO_UPDATE_ENABLED"
elif [ "$WEBSITE_STATIC_ENABLED" = "true" ]; then
  WEBSITE_AUTO_UPDATE_ENABLED="true"
else
  WEBSITE_AUTO_UPDATE_ENABLED="false"
fi
WEBSITE_AUTO_UPDATE_INTERVAL_MINUTES="${FORGE_WEBSITE_AUTO_UPDATE_INTERVAL_MINUTES:-30}"
WEBSITE_UPDATE_ROOT_DIR="${FORGE_WEBSITE_UPDATE_ROOT_DIR:-website}"

if [ "$(id -u)" -eq 0 ]; then
  SUDO=""
else
  if ! command -v sudo >/dev/null 2>&1; then
    echo "sudo is required when installing as a non-root user."
    exit 1
  fi
  SUDO="sudo"
fi

say() {
  printf '%s\n' "$*"
}

fail() {
  say "Error: $*"
  exit 1
}

normalize_install_mode() {
  INSTALL_MODE="$(printf '%s' "$INSTALL_MODE" | tr '[:upper:]' '[:lower:]')"
  case "$INSTALL_MODE" in
    image|docker|docker-image)
      INSTALL_MODE="image"
      ;;
    git|source)
      INSTALL_MODE="git"
      ;;
    *)
      fail "FORGE_INSTALL_MODE must be image or git."
      ;;
  esac
}

is_image_mode() {
  [ "$INSTALL_MODE" = "image" ]
}

require_linux() {
  [ "$(uname -s)" = "Linux" ] || fail "Forge's VPS installer currently supports Linux hosts."

  if [ -r /etc/os-release ]; then
    # shellcheck disable=SC1091
    . /etc/os-release
    case "${ID:-}" in
      ubuntu|debian) ;;
      *)
        say "Warning: this installer is tuned for Ubuntu/Debian. Continuing on ${PRETTY_NAME:-unknown Linux}."
        ;;
    esac
  fi
}

require_apt() {
  command -v apt-get >/dev/null 2>&1 || fail "apt-get was not found. The installer currently supports Ubuntu/Debian hosts."
}

install_base_packages() {
  require_apt
  say "Installing base packages..."
  $SUDO apt-get update
  packages="ca-certificates curl git openssl"
  if ! is_image_mode; then
    packages="$packages build-essential python3"
  fi
  $SUDO apt-get install -y $packages
}

node_major_version() {
  node -p "process.versions.node.split('.')[0]" 2>/dev/null || printf '0\n'
}

install_node() {
  if command -v node >/dev/null 2>&1 && [ "$(node_major_version)" -ge 22 ]; then
    return
  fi

  say "Installing Node.js 22..."
  curl -fsSL https://deb.nodesource.com/setup_22.x | $SUDO bash -
  $SUDO apt-get install -y nodejs
}

install_docker() {
  if command -v docker >/dev/null 2>&1; then
    if command -v systemctl >/dev/null 2>&1; then
      $SUDO systemctl enable --now docker >/dev/null 2>&1 || true
    fi
    return
  fi

  say "Installing Docker..."
  $SUDO apt-get install -y docker.io
  if command -v systemctl >/dev/null 2>&1; then
    $SUDO systemctl enable --now docker >/dev/null 2>&1 || true
  fi
}

install_compose_package() {
  for package_name in docker-compose-plugin docker-compose-v2; do
    if apt-cache show "$package_name" >/dev/null 2>&1; then
      $SUDO apt-get install -y "$package_name"
      return
    fi
  done

  fail "Docker Compose v2 was not found in apt. Enable Ubuntu universe or Docker's apt repository, then rerun this installer."
}

require_compose() {
  if $SUDO docker compose version >/dev/null 2>&1; then
    return
  fi

  say "Installing Docker Compose plugin..."
  install_compose_package

  $SUDO docker compose version >/dev/null 2>&1 || fail "Docker Compose plugin is still unavailable."
}

docker_bin() {
  command -v docker 2>/dev/null || fail "docker was not found after installation."
}

install_railpack() {
  if command -v railpack >/dev/null 2>&1; then
    return
  fi

  say "Installing Railpack..."
  curl -fsSL https://railpack.com/install.sh | $SUDO env RAILPACK_VERSION="$RAILPACK_VERSION" bash -s -- --bin-dir /usr/local/bin
}

random_secret() {
  if command -v openssl >/dev/null 2>&1; then
    openssl rand -base64 32 | tr '+/' '-_' | tr -d '='
    return
  fi
  dd if=/dev/urandom bs=32 count=1 2>/dev/null | od -An -tx1 | tr -d ' \n'
}

detect_public_url() {
  if [ -n "${FORGE_PUBLIC_URL:-}" ]; then
    printf '%s\n' "$FORGE_PUBLIC_URL"
    return
  fi

  public_ip=""
  if command -v curl >/dev/null 2>&1; then
    public_ip="$(curl -fsSL --max-time 4 https://api.ipify.org 2>/dev/null || true)"
  fi
  if [ -z "$public_ip" ] && command -v hostname >/dev/null 2>&1; then
    public_ip="$(hostname -I 2>/dev/null | awk '{print $1}' || true)"
  fi
  if [ -z "$public_ip" ]; then
    public_ip="localhost"
  fi

  printf 'http://%s:%s\n' "$public_ip" "$PORT"
}

get_env_value() {
  env_file="$1"
  key="$2"
  [ -f "$env_file" ] || return 0
  value="$(grep "^$key=" "$env_file" 2>/dev/null | tail -n 1 | cut -d= -f2- || true)"
  printf '%s' "$value" | sed "s/^[\"']//;s/[\"']$//"
}

prepare_install_dir() {
  if [ -e "$INSTALL_DIR" ] && [ ! -d "$INSTALL_DIR" ]; then
    fail "$INSTALL_DIR exists and is not a directory."
  fi

  if [ ! -e "$INSTALL_DIR" ]; then
    say "Creating $INSTALL_DIR..."
    $SUDO mkdir -p "$INSTALL_DIR"
    if [ -n "$SUDO" ]; then
      $SUDO chown "$(id -u):$(id -g)" "$INSTALL_DIR"
    fi
  fi
}

clone_or_update_repo() {
  prepare_install_dir

  if [ -d "$INSTALL_DIR/.git" ]; then
    say "Updating Forge source..."
    git -C "$INSTALL_DIR" fetch origin "$REPO_BRANCH"
    git -C "$INSTALL_DIR" checkout "$REPO_BRANCH"
    git -C "$INSTALL_DIR" pull --ff-only origin "$REPO_BRANCH"
  else
    if [ -n "$(ls -A "$INSTALL_DIR" 2>/dev/null)" ]; then
      fail "$INSTALL_DIR exists but is not an empty directory or Git checkout. Move it aside and rerun the installer."
    fi

    say "Cloning Forge..."
    git clone --branch "$REPO_BRANCH" --single-branch "$REPO_URL" "$INSTALL_DIR"
  fi

  if [ ! -f "$APP_DIR/package.json" ] && [ -f "$INSTALL_DIR/deploy/package.json" ]; then
    APP_DIR="$INSTALL_DIR/deploy"
    DATA_DIR="$APP_DIR/data"
  fi

  [ -f "$APP_DIR/package.json" ] || fail "$APP_DIR/package.json was not found after clone. Check FORGE_REPO_URL, FORGE_APP_DIR, and FORGE_REPO_BRANCH."
  mkdir -p "$DATA_DIR"
}

prepare_image_install() {
  prepare_install_dir
  APP_DIR="$INSTALL_DIR"
  DATA_DIR="$APP_DIR/data"
  mkdir -p "$DATA_DIR"
}

write_env_file() {
  env_file="$APP_DIR/.env"
  secret_key="$(get_env_value "$env_file" FORGE_SECRET_KEY)"
  operator_bootstrap_token="$(get_env_value "$env_file" FORGE_OPERATOR_BOOTSTRAP_TOKEN)"
  public_url="$(get_env_value "$env_file" PUBLIC_URL)"
  control_plane_hostname="$(get_env_value "$env_file" CONTROL_PLANE_HOSTNAME)"
  admin_hostname="$(get_env_value "$env_file" FORGE_ADMIN_HOSTNAME)"
  website_hostnames="$(get_env_value "$env_file" FORGE_WEBSITE_HOSTNAMES)"
  product_domain="$(get_env_value "$env_file" FORGE_PRODUCT_DOMAIN)"
  forge_image="$(get_env_value "$env_file" FORGE_IMAGE)"
  auto_update_enabled="$(get_env_value "$env_file" FORGE_AUTO_UPDATE_ENABLED)"
  auto_update_interval_minutes="$(get_env_value "$env_file" FORGE_AUTO_UPDATE_INTERVAL_MINUTES)"
  website_static_enabled="$(get_env_value "$env_file" FORGE_WEBSITE_STATIC_ENABLED)"
  website_auto_update_enabled="$(get_env_value "$env_file" FORGE_WEBSITE_AUTO_UPDATE_ENABLED)"
  website_auto_update_interval_minutes="$(get_env_value "$env_file" FORGE_WEBSITE_AUTO_UPDATE_INTERVAL_MINUTES)"
  website_update_root_dir="$(get_env_value "$env_file" FORGE_WEBSITE_UPDATE_ROOT_DIR)"

  if [ -z "$secret_key" ]; then
    secret_key="$(random_secret)"
  fi
  if [ -z "$operator_bootstrap_token" ]; then
    operator_bootstrap_token="$(random_secret)"
  fi
  if [ -z "$public_url" ]; then
    public_url="$(detect_public_url)"
  fi
  if [ -z "$website_hostnames" ]; then
    website_hostnames="$WEBSITE_HOSTNAMES"
  fi
  if [ -z "$product_domain" ]; then
    product_domain="$PRODUCT_DOMAIN"
  fi
  if [ -z "$admin_hostname" ]; then
    admin_hostname="$ADMIN_HOSTNAME"
  fi
  if [ -z "$admin_hostname" ] && [ -n "$product_domain" ]; then
    admin_hostname="admin.$product_domain"
  fi
  if [ -z "$forge_image" ]; then
    forge_image="$FORGE_IMAGE"
  fi
  if [ -z "$auto_update_enabled" ]; then
    auto_update_enabled="$AUTO_UPDATE_ENABLED"
  fi
  if [ -z "$auto_update_interval_minutes" ]; then
    auto_update_interval_minutes="$AUTO_UPDATE_INTERVAL_MINUTES"
  fi
  if [ -z "$website_static_enabled" ]; then
    website_static_enabled="$WEBSITE_STATIC_ENABLED"
  fi
  if [ -z "$website_auto_update_enabled" ]; then
    website_auto_update_enabled="$WEBSITE_AUTO_UPDATE_ENABLED"
  fi
  if [ -z "$website_auto_update_interval_minutes" ]; then
    website_auto_update_interval_minutes="$WEBSITE_AUTO_UPDATE_INTERVAL_MINUTES"
  fi
  if [ -z "$website_update_root_dir" ]; then
    website_update_root_dir="$WEBSITE_UPDATE_ROOT_DIR"
  fi

  tmp_file="$(mktemp)"
  if [ -f "$env_file" ]; then
    grep -v -E '^(FORGE_INSTALL_MODE|FORGE_INSTALL_DIR|FORGE_ENV_PATH|FORGE_REPO_URL|FORGE_REPO_BRANCH|FORGE_IMAGE|FORGE_IMAGE_UPDATE_CMD|FORGE_COMMIT_SHA|FORGE_COMPOSE_FILE|FORGE_UPDATE_REPO_URL|FORGE_UPDATE_BRANCH|FORGE_UPDATE_RESTART_CMD|FORGE_AUTO_UPDATE_ENABLED|FORGE_AUTO_UPDATE_INTERVAL_MINUTES|FORGE_WEBSITE_STATIC_ENABLED|FORGE_WEBSITE_SERVE_DIR|FORGE_WEBSITE_CADDY_SERVE_DIR|FORGE_WEBSITE_UPDATE_REPO_URL|FORGE_WEBSITE_UPDATE_BRANCH|FORGE_WEBSITE_UPDATE_ROOT_DIR|FORGE_WEBSITE_AUTO_UPDATE_ENABLED|FORGE_WEBSITE_AUTO_UPDATE_INTERVAL_MINUTES|FORGE_SECRET_KEY|FORGE_OPERATOR_BOOTSTRAP_TOKEN|DATA_DIR|DEPLOY_DRY_RUN|CADDY_CONFIG_PATH|CADDY_DATA_DIR|CADDY_RELOAD_CMD|PORT|HOST|HOME|DOCKER_CONFIG|PUBLIC_URL|CONTROL_PLANE_HOSTNAME|FORGE_ADMIN_HOSTNAME|FORGE_WEBSITE_HOSTNAMES|FORGE_PRODUCT_DOMAIN|BUILDKIT_HOST|FORGE_RUNTIME_NETWORK|FORGE_EMAIL_ENABLED|FORGE_EMAIL_INBOUND_ENABLED)=' "$env_file" > "$tmp_file" || true
  else
    : > "$tmp_file"
  fi

  if is_image_mode; then
    cat >> "$tmp_file" <<EOF
FORGE_INSTALL_MODE=image
FORGE_INSTALL_DIR=$APP_DIR
FORGE_ENV_PATH=$APP_DIR/.env
FORGE_IMAGE=$forge_image
FORGE_COMPOSE_FILE=$APP_DIR/compose.yml
FORGE_UPDATE_REPO_URL=$REPO_URL
FORGE_UPDATE_BRANCH=$REPO_BRANCH
FORGE_AUTO_UPDATE_ENABLED=$auto_update_enabled
FORGE_AUTO_UPDATE_INTERVAL_MINUTES=$auto_update_interval_minutes
FORGE_WEBSITE_STATIC_ENABLED=$website_static_enabled
FORGE_WEBSITE_SERVE_DIR=$DATA_DIR/website/current
FORGE_WEBSITE_CADDY_SERVE_DIR=/srv/deploy-data/website/current
FORGE_WEBSITE_UPDATE_REPO_URL=$REPO_URL
FORGE_WEBSITE_UPDATE_BRANCH=$REPO_BRANCH
FORGE_WEBSITE_UPDATE_ROOT_DIR=$website_update_root_dir
FORGE_WEBSITE_AUTO_UPDATE_ENABLED=$website_auto_update_enabled
FORGE_WEBSITE_AUTO_UPDATE_INTERVAL_MINUTES=$website_auto_update_interval_minutes
FORGE_SECRET_KEY=$secret_key
FORGE_OPERATOR_BOOTSTRAP_TOKEN=$operator_bootstrap_token
DATA_DIR=$DATA_DIR
DEPLOY_DRY_RUN=false
CADDY_CONFIG_PATH=$DATA_DIR/Caddyfile
CADDY_DATA_DIR=/srv/deploy-data
CADDY_RELOAD_CMD=docker exec deploy-caddy caddy reload --config /srv/deploy-data/Caddyfile --adapter caddyfile
PORT=$PORT
HOST=0.0.0.0
HOME=$DATA_DIR/runtime-home
DOCKER_CONFIG=$DATA_DIR/docker-config
PUBLIC_URL=$public_url
BUILDKIT_HOST=tcp://127.0.0.1:1234
FORGE_RUNTIME_NETWORK=forge-runtime
FORGE_EMAIL_ENABLED=$EMAIL_ENABLED
FORGE_EMAIL_INBOUND_ENABLED=$EMAIL_INBOUND_ENABLED
EOF
  else
    cat >> "$tmp_file" <<EOF
FORGE_INSTALL_MODE=git
FORGE_INSTALL_DIR=$APP_DIR
FORGE_ENV_PATH=$APP_DIR/.env
FORGE_REPO_URL=$REPO_URL
FORGE_REPO_BRANCH=$REPO_BRANCH
FORGE_IMAGE=$forge_image
FORGE_COMPOSE_FILE=$APP_DIR/compose.yml
FORGE_UPDATE_REPO_URL=$REPO_URL
FORGE_UPDATE_BRANCH=$REPO_BRANCH
FORGE_UPDATE_RESTART_CMD=systemctl restart forge-cloud
FORGE_AUTO_UPDATE_ENABLED=$auto_update_enabled
FORGE_AUTO_UPDATE_INTERVAL_MINUTES=$auto_update_interval_minutes
FORGE_WEBSITE_STATIC_ENABLED=$website_static_enabled
FORGE_WEBSITE_SERVE_DIR=$DATA_DIR/website/current
FORGE_WEBSITE_CADDY_SERVE_DIR=/srv/deploy-data/website/current
FORGE_WEBSITE_UPDATE_REPO_URL=$REPO_URL
FORGE_WEBSITE_UPDATE_BRANCH=$REPO_BRANCH
FORGE_WEBSITE_UPDATE_ROOT_DIR=$website_update_root_dir
FORGE_WEBSITE_AUTO_UPDATE_ENABLED=$website_auto_update_enabled
FORGE_WEBSITE_AUTO_UPDATE_INTERVAL_MINUTES=$website_auto_update_interval_minutes
FORGE_SECRET_KEY=$secret_key
FORGE_OPERATOR_BOOTSTRAP_TOKEN=$operator_bootstrap_token
DATA_DIR=$DATA_DIR
DEPLOY_DRY_RUN=false
CADDY_CONFIG_PATH=$DATA_DIR/Caddyfile
CADDY_DATA_DIR=/srv/deploy-data
CADDY_RELOAD_CMD=docker exec deploy-caddy caddy reload --config /srv/deploy-data/Caddyfile --adapter caddyfile
PORT=$PORT
HOST=0.0.0.0
PUBLIC_URL=$public_url
BUILDKIT_HOST=tcp://127.0.0.1:1234
FORGE_RUNTIME_NETWORK=forge-runtime
FORGE_EMAIL_ENABLED=$EMAIL_ENABLED
FORGE_EMAIL_INBOUND_ENABLED=$EMAIL_INBOUND_ENABLED
EOF
  fi

  if [ -n "$control_plane_hostname" ]; then
    printf 'CONTROL_PLANE_HOSTNAME=%s\n' "$control_plane_hostname" >> "$tmp_file"
  fi
  if [ -n "$admin_hostname" ]; then
    printf 'FORGE_ADMIN_HOSTNAME=%s\n' "$admin_hostname" >> "$tmp_file"
  fi
  if [ -n "$website_hostnames" ]; then
    printf 'FORGE_WEBSITE_HOSTNAMES=%s\n' "$website_hostnames" >> "$tmp_file"
  fi
  if [ -n "$product_domain" ]; then
    printf 'FORGE_PRODUCT_DOMAIN=%s\n' "$product_domain" >> "$tmp_file"
  fi

  mv "$tmp_file" "$env_file"
  chmod 600 "$env_file"
}

build_forge() {
  say "Installing Forge dependencies..."
  cd "$APP_DIR"
  npm ci --include=dev
  if [ -f "$APP_DIR/website/package.json" ]; then
    say "Installing Forge website dependencies..."
    npm --prefix website ci
  fi

  say "Building Forge..."
  if [ -f "$APP_DIR/website/package.json" ]; then
    npm run build:production
  else
    npm run build
  fi
  npm prune --omit=dev --package-lock=false
}

write_compose_file() {
  if is_image_mode; then
    cat > "$APP_DIR/compose.yml" <<EOF
services:
  forge:
    image: \${FORGE_IMAGE:-ghcr.io/adeyomilawal/forge:latest}
    container_name: forge-cloud
    network_mode: host
    restart: unless-stopped
    env_file:
      - .env
    environment:
      NODE_ENV: production
      FORGE_INSTALL_MODE: image
      FORGE_INSTALL_DIR: $APP_DIR
      FORGE_ENV_PATH: $APP_DIR/.env
      DATA_DIR: $DATA_DIR
      CADDY_CONFIG_PATH: $DATA_DIR/Caddyfile
      CADDY_DATA_DIR: /srv/deploy-data
      CADDY_RELOAD_CMD: docker exec deploy-caddy caddy reload --config /srv/deploy-data/Caddyfile --adapter caddyfile
      BUILDKIT_HOST: tcp://127.0.0.1:1234
      HOME: $DATA_DIR/runtime-home
      DOCKER_CONFIG: $DATA_DIR/docker-config
    volumes:
      - $APP_DIR:$APP_DIR
      - /var/run/docker.sock:/var/run/docker.sock
    depends_on:
      - buildkit
      - caddy

  buildkit:
    image: moby/buildkit:latest
    container_name: deploy-buildkit
    privileged: true
    command: ["--addr", "tcp://0.0.0.0:1234"]
    ports:
      - "127.0.0.1:1234:1234"
    restart: unless-stopped

  caddy:
    image: caddy:2
    container_name: deploy-caddy
    network_mode: host
    command: ["sh", "-c", "mkdir -p /srv/deploy-data && touch /srv/deploy-data/Caddyfile && caddy run --config /srv/deploy-data/Caddyfile --adapter caddyfile --watch"]
    volumes:
      - $DATA_DIR:/srv/deploy-data
      - caddy_data:/data
      - caddy_config:/config
    restart: unless-stopped

volumes:
  caddy_data:
  caddy_config:
EOF
    return
  fi

  cat > "$APP_DIR/compose.yml" <<EOF
services:
  buildkit:
    image: moby/buildkit:latest
    container_name: deploy-buildkit
    privileged: true
    command: ["--addr", "tcp://0.0.0.0:1234"]
    ports:
      - "127.0.0.1:1234:1234"
    restart: unless-stopped

  caddy:
    image: caddy:2
    container_name: deploy-caddy
    network_mode: host
    command: ["sh", "-c", "mkdir -p /srv/deploy-data && touch /srv/deploy-data/Caddyfile && caddy run --config /srv/deploy-data/Caddyfile --adapter caddyfile --watch"]
    volumes:
      - $DATA_DIR:/srv/deploy-data
      - caddy_data:/data
      - caddy_config:/config
    restart: unless-stopped

volumes:
  caddy_data:
  caddy_config:
EOF
}

ensure_service_user() {
  if ! id forgecloud >/dev/null 2>&1; then
    $SUDO useradd --system --create-home --shell /bin/bash forgecloud
  fi
  $SUDO usermod -aG docker forgecloud >/dev/null 2>&1 || true
  $SUDO install -d -m 700 -o forgecloud -g forgecloud "$DATA_DIR/runtime-home" "$DATA_DIR/docker-config"
  if [ ! -f "$DATA_DIR/docker-config/config.json" ]; then
    printf '{}\n' | $SUDO tee "$DATA_DIR/docker-config/config.json" >/dev/null
  fi
  $SUDO chown forgecloud:forgecloud "$DATA_DIR/docker-config/config.json"
  $SUDO chmod 600 "$DATA_DIR/docker-config/config.json"
  if ! is_image_mode; then
    $SUDO chown -R forgecloud:forgecloud "$INSTALL_DIR"
  fi
}

write_systemd_unit() {
  command -v systemctl >/dev/null 2>&1 || fail "systemd is required to run Forge."

  if is_image_mode; then
    docker_cmd="$(docker_bin)"
    $SUDO tee /etc/systemd/system/forge-cloud.service >/dev/null <<EOF
[Unit]
Description=Forge Cloud Docker image stack
Documentation=https://github.com/adeyomilawal/forge
After=network-online.target docker.service
Wants=network-online.target docker.service

[Service]
Type=oneshot
WorkingDirectory=$APP_DIR
RemainAfterExit=yes
Environment=DOCKER_CONFIG=$DATA_DIR/docker-config
Environment=PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/snap/bin
ExecStart=$docker_cmd compose -f $APP_DIR/compose.yml up -d buildkit caddy forge
ExecReload=$docker_cmd compose -f $APP_DIR/compose.yml up -d buildkit caddy forge
ExecStop=$docker_cmd compose -f $APP_DIR/compose.yml stop forge
TimeoutStartSec=0
TimeoutStopSec=60

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

  $SUDO tee /etc/systemd/system/forge-cloud.service >/dev/null <<EOF
[Unit]
Description=Forge Cloud control plane
Documentation=https://github.com/adeyomilawal/forge
After=network-online.target docker.service
Wants=network-online.target docker.service

[Service]
Type=simple
User=forgecloud
Group=forgecloud
SupplementaryGroups=docker
WorkingDirectory=$APP_DIR
Environment=NODE_ENV=production
Environment=FORGE_ENV_PATH=$APP_DIR/.env
Environment=HOME=$DATA_DIR/runtime-home
Environment=DOCKER_CONFIG=$DATA_DIR/docker-config
Environment=PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/snap/bin
EnvironmentFile=-$APP_DIR/.env
ExecStart=/usr/bin/env node dist/server/index.js
Restart=always
RestartSec=3
KillSignal=SIGTERM
TimeoutStopSec=30
NoNewPrivileges=true
ProtectSystem=strict
ProtectHome=true
ReadWritePaths=$APP_DIR/.env $DATA_DIR /var/run/docker.sock /tmp
PrivateTmp=true
PrivateDevices=false

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

start_runtime_services() {
  if is_image_mode; then
    return
  fi
  cd "$APP_DIR"
  say "Starting BuildKit and Caddy..."
  $SUDO docker compose up -d buildkit caddy
}

start_forge() {
  if is_image_mode; then
    say "Starting Forge Docker stack..."
  else
    say "Starting Forge..."
  fi
  $SUDO systemctl daemon-reload
  $SUDO systemctl enable --now forge-cloud
  $SUDO systemctl restart forge-cloud
}

print_firewall_hint() {
  if command -v ufw >/dev/null 2>&1 && $SUDO ufw status 2>/dev/null | grep -qi "Status: active"; then
    say ""
    say "UFW is active. Make sure these ports are allowed:"
    say "  sudo ufw allow 80/tcp"
    say "  sudo ufw allow 443/tcp"
    say "Keep the internal Forge port $PORT/tcp closed; Caddy reaches it locally."
    if [ "$EMAIL_INBOUND_ENABLED" = "true" ]; then
      say "  sudo ufw allow 25/tcp"
    fi
  fi
}

main() {
  require_linux
  normalize_install_mode
  install_base_packages
  install_docker
  require_compose

  if is_image_mode; then
    prepare_image_install
  else
    install_node
    install_railpack
    clone_or_update_repo
  fi

  write_env_file
  if ! is_image_mode; then
    build_forge
  fi
  write_compose_file
  ensure_service_user
  write_systemd_unit
  start_runtime_services
  start_forge

  public_url="$(get_env_value "$APP_DIR/.env" PUBLIC_URL)"
  print_firewall_hint
  say ""
  say "Forge is installed."
  say "Open: $public_url"
  say "First-run bootstrap token: $(get_env_value "$APP_DIR/.env" FORGE_OPERATOR_BOOTSTRAP_TOKEN)"
  say "Enter this token only on the Forge operator onboarding screen."
  say ""
  say "Manage it with:"
  say "  sudo journalctl -u forge-cloud -f"
  if is_image_mode; then
    say "  cd $APP_DIR && sudo docker compose logs -f forge caddy buildkit"
    say "  cd $APP_DIR && sudo docker compose -f $APP_DIR/compose.yml pull forge && sudo systemctl restart forge-cloud"
  else
    say "  cd $INSTALL_DIR && git status"
    say "  cd $APP_DIR && sudo docker compose logs -f caddy buildkit"
  fi
}

main "$@"
