#!/bin/bash set -euo pipefail SCRIPT_DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd) PROJECT_ROOT=$(dirname "$SCRIPT_DIR") usage() { cat <<'USAGE' Usage: update/debug.sh [--keep] [arch|ubuntu] [-- update-args...] Spins up a temporary VM-like container, mounts the repository, and runs the update script with UPDATE_DEBUG=1. Requires podman (preferred) or docker with systemd support. Pass --keep to leave the container running for inspection. Any arguments after -- are forwarded to update/update.sh. USAGE } KEEP_CONTAINER=0 DISTRO="" FORWARDED_ARGS=() while (( $# )); do case "$1" in --keep) KEEP_CONTAINER=1 shift ;; arch|ubuntu) DISTRO="$1" shift ;; --) shift FORWARDED_ARGS=("$@") break ;; -h|--help) usage exit 0 ;; *) usage exit 1 ;; esac done if [[ -z "$DISTRO" ]]; then DISTRO="ubuntu" fi case "$DISTRO" in arch) IMAGE="docker.io/archlinux:latest" INIT_PATH="/usr/lib/systemd/systemd" PREPARE_CMD='pacman -Syyu --noconfirm sudo git base-devel'; ;; ubuntu) IMAGE="docker.io/library/ubuntu:24.04" INIT_PATH="/sbin/init" PREPARE_CMD='apt-get update && DEBIAN_FRONTEND=noninteractive apt-get install -y sudo git' ;; *) usage exit 1 ;; esac if command -v podman >/dev/null 2>&1; then RUNTIME="podman" MOUNT_OPTS=":Z" elif command -v docker >/dev/null 2>&1; then RUNTIME="docker" MOUNT_OPTS="" else echo "[!] Neither podman nor docker found in PATH." >&2 exit 1 fi CONTAINER_NAME="linuxbox-update-debug-${DISTRO}-$$" cleanup() { if (( KEEP_CONTAINER )); then echo "[i] Keeping container ${CONTAINER_NAME} running for inspection." >&2 return fi if "$RUNTIME" ps -a --format '{{.Names}}' 2>/dev/null | grep -qx "$CONTAINER_NAME"; then "$RUNTIME" rm -f "$CONTAINER_NAME" >/dev/null 2>&1 || true fi } trap cleanup EXIT start_container() { if [[ "$RUNTIME" == "podman" ]]; then "$RUNTIME" run -d --name "$CONTAINER_NAME" --privileged \ --tmpfs /tmp --tmpfs /run \ -v /sys/fs/cgroup:/sys/fs/cgroup:ro \ -v "$PROJECT_ROOT:/workspace${MOUNT_OPTS}" \ -w /workspace "$IMAGE" "$INIT_PATH" >/dev/null else "$RUNTIME" run -d --name "$CONTAINER_NAME" --privileged --cgroupns=host \ --tmpfs /tmp --tmpfs /run \ -v /sys/fs/cgroup:/sys/fs/cgroup:ro \ -v "$PROJECT_ROOT:/workspace" \ -w /workspace "$IMAGE" "$INIT_PATH" >/dev/null fi } wait_for_systemd() { local attempts=0 while (( attempts < 30 )); do local status status=$("$RUNTIME" exec "$CONTAINER_NAME" systemctl is-system-running 2>/dev/null || true) case "$status" in running|degraded) return 0 ;; esac sleep 1 ((attempts++)) done echo "[!] systemd did not reach running state inside container (last status: $status)." >&2 } bootstrap_container() { if [[ "$DISTRO" == "ubuntu" ]]; then "$RUNTIME" exec "$CONTAINER_NAME" bash -lc "export DEBIAN_FRONTEND=noninteractive; $PREPARE_CMD" "$RUNTIME" exec "$CONTAINER_NAME" bash -lc "useradd -m debugger || true" "$RUNTIME" exec "$CONTAINER_NAME" bash -lc "echo 'debugger ALL=(ALL) NOPASSWD:ALL' >/etc/sudoers.d/debugger && chmod 0440 /etc/sudoers.d/debugger" else "$RUNTIME" exec "$CONTAINER_NAME" bash -lc "$PREPARE_CMD" "$RUNTIME" exec "$CONTAINER_NAME" bash -lc "useradd -m debugger || true" "$RUNTIME" exec "$CONTAINER_NAME" bash -lc "echo 'debugger ALL=(ALL) NOPASSWD:ALL' >/etc/sudoers.d/debugger && chmod 0440 /etc/sudoers.d/debugger" fi "$RUNTIME" exec "$CONTAINER_NAME" bash -lc "chown -R debugger:debugger /workspace" } run_update() { local update_cmd printf -v update_cmd 'UPDATE_DEBUG=1 ./update/update.sh %q' "$DISTRO" for arg in "${FORWARDED_ARGS[@]}"; do printf -v update_cmd '%s %q' "$update_cmd" "$arg" done "$RUNTIME" exec "$CONTAINER_NAME" bash -lc "su - debugger -c \"cd /workspace && $update_cmd\"" } start_container wait_for_systemd bootstrap_container run_update echo "[✓] Debug run completed." >&2