#!/usr/bin/env bash
# HiveCode Runtime installer. Download metadata and packages only from the official release service.
set -euo pipefail
umask 077

fail() { printf 'HiveCode: %s\n' "$*" >&2; exit 1; }
info() { printf 'HiveCode: %s\n' "$*"; }
as_root() {
  if [[ $(id -u) == 0 ]]; then "$@"; else sudo -- "$@"; fi
}

read_release() {
  jq -er --arg arch "$architecture" --arg channel "$channel" --arg format "$format" '
    (.data // .) as $r | $r.latest as $v | ($r.artifact // $v.artifact) as $a |
    if $v == null then error("No published Runtime package for this architecture/channel")
    elif ($r.blockReason != null and $r.blockReason != "" and
      ($r.updateRequired == true and (["below_min_supported_build", "release_marked_mandatory"] | index($r.blockReason)) != null | not))
      then error("Release is blocked")
    elif ($a.packageFormat != $format or $a.architecture != $arch or $a.distributionType != "direct")
      then error("Not an official Runtime package for this architecture and method")
    elif ($v.versionName | type) != "string" then error("Missing version")
    elif ($v.versionName | test(if $channel == "beta" then "^[0-9]+\\.[0-9]+\\.[0-9]+-beta\\.[0-9]+$" else "^[0-9]+\\.[0-9]+\\.[0-9]+$" end) | not)
      then error("Unexpected release version")
    elif ($v.buildNumber | type) != "number" then error("Missing build number")
    elif ($v.buildNumber < 1 or $v.buildNumber > 9007199254740991 or $v.buildNumber != ($v.buildNumber | floor))
      then error("Invalid build number")
    elif ($a.size | type) != "number" then error("Missing package size")
    elif ($a.size < 1 or $a.size > 536870912 or $a.size != ($a.size | floor)) then error("Invalid package size")
    elif ($a.sha256 | type) != "string" then error("Missing SHA-256")
    elif ($a.sha256 | test("^[a-fA-F0-9]{64}$") | not) then error("Invalid SHA-256")
    elif ($a.fileName | type) != "string" then error("Missing package name")
    elif ($a.fileName | test(if $format == "deb" then "^hivecode-runtime[_-][A-Za-z0-9_.~-]+\\.deb$" else "^hivecode-runtime-(x64|arm64)\\.tar\\.gz$" end) | not) then error("Not a Runtime package")
    elif ($a.downloadUrl | type) != "string" then error("Missing download URL")
    elif ($a.downloadUrl | test("[[:space:]]")) then error("Invalid download URL")
    elif ($a.downloadUrl | test("^https://releases\\.hivekernel\\.com/hive/v1/update-artifacts/[a-fA-F0-9]{8}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{12}/download(/[^/?#[:space:]]+)?$") | not)
      then error("Untrusted download URL")
    else [$v.versionName, $v.buildNumber, $a.size, ($a.sha256 | ascii_downcase), $a.downloadUrl] | @tsv end
  ' "$work_dir/release.json"
}

wait_for_runtime() {
  local invocation state attempt
  for ((attempt = 0; attempt < 60; attempt++)); do
    state=$(as_root systemctl is-active hivecode-runtime.service 2>/dev/null || true)
    [[ $state != failed ]] || fail 'Runtime startup failed. Run: sudo journalctl -u hivecode-runtime.service -n 80'
    invocation=$(as_root systemctl show hivecode-runtime.service --property=InvocationID --value)
    if [[ $state == active && $invocation =~ ^[a-fA-F0-9]{32}$ ]]; then
      as_root journalctl "_SYSTEMD_INVOCATION_ID=$invocation" --output=cat --no-pager > "$work_dir/startup.log"
      if jq -Rse 'split("\n") | map(fromjson? | select(.type == "orca_server_ready" and .schemaVersion == 1)) | length > 0' "$work_dir/startup.log" >/dev/null; then
        jq -Rse 'split("\n") | map(fromjson? | select(.type == "orca_server_ready" and .schemaVersion == 1)) | last |
          .health.terminalDaemon | .state == "live" and .ownsFreshSessions == true and .selfTest.ok == true and
          .selfTest.coverage == "pty-spawn" and (.cgroupUnit | type == "string" and test("^orca-daemon-.+\\.scope$"))' \
          "$work_dir/startup.log" >/dev/null || fail 'Runtime started but its terminal daemon or PTY health check failed. Inspect the service journal.'
        return
      fi
    fi
    sleep 2
  done
  fail 'Runtime did not become ready within 120 seconds. Run: sudo journalctl -u hivecode-runtime.service -n 80'
}

at_least() {
  [[ $1 =~ ^([0-9]+)\.([0-9]+)(\.[0-9]+)?$ ]] || return 1
  local major=${BASH_REMATCH[1]} minor=${BASH_REMATCH[2]}
  ((10#$major > $2 || (10#$major == $2 && 10#$minor >= $3)))
}

cleanup() {
  local result=$?
  trap - EXIT
  if [[ ${activation_pending:-0} == 1 && $result != 0 ]]; then
    info 'Startup failed; restoring the previously installed Runtime.'
    if [[ -z ${previous_target:-} && ! -f ${work_dir:-}/previous.service ]]; then
      systemctl disable --now hivecode-runtime.service >/dev/null 2>&1 || true
    fi
    if [[ -n ${previous_target:-} ]]; then
      ln -sfn -- "$previous_target" /opt/hivecode-runtime/.current-rollback
      mv -Tf -- /opt/hivecode-runtime/.current-rollback /opt/hivecode-runtime/current
    else
      rm -f -- /opt/hivecode-runtime/current
    fi
    if [[ -f ${work_dir:-}/previous.service ]]; then
      install -m 644 "$work_dir/previous.service" /etc/systemd/system/hivecode-runtime.service
    else
      rm -f /etc/systemd/system/hivecode-runtime.service
    fi
    systemctl daemon-reload || true
    if [[ -n ${previous_target:-} ]]; then systemctl restart hivecode-runtime.service || true
    else systemctl stop hivecode-runtime.service || true; fi
  fi
  if [[ ${stage_dir:-} == /opt/hivecode-runtime/.stage.* ]]; then rm -rf -- "$stage_dir"; fi
  if [[ ${work_dir:-} == /tmp/hivecode-install.* ]]; then rm -rf -- "$work_dir"; fi
  exit "$result"
}

extract_archive() {
  python3 - "$work_dir/package.tar.gz" "$stage_dir" <<'PY'
import os, pathlib, shutil, sys, tarfile
destination = pathlib.Path(sys.argv[2])
with tarfile.open(sys.argv[1], 'r:gz') as archive:
    members, seen, total = [], set(), 0
    for member in archive:
        name = member.name.rstrip('/')
        path = pathlib.PurePosixPath(name)
        if (not name or name.startswith('/') or '\\' in name or
            any(ord(char) < 32 or ord(char) == 127 for char in name) or
            any(part in ('', '.', '..') for part in name.split('/')) or
            path.parts[0] != 'hivecode-runtime' or name in seen or
            not (member.isdir() or member.isfile())):
            raise SystemExit('Unsafe Runtime archive member: ' + repr(member.name))
        seen.add(name)
        total += member.size
        if len(seen) > 100000 or total > 2147483648:
            raise SystemExit('Runtime archive exceeds extraction limits')
        members.append(member)
    for member in members:
        target = destination.joinpath(*pathlib.PurePosixPath(member.name).parts)
        if member.isdir():
            target.mkdir(parents=True, exist_ok=True)
            target.chmod(0o755)
        else:
            target.parent.mkdir(parents=True, exist_ok=True)
            with archive.extractfile(member) as source, target.open('xb') as output:
                shutil.copyfileobj(source, output)
            target.chmod(0o755 if member.mode & 0o111 else 0o644)
PY
}

check_standalone_upgrade() {
  # Build counters are independent per channel; version precedence comes first.
  python3 - /opt/hivecode-runtime/current/runtime-manifest.json "$architecture" "$version" "$build" <<'PY'
import json, re, sys

def version_key(value):
    match = re.fullmatch(r'([0-9]+)\.([0-9]+)\.([0-9]+)(?:-beta\.([0-9]+))?', value)
    if not match:
        raise ValueError('Invalid Runtime version')
    major, minor, patch, beta = match.groups()
    return (int(major), int(minor), int(patch), beta is None, int(beta or 0))

try:
    with open(sys.argv[1], encoding='utf-8') as source:
        installed = json.load(source)
    if (not isinstance(installed, dict) or installed.get('schemaVersion') != 1 or
        installed.get('packageName') != 'hivecode-runtime' or installed.get('architecture') != sys.argv[2] or
        type(installed.get('buildNumber')) is not int or not 1 <= installed['buildNumber'] <= 9007199254740991):
        raise ValueError('Invalid Runtime manifest')
    current = (version_key(installed['versionName']), installed['buildNumber'])
    requested = (version_key(sys.argv[3]), int(sys.argv[4]))
except (OSError, ValueError, TypeError, KeyError):
    raise SystemExit('Existing Runtime manifest is invalid; no version was activated.')
if current > requested:
    raise SystemExit('Installed Runtime version/build is newer; refusing a downgrade.')
PY
}

install_standalone() {
  local package_root manifest node_hash node_path target
  if command -v dpkg-query >/dev/null && [[ $(dpkg-query -W -f='${db:Status-Status}' hivecode-runtime 2>/dev/null || true) == installed ]]; then
    fail 'Runtime DEB is installed. Keep using --method deb, or remove that package before switching methods; preserve your state.'
  fi
  [[ ! -L /opt/hivecode-runtime && ! -L /opt/hivecode-runtime/versions ]] || fail 'Runtime installation directories must be real directories.'
  install -d -m 755 /opt/hivecode-runtime /opt/hivecode-runtime/versions
  if [[ -e /opt/hivecode-runtime/current && ! -L /opt/hivecode-runtime/current ]]; then
    fail 'The Runtime current path is not a managed symbolic link.'
  fi
  previous_target=$(readlink /opt/hivecode-runtime/current || true)
  if [[ -n $previous_target ]]; then
    [[ $previous_target =~ ^versions/[0-9]+\.[0-9]+\.[0-9]+(-beta\.[0-9]+)?-[0-9]+-(x64|arm64)$ ]] || fail 'Unexpected existing Runtime installation path.'
    check_standalone_upgrade
  fi
  stage_dir=$(mktemp -d /opt/hivecode-runtime/.stage.XXXXXXXX)
  extract_archive
  package_root="$stage_dir/hivecode-runtime"
  manifest="$package_root/runtime-manifest.json"
  jq -e --arg version "$version" --argjson build "$build" --arg arch "$architecture" '
    .schemaVersion == 1 and .packageName == "hivecode-runtime" and .versionName == $version and
    .buildNumber == $build and .architecture == $arch and .glibcMinimum == "2.31" and
    (.nodeVersion | type == "string" and test("^[0-9]+\\.[0-9]+\\.[0-9]+$"))' "$manifest" >/dev/null || fail 'Runtime archive identity does not match the release.'
  [[ -x $package_root/bin/hive && -f $package_root/orcad/orcad-server.js &&
     -f $package_root/orcad/node_modules/node-pty/build/Release/pty.node &&
     -f $package_root/orcad/node_modules/@parcel/watcher/watcher.node &&
     -f $package_root/orcad/out/web/web-index.html ]] || fail 'Runtime archive is incomplete.'
  [[ $(cat "$package_root/orcad/.server-target") == "linux-$architecture-glibc" ]] || fail 'Wrong Runtime target.'
  node_hash=$(cat "$package_root/orcad/.runtime-node")
  [[ $node_hash =~ ^[a-f0-9]{64}$ ]] || fail 'Invalid bundled Node identity.'
  node_path="$package_root/runtimes/node-$node_hash/bin/node"
  [[ -x $node_path ]] || fail 'Bundled Node executable is missing.'
  printf '%s  %s\n' "$node_hash" "$node_path" | sha256sum --check --status || fail 'Bundled Node verification failed.'
  [[ $("$node_path" --version) == "v$(jq -r '.nodeVersion' "$manifest")" ]] || fail 'Bundled Node cannot run or has the wrong version.'
  target="versions/$version-$build-$architecture"
  if [[ -e /opt/hivecode-runtime/$target ]]; then
    [[ ! -L /opt/hivecode-runtime/$target && $(cat "/opt/hivecode-runtime/$target/.artifact-sha256") == "$digest" ]] || fail 'This Runtime version directory already contains different bytes.'
  else
    printf '%s\n' "$digest" > "$package_root/.artifact-sha256"
    mv -- "$package_root" "/opt/hivecode-runtime/$target"
  fi
  if ! id -u hivecode-runtime >/dev/null 2>&1; then
    useradd --system --user-group --home-dir /var/lib/hivecode-runtime --shell /usr/sbin/nologin hivecode-runtime
  fi
  [[ $(id -u hivecode-runtime) != 0 && $(getent passwd hivecode-runtime | cut -d: -f6) == /var/lib/hivecode-runtime ]] || fail 'Runtime account must be unprivileged with home /var/lib/hivecode-runtime.'
  install -d -o hivecode-runtime -g hivecode-runtime -m 750 /var/lib/hivecode-runtime
  [[ ! -f /etc/systemd/system/hivecode-runtime.service ]] || cp /etc/systemd/system/hivecode-runtime.service "$work_dir/previous.service"
  cat > "$work_dir/runtime.service" <<'UNIT'
[Unit]
Description=HiveCode headless Runtime
After=network-online.target
Wants=network-online.target
StartLimitIntervalSec=300
StartLimitBurst=5
[Service]
Type=simple
User=hivecode-runtime
Group=hivecode-runtime
WorkingDirectory=/var/lib/hivecode-runtime
Environment=HOME=/var/lib/hivecode-runtime
Environment=HIVECODE_RUNTIME_ARGS=
EnvironmentFile=-/etc/hivecode/runtime.env
ExecStart=/opt/hivecode-runtime/current/bin/hive serve --port 6768 --json $HIVECODE_RUNTIME_ARGS --no-pairing
StandardOutput=journal
StandardError=journal
SyslogIdentifier=hivecode-runtime
KillMode=mixed
TimeoutStopSec=30
Restart=on-failure
RestartPreventExitStatus=3
RestartSec=5
NoNewPrivileges=true
ProtectSystem=full
UMask=0077
[Install]
WantedBy=multi-user.target
UNIT
  activation_pending=1
  install -m 644 "$work_dir/runtime.service" /etc/systemd/system/hivecode-runtime.service
  write_uninstaller
  ln -sfn -- "$target" /opt/hivecode-runtime/.current-new
  mv -Tf -- /opt/hivecode-runtime/.current-new /opt/hivecode-runtime/current
  if command -v restorecon >/dev/null; then restorecon -RF /opt/hivecode-runtime; fi
  runtime_cli=/opt/hivecode-runtime/current/bin/hive
}

write_uninstaller() {
  cat > "$work_dir/uninstall.sh" <<'UNINSTALL'
#!/usr/bin/env bash
set -euo pipefail
[[ $(id -u) == 0 ]] || { echo 'Run this uninstaller as root.' >&2; exit 1; }
[[ $(readlink -f /opt/hivecode-runtime) == /opt/hivecode-runtime && ! -L /opt/hivecode-runtime ]] || exit 1
exec 200>/run/lock/hivecode-runtime-install.lock
flock -n 200 || { echo 'Another Runtime operation is running.' >&2; exit 1; }
systemctl disable --now hivecode-runtime.service
rm -f /etc/systemd/system/hivecode-runtime.service /etc/systemd/system/hivecode-runtime.service.d/user-manager.conf
rmdir /etc/systemd/system/hivecode-runtime.service.d 2>/dev/null || true
if [[ $(readlink /usr/local/bin/hivecode-runtime) == /opt/hivecode-runtime/current/bin/hive ]]; then rm /usr/local/bin/hivecode-runtime; fi
systemctl daemon-reload
rm -rf -- /opt/hivecode-runtime
echo 'Runtime software removed. Projects, account state and /etc/hivecode/runtime.env were preserved.'
UNINSTALL
  install -m 755 "$work_dir/uninstall.sh" /opt/hivecode-runtime/uninstall.sh
}

main() {
  local channel=beta method=auto format extension architecture deb_arch ID VERSION_ID NAME libc metadata version build size digest address expected_version installed_version service_uid runtime_cli package
  while [[ $# -gt 0 ]]; do
    case "$1" in
      --channel) [[ $# -ge 2 ]] || fail '--channel needs beta or stable'; channel=$2; shift 2 ;;
      --method) [[ $# -ge 2 ]] || fail '--method needs auto, deb or standalone'; method=$2; shift 2 ;;
      --headless) shift ;;
      --help|-h) printf 'Usage: bash install.sh [--method auto|deb|standalone] [--channel beta|stable]\nLinux x64/ARM64, glibc 2.31+, running systemd required.\nAuto: Ubuntu/Debian use DEB; CentOS Stream 9+/openEuler 22.03+ use standalone.\n'; return ;;
      *) fail "Unknown argument: $1" ;;
    esac
  done
  [[ $channel == beta || $channel == stable ]] || fail 'Channel must be beta or stable.'
  [[ $method == auto || $method == deb || $method == standalone ]] || fail 'Method must be auto, deb or standalone.'
  [[ $(uname -s) == Linux ]] || fail 'This installer supports Linux only.'
  [[ -r /etc/os-release ]] || fail 'Cannot identify the Linux distribution.'
  . /etc/os-release
  case "${ID:-}" in
    ubuntu|debian)
      if [[ $method == auto ]]; then
        if [[ -L /opt/hivecode-runtime/current ]]; then method=standalone; else method=deb; fi
      fi ;;
    centos)
      if [[ ${NAME:-} != *Stream* ]] || ! at_least "${VERSION_ID:-}.0" 9 0; then
        fail 'CentOS Stream 9+ is required. CentOS Linux 7/8 are unsupported.'
      fi
      [[ $method != auto ]] || method=standalone ;;
    openEuler|openeuler)
      at_least "${VERSION_ID:-}" 22 3 || fail 'openEuler 22.03+ is required.'
      [[ $method != auto ]] || method=standalone ;;
    *) fail 'Supported distributions: Ubuntu, Debian, CentOS Stream 9+ and openEuler 22.03+.' ;;
  esac
  [[ $method != deb || $ID == ubuntu || $ID == debian ]] || fail 'DEB installation requires Ubuntu or Debian. Use --method standalone on this system.'
  format=tar_gz
  extension=tar.gz
  if [[ $method == deb ]]; then format=deb; extension=deb; fi
  for tool in getconf sha256sum systemctl journalctl loginctl flock stat install mktemp; do
    command -v "$tool" >/dev/null || fail "Required command missing: $tool"
  done
  libc=$(getconf GNU_LIBC_VERSION) || fail 'glibc 2.31+ is required; Alpine/musl is not supported.'
  if [[ $libc != 'glibc '* ]] || ! at_least "${libc#glibc }" 2 31; then fail 'glibc 2.31+ is required.'; fi
  if [[ $(id -u) != 0 ]]; then
    command -v sudo >/dev/null || fail 'Run as root or install sudo.'
    sudo -v || fail 'Administrator privileges are required to install the package and service.'
    # Preserve the installer when launched through a pipe; sudo receives a complete program.
    { printf 'set -euo pipefail\numask 077\n'; declare -f; printf 'main --method %q --channel %q\n' "$method" "$channel"; } | sudo -- bash
    return
  fi
  systemctl show --property=Version --value >/dev/null || fail 'A running systemd system manager is required.'
  exec 200>/run/lock/hivecode-runtime-install.lock
  flock -n 200 || fail 'Another HiveCode Runtime installation or removal is running.'
  if [[ -L /usr/local/bin/hivecode-runtime ]]; then
    case "$(readlink /usr/local/bin/hivecode-runtime)" in /usr/bin/hive|/opt/hivecode-runtime/current/bin/hive) ;; *) fail 'The Runtime command path is already used by another symbolic link.' ;; esac
  elif [[ -e /usr/local/bin/hivecode-runtime ]]; then
    fail 'The Runtime command path is already used by another file.'
  fi
  case "$(uname -m)" in
    x86_64) architecture=x64; deb_arch=amd64 ;;
    aarch64|arm64) architecture=arm64; deb_arch=arm64 ;;
    *) fail 'Supported CPU architectures: x64 and ARM64.' ;;
  esac
  if command -v dpkg-query >/dev/null && [[ $(dpkg-query -W -f='${db:Status-Status}' hivecode 2>/dev/null || true) == installed ]]; then
    fail 'The HiveCode desktop DEB is installed. Remove that package before installing hivecode-runtime; keep your project data.'
  fi

  work_dir=$(mktemp -d /tmp/hivecode-install.XXXXXXXX)
  stage_dir=''
  activation_pending=0
  trap cleanup EXIT
  info "Installing the official $channel Runtime for $architecture using $method."
  if [[ $method == deb ]]; then
    for tool in apt-get dpkg dpkg-deb dpkg-query; do command -v "$tool" >/dev/null || fail "Required command missing: $tool"; done
    [[ ! -e /opt/hivecode-runtime/current ]] || fail 'Standalone Runtime is installed. Keep using --method standalone, or uninstall it before changing methods.'
    [[ $(dpkg --print-architecture) == "$deb_arch" ]] || fail 'The operating system and CPU architecture do not match.'
  fi
  case "$ID" in
    ubuntu|debian)
      as_root env DEBIAN_FRONTEND=noninteractive apt-get update
      as_root env DEBIAN_FRONTEND=noninteractive apt-get install --yes --no-remove --no-install-recommends curl jq ca-certificates python3 git libstdc++6 dbus-user-session libpam-systemd ;;
    centos|openEuler|openeuler)
      command -v dnf >/dev/null || fail 'DNF is required to install system prerequisites.'
      as_root dnf install --assumeyes curl jq ca-certificates python3 git libstdc++ dbus-daemon dbus-tools systemd-pam util-linux ;;
  esac
  for tool in curl jq; do command -v "$tool" >/dev/null || fail "Required command missing after prerequisite installation: $tool"; done
  if [[ $method == standalone ]]; then
    for tool in python3 useradd getent cut readlink mv ln cp; do command -v "$tool" >/dev/null || fail "Required command missing: $tool"; done
  fi
  curl --fail --silent --show-error --proto '=https' --connect-timeout 15 --max-time 60 --max-filesize 1048576 \
    --retry 2 --retry-max-time 120 --output "$work_dir/release.json" --get \
    --data-urlencode product=hivecode --data-urlencode platform=linux --data-urlencode "architecture=$architecture" \
    --data-urlencode "channel=$channel" --data-urlencode currentVersion=0.0.0 --data-urlencode currentBuild=1 \
    --data-urlencode "packageFormat=$format" https://releases.hivekernel.com/hive/v1/updates/check
  metadata=$(read_release) || fail 'No usable official Runtime release. No Runtime package was installed.'
  IFS=$'\t' read -r version build size digest address <<< "$metadata"
  package="$work_dir/package.$extension"
  curl --fail --silent --show-error --location --max-redirs 3 --proto '=https' --proto-redir '=https' \
    --connect-timeout 15 --max-time 600 --retry 2 --retry-max-time 120 --max-filesize "$size" \
    --output "$package" "$address"
  [[ $(stat -c %s "$package") == "$size" ]] || fail 'Downloaded package size does not match.'
  printf '%s  %s\n' "$digest" "$package" | sha256sum --check --status || fail 'SHA-256 verification failed.'
  if [[ $method == standalone ]]; then
    install_standalone
  else
  expected_version="${version/-/\~}-$build"
  installed_version=$(dpkg-query -W -f='${Version}' hivecode-runtime 2>/dev/null || true)
  if [[ -n $installed_version ]] && dpkg --compare-versions "$installed_version" gt "$expected_version"; then
    fail "Installed version $installed_version is newer than $expected_version; refusing a downgrade."
  fi
  mv -- "$package" "$work_dir/hivecode-runtime.deb"
  [[ $(dpkg-deb --field "$work_dir/hivecode-runtime.deb" Package) == hivecode-runtime ]] || fail 'Wrong Debian package name.'
  [[ $(dpkg-deb --field "$work_dir/hivecode-runtime.deb" Architecture) == "$deb_arch" ]] || fail 'Wrong Debian package architecture.'
  [[ $(dpkg-deb --field "$work_dir/hivecode-runtime.deb" Version) == "$expected_version" ]] || fail 'Debian package version does not match the release.'
  info "Verified HiveCode Runtime $version (build $build)."
  # APT's sandbox must be able to read the already verified local package.
  chmod 755 "$work_dir"
  chmod 644 "$work_dir/hivecode-runtime.deb"
  as_root env DEBIAN_FRONTEND=noninteractive apt-get install --yes --no-remove --no-install-recommends \
    "$work_dir/hivecode-runtime.deb" dbus-user-session libpam-systemd git ca-certificates
  [[ $(dpkg-query -W -f='${Version}' hivecode-runtime) == "$expected_version" ]] || fail 'Installed package version verification failed.'
  runtime_cli=/usr/bin/hive
  fi

  # The detached terminal daemon uses a real user manager so terminal sessions survive service restarts.
  service_uid=$(id -u hivecode-runtime)
  [[ $service_uid =~ ^[0-9]+$ && $service_uid != 0 ]] || fail 'The Runtime service must use its dedicated unprivileged account.'
  as_root loginctl enable-linger hivecode-runtime
  as_root systemctl start "user@$service_uid.service"
  as_root test -S "/run/user/$service_uid/bus" || fail 'The service account has no systemd user bus. Inspect user@ UID.service before starting Runtime.'
  printf '[Unit]\nWants=user@%s.service\nAfter=user@%s.service\n[Service]\nEnvironment=XDG_RUNTIME_DIR=/run/user/%s\nEnvironment=DBUS_SESSION_BUS_ADDRESS=unix:path=/run/user/%s/bus\n' "$service_uid" "$service_uid" "$service_uid" "$service_uid" > "$work_dir/user-manager.conf"
  as_root install -d -m 755 /etc/systemd/system/hivecode-runtime.service.d
  as_root install -m 644 "$work_dir/user-manager.conf" /etc/systemd/system/hivecode-runtime.service.d/user-manager.conf
  if ! as_root test -e /etc/hivecode/runtime.env; then
    printf 'HIVECODE_RUNTIME_ARGS=--no-pairing\n' > "$work_dir/runtime.env"
    as_root install -d -m 755 /etc/hivecode
    as_root install -m 644 "$work_dir/runtime.env" /etc/hivecode/runtime.env
  fi
  as_root systemctl daemon-reload
  as_root systemctl reset-failed hivecode-runtime.service
  as_root systemctl enable hivecode-runtime.service
  as_root systemctl restart hivecode-runtime.service
  wait_for_runtime
  install -d -m 755 /usr/local/bin
  ln -sfn -- "$runtime_cli" /usr/local/bin/hivecode-runtime
  activation_pending=0
  info 'Runtime is ready and enabled at boot. Existing state and configuration were preserved.'
  printf '\nClaim this Runtime with your HiveCode account:\n'
  if command -v sudo >/dev/null; then
    printf '  sudo -u hivecode-runtime -H /usr/local/bin/hivecode-runtime runtime claim\n'
  else
    printf '  runuser -u hivecode-runtime -- /usr/local/bin/hivecode-runtime runtime claim\n'
  fi
  printf '\nStatus: sudo systemctl status hivecode-runtime.service\nLogs: sudo journalctl -u hivecode-runtime.service -f\n'
  printf 'Configuration: /etc/hivecode/runtime.env\nState: /var/lib/hivecode-runtime\n'
}

main "$@"
