#!/usr/bin/env bash
set -euo pipefail
umask 077

ENDPOINT="${PERFMON_ENDPOINT:-https://perfmon.ru}"
BASE_URL="${PERFMON_AGENT_BASE_URL:-https://perfmon.ru/agent}"
RELEASE_VERSION="1.3.1"
MODE="install"
CLAIM_TOKEN=""
case "${1:-}" in
  --update) MODE="update" ;;
  --claim-token-stdin) IFS= read -r CLAIM_TOKEN || [[ -n "${CLAIM_TOKEN}" ]] ;;
  --help|-h)
    printf '%s\n' 'Usage: install.sh [--claim-token-stdin | --update]' 'Without arguments, the token is read privately from the terminal.'
    exit 0 ;;
  '') ;;
  --*) echo 'Unknown option' >&2; exit 1 ;;
  *) CLAIM_TOKEN="$1"; echo 'Prefer the interactive prompt: positional tokens may appear in shell history and process arguments.' >&2 ;;
esac

case "${BASE_URL}" in https://*) ;; *) echo 'Artifact downloads require HTTPS' >&2; exit 1 ;; esac
case "$(uname -m)" in
  x86_64) BIN="perfmon-agent-linux-amd64" ;;
  aarch64|arm64) BIN="perfmon-agent-linux-arm64" ;;
  *) echo 'Unsupported architecture' >&2; exit 1 ;;
esac
for tool in systemctl curl openssl sha256sum install runuser; do
  command -v "${tool}" >/dev/null || { echo "Required tool missing: ${tool}" >&2; exit 1; }
done
if [[ "${EUID}" -ne 0 ]]; then
  command -v sudo >/dev/null || { echo 'sudo is required' >&2; exit 1; }
  sudo -v
fi

# Empty arrays under nounset fail on Bash 4.3 and older, including root updates.
# Keep argument boundaries intact without an optional command-prefix array.
run_as_root() {
  if [[ "${EUID}" -eq 0 ]]; then
    "$@"
  else
    sudo "$@"
  fi
}

tmp_dir="$(mktemp -d)"
claim_config=""
trap 'rm -rf -- "${tmp_dir}"; if [[ -n "${claim_config}" ]]; then run_as_root rm -f -- "${claim_config}"; fi' EXIT

# Trust anchor: never replace this key with one downloaded alongside a binary.
cat > "${tmp_dir}/public.pem" <<'PUBLIC_KEY'
-----BEGIN PUBLIC KEY-----
MIIBojANBgkqhkiG9w0BAQEFAAOCAY8AMIIBigKCAYEAi/2Q9m5XGDv+26YDT2Wm
EEWbSW4zvdHHaB2TTFagV9S9GBVSrGOpEuCQwYq34epAMS/QrB1ymxLGNl629tb5
60eB0lDd3J0EGHElaGjDMUeNfr9y5E55av2FPailU7zllVD514utjRvhc78xg2Q5
JC0JVBYSqm5pUISNE48mrA14ttOUIM67Kcm/OFErUONiBDZR34ntnM7JZtzWrxn/
yVqdSjNHGTJkwUrZpJaNM/FCVdEC4y55yjErE5IwaL4qUinMDx+qs0JRR0LOxckg
NHo4uHxhmxRedjlYaENjOtkMTdVSzDVjOlhNVqbXTAcQ2HEhsHm5H6kPKFNazM8d
4f7Qqz0u64+HysVEzfbQZEG8dG87a3BQK95Gsjp058PGXyZxVRt69gbG5XkKFAVJ
9GplRot5sjrwWxi0ZiWZICWm5hqcbB2yTguaEHYrzBhChNZU3F/lLYuEdOvscCEl
zOxSqRbfBODY3P8NN5+V79OAhSMkn8Kig+HJy8QrsWB7AgMBAAE=
-----END PUBLIC KEY-----
PUBLIC_KEY

download() {
  curl --proto '=https' --proto-redir '=https' --tlsv1.2 --fail --silent --show-error --location \
    --connect-timeout 10 --max-time 120 --max-filesize 33554432 \
    "${BASE_URL%/}/releases/${RELEASE_VERSION}/$1" -o "${tmp_dir}/$1"
}
download SHA256SUMS
download SHA256SUMS.sig
[[ "$(wc -c < "${tmp_dir}/SHA256SUMS.sig")" -eq 384 ]] || { echo 'Invalid RSA-3072 signature length' >&2; exit 1; }
openssl dgst -sha256 -verify "${tmp_dir}/public.pem" -signature "${tmp_dir}/SHA256SUMS.sig" "${tmp_dir}/SHA256SUMS" >/dev/null || {
  echo 'Release signature verification failed; nothing was installed.' >&2; exit 1;
}
download "${BIN}"
download VERSION
verify_file() {
  local expected_checksum
  expected_checksum="$(awk -v name="$1" '$2 == name {print $1}' "${tmp_dir}/SHA256SUMS")"
  [[ "${expected_checksum}" =~ ^[a-f0-9]{64}$ ]] || { echo 'Invalid signed manifest' >&2; exit 1; }
  printf '%s  %s\n' "${expected_checksum}" "${tmp_dir}/$1" | sha256sum --check --status || {
    echo 'Artifact does not match the signed release; nothing was installed.' >&2; exit 1;
  }
}
verify_file "${BIN}"
verify_file VERSION
[[ "$(cat "${tmp_dir}/VERSION")" == "${RELEASE_VERSION}" ]] || { echo 'Signed release version mismatch' >&2; exit 1; }

