#!/bin/sh
set -eu

# Hikaproj Tunnel - Server installer
#
# IMPORTANT:
# - This installer does NOT depend on GitHub Releases or any public repository.
# - It installs from a local release archive supplied via one of:
#     1) HIKAPROJ_LOCAL_ARCHIVE=/path/to/hikaproj-<arch>-linux.tar.gz
#     2) An archive placed next to this installer with the expected filename.
# - The archive must contain an executable named "hpjtunnel".
# - Optional SHA-256 verification can be supplied with HIKAPROJ_LOCAL_SHA256.
#
# Example:
#   sudo HIKAPROJ_LOCAL_ARCHIVE=./hikaproj-amd64-linux.tar.gz sh ./install-server.sh
#
# This design intentionally avoids curl/wget/package-manager downloads. The
# installer only needs standard local POSIX/Linux tools plus systemd.

INSTALL_DIR="${HIKAPROJ_INSTALL_DIR:-/usr/local/bin}"
CONF_DIR="/etc/hikaproj"
STATE_DIR="/var/lib/hikaproj"
RUN_DIR="/run/hikaproj"
LOG_DIR="/var/log/hikaproj"
SERVICE="hikaproj-gateway.service"
BINARY="hpjtunnel"
TMP=""
SCRIPT_DIR=""

say() { printf '%s\n' "[hikaproj] $*"; }
fail() { printf '%s\n' "[hikaproj] ERROR: $*" >&2; exit 1; }
cleanup() { [ -n "${TMP}" ] && rm -rf "$TMP" 2>/dev/null || true; }
trap cleanup EXIT INT TERM

[ "$(id -u)" -eq 0 ] || fail "root権限が必要です。sudo sh install-server.sh を実行してください。"

