Provisioning was still installing `claude` and `pi` through a separate npm-global prefix even after the guest images had switched to `mise` for Node and opencode. That left two competing install paths and made the runtime layout harder to reason about. Switch the Debian and Void image setup flows to install `claude` and `pi` as `mise` npm tools, assert their shims exist after `mise reshim`, and symlink `node`, `npm`, `opencode`, `claude`, and `pi` directly from the mise shim directory into `/usr/local/bin`. Update the imagebuild test expectations and bump the Void rootfs default size to 4G so the larger default toolset still fits reliably.
616 lines
18 KiB
Bash
Executable file
616 lines
18 KiB
Bash
Executable file
#!/usr/bin/env bash
|
|
set -euo pipefail
|
|
|
|
log() {
|
|
printf '[make-rootfs-void] %s\n' "$*"
|
|
}
|
|
|
|
usage() {
|
|
cat <<'EOF'
|
|
Usage: ./scripts/make-rootfs-void.sh [--out <path>] [--size <size>] [--mirror <url>] [--arch <arch>]
|
|
|
|
Build an experimental Void Linux rootfs image plus a matching /root work-seed.
|
|
|
|
Defaults:
|
|
--out ./build/manual/rootfs-void.ext4
|
|
--size 4G
|
|
--mirror https://repo-default.voidlinux.org
|
|
--arch x86_64
|
|
|
|
This path is experimental and local-only. If ./build/manual/void-kernel exists
|
|
it uses the staged Void kernel modules from that directory. It does not change
|
|
the default Debian image flow.
|
|
EOF
|
|
}
|
|
|
|
parse_size() {
|
|
local raw="$1"
|
|
if [[ "$raw" =~ ^([0-9]+)([KMG])?$ ]]; then
|
|
local num="${BASH_REMATCH[1]}"
|
|
local unit="${BASH_REMATCH[2]}"
|
|
case "$unit" in
|
|
K) printf '%s\n' $((num * 1024)) ;;
|
|
M|"") printf '%s\n' $((num * 1024 * 1024)) ;;
|
|
G) printf '%s\n' $((num * 1024 * 1024 * 1024)) ;;
|
|
esac
|
|
return 0
|
|
fi
|
|
return 1
|
|
}
|
|
|
|
require_command() {
|
|
local name="$1"
|
|
command -v "$name" >/dev/null 2>&1 || {
|
|
log "required command not found: $name"
|
|
exit 1
|
|
}
|
|
}
|
|
|
|
resolve_banger_bin() {
|
|
if [[ -n "${BANGER_BIN:-}" ]]; then
|
|
printf '%s\n' "$BANGER_BIN"
|
|
return
|
|
fi
|
|
if [[ -x "$REPO_ROOT/build/bin/banger" ]]; then
|
|
printf '%s\n' "$REPO_ROOT/build/bin/banger"
|
|
return
|
|
fi
|
|
if [[ -x "$REPO_ROOT/banger" ]]; then
|
|
printf '%s\n' "$REPO_ROOT/banger"
|
|
return
|
|
fi
|
|
if command -v banger >/dev/null 2>&1; then
|
|
command -v banger
|
|
return
|
|
fi
|
|
log "banger binary not found; build it first with 'make build' or set BANGER_BIN"
|
|
exit 1
|
|
}
|
|
|
|
normalize_mirror() {
|
|
local mirror="${1%/}"
|
|
mirror="${mirror%/current}"
|
|
mirror="${mirror%/static}"
|
|
printf '%s\n' "$mirror"
|
|
}
|
|
|
|
find_latest_module_dir() {
|
|
local root="$1"
|
|
if [[ ! -d "$root" ]]; then
|
|
return 1
|
|
fi
|
|
find "$root" -mindepth 1 -maxdepth 1 -type d | sort | tail -n 1
|
|
}
|
|
|
|
find_static_binary() {
|
|
local name="$1"
|
|
find "$STATIC_DIR" -type f \( -name "$name" -o -name "$name.static" \) -perm -u+x | sort | head -n 1
|
|
}
|
|
|
|
find_static_keys_dir() {
|
|
find "$STATIC_DIR" -type d -path '*/var/db/xbps/keys' | sort | head -n 1
|
|
}
|
|
|
|
load_package_preset() {
|
|
local preset="$1"
|
|
local -n out="$2"
|
|
mapfile -t out < <("$BANGER_BIN" internal packages "$preset")
|
|
(( ${#out[@]} > 0 ))
|
|
}
|
|
|
|
write_rootfs_manifest_metadata() {
|
|
local rootfs_path="$1"
|
|
local manifest_hash="$2"
|
|
printf '%s\n' "$manifest_hash" > "${rootfs_path}.packages.sha256"
|
|
}
|
|
|
|
install_root_authorized_key() {
|
|
local public_key
|
|
public_key="$(ssh-keygen -y -f "$SSH_KEY")"
|
|
sudo mkdir -p "$ROOT_MOUNT/root/.ssh"
|
|
printf '%s\n' "$public_key" | sudo tee "$ROOT_MOUNT/root/.ssh/authorized_keys" >/dev/null
|
|
sudo chmod 700 "$ROOT_MOUNT/root/.ssh"
|
|
sudo chmod 600 "$ROOT_MOUNT/root/.ssh/authorized_keys"
|
|
}
|
|
|
|
ensure_sshd_include() {
|
|
local cfg="$ROOT_MOUNT/etc/ssh/sshd_config"
|
|
local tmp_cfg="$TMP_DIR/sshd_config"
|
|
local include_line="Include /etc/ssh/sshd_config.d/*.conf"
|
|
|
|
sudo mkdir -p "$ROOT_MOUNT/etc/ssh/sshd_config.d"
|
|
if sudo test -f "$cfg"; then
|
|
sudo cat "$cfg" > "$tmp_cfg"
|
|
else
|
|
: > "$tmp_cfg"
|
|
fi
|
|
|
|
if ! grep -Eq '^[[:space:]]*Include[[:space:]]+/etc/ssh/sshd_config\.d/\*\.conf([[:space:]]|$)' "$tmp_cfg"; then
|
|
{
|
|
printf '%s\n' "$include_line"
|
|
cat "$tmp_cfg"
|
|
} > "${tmp_cfg}.new"
|
|
mv "${tmp_cfg}.new" "$tmp_cfg"
|
|
sudo install -m 0644 "$tmp_cfg" "$cfg"
|
|
fi
|
|
}
|
|
|
|
install_vsock_service() {
|
|
local service_dir="$ROOT_MOUNT/etc/sv/banger-vsock-agent"
|
|
local run_path="$service_dir/run"
|
|
local finish_path="$service_dir/finish"
|
|
|
|
sudo mkdir -p "$service_dir"
|
|
cat <<'EOF' | sudo tee "$run_path" >/dev/null
|
|
#!/bin/sh
|
|
modprobe vsock 2>/dev/null || true
|
|
modprobe vmw_vsock_virtio_transport 2>/dev/null || true
|
|
exec /usr/local/bin/banger-vsock-agent
|
|
EOF
|
|
cat <<'EOF' | sudo tee "$finish_path" >/dev/null
|
|
#!/bin/sh
|
|
exit 0
|
|
EOF
|
|
sudo chmod 0755 "$run_path" "$finish_path"
|
|
sudo mkdir -p "$ROOT_MOUNT/etc/runit/runsvdir/default"
|
|
sudo ln -snf /etc/sv/banger-vsock-agent "$ROOT_MOUNT/etc/runit/runsvdir/default/banger-vsock-agent"
|
|
}
|
|
|
|
install_opencode_service() {
|
|
local service_dir="$ROOT_MOUNT/etc/sv/banger-opencode"
|
|
local run_path="$service_dir/run"
|
|
local finish_path="$service_dir/finish"
|
|
|
|
sudo mkdir -p "$service_dir"
|
|
cat <<'EOF' | sudo tee "$run_path" >/dev/null
|
|
#!/bin/sh
|
|
set -e
|
|
export HOME=/root
|
|
cd /root
|
|
exec /usr/local/bin/opencode serve --hostname 0.0.0.0 --port 4096
|
|
EOF
|
|
cat <<'EOF' | sudo tee "$finish_path" >/dev/null
|
|
#!/bin/sh
|
|
exit 0
|
|
EOF
|
|
sudo chmod 0755 "$run_path" "$finish_path"
|
|
sudo mkdir -p "$ROOT_MOUNT/etc/runit/runsvdir/default"
|
|
sudo ln -snf /etc/sv/banger-opencode "$ROOT_MOUNT/etc/runit/runsvdir/default/banger-opencode"
|
|
}
|
|
|
|
install_guest_network_bootstrap() {
|
|
sudo mkdir -p "$ROOT_MOUNT/usr/local/libexec" "$ROOT_MOUNT/etc/runit/core-services"
|
|
sudo install -m 0755 "$GUESTNET_BOOTSTRAP_SCRIPT" "$ROOT_MOUNT/usr/local/libexec/banger-network-bootstrap"
|
|
sudo install -m 0644 "$GUESTNET_VOID_CORE_SERVICE" "$ROOT_MOUNT/etc/runit/core-services/20-banger-network.sh"
|
|
}
|
|
|
|
configure_docker_bootstrap() {
|
|
local modules_conf="$ROOT_MOUNT/etc/modules-load.d/docker-netfilter.conf"
|
|
local sysctl_conf="$ROOT_MOUNT/etc/sysctl.d/99-docker.conf"
|
|
local service_dir="$ROOT_MOUNT/etc/sv/docker"
|
|
local run_path="$service_dir/run"
|
|
local orig_run_path="$service_dir/run.orig"
|
|
local preflight_path="$ROOT_MOUNT/usr/local/bin/banger-docker-preflight"
|
|
|
|
sudo mkdir -p "$ROOT_MOUNT/etc/modules-load.d" "$ROOT_MOUNT/etc/sysctl.d" "$ROOT_MOUNT/usr/local/bin"
|
|
cat <<'EOF' | sudo tee "$modules_conf" >/dev/null
|
|
nf_tables
|
|
nft_chain_nat
|
|
veth
|
|
br_netfilter
|
|
overlay
|
|
EOF
|
|
cat <<'EOF' | sudo tee "$sysctl_conf" >/dev/null
|
|
net.bridge.bridge-nf-call-iptables = 1
|
|
net.bridge.bridge-nf-call-ip6tables = 1
|
|
net.ipv4.ip_forward = 1
|
|
EOF
|
|
cat <<'EOF' | sudo tee "$preflight_path" >/dev/null
|
|
#!/bin/sh
|
|
for module in nf_tables nft_chain_nat veth br_netfilter overlay; do
|
|
modprobe "$module" 2>/dev/null || true
|
|
done
|
|
if command -v sysctl >/dev/null 2>&1; then
|
|
sysctl --load /etc/sysctl.d/99-docker.conf >/dev/null 2>&1 || true
|
|
fi
|
|
EOF
|
|
|
|
if [[ ! -f "$run_path" ]]; then
|
|
log "Void rootfs is missing /etc/sv/docker/run after docker install"
|
|
exit 1
|
|
fi
|
|
sudo install -m 0755 "$run_path" "$orig_run_path"
|
|
cat <<'EOF' | sudo tee "$run_path" >/dev/null
|
|
#!/bin/sh
|
|
set -e
|
|
/usr/local/bin/banger-docker-preflight
|
|
exec /etc/sv/docker/run.orig
|
|
EOF
|
|
sudo chmod 0644 "$modules_conf" "$sysctl_conf"
|
|
sudo chmod 0755 "$preflight_path" "$run_path" "$orig_run_path"
|
|
}
|
|
|
|
enable_sshd_service() {
|
|
if [[ ! -d "$ROOT_MOUNT/etc/sv/sshd" ]]; then
|
|
log "Void rootfs is missing /etc/sv/sshd after openssh install"
|
|
exit 1
|
|
fi
|
|
sudo mkdir -p "$ROOT_MOUNT/etc/runit/runsvdir/default"
|
|
sudo ln -snf /etc/sv/sshd "$ROOT_MOUNT/etc/runit/runsvdir/default/sshd"
|
|
}
|
|
|
|
enable_docker_service() {
|
|
if [[ ! -d "$ROOT_MOUNT/etc/sv/docker" ]]; then
|
|
log "Void rootfs is missing /etc/sv/docker after docker install"
|
|
exit 1
|
|
fi
|
|
sudo mkdir -p "$ROOT_MOUNT/etc/runit/runsvdir/default"
|
|
sudo ln -snf /etc/sv/docker "$ROOT_MOUNT/etc/runit/runsvdir/default/docker"
|
|
}
|
|
|
|
normalize_root_shell() {
|
|
local passwd="$ROOT_MOUNT/etc/passwd"
|
|
local shells="$ROOT_MOUNT/etc/shells"
|
|
local wanted_shell="/bin/bash"
|
|
local tmp_passwd="$TMP_DIR/passwd"
|
|
local root_shell=""
|
|
|
|
if [[ ! -x "$ROOT_MOUNT$wanted_shell" ]]; then
|
|
log "required root shell is missing from the Void image: $wanted_shell"
|
|
exit 1
|
|
fi
|
|
if [[ ! -f "$shells" ]]; then
|
|
log "Void image is missing /etc/shells"
|
|
exit 1
|
|
fi
|
|
if ! sudo grep -Fxq "$wanted_shell" "$shells"; then
|
|
log "Void image does not allow $wanted_shell in /etc/shells"
|
|
exit 1
|
|
fi
|
|
|
|
sudo cat "$passwd" > "$tmp_passwd"
|
|
awk -F: -v OFS=: -v shell="$wanted_shell" '
|
|
$1 == "root" {
|
|
$7 = shell
|
|
found = 1
|
|
}
|
|
{ print }
|
|
END {
|
|
if (!found) {
|
|
exit 1
|
|
}
|
|
}
|
|
' "$tmp_passwd" > "${tmp_passwd}.new" || {
|
|
log "failed to rewrite root shell in /etc/passwd"
|
|
exit 1
|
|
}
|
|
mv "${tmp_passwd}.new" "$tmp_passwd"
|
|
sudo install -m 0644 "$tmp_passwd" "$passwd"
|
|
|
|
root_shell="$(sudo awk -F: '$1 == "root" { print $7 }' "$passwd")"
|
|
if [[ "$root_shell" != "$wanted_shell" ]]; then
|
|
log "root shell normalization failed: expected $wanted_shell, got ${root_shell:-<empty>}"
|
|
exit 1
|
|
fi
|
|
}
|
|
|
|
configure_root_bash_prompt() {
|
|
local bashrc="$ROOT_MOUNT/root/.bashrc"
|
|
local bash_profile="$ROOT_MOUNT/root/.bash_profile"
|
|
local profile_prompt="$ROOT_MOUNT/etc/profile.d/banger-bash-prompt.sh"
|
|
|
|
sudo mkdir -p "$ROOT_MOUNT/root" "$ROOT_MOUNT/etc/profile.d"
|
|
cat <<'EOF' | sudo tee "$bashrc" >/dev/null
|
|
# banger: default interactive prompt for experimental Void guests
|
|
case "$-" in
|
|
*i*) ;;
|
|
*) return ;;
|
|
esac
|
|
|
|
if [ -z "${BANGER_MISE_ACTIVATED:-}" ] && [ -x '/usr/local/bin/mise' ]; then
|
|
export BANGER_MISE_ACTIVATED=1
|
|
eval "$(/usr/local/bin/mise activate bash)"
|
|
fi
|
|
|
|
PS1='\u@\h:\w\$ '
|
|
EOF
|
|
cat <<'EOF' | sudo tee "$bash_profile" >/dev/null
|
|
if [ -f ~/.bashrc ]; then
|
|
. ~/.bashrc
|
|
fi
|
|
EOF
|
|
cat <<'EOF' | sudo tee "$profile_prompt" >/dev/null
|
|
case "$-" in
|
|
*i*) ;;
|
|
*) return 0 2>/dev/null || exit 0 ;;
|
|
esac
|
|
|
|
if [ -n "${BASH_VERSION:-}" ]; then
|
|
PS1='\u@\h:\w\$ '
|
|
fi
|
|
EOF
|
|
sudo chmod 0644 "$bashrc" "$bash_profile" "$profile_prompt"
|
|
}
|
|
|
|
install_guest_tools() {
|
|
local profile_mise="$ROOT_MOUNT/etc/profile.d/mise.sh"
|
|
|
|
sudo mkdir -p "$ROOT_MOUNT/etc/profile.d"
|
|
if [[ -r /etc/resolv.conf ]]; then
|
|
sudo install -m 0644 /etc/resolv.conf "$ROOT_MOUNT/etc/resolv.conf"
|
|
fi
|
|
|
|
sudo env HOME=/root PATH=/usr/local/bin:/usr/bin:/bin chroot "$ROOT_MOUNT" /bin/bash -se <<EOF
|
|
set -euo pipefail
|
|
curl -fsSL https://mise.run | MISE_INSTALL_PATH="$MISE_INSTALL_PATH" MISE_VERSION="$MISE_VERSION" sh
|
|
"$MISE_INSTALL_PATH" use -g "$NODE_TOOL"
|
|
"$MISE_INSTALL_PATH" use -g "$OPENCODE_TOOL"
|
|
"$MISE_INSTALL_PATH" use -g "$CLAUDE_CODE_TOOL"
|
|
"$MISE_INSTALL_PATH" use -g "$PI_TOOL"
|
|
"$MISE_INSTALL_PATH" reshim
|
|
if [[ ! -e /root/.local/share/mise/shims/node ]]; then
|
|
echo "node shim not found after mise install" >&2
|
|
exit 1
|
|
fi
|
|
if [[ ! -e /root/.local/share/mise/shims/npm ]]; then
|
|
echo "npm shim not found after mise install" >&2
|
|
exit 1
|
|
fi
|
|
if [[ ! -e /root/.local/share/mise/shims/opencode ]]; then
|
|
echo "opencode shim not found after mise install" >&2
|
|
exit 1
|
|
fi
|
|
if [[ ! -e /root/.local/share/mise/shims/claude ]]; then
|
|
echo "claude shim not found after mise install" >&2
|
|
exit 1
|
|
fi
|
|
if [[ ! -e /root/.local/share/mise/shims/pi ]]; then
|
|
echo "pi shim not found after mise install" >&2
|
|
exit 1
|
|
fi
|
|
ln -snf /root/.local/share/mise/shims/node /usr/local/bin/node
|
|
ln -snf /root/.local/share/mise/shims/npm /usr/local/bin/npm
|
|
ln -snf /root/.local/share/mise/shims/opencode /usr/local/bin/opencode
|
|
ln -snf /root/.local/share/mise/shims/claude /usr/local/bin/claude
|
|
ln -snf /root/.local/share/mise/shims/pi /usr/local/bin/pi
|
|
EOF
|
|
|
|
cat <<'EOF' | sudo tee "$profile_mise" >/dev/null
|
|
if [ -n "${BASH_VERSION:-}" ] && [ -z "${BANGER_MISE_ACTIVATED:-}" ] && [ -x '/usr/local/bin/mise' ]; then
|
|
export BANGER_MISE_ACTIVATED=1
|
|
eval "$(/usr/local/bin/mise activate bash)"
|
|
fi
|
|
EOF
|
|
sudo chmod 0644 "$profile_mise"
|
|
}
|
|
|
|
cleanup() {
|
|
if [[ -n "${ROOT_MOUNT:-}" ]] && command -v mountpoint >/dev/null 2>&1 && mountpoint -q "$ROOT_MOUNT"; then
|
|
sudo umount "$ROOT_MOUNT" || true
|
|
fi
|
|
if [[ "${BUILD_DONE:-0}" != "1" ]]; then
|
|
rm -f "${OUT_ROOTFS:-}" "${WORK_SEED:-}" "${OUT_ROOTFS:-}.packages.sha256"
|
|
fi
|
|
if [[ -n "${TMP_DIR:-}" && -d "${TMP_DIR:-}" ]]; then
|
|
rm -rf "$TMP_DIR"
|
|
fi
|
|
}
|
|
|
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
REPO_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
|
|
MANUAL_DIR="${BANGER_MANUAL_DIR:-$REPO_ROOT/build/manual}"
|
|
BANGER_BIN="$(resolve_banger_bin)"
|
|
SSH_KEY="$("$BANGER_BIN" internal ssh-key-path)"
|
|
OUT_ROOTFS="$MANUAL_DIR/rootfs-void.ext4"
|
|
SIZE_SPEC="4G"
|
|
MIRROR="https://repo-default.voidlinux.org"
|
|
ARCH="x86_64"
|
|
MISE_VERSION="v2025.12.0"
|
|
MISE_INSTALL_PATH="/usr/local/bin/mise"
|
|
NODE_TOOL="node@22"
|
|
OPENCODE_TOOL="github:anomalyco/opencode"
|
|
CLAUDE_CODE_TOOL="npm:@anthropic-ai/claude-code"
|
|
PI_TOOL="npm:@mariozechner/pi-coding-agent"
|
|
GUESTNET_BOOTSTRAP_SCRIPT="$REPO_ROOT/internal/guestnet/assets/bootstrap.sh"
|
|
GUESTNET_VOID_CORE_SERVICE="$REPO_ROOT/internal/guestnet/assets/void-core-service.sh"
|
|
MODULES_DIR=""
|
|
VOID_KERNEL_MODULES_DIR="$(find_latest_module_dir "$MANUAL_DIR/void-kernel/lib/modules" || true)"
|
|
VSOCK_AGENT="$("$BANGER_BIN" internal vsock-agent-path)"
|
|
if [[ -n "$VOID_KERNEL_MODULES_DIR" ]]; then
|
|
MODULES_DIR="$VOID_KERNEL_MODULES_DIR"
|
|
fi
|
|
|
|
while [[ $# -gt 0 ]]; do
|
|
case "$1" in
|
|
--out)
|
|
OUT_ROOTFS="${2:-}"
|
|
shift 2
|
|
;;
|
|
--size)
|
|
SIZE_SPEC="${2:-}"
|
|
shift 2
|
|
;;
|
|
--mirror)
|
|
MIRROR="${2:-}"
|
|
shift 2
|
|
;;
|
|
--arch)
|
|
ARCH="${2:-}"
|
|
shift 2
|
|
;;
|
|
-h|--help)
|
|
usage
|
|
exit 0
|
|
;;
|
|
*)
|
|
log "unknown option: $1"
|
|
usage
|
|
exit 1
|
|
;;
|
|
esac
|
|
done
|
|
|
|
MIRROR="$(normalize_mirror "$MIRROR")"
|
|
REPO_URL="$MIRROR/current"
|
|
STATIC_ARCHIVE_URL="$MIRROR/static/xbps-static-latest.x86_64-musl.tar.xz"
|
|
|
|
if [[ "$ARCH" != "x86_64" ]]; then
|
|
log "unsupported arch: $ARCH"
|
|
log "this experimental builder currently supports only x86_64-glibc"
|
|
exit 1
|
|
fi
|
|
|
|
if [[ -z "$MODULES_DIR" || ! -d "$MODULES_DIR" ]]; then
|
|
log "modules dir not found; run 'make void-kernel' first"
|
|
exit 1
|
|
fi
|
|
if [[ ! -x "$VSOCK_AGENT" ]]; then
|
|
log "vsock agent not found or not executable: $VSOCK_AGENT"
|
|
log "run 'make build'"
|
|
exit 1
|
|
fi
|
|
if [[ ! -f "$GUESTNET_BOOTSTRAP_SCRIPT" ]]; then
|
|
log "guest network bootstrap script not found: $GUESTNET_BOOTSTRAP_SCRIPT"
|
|
exit 1
|
|
fi
|
|
if [[ ! -f "$GUESTNET_VOID_CORE_SERVICE" ]]; then
|
|
log "guest network core-service shim not found: $GUESTNET_VOID_CORE_SERVICE"
|
|
exit 1
|
|
fi
|
|
if [[ -e "$OUT_ROOTFS" ]]; then
|
|
log "output rootfs already exists: $OUT_ROOTFS"
|
|
exit 1
|
|
fi
|
|
|
|
require_command curl
|
|
require_command tar
|
|
require_command sudo
|
|
require_command mkfs.ext4
|
|
require_command ssh-keygen
|
|
require_command mount
|
|
require_command umount
|
|
require_command install
|
|
require_command find
|
|
require_command awk
|
|
require_command sed
|
|
require_command sha256sum
|
|
require_command truncate
|
|
require_command mountpoint
|
|
|
|
VOID_PACKAGES=()
|
|
if ! load_package_preset void VOID_PACKAGES; then
|
|
log "void package preset is empty"
|
|
exit 1
|
|
fi
|
|
if ! PACKAGES_HASH="$(printf '%s\n' "${VOID_PACKAGES[@]}" | sha256sum | awk '{print $1}')"; then
|
|
log "failed to hash package preset"
|
|
exit 1
|
|
fi
|
|
if ! SIZE_BYTES="$(parse_size "$SIZE_SPEC")"; then
|
|
log "invalid size: $SIZE_SPEC"
|
|
exit 1
|
|
fi
|
|
|
|
if [[ "$OUT_ROOTFS" == *.ext4 ]]; then
|
|
WORK_SEED="${OUT_ROOTFS%.ext4}.work-seed.ext4"
|
|
else
|
|
WORK_SEED="${OUT_ROOTFS}.work-seed"
|
|
fi
|
|
|
|
TMP_DIR="$(mktemp -d -t banger-void-rootfs-XXXXXX)"
|
|
STATIC_DIR="$TMP_DIR/static"
|
|
ROOT_MOUNT="$TMP_DIR/rootfs"
|
|
STATIC_ARCHIVE="$TMP_DIR/xbps-static.tar.xz"
|
|
BUILD_DONE=0
|
|
trap cleanup EXIT
|
|
|
|
mkdir -p "$STATIC_DIR" "$ROOT_MOUNT"
|
|
|
|
log "downloading static XBPS from $STATIC_ARCHIVE_URL"
|
|
curl -fsSL "$STATIC_ARCHIVE_URL" -o "$STATIC_ARCHIVE"
|
|
tar -xf "$STATIC_ARCHIVE" -C "$STATIC_DIR"
|
|
|
|
XBPS_INSTALL="$(find_static_binary xbps-install)"
|
|
XBPS_QUERY="$(find_static_binary xbps-query)"
|
|
STATIC_KEYS_DIR="$(find_static_keys_dir)"
|
|
|
|
if [[ -z "$XBPS_INSTALL" || ! -x "$XBPS_INSTALL" ]]; then
|
|
log "failed to locate xbps-install in the static archive"
|
|
exit 1
|
|
fi
|
|
if [[ -z "$STATIC_KEYS_DIR" || ! -d "$STATIC_KEYS_DIR" ]]; then
|
|
log "failed to locate Void repository keys in the static archive"
|
|
exit 1
|
|
fi
|
|
|
|
log "creating $OUT_ROOTFS ($SIZE_SPEC)"
|
|
truncate -s "$SIZE_BYTES" "$OUT_ROOTFS"
|
|
mkfs.ext4 -F -m 0 -L banger-void-root "$OUT_ROOTFS" >/dev/null
|
|
sudo mount -o loop "$OUT_ROOTFS" "$ROOT_MOUNT"
|
|
sudo mkdir -p "$ROOT_MOUNT/var/db/xbps/keys"
|
|
sudo cp -a "$STATIC_KEYS_DIR/." "$ROOT_MOUNT/var/db/xbps/keys/"
|
|
|
|
log "installing Void packages into the rootfs"
|
|
sudo env XBPS_ARCH="$ARCH" "$XBPS_INSTALL" -S -y -r "$ROOT_MOUNT" -R "$REPO_URL" "${VOID_PACKAGES[@]}"
|
|
|
|
if [[ -n "$XBPS_QUERY" && -x "$XBPS_QUERY" ]]; then
|
|
log "installed package set:"
|
|
sudo env XBPS_ARCH="$ARCH" "$XBPS_QUERY" -r "$ROOT_MOUNT" -l | awk '/^ii/ {print " " $2}' || true
|
|
fi
|
|
|
|
if [[ -n "$VOID_KERNEL_MODULES_DIR" ]]; then
|
|
log "copying staged Void kernel modules into the guest"
|
|
else
|
|
log "copying bundled kernel modules into the guest"
|
|
fi
|
|
sudo mkdir -p "$ROOT_MOUNT/lib/modules"
|
|
sudo cp -a "$MODULES_DIR" "$ROOT_MOUNT/lib/modules/"
|
|
|
|
log "installing the guest-side vsock agent"
|
|
sudo mkdir -p "$ROOT_MOUNT/usr/local/bin"
|
|
sudo install -m 0755 "$VSOCK_AGENT" "$ROOT_MOUNT/usr/local/bin/banger-vsock-agent"
|
|
|
|
log "preparing SSH and runit services"
|
|
install_guest_network_bootstrap
|
|
ensure_sshd_include
|
|
enable_sshd_service
|
|
install_vsock_service
|
|
configure_docker_bootstrap
|
|
enable_docker_service
|
|
normalize_root_shell
|
|
configure_root_bash_prompt
|
|
log "installing guest tools"
|
|
install_guest_tools
|
|
install_opencode_service
|
|
install_root_authorized_key
|
|
sudo touch "$ROOT_MOUNT/etc/fstab" "$ROOT_MOUNT/etc/hostname"
|
|
sudo chroot "$ROOT_MOUNT" /usr/bin/ssh-keygen -A
|
|
|
|
log "removing bulky caches, docs, and stale installer artifacts from the experimental image"
|
|
sudo rm -rf \
|
|
"$ROOT_MOUNT/var/cache/xbps" \
|
|
"$ROOT_MOUNT/usr/share/doc" \
|
|
"$ROOT_MOUNT/usr/share/info" \
|
|
"$ROOT_MOUNT/usr/share/man"
|
|
sudo rm -f \
|
|
"$ROOT_MOUNT/root/get-docker" \
|
|
"$ROOT_MOUNT/root/get-docker.sh" \
|
|
"$ROOT_MOUNT/root/.cache/opencode" \
|
|
"$ROOT_MOUNT/tmp/get-docker" \
|
|
"$ROOT_MOUNT/tmp/get-docker.sh"
|
|
sudo rm -rf \
|
|
"$ROOT_MOUNT/root/.cache/mise" \
|
|
"$ROOT_MOUNT/root/.local/share/mise/downloads" \
|
|
"$ROOT_MOUNT/root/.local/share/mise/tmp"
|
|
|
|
sudo umount "$ROOT_MOUNT"
|
|
|
|
write_rootfs_manifest_metadata "$OUT_ROOTFS" "$PACKAGES_HASH"
|
|
|
|
log "building work-seed $WORK_SEED"
|
|
"$BANGER_BIN" internal work-seed --rootfs "$OUT_ROOTFS" --out "$WORK_SEED"
|
|
|
|
BUILD_DONE=1
|
|
log "built experimental Void rootfs: $OUT_ROOTFS"
|
|
log "built experimental Void work-seed: $WORK_SEED"
|
|
log "use examples/void.config.toml as the local config override template"
|