#!/usr/bin/env bash
#
# LLM host ISO — the Phase 3 Ollama machine (hosts/llm-host/), as an unattended-install
# ISO with everything burnt in.
#
# The twin of build-container-host-iso.sh. Both read the same CoreSystemConfig.json, so
# this image's address is the one the container host was built to call, by construction
# rather than by agreement.
#
# This half is deliberately the simpler one, and that asymmetry is the design: the LLM
# host is a *server*. It doesn't need to know the container host's address, hold any
# service token, or reach anything at boot beyond a model registry. Nothing here is
# load-bearing for the house — see hosts/llm-host/README.md's guardrail. Keeping this
# image dumb is what lets you power it off, reinstall it, or swap the GPU without any
# of that touching the smart home.
#
# Normally invoked via tools/build-core-pair.sh.

set -euo pipefail

SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
# shellcheck source=lib/coreconfig.sh
source "${SCRIPT_DIR}/lib/coreconfig.sh"

core_load
core_require_root

HOST_DIR="${CORE_REPO_ROOT}/hosts/llm-host"
LIVE_BUILD_DIR="${HOST_DIR}/live-build"
INCLUDES="${LIVE_BUILD_DIR}/config/includes.chroot"
PAYLOAD="${INCLUDES}/opt/llm-host"
OUTPUT_DIR="${CORE_REPO_ROOT}/${CORE_BUILD_OUTPUT_DIR}"

command -v lb >/dev/null 2>&1 || core_die "live-build is not installed (apt install live-build)"

core_log "Preparing ${LIVE_BUILD_DIR}"
rm -rf "$INCLUDES"
mkdir -p \
  "$INCLUDES/etc/systemd/system" \
  "$INCLUDES/etc/default" \
  "$INCLUDES/etc/network/interfaces.d" \
  "$LIVE_BUILD_DIR/config/package-lists" \
  "$LIVE_BUILD_DIR/config/includes.installer" \
  "$PAYLOAD" \
  "$OUTPUT_DIR"

mkdir -p "$PAYLOAD/scripts"
cp "${CORE_TOOLS_DIR}/setup-llm-host.sh" "$PAYLOAD/scripts/"
chmod +x "$PAYLOAD/scripts/setup-llm-host.sh"

# ---------------------------------------------------------------------------
# 1. Bake the config into setup-llm-host.sh's own variables, so the first-boot run
#    needs no arguments and no editing. Values come from CoreSystemConfig.json; the
#    script keeps its defaults for anything not managed centrally.
# ---------------------------------------------------------------------------
core_log "Generating the LLM host's setup overrides"
cat > "$PAYLOAD/llm-host.env" <<EOF
# GENERATED at image build time by tools/build-llm-host-iso.sh from
# CoreSystemConfig.json. Sourced by the first-boot unit to override
# setup-llm-host.sh's defaults.
# Pair ID: $(core_pair_id)
TIER=${CORE_LLM_TIER}
OLLAMA_PORT=${CORE_PORT_OLLAMA}
GPU_TEXT_MODEL=${CORE_LLM_TEXT_MODEL_GPU}
CPU_TEXT_MODEL=${CORE_LLM_TEXT_MODEL_CPU}
VISION_MODEL=${CORE_LLM_VISION_MODEL}
PULL_VISION_MODEL=${CORE_LLM_PULL_VISION_MODEL}
OLLAMA_KEEP_ALIVE=${CORE_LLM_KEEP_ALIVE}
OLLAMA_MAX_LOADED_MODELS=${CORE_LLM_MAX_LOADED_MODELS}
OLLAMA_NUM_PARALLEL=${CORE_LLM_NUM_PARALLEL}
EOF
chmod 600 "$PAYLOAD/llm-host.env"