# Resolve the directory containing this installer without requiring readlink -f.
case "$0" in
  */*) SCRIPT_DIR=${0%/*}; [ -n "$SCRIPT_DIR" ] || SCRIPT_DIR=/ ;;
  *) SCRIPT_DIR=. ;;
esac
SCRIPT_DIR=$(CDPATH= cd -- "$SCRIPT_DIR" 2>/dev/null && pwd) || fail "installer directoryを解決できません。"

command_exists() { command -v "$1" >/dev/null 2>&1; }

# Required local tools. No network bootstrap is performed.
for cmd in tar install mkdir chmod chown id awk; do
  command_exists "$cmd" || fail "必要なコマンドがありません: $cmd（ネットワークから自動取得は行いません）"
done
command_exists systemctl || fail "systemd/systemctl が必要です。"
command_exists getent || fail "必要なコマンドがありません: getent"

OS_ID="unknown"
OS_VER="unknown"
if [ -r /etc/os-release ]; then
  . /etc/os-release
  OS_ID="${ID:-unknown}"
  OS_VER="${VERSION_ID:-unknown}"
fi

ARCH="$(uname -m)"
case "$ARCH" in
  x86_64|amd64) ARCH="amd64" ;;
  aarch64|arm64) ARCH="arm64" ;;
  armv7l|armv7) ARCH="armv7" ;;
  *) fail "未対応architecture: $ARCH" ;;
esac

ARCHIVE_NAME="hikaproj-${ARCH}-linux.tar.gz"

# Locate local archive. No remote URL is ever constructed or fetched.
LOCAL_ARCHIVE="${HIKAPROJ_LOCAL_ARCHIVE:-}"
if [ -z "$LOCAL_ARCHIVE" ]; then
  if [ -f "$SCRIPT_DIR/$ARCHIVE_NAME" ]; then
    LOCAL_ARCHIVE="$SCRIPT_DIR/$ARCHIVE_NAME"
  elif [ -f "./$ARCHIVE_NAME" ]; then
    LOCAL_ARCHIVE="./$ARCHIVE_NAME"
  fi
fi

[ -n "$LOCAL_ARCHIVE" ] || fail "ローカルrelease archiveが見つかりません: $ARCHIVE_NAME\nHIKAPROJ_LOCAL_ARCHIVE=/path/to/$ARCHIVE_NAME を指定するか、installerと同じディレクトリに置いてください。"
[ -f "$LOCAL_ARCHIVE" ] || fail "指定されたrelease archiveが存在しません: $LOCAL_ARCHIVE"

# Refuse obviously wrong file types before touching the system.
case "$LOCAL_ARCHIVE" in
  *.tar.gz|*.tgz) ;;
  *) fail "release archiveは .tar.gz / .tgz を指定してください: $LOCAL_ARCHIVE" ;;
esac

say "OS=$OS_ID $OS_VER arch=$ARCH"
say "local release: $LOCAL_ARCHIVE"

TMP="$(mktemp -d /tmp/hikaproj-server.XXXXXX)"
chmod 0700 "$TMP"

# Copy input into a private temporary directory so later operations do not
# depend on a removable/network-mounted source path.
cp "$LOCAL_ARCHIVE" "$TMP/$ARCHIVE_NAME"
chmod 0600 "$TMP/$ARCHIVE_NAME"

# Optional SHA-256 verification.
SHA_FILE="${HIKAPROJ_LOCAL_SHA256:-}"
if [ -n "$SHA_FILE" ]; then
  [ -f "$SHA_FILE" ] || fail "SHA-256 fileが存在しません: $SHA_FILE"
  if command_exists sha256sum; then
    (cd "$TMP" && sha256sum -c "$SHA_FILE" --ignore-missing >/dev/null 2>&1) || fail "SHA-256検証に失敗しました。"
  elif command_exists shasum; then
    expected="$(awk -v n="$ARCHIVE_NAME" '$2==n {print $1; exit}' "$SHA_FILE")"
    [ -n "$expected" ] || fail "SHA-256 fileに $ARCHIVE_NAME のchecksumがありません。"
    actual="$(shasum -a 256 "$TMP/$ARCHIVE_NAME" | awk '{print $1}')"
    [ "$expected" = "$actual" ] || fail "SHA-256検証に失敗しました。"
  elif command_exists openssl; then
    expected="$(awk -v n="$ARCHIVE_NAME" '$2==n {print $1; exit}' "$SHA_FILE")"
    [ -n "$expected" ] || fail "SHA-256 fileに $ARCHIVE_NAME のchecksumがありません。"
    actual="$(openssl dgst -sha256 "$TMP/$ARCHIVE_NAME" | awk '{print $NF}')"
    [ "$expected" = "$actual" ] || fail "SHA-256検証に失敗しました。"
  else
    fail "SHA-256検証を要求されましたが、sha256sum/shasum/openssl がありません。"
  fi
  say "SHA-256 verification: OK"
else
  say "WARNING: SHA-256 file未指定。ローカル配布物のため自動取得は行いません。"
fi

# Inspect archive before extraction; only the expected binary path is allowed
# to become the installed executable.
tar -tzf "$TMP/$ARCHIVE_NAME" >/dev/null 2>&1 || fail "release archiveが壊れているか、tar.gzではありません。"
tar -xzf "$TMP/$ARCHIVE_NAME" -C "$TMP"

BIN="$TMP/$BINARY"
if [ ! -f "$BIN" ]; then
  # Accept a single top-level directory containing hpjtunnel.
  FOUND="$(find "$TMP" -type f -name "$BINARY" -perm -0100 -print 2>/dev/null | head -n 1 || true)"
  [ -n "$FOUND" ] && BIN="$FOUND"
fi

[ -f "$BIN" ] || fail "$BINARY がrelease内にありません。"
[ -x "$BIN" ] || chmod 0755 "$BIN"
[ -x "$BIN" ] || fail "$BINARY が実行可能ではありません。"

# Create directories and service account.
mkdir -p "$CONF_DIR" "$STATE_DIR/backups" "$STATE_DIR/updates" "$RUN_DIR" "$LOG_DIR" "$INSTALL_DIR"
chmod 0750 "$CONF_DIR" "$STATE_DIR" "$LOG_DIR"

if ! getent group hikaproj >/dev/null 2>&1; then
  groupadd --system hikaproj
fi
if ! id hikaproj >/dev/null 2>&1; then
  useradd --system --gid hikaproj --home-dir "$STATE_DIR" --shell /usr/sbin/nologin hikaproj
fi

# Install atomically: copy to a temporary path in the same filesystem, fsync
# when available, then replace the target.
NEW_BIN="$INSTALL_DIR/.${BINARY}.new.$$"
install -m 0755 "$BIN" "$NEW_BIN"
if command_exists sync; then sync -f "$NEW_BIN" 2>/dev/null || true; fi
mv -f "$NEW_BIN" "$INSTALL_DIR/$BINARY"

# Initial configuration is intentionally conservative. Existing configuration
# is never overwritten on a re-install.
if [ ! -f "$CONF_DIR/server.yaml" ]; then
  cat > "$CONF_DIR/server.yaml" <<CFG
server:
  control_listen: unix:///run/hikaproj/hikaproj.sock
  tunnel_listen: 0.0.0.0:7444
  udp_listen: 0.0.0.0:7445
storage:
  path: /var/lib/hikaproj/hikaproj.db
security:
  require_tls: true
CFG
fi

chown root:hikaproj "$CONF_DIR/server.yaml"
chmod 0640 "$CONF_DIR/server.yaml"
chown -R hikaproj:hikaproj "$STATE_DIR" "$LOG_DIR"

# Do not overwrite an existing service file. This preserves deliberate local
# administrator changes across upgrades.
if [ ! -f "/etc/systemd/system/$SERVICE" ]; then
  cat > "/etc/systemd/system/$SERVICE" <<UNIT
[Unit]
Description=Hikaproj Tunnel Gateway
After=network-online.target
Wants=network-online.target

[Service]
Type=simple
User=hikaproj
Group=hikaproj
ExecStart=$INSTALL_DIR/$BINARY server run --config $CONF_DIR/server.yaml
Restart=always
RestartSec=2
NoNewPrivileges=true
PrivateTmp=true
PrivateDevices=true
ProtectHome=true
ProtectSystem=strict
ProtectKernelTunables=true
ProtectKernelModules=true
ProtectControlGroups=true
LockPersonality=true
RestrictRealtime=true
RestrictSUIDSGID=true
ReadWritePaths=$STATE_DIR $LOG_DIR $RUN_DIR
RuntimeDirectory=hikaproj
RuntimeDirectoryMode=0750

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

systemctl daemon-reload
systemctl enable --now "$SERVICE"
sleep 1

if ! systemctl is-active --quiet "$SERVICE"; then
  journalctl -u "$SERVICE" -n 80 --no-pager >&2 || true
  fail "Gateway service起動失敗"
fi

say "Server installation complete."
say "Binary: $INSTALL_DIR/$BINARY"
say "Config: $CONF_DIR/server.yaml"
say "State:  $STATE_DIR/hikaproj.db"
say "Service: $SERVICE"
say "Next:   $INSTALL_DIR/$BINARY help"
say "Then:   $INSTALL_DIR/$BINARY agent create --name <NAME> --install-command"
