#!/bin/sh
# Igneum rig first boot (root, once; igneum-rig-firstboot.service). Debian 13 only; every package from Debian's own
# archive (trixie, trixie-backports, non-free, non-free-firmware) except the NVIDIA driver, which comes from NVIDIA's
# own Debian 13 repository (13 October 2026, main's word: Debian's nvidia-driver 550 cannot probe Blackwell).
#   NVIDIA: nvidia-open (the open kernel module, 615 at the time of writing; Turing and later: the 2060, 3060, 4070,
#        5060 Ti, A5000) from developer.download.nvidia.com's debian13 repository by its cuda-keyring package
#   AMD: amdgpu in the kernel + firmware-amd-graphics; OpenCL = Mesa rusticl (mesa-opencl-icd from trixie-backports,
#        RUSTICL_ENABLE=radeonsi system-wide) for every GCN/RDNA card. Debian's rocm-opencl-icd 5.7.1 (gfx803 to
#        gfx1102, no RDNA 4) BREAKS mesa-opencl-icd (one OpenCL ICD of the two at a time, found in the qemu test), so
#        it is NOT installed here: a row wanting ROCm's OpenCL on a 7600-class card swaps it in by a job step
#        (apt-get install rocm-opencl-icd; the row records the runtime string) and swaps rusticl back after
#   Intel: Debian 13 has no Intel compute runtime package (intel-opencl-icd is not in trixie); an Arc card has no
#        OpenCL here until the founder names Intel's own package
# Then: the bench agent registered as the hostname (provider owned, pcie_link "x1 electrical"), its public key under
# /var/lib/igneum/pub for the operator's `igneum-bench authorize` on build-1, the user unit enabled under lingering,
# one reboot. Log: /var/lib/igneum/firstboot.log.
set -u
mkdir -p /var/lib/igneum
exec >>/var/lib/igneum/firstboot.log 2>&1
echo "firstboot start $(date -u +%FT%TZ) host=$(hostname) kernel=$(uname -r)"
export DEBIAN_FRONTEND=noninteractive
n=0; until apt-get update; do n=$((n+1)); [ $n -ge 30 ] && { echo "apt-get update failed 30 times"; break; }; sleep 10; done
apt-get install -y avahi-daemon libnss-mdns || echo "mdns packages: apt rc $?"   # the relay on the mini is read by its .local name
apt-get install -y linux-headers-amd64 firmware-amd-graphics firmware-misc-nonfree firmware-linux-nonfree \
  ocl-icd-libopencl1 ocl-icd-opencl-dev clinfo pciutils usbutils lm-sensors python3 git build-essential cmake pkg-config \
  || echo "base packages: apt rc $?"
apt-get install -y -t trixie-backports mesa-opencl-icd || apt-get install -y mesa-opencl-icd || echo "mesa-opencl-icd: apt rc $?"
# the NVIDIA path goes on every rig whether or not a card is on the bus at first boot (main's order of 11 October
# 2026 22:5x UK: a card seated later must find its driver without a hand); the DKMS build runs here, the module
# loads at the reboot below
# NVIDIA's Debian 13 repository (its signing key by the cuda-keyring package) and the open kernel module driver; the
# Debian 550 packages are never installed (they conflict and cannot probe a Blackwell part)
( cd /tmp && curl -fsS -m 120 -O https://developer.download.nvidia.com/compute/cuda/repos/debian13/x86_64/cuda-keyring_1.1-1_all.deb \
  && dpkg -i cuda-keyring_1.1-1_all.deb && apt-get update -q ) || echo "cuda-keyring: rc $?"