# ---------------------------------------------------------------------------
# 2. Static networking — this address is what the container host's OLLAMA_HOST was
#    built to point at, so it cannot be left to DHCP.
# ---------------------------------------------------------------------------
cat > "$INCLUDES/etc/network/interfaces.d/smarthome" <<EOF
# GENERATED from CoreSystemConfig.json. The container host image built alongside this
# one has ${CORE_OLLAMA_HOST} compiled into its service env files — if this address
# moves on its own, that host quietly loses inference and (by design) degrades rather
# than erroring, so the breakage is easy to miss. Change it in CoreSystemConfig.json
# and rebuild both halves.
auto lo
iface lo inet loopback

allow-hotplug eth0
iface eth0 inet static
    address ${CORE_LLM_HOST_IP}
    netmask ${CORE_NETMASK}
    gateway ${CORE_GATEWAY}
    dns-nameservers ${CORE_DNS_SERVERS}
EOF

echo "${CORE_LLM_HOST_NAME}" > "$INCLUDES/etc/hostname"
cat > "$INCLUDES/etc/hosts" <<EOF
127.0.0.1	localhost
127.0.1.1	${CORE_LLM_HOST_NAME}
${CORE_LLM_HOST_IP}	${CORE_LLM_HOST_NAME}
# The twin — recorded for diagnosis, not used. This host never initiates anything
# toward the container host; it only answers.
${CORE_CONTAINER_HOST_IP}	${CORE_CONTAINER_HOST_NAME}
EOF

cat > "$INCLUDES/etc/default/keyboard" <<EOF
XKBMODEL="pc105"
XKBLAYOUT="${CORE_KEYBOARD_LAYOUT}"
XKBVARIANT=""
XKBOPTIONS=""
BACKSPACE="guess"
EOF

core_write_build_stamp "$INCLUDES/etc/smarthome-build" "llm-host"

if [[ -n "$CORE_SSH_AUTHORIZED_KEY" ]]; then
  mkdir -p "$INCLUDES/home/${CORE_LLM_HOST_USER}/.ssh"
  echo "$CORE_SSH_AUTHORIZED_KEY" > "$INCLUDES/home/${CORE_LLM_HOST_USER}/.ssh/authorized_keys"
  chmod 700 "$INCLUDES/home/${CORE_LLM_HOST_USER}/.ssh"
  chmod 600 "$INCLUDES/home/${CORE_LLM_HOST_USER}/.ssh/authorized_keys"
else
  core_warn "No ssh_authorized_key in the config — this headless host will have no SSH access."
fi

# ---------------------------------------------------------------------------
# 3. First-boot unit. Long timeout on purpose: this pulls several GB of model weights
#    on a first run, and a model download on a slow link genuinely can outlast a
#    conservative systemd timeout.
# ---------------------------------------------------------------------------
cat > "$INCLUDES/etc/systemd/system/smarthome-llm-firstboot.service" <<'EOF'
[Unit]
Description=SmartestHome first-boot setup (LLM host)
After=network-online.target
Wants=network-online.target
ConditionPathExists=!/opt/llm-host/.firstboot-done

[Service]
Type=oneshot
RemainAfterExit=yes
EnvironmentFile=/opt/llm-host/llm-host.env
ExecStart=/opt/llm-host/scripts/setup-llm-host.sh
ExecStartPost=/usr/bin/touch /opt/llm-host/.firstboot-done
ExecStartPost=/bin/systemctl disable smarthome-llm-firstboot.service
StandardOutput=journal+console
StandardError=journal+console
# Model pulls are multi-GB; 4h is generous rather than optimistic.
TimeoutStartSec=14400

[Install]
WantedBy=multi-user.target
EOF

mkdir -p "$INCLUDES/etc/systemd/system/multi-user.target.wants"
ln -sf /etc/systemd/system/smarthome-llm-firstboot.service \
  "$INCLUDES/etc/systemd/system/multi-user.target.wants/smarthome-llm-firstboot.service"

