#!/usr/bin/env bash
# Latent robot install (docs/robot-connect-contract.md 6.1 and 6.2; task start plan, phase 3).
#
#   curl -fsSL https://latentenergy.com/robot/install.sh | sudo bash -s -- --enroll enr_<token> [--no-people] [--origin URL]
#
# Puts Almond's control panel software on the version latent-axol was checked
# against, installs the Latent hooks into the Axol control panel and the Latent
# agent, starts the agent, the maintenance timer and the Almond pin timer, then
# pairs the robot with the provider console: with --enroll the robot appears in
# "Connect a robot" for one-click approval; without it, the robot prints a code
# to enter at https://latentenergy.com/pair. Stops at the first error. Pipes
# nothing else into a shell. The whole script is one function, run only after
# bash has read all of it, so no command can swallow the rest of the piped text.
#
# Nothing here moves the arms. It refuses to run while an operation runs in the
# control panel (GET /api/op/status on loopback, the only panel route it reads
# besides /api/update/status), and it never reboots: when Almond's provisioning
# asks for a reboot it says so and leaves it to the operator.
#
# Almond (ALMOND_VER, the last of SUPPORTED_SDK in
# tools/latent-axol/latent_axol/sdk.py): when almond-axol is missing or on
# another version that the running hooks do not accept, it is installed the way
# Almond's own installer and its Update do it (uv tool install
# "almond-axol[lerobot,sim]==ALMOND_VER" from PyPI into /opt/axol/uv/tools, then
# "axol provision --require-rt --no-reboot"). Almond's installer must have run
# once (it writes /etc/systemd/system/axol.service). After the install, the
# root timer latent-almond-pin puts the same version back when the panel was
# updated to an unverified one, only after it has been idle for 2 minutes, at
# most every 6 hours, and never with a reboot (owner decision J);
# LATENT_ALMOND_REPIN=off in /etc/latent-axol/env turns it off. Both reinstalls
# run the way Almond serializes its own: never while Almond reports an Update
# or its startup heal (GET /api/update/status "updating" or "installing"),
# holding Almond's host update lock (/var/lib/almond-axol/update.lock, passed to
# axol provision as AXOL_UPDATE_LOCK_FD), with the control panel stopped (only
# while it is idle) so no session, heal or Update can start on a half-written
# environment, and with /var/lib/latent-axol/almond-pin.busy present, which the
# agent's Ready check and the hooks read to refuse a start from the headset.
#
# LATENT_START (the start page's kill switch, read by the agent): written as
# "observe", so the robot only logs what it would start; an "off" already in
# /etc/latent-axol/env is kept, and a "headset" is put back to observe with one
# line saying so (the owner sets headset again after checking this version). A
# ~/.latent/latent.env of the operator that points the CLI at another server or
# key is renamed, with one line saying so.
#
# The people-in-frame check (p2p3 addendum P) is installed into the agent's
# environment too, with its two detector models; it only warns on the panel
# after a save, and a failure to install it never stops the install.
# --no-people leaves it out.
#
# --origin names the server both wheels come from and the robot pairs with:
# https://latentenergy.com (the default) or https://staging.latentenergy.com,
# the two origins latent-axol connects to (tools/latent-axol/latent_axol/origin.py
# PRODUCTION_ORIGINS). Anything else is refused before the install starts.
#
# Release step (owner): set VER and WHEEL_SHA256 below to the served wheel in
# public/tools and its line of public/tools/SHA256SUMS, and CHECKER_VER and
# CHECKER_SHA256 likewise for the latent-checker wheel
# (`node scripts/robot-release.mjs` writes all four); tests/unit/robot-install.test.ts
# fails while they disagree.