# the compute parts alone (the nvidia-open metapackage's recommends pull a desktop stack: EGL, Vulkan, Xorg); NVIDIA's
# OpenCL ICD is diverted off, since its init inside the ICD loader segfaults clinfo and every OpenCL worker beside
# rusticl (rig 1, 13 October 2026 11:1x UK); the NVIDIA cards are CUDA's, the AMD cards rusticl's
dpkg-divert --add --rename --divert /etc/OpenCL/vendors/nvidia.icd.diverted /etc/OpenCL/vendors/nvidia.icd >/dev/null 2>&1 || true
apt-get install -y --no-install-recommends nvidia-kernel-open-dkms nvidia-driver-cuda libcuda1 libnvidia-ml1 || echo "nvidia-open parts: apt rc $?"
lspci -n | grep -qi ' 0300: 10de\| 0302: 10de' || echo "no NVIDIA device on the bus at first boot; the driver is installed for a later card"
lspci -nn | grep -i 'VGA\|3D\|Display' > /var/lib/igneum/gpus.txt
# an 8 GB swapfile beside the installer's swap partition (main's word, 13 October 2026 14:1x UK: a CUDA worker's host
# footprint reached 7.3 GB on rig 1's 7 GB and the kernel's OOM killer took it mid-row; the swap is headroom, never
# a budget: a row that swaps is a row to read)
if [ ! -f /swapfile ]; then fallocate -l 8G /swapfile && chmod 600 /swapfile && mkswap /swapfile >/dev/null && swapon /swapfile && echo "/swapfile none swap sw 0 0" >> /etc/fstab || echo "swapfile: rc $?"; fi
# the PSU power probe (main's order, 10 October 2026): does the board expose its hot-swap supplies over PMBus (an
# i2c scan for PMBus-class devices, the pmbus kernel drivers, any hwmon power node that is not a GPU's)? The result
# is one line in the registration record, "psu_power: pmbus <bus:addr ...>" or "psu_power: none"; a PMBus reading
# becomes a meter at grade cable only after a check against the Tapo plug on the same rig (two readings, one window,
# within 3 percent); until then the plug at the wall is the rig's meter at grade wall (BENCH-JOB-FORMS section 3).
apt-get install -y i2c-tools >/dev/null 2>&1 || true
modprobe i2c-dev 2>/dev/null || true; modprobe pmbus 2>/dev/null || true
{
  echo "i2c buses:"; i2cdetect -l 2>/dev/null
  for b in $(i2cdetect -l 2>/dev/null | awk '{print $1}' | sed 's/i2c-//'); do
    case "$(i2cdetect -l 2>/dev/null | grep "^i2c-$b " )" in *"NVIDIA"*|*"AMDGPU"*|*"amdgpu"*|*"nvkm"*|*"radeon"*|*"DDC"*) continue ;; esac
    echo "bus $b:"; timeout 20 i2cdetect -y -r "$b" 2>/dev/null
  done
  echo "hwmon:"; for h in /sys/class/hwmon/hwmon*; do n=$(cat "$h/name" 2>/dev/null); p=$(ls "$h"/power*_input 2>/dev/null | head -1); [ -n "$p" ] && echo "$n $(basename "$h") $p $(cat "$p")"; done
} > /var/lib/igneum/psu-probe.txt 2>&1
pmb=""; for h in /sys/class/hwmon/hwmon*; do n=$(cat "$h/name" 2>/dev/null); case "$n" in amdgpu|nvidia|nouveau|radeon|coretemp|acpitz|nvme|k10temp) continue ;; esac; [ -e "$h/power1_input" ] && pmb="$pmb $n:$(basename "$h")"; done
# PMBus parts answer at 0x58 to 0x5f or 0x40 to 0x47 on a system bus; a scan hit there without a driver is named as a candidate
cand="$(awk '/^bus /{b=$2} /^[0-9a-f]0:/{for(i=2;i<=NF;i++) if ($i ~ /^(4[0-7]|5[8-9a-f])$/) printf "%s:0x%s ", b, $i}' /var/lib/igneum/psu-probe.txt 2>/dev/null)"
if [ -n "$pmb" ]; then echo "psu_power: pmbus$pmb" > /var/lib/igneum/psu_power; elif [ -n "$cand" ]; then echo "psu_power: candidate $cand (no driver bound; needs the founder's word before a driver is tried)" > /var/lib/igneum/psu_power; else echo "psu_power: none" > /var/lib/igneum/psu_power; fi
cat /var/lib/igneum/psu_power
{ echo "clinfo -l:"; clinfo -l 2>&1; echo; echo "dpkg:"; dpkg -l nvidia-driver mesa-opencl-icd rocm-opencl-icd libclc-19 2>/dev/null | awk '/^ii/{print $2, $3}'; } > /var/lib/igneum/runtimes.txt
# the bench agent as igneum (no password; the queue's key arrives once build-1 authorizes this machine)
H=$(hostname)
case "$H" in n100) LINK="integrated (the N100's iGPU, no slot)";; *) LINK="x1 electrical";; esac   # the N100 entry of the image (12 October 2026)
# Ember Fleet: an operator's console host from the stick (igneum/console.txt, DESIGN.md section 7), placed beside the
# agent before it registers, so the rig enrols with the operator's own console host and never with the project's
if [ -s /var/lib/igneum/stick/console.txt ]; then install -o igneum -g igneum -m 600 /var/lib/igneum/stick/console.txt /home/igneum/.igneum-bench/console.txt; echo "console: $(head -c 160 /var/lib/igneum/stick/console.txt)" >> /var/lib/igneum/firstboot.log; fi
sudo -u igneum -H python3 /home/igneum/.igneum-bench/igneum-bench-agent.py register --name "$H" --provider owned --handle "$H" --pcie_link "$LINK" > /var/lib/igneum/register.txt 2>&1 || echo "register: rc $?"
cat /var/lib/igneum/psu_power >> /var/lib/igneum/register.txt
awk '/^  ssh-ed25519 /{print $1" "$2" "$3}' /var/lib/igneum/register.txt > /var/lib/igneum/pub; chmod 644 /var/lib/igneum/pub
echo "public line: $(cat /var/lib/igneum/pub)"
loginctl enable-linger igneum || true

