Luigit
repositories / bugabinga.net

bugabinga.net

personal infrastructure for bugabinga!

owned by admin

scripts/remote/start-luci.sh

Raw
#!/usr/bin/env bash
set -euo pipefail

user="${CI_USER:-ci}"
default_image="localhost/bugabinga-luci:latest"
image="${LUCI_IMAGE:-${default_image}}"
rollback_image="localhost/bugabinga-luci:rollback"
home_dir="/home/${user}"
uid="$(id -u "${user}")"
runtime="/run/user/${uid}"
mode="${1:-start}"

[[ "$#" -le 1 ]] || {
  echo "usage: start-luci.sh [backup|deploy]" >&2
  exit 2
}
[[ "${image}" == "${default_image}" ]] || {
  echo "LUCI_IMAGE must remain ${default_image}; the installed Quadlet selects this image" >&2
  exit 2
}

install -d -o "${user}" -g "${user}" -m 0700 "${runtime}"

run_ci() {
  runuser -u "${user}" -- env \
    HOME="${home_dir}" \
    USER="${user}" \
    LOGNAME="${user}" \
    XDG_RUNTIME_DIR="${runtime}" \
    DBUS_SESSION_BUS_ADDRESS="unix:path=${runtime}/bus" \
    bash -lc "cd '${home_dir}' && $1"
}

image_exists() {
  run_ci "podman image exists '${1}'"
}

quadlet_installed() {
  [[ -f "${home_dir}/.config/containers/systemd/luci.container" ]]
}

restart_luci() {
  run_ci 'systemctl --user daemon-reload'
  run_ci 'systemctl --user restart luci.service || systemctl --user start luci.service'
}

container_port() {
  local port
  port="$(run_ci "podman port luci '${1}/tcp'" | head -n 1)"
  port="${port##*:}"
  [[ "${port}" =~ ^[0-9]+$ ]] || return 1
  printf '%s\n' "${port}"
}

wait_for_health() (
  local authorized_keys=/data/ci/authorized_keys health_dir='' health_key health_line='' http_port ssh_port status=1 added=false _
  # shellcheck disable=SC2317,SC2329
  cleanup_health_key() {
    local exit_status=$?
    if [[ "${added}" == true ]]; then
      (
        flock -x 9
        local tmp
        tmp="$(mktemp "${authorized_keys}.XXXXXX")"
        awk -v line="${health_line}" '$0 != line' "${authorized_keys}" >"${tmp}"
        chmod --reference="${authorized_keys}" "${tmp}"
        chown --reference="${authorized_keys}" "${tmp}"
        mv -f "${tmp}" "${authorized_keys}"
      ) 9>>"${authorized_keys}.lock" || echo "failed to remove temporary Luci health key" >&2
    fi
    [[ -z "${health_dir}" ]] || rm -rf "${health_dir}"
    exit "${exit_status}"
  }
  trap cleanup_health_key EXIT

  [[ -f "${authorized_keys}" ]] || exit 1
  health_dir="$(mktemp -d)"
  health_key="${health_dir}/key"
  ssh-keygen -q -t ed25519 -N '' -f "${health_key}"
  health_line="restrict $(<"${health_key}.pub")"
  (
    flock -x 9
    printf '%s\n' "${health_line}" >>"${authorized_keys}"
  ) 9>>"${authorized_keys}.lock"
  added=true

  for _ in $(seq 1 30); do
    if http_port="$(container_port 8080)" && ssh_port="$(container_port 2223)" &&
      curl --fail --silent --show-error --connect-timeout 2 "http://127.0.0.1:${http_port}/llms.txt" | grep -qx '# Luci CI' &&
      ssh -i "${health_key}" -o IdentitiesOnly=yes -o BatchMode=yes -o ConnectTimeout=2 -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -p "${ssh_port}" root@127.0.0.1 'ci env-check' | grep -qx ok; then
      status=0
      break
    fi
    sleep 1
  done
  exit "${status}"
)

backup_image() {
  if image_exists "${image}"; then
    run_ci "podman tag '${image}' '${rollback_image}'"
    echo "retained ${rollback_image}"
  else
    echo "no existing Luci image to retain"
  fi
}

rollback() {
  if ! image_exists "${rollback_image}"; then
    echo "Luci candidate failed and no rollback image exists" >&2
    return 1
  fi
  echo "Luci candidate failed; restoring ${rollback_image}" >&2
  run_ci "podman tag '${rollback_image}' '${image}'"
  restart_luci
}

restore_healthy_service() {
  rollback || return 1
  if ! wait_for_health; then
    echo "Luci rollback image failed health checks" >&2
    return 1
  fi
  echo "Luci rollback image is healthy" >&2
}

case "${mode}" in
  backup)
    backup_image
    ;;
  deploy)
    if ! quadlet_installed; then
      echo "luci quadlet not installed yet; image retained for just apply"
      exit 0
    fi
    image_exists "${image}"
    if ! restart_luci; then
      echo "Luci candidate restart failed" >&2
      restore_healthy_service || true
      exit 1
    fi
    if ! wait_for_health; then
      restore_healthy_service || true
      exit 1
    fi
    echo "Luci HTTP and forced SSH are healthy"
    ;;
  start)
    if image_exists "${image}"; then
      restart_luci
    else
      echo "Luci image not loaded yet; run: just deploy luci-image"
    fi
    ;;
  *)
    echo "usage: start-luci.sh [backup|deploy]" >&2
    exit 2
    ;;
esac