# firmware-misc-nonfree/nvidia-driver are NOT preinstalled here. Driver choice is the
# most hardware-specific decision on this machine and picking one blind is how you
# produce a box that doesn't boot — setup-llm-host.sh checks for a working nvidia-smi
# and tells you what to install if it's missing. See hosts/llm-host/README.md.
cat > "$LIVE_BUILD_DIR/config/package-lists/llm-host.list.chroot" <<'EOF'
ca-certificates
curl
gnupg
openssh-server
sudo
python3
pciutils
EOF

cat > "$LIVE_BUILD_DIR/config/includes.installer/preseed.cfg" <<EOF
# GENERATED from CoreSystemConfig.json by tools/build-llm-host-iso.sh
d-i debian-installer/locale string ${CORE_LOCALE}
d-i keyboard-configuration/xkb-keymap select ${CORE_KEYBOARD_LAYOUT}
d-i time/zone string ${CORE_TIMEZONE}
d-i clock-setup/utc boolean true

d-i netcfg/choose_interface select auto
d-i netcfg/disable_autoconfig boolean true
d-i netcfg/get_ipaddress string ${CORE_LLM_HOST_IP}
d-i netcfg/get_netmask string ${CORE_NETMASK}
d-i netcfg/get_gateway string ${CORE_GATEWAY}
d-i netcfg/get_nameservers string ${CORE_DNS_SERVERS}
d-i netcfg/confirm_static boolean true
d-i netcfg/get_hostname string ${CORE_LLM_HOST_NAME}
d-i netcfg/get_domain string local

d-i passwd/root-login boolean false
d-i passwd/user-fullname string ${CORE_LLM_HOST_USER}
d-i passwd/username string ${CORE_LLM_HOST_USER}
$(if [[ -n "$CORE_ADMIN_PASSWORD_HASH" ]]; then
    echo "d-i passwd/user-password-crypted password ${CORE_ADMIN_PASSWORD_HASH}"
  else
    echo "# No admin_password_hash set — the installer will prompt for a password."
    echo "# Generate one with: mkpasswd -m sha-512"
  fi)
d-i user-setup/allow-password-weak boolean false
d-i user-setup/encrypt-home boolean false

# WHOLE-DISK, AUTOMATIC, NO CONFIRMATION — erases ${CORE_LLM_HOST_DISK} without asking.
# Model storage wants room: a 14B Q4 model is ~9GB and a vision model another 5-8GB.
d-i partman-auto/disk string ${CORE_LLM_HOST_DISK}
d-i partman-auto/method string regular
d-i partman-auto/choose_recipe select atomic
d-i partman-partitioning/confirm_write_new_label boolean true
d-i partman/choose_partition select finish
d-i partman/confirm boolean true
d-i partman/confirm_nooverwrite boolean true

d-i pkgsel/include string openssh-server sudo curl ca-certificates python3 pciutils
tasksel tasksel/first multiselect standard, ssh-server
popularity-contest popularity-contest/participate boolean false

d-i grub-installer/only_debian boolean true
d-i grub-installer/bootdev string ${CORE_LLM_HOST_DISK}
d-i finish-install/reboot_in_progress note
EOF

cd "$LIVE_BUILD_DIR"
core_log "Running lb config"
lb clean --purge >/dev/null 2>&1 || true
lb config \
  --distribution "$CORE_DEBIAN_RELEASE" \
  --architecture amd64 \
  --binary-images iso-hybrid \
  --debian-installer netinst \
  --debian-installer-gui false \
  --archive-areas "main contrib non-free non-free-firmware" \
  --iso-application "SmartestHome LLM host" \
  --iso-volume "smarthome-llm-$(core_pair_id)"

core_log "Running lb build (long, needs network)"
lb build

ISO="$(find "$LIVE_BUILD_DIR" -maxdepth 1 -name 'live-image-amd64.hybrid.iso' -print -quit)"
[[ -n "$ISO" ]] || core_die "lb build finished but no ISO was produced — check the log above."

DEST="${OUTPUT_DIR}/smarthome-llm-host-$(core_pair_id).iso"
mv "$ISO" "$DEST"
core_log "LLM host ISO: ${DEST}"
