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

# Production entry point for first-admin setup. The installer keeps the
# EnvironmentFile root-readable only, so parse simple KEY=VALUE assignments
# without sourcing arbitrary shell code.
PATH=/usr/sbin:/usr/bin:/sbin:/bin:/usr/local/bin
export PATH
umask 077

CONFIG_DIR=${TALLYNOTE_CONFIG_DIR:-/etc/tallynote}
CONFIG_FILE="$CONFIG_DIR/tallynote.env"
PREFIX=${TALLYNOTE_INSTALL_PREFIX:-/opt/tallynote}
SERVICE_NAME=${TALLYNOTE_SERVICE_NAME:-tallynote.service}

die() { printf 'tallynote admin-init: %s\n' "$*" >&2; exit 1; }

load_environment_file() {
  [[ -e "$CONFIG_FILE" ]] || return 0
  [[ -f "$CONFIG_FILE" && ! -L "$CONFIG_FILE" ]] || die '环境文件不是安全的普通文件'
  local uid mode_bits line key value
  uid=$(stat -c '%u' "$CONFIG_FILE" 2>/dev/null || stat -f '%u' "$CONFIG_FILE")
  [[ "$uid" == 0 ]] || die '环境文件必须由 root 拥有'
  mode_bits=$(stat -c '%a' "$CONFIG_FILE" 2>/dev/null || stat -f '%Lp' "$CONFIG_FILE")
  [[ "$mode_bits" =~ ^[0-7]+$ ]] || die '无法读取环境文件权限'
  (( (8#$mode_bits & 18) == 0 )) || die '环境文件不能被组或其他用户写入'

  while IFS= read -r line || [[ -n "$line" ]]; do
    [[ -z "$line" || "$line" == \#* ]] && continue
    [[ "$line" =~ ^([A-Z][A-Z0-9_]*)=(.*)$ ]] || die '环境文件包含无法识别的配置行'
    key=${BASH_REMATCH[1]}
    value=${BASH_REMATCH[2]}
    [[ "$value" != *$'\n'* && "$value" != *$'\r'* ]] || die "环境文件中的 $key 包含控制字符"
    export "$key=$value"
  done < "$CONFIG_FILE"
  PREFIX=${TALLYNOTE_INSTALL_PREFIX:-$PREFIX}
}

resolve_release_root() {
  local root prefix_root
  [[ "$PREFIX" = /* && "$PREFIX" != *$'\n'* && "$PREFIX" != *$'\r'* ]] || die '安装目录无效'
  [[ -L "$PREFIX/current" ]] || die '当前 release 链接不存在'
  prefix_root=$(readlink -f -- "$PREFIX" 2>/dev/null || realpath "$PREFIX" 2>/dev/null || true)
  [[ -n "$prefix_root" && -d "$prefix_root" && ! -L "$prefix_root" ]] || die '安装目录不安全'
  root=$(readlink -f -- "$PREFIX/current" 2>/dev/null || realpath "$PREFIX/current" 2>/dev/null || true)
  [[ -n "$root" && "$root" == "$prefix_root/releases/"* && -d "$root" && ! -L "$root" ]] || die '当前 release 链接不安全'
  printf '%s' "$root"
}

run_as_service_user() {
  local root=$1 node=$2 cli=$3
  if [[ "${EUID:-$(id -u)}" == 0 ]]; then
    local service_uid
    service_uid=$(id -u tallynote 2>/dev/null) || die '找不到 tallynote 服务用户，拒绝以 root 身份执行管理员初始化'
    [[ "$service_uid" =~ ^[1-9][0-9]*$ ]] || die 'tallynote 服务用户 UID 无效，拒绝以 root 身份执行管理员初始化'
    command -v runuser >/dev/null 2>&1 || die '找不到 runuser，无法以 tallynote 用户初始化'
    local -a environment=(
      "NODE_ENV=production"
      "TALLYNOTE_DATA_DIR=${TALLYNOTE_DATA_DIR:-/var/lib/tallynote}"
      "TALLYNOTE_INSTALL_PREFIX=${TALLYNOTE_INSTALL_PREFIX:-$PREFIX}"
      "TALLYNOTE_TRUST_PROXY=${TALLYNOTE_TRUST_PROXY:-false}"
      "TALLYNOTE_UPDATE_STRATEGY=${TALLYNOTE_UPDATE_STRATEGY:-systemd}"
      "TALLYNOTE_HOST=${TALLYNOTE_HOST:-127.0.0.1}"
      "TALLYNOTE_PORT=${TALLYNOTE_PORT:-3000}"
      "TALLYNOTE_PUBLIC_ORIGIN=${TALLYNOTE_PUBLIC_ORIGIN:-http://127.0.0.1:3000}"
      "TALLYNOTE_COOKIE_SECURE=${TALLYNOTE_COOKIE_SECURE:-false}"
      "TALLYNOTE_ALLOW_INSECURE_HTTP=${TALLYNOTE_ALLOW_INSECURE_HTTP:-false}"
      "TALLYNOTE_TIMEZONE=${TALLYNOTE_TIMEZONE:-Asia/Shanghai}"
      "TALLYNOTE_UPDATE_METADATA_URL=${TALLYNOTE_UPDATE_METADATA_URL:-https://git.awaioi.com/api/v1/repos/awaioi/TallyNote/releases/latest}"
      "TALLYNOTE_UPDATE_ALLOWED_HOSTS=${TALLYNOTE_UPDATE_ALLOWED_HOSTS:-git.awaioi.com}"
      "TALLYNOTE_UPDATE_REQUIRE_SIGNATURE=${TALLYNOTE_UPDATE_REQUIRE_SIGNATURE:-false}"
      "TALLYNOTE_UPDATE_MAX_MB=${TALLYNOTE_UPDATE_MAX_MB:-512}"
      "TALLYNOTE_UPDATE_CHECK_COOLDOWN_SECONDS=${TALLYNOTE_UPDATE_CHECK_COOLDOWN_SECONDS:-60}"
      "TALLYNOTE_UPDATE_DOWNLOAD_COOLDOWN_SECONDS=${TALLYNOTE_UPDATE_DOWNLOAD_COOLDOWN_SECONDS:-15}"
      "TALLYNOTE_UPDATE_APPLY_COOLDOWN_SECONDS=${TALLYNOTE_UPDATE_APPLY_COOLDOWN_SECONDS:-15}"
      "TALLYNOTE_MAX_FILE_MB=${TALLYNOTE_MAX_FILE_MB:-20}"
      "TALLYNOTE_MAX_FILES_PER_REQUEST=${TALLYNOTE_MAX_FILES_PER_REQUEST:-20}"
      "TALLYNOTE_MAX_RECORD_MB=${TALLYNOTE_MAX_RECORD_MB:-100}"
      "TALLYNOTE_MAX_TOTAL_MB=${TALLYNOTE_MAX_TOTAL_MB:-2048}"
      "TALLYNOTE_MAX_CONCURRENT_EXPORTS=${TALLYNOTE_MAX_CONCURRENT_EXPORTS:-2}"
      "TALLYNOTE_MAX_EXPORT_RECORDS=${TALLYNOTE_MAX_EXPORT_RECORDS:-5000}"
      "TALLYNOTE_MAX_EXPORT_MB=${TALLYNOTE_MAX_EXPORT_MB:-1024}"
      "TALLYNOTE_MAX_EXPORT_STORAGE_MB=${TALLYNOTE_MAX_EXPORT_STORAGE_MB:-2048}"
      "TALLYNOTE_SESSION_IDLE_HOURS=${TALLYNOTE_SESSION_IDLE_HOURS:-24}"
      "TALLYNOTE_SESSION_ABSOLUTE_HOURS=${TALLYNOTE_SESSION_ABSOLUTE_HOURS:-168}"
      "TALLYNOTE_EXPORT_TTL_MINUTES=${TALLYNOTE_EXPORT_TTL_MINUTES:-15}"
    )
    [[ -n "${TALLYNOTE_UPDATE_PUBLIC_KEY:-}" ]] && environment+=("TALLYNOTE_UPDATE_PUBLIC_KEY=$TALLYNOTE_UPDATE_PUBLIC_KEY")
    if [[ -n "${TALLYNOTE_UPDATE_PUBLIC_KEY_FILE:-}" ]]; then
      environment+=("TALLYNOTE_UPDATE_PUBLIC_KEY_FILE=$TALLYNOTE_UPDATE_PUBLIC_KEY_FILE")
    fi
    if [[ -n "${TALLYNOTE_UPDATE_HELPER_PATH:-}" ]]; then
      environment+=("TALLYNOTE_UPDATE_HELPER_PATH=$TALLYNOTE_UPDATE_HELPER_PATH")
    fi
    runuser -u tallynote -- env -i "${environment[@]}" PATH="$PATH" "$node" "$cli" "${@:4}"
  else
    "$node" "$cli" "${@:4}"
  fi
}

main() {
  load_environment_file
  local root node cli systemctl service_was_active=0 result check_only=0
  for argument in "$@"; do
    [[ "$argument" == "--check" ]] && check_only=1
  done
  root=$(resolve_release_root)
  node="$root/runtime/bin/node"
  [[ -x "$node" ]] || node=$(command -v node || true)
  [[ -n "$node" && -x "$node" ]] || die '找不到 Node.js runtime'
  cli="$root/dist/server/cli/admin-init.js"
  [[ -f "$cli" && ! -L "$cli" ]] || die '管理员初始化程序不存在'

  # Validate the privilege boundary before stopping an active service. A
  # damaged installation must fail closed without causing avoidable downtime.
  if [[ "${EUID:-$(id -u)}" == 0 ]]; then
    local service_uid
    service_uid=$(id -u tallynote 2>/dev/null) || die '找不到 tallynote 服务用户，拒绝以 root 身份执行管理员初始化'
    [[ "$service_uid" =~ ^[1-9][0-9]*$ ]] || die 'tallynote 服务用户 UID 无效，拒绝以 root 身份执行管理员初始化'
    command -v runuser >/dev/null 2>&1 || die '找不到 runuser，无法以 tallynote 用户初始化'
  fi

  # admin-init uses the same instance lock as the web process. Pause an active
  # service for the duration, then restore exactly its previous active state.
  systemctl=$(command -v systemctl || true)
  if (( ! check_only )) && [[ "${EUID:-$(id -u)}" == 0 && -n "$systemctl" && -x "$systemctl" ]] && "$systemctl" is-active --quiet "$SERVICE_NAME"; then
    service_was_active=1
    printf 'tallynote admin-init: 暂停服务以完成管理员初始化\n' >&2
    "$systemctl" stop "$SERVICE_NAME" || die '无法暂停 TallyNote 服务'
  fi
  restore_service() {
    local exit_code=$?
    if (( service_was_active )); then
      printf 'tallynote admin-init: 恢复 TallyNote 服务\n' >&2
      "$systemctl" start "$SERVICE_NAME" || printf 'tallynote admin-init: 警告：服务恢复失败，请执行 systemctl start %s\n' "$SERVICE_NAME" >&2
    fi
    return "$exit_code"
  }
  trap restore_service EXIT

  cd -- "$root"
  set +e
  run_as_service_user "$root" "$node" "$cli" "$@"
  result=$?
  set -e
  exit "$result"
}

main "$@"