main() {
  set -eu
  VER=0.3.2
  WHEEL_SHA256=135fe5a49a96ce84dbe558a4f668e6dfadde094f1cb135d0fc3f87cccd7c5443
  CHECKER_VER=0.1.3
  CHECKER_SHA256=a1236ad3eea6eec8f360882629353e3f6a0067595968f03230f6184ba2337362
  ALMOND_VER=0.2.25
  ORIGIN=https://latentenergy.com
  ENROLL=""
  PEOPLE=1

  while [ $# -gt 0 ]; do
    case "$1" in
      --enroll) ENROLL="${2:-}"; shift 2 || shift ;;
      --enroll=*) ENROLL="${1#--enroll=}"; shift ;;
      --no-people) PEOPLE=0; shift ;;
      --origin) ORIGIN="${2:-}"; shift 2 || shift ;;
      --origin=*) ORIGIN="${1#--origin=}"; shift ;;
      *) echo "latent install: unknown option $1" >&2; return 2 ;;
    esac
  done

  fail() { echo "latent install: $*" >&2; exit 1; }

  # The origins latent-axol connects to (origin.py PRODUCTION_ORIGINS); one trailing slash is dropped, as there.
  ORIGIN="${ORIGIN%/}"
  case "$ORIGIN" in
    https://latentenergy.com | https://staging.latentenergy.com) ;;
    *) fail "--origin must be https://latentenergy.com or https://staging.latentenergy.com" ;;
  esac

  # The people check into the agent's environment; returns non-zero on any problem, never exits.
  people_install() {
    AGENT_PY=/opt/latent/uv/tools/latent-axol/bin/python
    curl -fsSL --proto '=https' -o "$CHECKER_WHEEL.part" "$ORIGIN/tools/latent_checker-$CHECKER_VER-py3-none-any.whl" </dev/null || return 1
    echo "$CHECKER_SHA256  $CHECKER_WHEEL.part" | sha256sum -c - >/dev/null || { rm -f "$CHECKER_WHEEL.part"; return 1; }
    mv "$CHECKER_WHEEL.part" "$CHECKER_WHEEL" || return 1
    /usr/local/bin/uv pip install --python "$AGENT_PY" "$CHECKER_WHEEL" "numpy==2.4.6" "opencv-python-headless==5.0.0.93" </dev/null || return 1
    install -d -m 0755 /opt/latent/models || return 1
    "$AGENT_PY" -m latent_checker models fetch --dir /opt/latent/models </dev/null >/dev/null || return 1
  }

  # The control panel on loopback: "busy" while an operation runs or the robot is reserved, "idle", or "down" when
  # it does not answer (then nothing can run). Read only; the panel serves a self-signed certificate.
  panel_state() {
    ST=$(curl -fsS -k --max-time 5 --proto '=https' https://127.0.0.1:8001/api/op/status </dev/null 2>/dev/null) || { echo down; return 0; }
    if printf '%s' "$ST" | grep -Eq '"(running|lockout)": ?true'; then echo busy; else echo idle; fi
  }

  # Almond rewrites or provisions its own software right now: an Update ("updating"), or the startup heal a fresh
  # control panel process runs ("installing": true while its state reads idle). Read only.
  almond_busy() {
    curl -fsS -k --max-time 5 --proto '=https' https://127.0.0.1:8001/api/update/status </dev/null 2>/dev/null | grep -Eq '"state": ?"updating"|"installing": ?true'
  }

  # The installed almond-axol version, from its dist-info in the Axol tool environment; empty when missing.
  almond_version() {
    for D in /opt/axol/uv/tools/almond-axol/lib/python*/site-packages/almond_axol-*.dist-info; do
      [ -d "$D" ] || continue
      D="${D##*/almond_axol-}"
      echo "${D%.dist-info}"
      return 0
    done
    echo ""
  }

  [ "$(uname -s)" = "Linux" ] || fail "this installs on the robot (Linux) only"
  command -v systemctl >/dev/null 2>&1 || fail "systemd is required"
  [ "$(id -u)" -eq 0 ] || fail "run it with sudo"
  if [ -n "$ENROLL" ]; then
    printf '%s' "$ENROLL" | grep -Eq '^enr_[A-Za-z0-9_-]{43}$' || fail "the --enroll token is malformed; copy the line again from Connect a robot"
  fi
  # Nothing is changed while the arms may be working: the operator ends the session in the control panel first.
  [ "$(panel_state)" != "busy" ] || fail "an operation is running in the Axol control panel. End it there, then run this line again. Nothing was changed"
  [ -f /etc/systemd/system/axol.service ] || fail "Almond's control panel is not set up on this robot (/etc/systemd/system/axol.service is missing). Run Almond's installer once (docs.almond.bot), then this line again"
  [ -x /usr/local/bin/uv ] || fail "uv is missing at /usr/local/bin/uv (it comes with the Almond install)"

  # The operator: the account that ran sudo, else the owner of the first /home directory (Almond's rule).
  if [ -n "${SUDO_USER:-}" ] && [ "${SUDO_USER}" != "root" ]; then
    OP="$SUDO_USER"
  else
    FIRST_HOME=$(ls -d /home/*/ 2>/dev/null | head -n 1 || true)
    [ -n "$FIRST_HOME" ] || fail "no operator account under /home"
    OP=$(stat -c %U "$FIRST_HOME")
  fi
  id "$OP" >/dev/null 2>&1 || fail "operator account $OP does not exist"

  AXOL_PY=/opt/axol/uv/tools/almond-axol/bin/python
  WHEEL=/opt/latent/wheels/latent_axol-$VER-py3-none-any.whl
  CHECKER_WHEEL=/opt/latent/wheels/latent_checker-$CHECKER_VER-py3-none-any.whl

  echo "latent install: latent-axol $VER and almond-axol $ALMOND_VER for operator $OP from $ORIGIN"

  # 0. Almond's software on the verified version, the way Almond's installer and its Update install it, with its
  #    provisioning and no reboot. Skipped when it is already there, or when the running hooks accept the installed
  #    version (a signed release list verified it).
  ALMOND_NOW=$(almond_version)
  ALMOND_PINNED=0
  PANEL_STOPPED=0
  PIN_BUSY=/var/lib/latent-axol/almond-pin.busy
  # On any exit: no busy marker left behind, and a control panel this run stopped is started again.
  on_exit() {
    rm -f "$PIN_BUSY"
    if [ "$PANEL_STOPPED" = 1 ]; then systemctl start axol.service || true; fi
  }
  trap on_exit EXIT
  if [ "$ALMOND_NOW" != "$ALMOND_VER" ] && ! { [ -n "$ALMOND_NOW" ] && grep -q '"pin_ok": true' /run/latent-axol-hooks/state.json 2>/dev/null && grep -q "\"sdk_version\": \"$ALMOND_NOW\"" /run/latent-axol-hooks/state.json 2>/dev/null; }; then
    ! almond_busy || fail "Almond is installing or provisioning its software right now. Wait a few minutes, then run this line again. Nothing was changed"
    # Almond's host update lock (almond_axol/utils/host_update_lock.py): no Almond install, provisioning or startup
    # heal overlaps this one; axol provision inherits it through AXOL_UPDATE_LOCK_FD.
    install -d -m 0751 -o root -g root /var/lib/almond-axol
    exec 9<>/var/lib/almond-axol/update.lock
    chown root:root /var/lib/almond-axol/update.lock
    chmod 0600 /var/lib/almond-axol/update.lock
    flock -n 9 || fail "another Almond install, update or provisioning is running. Wait for it to finish, then run this line again. Nothing was changed"
    install -d -m 0755 /var/lib/latent-axol
    date +%s > "$PIN_BUSY"
    # The control panel stops for the reinstall, only while it is idle: nothing can start on a half-written environment.
    [ "$(panel_state)" != "busy" ] || fail "an operation started in the Axol control panel. End it there, then run this line again. Nothing was changed"
    echo "latent install: almond-axol ${ALMOND_NOW:-is missing}${ALMOND_NOW:+ is not verified}; installing $ALMOND_VER with its provisioning (a few minutes, no reboot, the control panel is stopped meanwhile)"
    if systemctl is-active --quiet axol.service; then systemctl stop axol.service; PANEL_STOPPED=1; fi
    install -d -m 0755 /opt/axol/uv/tools /opt/axol/uv/python
    env UV_TOOL_DIR=/opt/axol/uv/tools UV_PYTHON_INSTALL_DIR=/opt/axol/uv/python UV_TOOL_BIN_DIR=/usr/local/bin \
      /usr/local/bin/uv tool install --python 3.13 --force "almond-axol[lerobot,sim]==$ALMOND_VER" </dev/null
    chmod -R a+rX /opt/axol/uv/tools /opt/axol/uv/python
    PROVISION_RC=0
    AXOL_UPDATE_LOCK_FD=9 /usr/local/bin/axol provision --require-rt --no-reboot </dev/null || PROVISION_RC=$?
    if [ "$PROVISION_RC" -eq 3 ]; then
      echo "latent install: Almond's provisioning skipped some optional features (named above); the control panel works without them" >&2
    elif [ "$PROVISION_RC" -ne 0 ]; then
      fail "Almond's provisioning failed (exit $PROVISION_RC, the step is named above). Fix that step, then run this line again"
    fi
    [ "$(almond_version)" = "$ALMOND_VER" ] || fail "almond-axol $ALMOND_VER did not end up in /opt/axol/uv/tools/almond-axol"
    exec 9>&-
    rm -f "$PIN_BUSY"
    ALMOND_PINNED=1
  fi
  [ -x "$AXOL_PY" ] || fail "Almond's Axol software is not installed here (/opt/axol/uv/tools/almond-axol is missing)"

  # 1. directories, and the wheel checked against the pinned hash
  install -d -m 0755 /opt/latent /opt/latent/wheels /opt/latent/bin /etc/latent-axol /var/lib/latent-axol
  install -d -m 0700 /var/lib/latent-axol/inbox
  curl -fsSL --proto '=https' -o "$WHEEL.part" "$ORIGIN/tools/latent_axol-$VER-py3-none-any.whl" </dev/null
  echo "$WHEEL_SHA256  $WHEEL.part" | sha256sum -c - >/dev/null || { rm -f "$WHEEL.part"; fail "the downloaded wheel does not match its published hash"; }
  mv "$WHEEL.part" "$WHEEL"

  # 2. the hooks, into the Axol tool environment (the SDK's own plugin idiom)
  /usr/local/bin/uv pip install --python "$AXOL_PY" --no-deps --reinstall "$WHEEL" </dev/null

  # 3. the agent, as its own uv tool with its own Python, untouched by panel Updates
  env UV_TOOL_DIR=/opt/latent/uv/tools UV_TOOL_BIN_DIR=/opt/latent/bin UV_PYTHON_INSTALL_DIR=/opt/latent/uv/python \
    /usr/local/bin/uv tool install --force --python 3.13 --with "pyarrow>=14" "$WHEEL" </dev/null

  # 3b. people in frame (optional): latent-checker, numpy and OpenCV in the agent's environment and
  #     the two detector models (pinned commits, SHA-256 checked); a failure never stops the install
  PEOPLE_OK=0
  if [ "$PEOPLE" = 1 ]; then
    if people_install; then
      PEOPLE_OK=1
      echo "latent install: people-in-frame check installed"
    else
      echo "latent install: the people-in-frame check was not installed (optional); the agent runs without it" >&2
    fi
  fi
  chmod -R a+rX /opt/latent

  # 4. the environment file both units load (root-owned): no LATENT_ORIGIN (the credential names its origin).
  #    LATENT_START starts in observe; an "off" already set stays off, and so does an Almond re-pin turned off.
  START_MODE=observe
  if grep -qx 'LATENT_START=off' /etc/latent-axol/env 2>/dev/null; then START_MODE=off; fi
  if grep -qx 'LATENT_START=headset' /etc/latent-axol/env 2>/dev/null; then
    echo "latent install: LATENT_START was headset; it is observe again until the owner checks latent-axol $VER and sets headset in /etc/latent-axol/env"
  fi
  REPIN=on
  if grep -qx 'LATENT_ALMOND_REPIN=off' /etc/latent-axol/env 2>/dev/null; then REPIN=off; fi
  cat > /etc/latent-axol/env <<ENVFILE
LATENT_HOME=/home/$OP/.latent
LATENT_RUNTIME_DIR=/run/latent-axol
LATENT_OPERATOR=$OP
LATENT_AXOL_WHEEL=$WHEEL
LATENT_CARD_BIND=0.0.0.0:8790
LATENT_AUTO_UPDATE=needed
LATENT_PANEL_AUTO_RESTART=1
LATENT_START=$START_MODE
LATENT_ALMOND_PIN=$ALMOND_VER
LATENT_ALMOND_REPIN=$REPIN
ENVFILE
  if [ "$PEOPLE_OK" = 1 ]; then
    printf 'LATENT_CHECKER_WHEEL=%s\nLATENT_CHECKER_MODELS=/opt/latent/models\n' "$CHECKER_WHEEL" >> /etc/latent-axol/env
  fi
  chown root:root /etc/latent-axol/env
  chmod 0644 /etc/latent-axol/env
  install -d -m 0700 -o "$OP" -g "$(id -gn "$OP")" "/home/$OP/.latent"
  printf '{"version":"%s","sha256":"%s","installed_at":%s}\n' "$VER" "$WHEEL_SHA256" "$(date +%s)" > /var/lib/latent-axol/installed.json

  # 4b. a leftover latent.env that points the CLI at another server or key (28 Oct: localhost:3000) is renamed;
  #     the robot credential then decides both
  STRAY="/home/$OP/.latent/latent.env"
  if [ -f "$STRAY" ] && [ ! -L "$STRAY" ] && grep -Eq '^[[:space:]]*(export[[:space:]]+)?LATENT_(ORIGIN|API_KEY)=' "$STRAY"; then
    MOVED="$STRAY.disabled-$(date +%s)"
    mv "$STRAY" "$MOVED"
    echo "latent install: renamed $STRAY to $MOVED: it pointed latent-axol at another server or key"
  fi

  # 5. units, drop-in, wrapper, apply script and login line, from the package's own texts
  AFF=$("$AXOL_PY" -c "from almond_axol.utils.affinity import core_groups; g = core_groups(); print(' '.join(map(str, sorted(g['background']))) if g else '')" </dev/null 2>/dev/null || true)
  /opt/latent/bin/latent-axol system-files --write --operator "$OP" --cpu-affinity "$AFF" </dev/null

  # 5b. the Almond pin timer (owner decision J): puts ALMOND_VER back when a panel Update moved it, only when idle
  cat > /opt/latent/bin/latent-almond-pin <<'PINSCRIPT'
#!/bin/sh
# Puts almond-axol back on the version latent-axol was checked against (LATENT_ALMOND_PIN) after a control panel
# Update moved it, the way Almond installs it (uv tool install, then axol provision --no-reboot), then the Latent
# hooks, then starts the control panel again. Only when the panel answered idle (no operation, no lockout) on this
# run and on one at least 2 minutes before, at most once every 6 hours, never while Almond installs or provisions
# its software (an Update, or the startup heal: update/status "updating" or "installing"), never while the Latent
# maintenance runs, and never with a reboot. The reinstall holds Almond's host update lock (axol provision inherits
# it), keeps almond-pin.busy in place (the agent and the hooks refuse a start from the headset meanwhile) and runs
# with the control panel stopped, so no session, heal or Update overlaps it; the panel starts again on any exit.
# LATENT_ALMOND_REPIN=off in /etc/latent-axol/env turns it off. Nothing here moves the arms. Written by
# public/robot/install.sh.
set -eu
ENV=/etc/latent-axol/env
STATE=/var/lib/latent-axol
IDLE="$STATE/almond-pin.idle-since"
LAST="$STATE/almond-pin.last"
PENDING="$STATE/almond-pin.restart-pending"
BUSY="$STATE/almond-pin.busy"
LOCK=/var/lib/almond-axol/update.lock
val() { grep -E "^$1=" "$ENV" 2>/dev/null | tail -n 1 | cut -d= -f2-; }
[ "$(val LATENT_ALMOND_REPIN)" != "off" ] || exit 0
PIN=$(val LATENT_ALMOND_PIN)
WHEEL=$(val LATENT_AXOL_WHEEL)
printf '%s' "$PIN" | grep -Eq '^[0-9]+(\.[0-9]+)+$' || exit 0
now=$(date +%s)
state() {
  ST=$(curl -fsS -k --max-time 5 --proto '=https' https://127.0.0.1:8001/api/op/status </dev/null 2>/dev/null) || { echo down; return 0; }
  if printf '%s' "$ST" | grep -Eq '"(running|lockout)": ?true'; then echo busy; else echo idle; fi
}
installed() {
  for D in /opt/axol/uv/tools/almond-axol/lib/python*/site-packages/almond_axol-*.dist-info; do
    [ -d "$D" ] || continue
    D="${D##*/almond_axol-}"
    echo "${D%.dist-info}"
    return 0
  done
  echo ""
}
almond_busy() {
  curl -fsS -k --max-time 5 --proto '=https' https://127.0.0.1:8001/api/update/status </dev/null 2>/dev/null | grep -Eq '"state": ?"updating"|"installing": ?true'
}
# Idle now, and since a run at least 2 minutes ago; anything else starts the count again.
if [ "$(state)" != "idle" ]; then rm -f "$IDLE"; exit 0; fi
[ -f "$IDLE" ] || { echo "$now" > "$IDLE"; exit 0; }
since=$(cat "$IDLE" 2>/dev/null || echo "$now")
[ $((now - since)) -ge 120 ] || exit 0
CUR=$(installed)
if [ "$CUR" = "$PIN" ]; then
  # A pin that could not restart the panel at once restarts it now, still idle.
  if [ -f "$PENDING" ]; then rm -f "$PENDING"; systemctl restart axol.service; echo "latent-almond-pin: control panel restarted on almond-axol $PIN"; fi
  exit 0
fi
# The running hooks accept this version (a signed release list verified it): leave it.
if [ -n "$CUR" ] && grep -q '"pin_ok": true' /run/latent-axol-hooks/state.json 2>/dev/null && grep -q "\"sdk_version\": \"$CUR\"" /run/latent-axol-hooks/state.json 2>/dev/null; then exit 0; fi
last=$(cat "$LAST" 2>/dev/null || echo 0)
[ $((now - last)) -ge 21600 ] || exit 0
systemctl is-active --quiet latent-axol-maint.service && exit 0
if almond_busy; then exit 0; fi
# Almond's host update lock (almond_axol/utils/host_update_lock.py): held for the whole reinstall.
install -d -m 0751 -o root -g root /var/lib/almond-axol
exec 9<>"$LOCK"
chown root:root "$LOCK"
chmod 0600 "$LOCK"
flock -n 9 || exit 0
STOPPED=0
on_exit() {
  rm -f "$BUSY"
  if [ "$STOPPED" = 1 ]; then systemctl start axol.service || true; fi
}
trap on_exit EXIT
echo "$now" > "$BUSY"
# Right before the panel stops: still idle, and Almond still not installing (the marker already holds off a start).
if [ "$(state)" != "idle" ] || almond_busy; then exit 0; fi
echo "$now" > "$LAST"
echo "latent-almond-pin: almond-axol ${CUR:-is missing}${CUR:+ is not verified for Latent}; installing $PIN with the control panel stopped"
systemctl stop axol.service
STOPPED=1
install -d -m 0755 /opt/axol/uv/tools /opt/axol/uv/python
env UV_TOOL_DIR=/opt/axol/uv/tools UV_PYTHON_INSTALL_DIR=/opt/axol/uv/python UV_TOOL_BIN_DIR=/usr/local/bin \
  /usr/local/bin/uv tool install --python 3.13 --force "almond-axol[lerobot,sim]==$PIN" </dev/null
chmod -R a+rX /opt/axol/uv/tools /opt/axol/uv/python
RC=0
AXOL_UPDATE_LOCK_FD=9 /usr/local/bin/axol provision --require-rt --no-reboot </dev/null || RC=$?
[ "$RC" -eq 0 ] || [ "$RC" -eq 3 ] || { echo "latent-almond-pin: axol provision failed (exit $RC); the control panel starts again, the next try is in 6 hours" >&2; exit 1; }
if [ -n "$WHEEL" ] && [ -f "$WHEEL" ]; then
  /usr/local/bin/uv pip install --python /opt/axol/uv/tools/almond-axol/bin/python --no-deps --reinstall "$WHEEL" </dev/null
fi
[ -e /run/almond-axol/reboot-required ] && echo "latent-almond-pin: Almond asks for a reboot to finish; it is left to the operator"
systemctl start axol.service
STOPPED=0
rm -f "$PENDING"
echo "latent-almond-pin: control panel started on almond-axol $PIN"
PINSCRIPT
  chmod 0755 /opt/latent/bin/latent-almond-pin
  cat > /etc/systemd/system/latent-almond-pin.service <<'PINUNIT'
[Unit]
Description=Latent: keep almond-axol on the verified version (only when the control panel is idle)
After=network-online.target
Wants=network-online.target

[Service]
Type=oneshot
ExecStart=/opt/latent/bin/latent-almond-pin
Nice=10
IOSchedulingClass=idle
TimeoutStartSec=45min
PINUNIT
  cat > /etc/systemd/system/latent-almond-pin.timer <<'PINTIMER'
[Unit]
Description=Check the almond-axol version every 2 minutes

[Timer]
OnBootSec=3min
OnUnitInactiveSec=2min
RandomizedDelaySec=20s

[Install]
WantedBy=timers.target
PINTIMER
  systemctl daemon-reload

  # 6. start the agent, the maintenance timer and the pin timer; the first maintenance run restarts the
  #    control panel as soon as it is idle
  systemctl enable --now latent-axol-agent.service latent-axol-maint.timer latent-almond-pin.timer
  # An upgrade replaces the agent's files under a running process: restart it so the new version runs now.
  systemctl restart latent-axol-agent.service
  systemctl start --no-block latent-axol-maint.service

  # 6b. a fresh Almond loads with the hooks once the panel restarts: now when idle, else at its next idle run
  if [ "$ALMOND_PINNED" = 1 ]; then
    if [ "$PANEL_STOPPED" = 1 ]; then
      systemctl start axol.service
      PANEL_STOPPED=0
      echo "latent install: the control panel started on almond-axol $ALMOND_VER (about a minute)"
    elif [ "$(panel_state)" = "busy" ]; then
      touch /var/lib/latent-axol/almond-pin.restart-pending
      echo "latent install: the control panel restarts on almond-axol $ALMOND_VER once the running operation ends"
    else
      systemctl restart axol.service
      echo "latent install: the control panel restarted on almond-axol $ALMOND_VER (about a minute)"
    fi
    if [ -e /run/almond-axol/reboot-required ]; then
      echo "latent install: Almond needs one reboot to finish (a camera driver). Reboot the robot when the arms are at rest."
    fi
  fi

  # 7. pair; the agent keeps waiting if this terminal closes
  if [ -t 1 ]; then WAIT=""; else WAIT="--no-wait"; fi
  if [ -n "$ENROLL" ]; then
    sudo -u "$OP" /opt/latent/bin/latent-axol pair --origin "$ORIGIN" --enroll "$ENROLL" $WAIT </dev/null
  else
    sudo -u "$OP" /opt/latent/bin/latent-axol pair --origin "$ORIGIN" $WAIT </dev/null
  fi
}

main "$@"