if [[ "${MODE}" == "update" ]] && ! run_as_root test -s /etc/perfmon/agent.json; then
  echo 'No agent configuration. Install with a new claim token first.' >&2; exit 1
fi
if [[ "${MODE}" == "install" && -z "${CLAIM_TOKEN}" ]]; then
  IFS= read -r -s -p 'Paste the one-time perfMon claim token: ' CLAIM_TOKEN </dev/tty
  printf '\n' >/dev/tty
fi
if [[ "${MODE}" == "install" && -z "${CLAIM_TOKEN}" ]]; then
  echo 'Claim token is required' >&2; exit 1
fi
if ! id -u perfmon >/dev/null 2>&1; then
  run_as_root useradd --system --user-group --no-create-home --shell /usr/sbin/nologin perfmon
fi
[[ "$(id -u perfmon)" != 0 ]] || { echo 'perfmon must not have UID 0' >&2; exit 1; }
run_as_root install -d -o perfmon -g perfmon -m 700 /etc/perfmon
run_as_root install -d -m 755 /usr/local/bin

# Config is mutable only by perfmon/root; the executable stays root-owned.
if run_as_root test -f /etc/perfmon/agent.json; then
  run_as_root chown perfmon:perfmon /etc/perfmon/agent.json
  run_as_root chmod 600 /etc/perfmon/agent.json
fi
run_as_root install -o root -g root -m 755 "${tmp_dir}/${BIN}" /usr/local/bin/perfmon-agent.new
if [[ "${MODE}" == "install" ]]; then
  claim_config="$(run_as_root runuser -u perfmon -- mktemp /etc/perfmon/.claim-XXXXXXXX.json)"
  # Bash builtin printf sends the token through a pipe, never the agent's argv.
  printf '%s\n' "${CLAIM_TOKEN}" | run_as_root runuser -u perfmon -- \
    /usr/local/bin/perfmon-agent.new --claim-token-stdin --endpoint "${ENDPOINT}" --config "${claim_config}"
  unset CLAIM_TOKEN
  # A failed claim leaves the previous configuration and running service intact.
  # Stop only after success, so the old process cannot overwrite the new config.
  if run_as_root systemctl is-active --quiet perfmon-agent; then
    run_as_root systemctl stop perfmon-agent
  fi
  run_as_root mv -f -- "${claim_config}" /etc/perfmon/agent.json
  claim_config=""
fi

# Preserve the known legacy HTTP configuration while upgrading it to HTTPS.
if run_as_root grep -Fq 'http://perfmon.ru/api/agent/heartbeat' /etc/perfmon/agent.json; then
  run_as_root sed -i 's#http://perfmon.ru/api/agent/heartbeat#https://perfmon.ru/api/agent/heartbeat#g' /etc/perfmon/agent.json
fi
run_as_root mv -f /usr/local/bin/perfmon-agent.new /usr/local/bin/perfmon-agent
run_as_root install -d -m 755 /etc/systemd/system
run_as_root tee /etc/systemd/system/perfmon-agent.service >/dev/null <<'UNIT'
[Unit]
Description=perfMon Agent
After=network-online.target
Wants=network-online.target

[Service]
Type=simple
User=perfmon
Group=perfmon
ExecStart=/usr/local/bin/perfmon-agent --config /etc/perfmon/agent.json
Restart=always
RestartSec=5
UMask=0077
NoNewPrivileges=true
CapabilityBoundingSet=
AmbientCapabilities=
ProtectSystem=strict
ReadWritePaths=/etc/perfmon
ProtectHome=true
PrivateTmp=true
PrivateDevices=true
ProtectProc=invisible
ProtectKernelTunables=true
ProtectKernelModules=true
ProtectKernelLogs=true
ProtectControlGroups=true
ProtectClock=true
LockPersonality=true
RestrictRealtime=true
RestrictSUIDSGID=true
RestrictNamespaces=true
RestrictAddressFamilies=AF_UNIX AF_INET AF_INET6 AF_NETLINK
MemoryDenyWriteExecute=true
SystemCallArchitectures=native
SystemCallFilter=@system-service
SystemCallErrorNumber=EPERM

[Install]
WantedBy=multi-user.target
UNIT
run_as_root chmod 644 /etc/systemd/system/perfmon-agent.service
run_as_root systemctl daemon-reload
run_as_root systemctl enable perfmon-agent
run_as_root systemctl restart perfmon-agent
run_as_root systemctl is-active --quiet perfmon-agent
printf 'perfMon agent %s installed. Check: journalctl -u perfmon-agent -n 20\n' "${RELEASE_VERSION}"
