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