#!/usr/bin/env bash
# Root-owned socket helper. The only request is: <attempt UUID> <published commit>.
set -Eeuo pipefail
DOWNLOAD_ROOT="https://downloads.nilo-assistant.com/nilo-runner/linux/releases"
BINARY_DIR=/usr/local/bin
HELPER_DIR=/usr/local/libexec
RECEIPT_DIR=/var/lib/nilo-runner-update
LOCK_FILE=/run/nilo-runner-update/update.lock
FAILURE_CODE=INVALID_RELEASE

progress() { printf '{"stage":"%s"}\n' "$1" || true; }
failed() { printf '{"stage":"FAILED","failureCode":"%s"}\n' "$FAILURE_CODE" || true; }
validate_command() {
  [[ -z "${3:-}" && "$1" =~ ^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$ && "$2" =~ ^[0-9a-f]{40}$ ]]
}
fetch() { curl --fail --silent --show-error --proto '=https' --connect-timeout 10 --max-time 120 -o "$2" "$1"; }
verified_artifact() {
  local artifact="$1" expected actual
  expected=$(awk -v artifact="$artifact" '$2 == artifact {print $1}' "$TEMP_DIR/SHA256SUMS")
  [[ "$expected" =~ ^[0-9a-f]{64}$ ]] || return 1
  fetch "$DOWNLOAD_ROOT/$RELEASE/$artifact" "$TEMP_DIR/$artifact" || return 1
  actual=$(sha256sum "$TEMP_DIR/$artifact" | awk '{print $1}')
  [[ "$actual" == "$expected" ]]
}
replace_file() {
  local source="$1" destination="$2" staged
  staged=$(mktemp "$(dirname "$destination")/.nilo-update.XXXXXX")
  if ! install -m 0755 "$source" "$staged" || ! mv -f "$staged" "$destination"; then
    rm -f "$staged"
    return 1
  fi
}
apply_update() {
  local attempt="$1" architecture
  RELEASE="$2"
  validate_command "$attempt" "$RELEASE" || return 1
  case "$(uname -m)" in x86_64) architecture=amd64 ;; aarch64|arm64) architecture=arm64 ;; *) return 1 ;; esac
  TEMP_DIR=$(mktemp -d)
  trap 'rm -rf "$TEMP_DIR"' EXIT
  FAILURE_CODE=DOWNLOAD_FAILED
  progress DOWNLOADING
  fetch "$DOWNLOAD_ROOT/$RELEASE/SHA256SUMS" "$TEMP_DIR/SHA256SUMS" || return 1
  FAILURE_CODE=VERIFICATION_FAILED
  verified_artifact "nilo-runner-linux-$architecture" || return 1
  verified_artifact updater.sh || return 1
  chmod 0755 "$TEMP_DIR/nilo-runner-linux-$architecture"
  [[ "$("$TEMP_DIR/nilo-runner-linux-$architecture" --version)" == "$RELEASE" ]] || return 1
  bash -n "$TEMP_DIR/updater.sh" || return 1
  FAILURE_CODE=INSTALL_FAILED
  progress INSTALLING
  replace_file "$TEMP_DIR/updater.sh" "$HELPER_DIR/nilo-runner-updater" || return 1
  replace_file "$TEMP_DIR/nilo-runner-linux-$architecture" "$BINARY_DIR/nilo-runner-linux" || return 1
  local receipt
  receipt=$(mktemp "$RECEIPT_DIR/.receipt.XXXXXX")
  printf '{"attemptId":"%s","version":"%s"}\n' "$attempt" "$RELEASE" > "$receipt"
  chmod 0640 "$receipt"
  mv -f "$receipt" "$RECEIPT_DIR/receipt.json"
  FAILURE_CODE=RESTART_FAILED
  # Queuing a restart does not confirm success; the new process reports its receipt.
  systemctl --no-block restart nilo-runner.service || return 1
  progress RESTARTING
}
main() {
  [[ "$EUID" -eq 0 ]]
  trap 'trap - ERR; failed; exit 1' ERR
  local attempt release extra
  IFS=' ' read -r -n 100 attempt release extra
  if ! validate_command "$attempt" "$release" "$extra"; then return 1; fi
  exec 9>"$LOCK_FILE"
  FAILURE_CODE=RUNNER_BUSY
  flock --nonblock 9
  FAILURE_CODE=INVALID_RELEASE
  apply_update "$attempt" "$release"
}
if [[ "${BASH_SOURCE[0]:-$0}" == "$0" ]]; then main; fi