# Ember, the Igneum miner, from the stick (main's order of 12 October 2026 09:0x UK, the founder's Octominer: the rig
# mines the pool from its first boot): the archive unpacked to /opt/ember, the config from the stick's miner.conf
# (pool, address) with this rig's name, a system unit as the igneum user (Restart=always, every card the CUDA worker
# lists; the state at http://127.0.0.1:4068/state); product A's run form of 09:1x UK. Nothing of it when the stick
# carries no archive.
if [ -s /var/lib/igneum/stick/ember.tar.gz ] && [ -s /var/lib/igneum/stick/miner.conf ]; then
  mkdir -p /opt/ember /var/lib/igneum/ember/worker-cache
  tar -xzf /var/lib/igneum/stick/ember.tar.gz -C /opt/ember --strip-components=1 && chown -R igneum:igneum /opt/ember /var/lib/igneum/ember
  pool="$(sed -n 's/^pool=//p' /var/lib/igneum/stick/miner.conf | head -1)"; addr="$(sed -n 's/^address=//p' /var/lib/igneum/stick/miner.conf | head -1)"
  # Ember Fleet (docs/fleet/DESIGN.md 3.1, 13 October 2026): the config under the igneum user's own home and the unit
  # the igneum user's own (systemctl --user igneum-ember, lingering), so the fleet agent restarts, stops and starts
  # it with no elevation; the one sudoers line below is the reboot verb's
  mkdir -p /etc/igneum /home/igneum/.config/ember /home/igneum/.config/systemd/user
  printf 'pool = %s\naddress = %s\nworker_name = %s\nalgo = igneumhash\nno_tls = true\napi = 127.0.0.1:4068\nworkers_dir = /opt/ember/workers\ndata_dir = /var/lib/igneum/ember\n' "$pool" "$addr" "$H" > /home/igneum/.config/ember/ember.conf
  chmod 600 /home/igneum/.config/ember/ember.conf; ln -sf /home/igneum/.config/ember/ember.conf /etc/igneum/ember.conf
  printf 'igneum ALL=(root) NOPASSWD: /usr/bin/systemctl reboot\n' > /etc/sudoers.d/igneum-fleet; chmod 440 /etc/sudoers.d/igneum-fleet; visudo -cf /etc/sudoers.d/igneum-fleet >/dev/null 2>&1 || rm -f /etc/sudoers.d/igneum-fleet
  printf '%s\n' "[Unit]" "Description=Ember, the Igneum miner (the rig mines the pool from its first boot; the igneum user's own unit, the fleet agent's hands)" "After=network-online.target" "" "[Service]" "Type=simple" "WorkingDirectory=/opt/ember" "ExecStart=/opt/ember/bin/ember --workers-dir /opt/ember/workers --data-dir /var/lib/igneum/ember mine --config /home/igneum/.config/ember/ember.conf --every 30" "Restart=always" "RestartSec=30" "Environment=IGNEUM_WORKER_CACHE=/var/lib/igneum/ember/worker-cache" "Environment=IGNEUM_DEV_UNCHECKED_WORKERS=0" "Nice=5" "" "[Install]" "WantedBy=default.target" > /home/igneum/.config/systemd/user/igneum-ember.service
  chown -R igneum:igneum /home/igneum/.config
  rm -f /etc/systemd/system/igneum-ember.service; systemctl daemon-reload
  # enabled as the user's unit under lingering; it starts with the user manager at the reboot below
  su -s /bin/sh igneum -c 'XDG_RUNTIME_DIR=/run/user/$(id -u) systemctl --user enable igneum-ember.service' >/dev/null 2>&1 || mkdir -p /home/igneum/.config/systemd/user/default.target.wants && ln -sf ../igneum-ember.service /home/igneum/.config/systemd/user/default.target.wants/igneum-ember.service && chown -R igneum:igneum /home/igneum/.config
  echo "ember: $(cat /opt/ember/VERSION 2>/dev/null || echo ?) at /opt/ember, pool $pool, address $addr, worker $H; the unit starts at the reboot below" >> /var/lib/igneum/firstboot.log
fi
systemctl start user@"$(id -u igneum)".service || true
echo "firstboot done $(date -u +%FT%TZ); rebooting once for the drivers"
date -u +%FT%TZ > /var/lib/igneum/firstboot.done
systemctl reboot
