Manage image artifacts and show VM create progress

Stop relying on ad hoc rootfs handling by adding image promotion, managed work-seed fingerprint metadata, and lazy self-healing for older managed images after the first create.

Rebuild guest images with baked SSH access, a guest NIC bootstrap, and default opencode services, and add the staged Void kernel/initramfs/modules workflow so void-exp uses a matching Void boot stack.

Replace the opaque blocking vm.create RPC with a begin/status flow that prints live stages in the CLI while still waiting for vsock health and opencode on guest port 4096.

Validate with GOCACHE=/tmp/banger-gocache go test ./... and live void-exp create/delete smoke runs.
This commit is contained in:
Thales Maciel 2026-03-21 14:48:01 -03:00
parent 9f09b0d25c
commit 30f0c0b54a
No known key found for this signature in database
GPG key ID: 33112E6833C34679
37 changed files with 2334 additions and 99 deletions

View file

@ -0,0 +1,132 @@
#!/bin/sh
set -eu
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
if ! command -v ip >/dev/null 2>&1; then
exit 0
fi
cmdline="$(cat /proc/cmdline 2>/dev/null || true)"
ip_arg=""
for arg in $cmdline; do
case "$arg" in
ip=*)
ip_arg="${arg#ip=}"
break
;;
esac
done
if [ -z "$ip_arg" ]; then
exit 0
fi
field() {
printf '%s' "$ip_arg" | cut -d: -f"$1"
}
mask_to_prefix() {
case "$1" in
[0-9]|[1-2][0-9]|3[0-2])
printf '%s\n' "$1"
return 0
;;
esac
prefix=0
old_ifs=$IFS
IFS=.
set -- $1
IFS=$old_ifs
if [ "$#" -ne 4 ]; then
return 1
fi
for octet in "$@"; do
case "$octet" in
255) prefix=$((prefix + 8)) ;;
254) prefix=$((prefix + 7)) ;;
252) prefix=$((prefix + 6)) ;;
248) prefix=$((prefix + 5)) ;;
240) prefix=$((prefix + 4)) ;;
224) prefix=$((prefix + 3)) ;;
192) prefix=$((prefix + 2)) ;;
128) prefix=$((prefix + 1)) ;;
0) ;;
*) return 1 ;;
esac
done
printf '%s\n' "$prefix"
}
find_iface() {
hint="$1"
if [ -n "$hint" ] && [ -d "/sys/class/net/$hint" ]; then
printf '%s\n' "$hint"
return 0
fi
for path in /sys/class/net/*; do
[ -e "$path" ] || continue
iface="${path##*/}"
if [ "$iface" = "lo" ]; then
continue
fi
printf '%s\n' "$iface"
return 0
done
return 1
}
guest_ip="$(field 1)"
gateway_ip="$(field 3)"
netmask="$(field 4)"
iface_hint="$(field 6)"
dns1="$(field 8)"
dns2="$(field 9)"
if [ -z "$guest_ip" ]; then
exit 0
fi
iface=""
attempt=0
while [ "$attempt" -lt 50 ]; do
iface="$(find_iface "$iface_hint" || true)"
if [ -n "$iface" ]; then
break
fi
attempt=$((attempt + 1))
sleep 0.2
done
if [ -z "$iface" ]; then
exit 0
fi
prefix="$(mask_to_prefix "$netmask" || printf '24\n')"
ip link set "$iface" up
ip addr replace "$guest_ip/$prefix" dev "$iface"
if [ -n "$gateway_ip" ]; then
ip route replace default via "$gateway_ip" dev "$iface"
fi
if [ -n "$dns1" ] || [ -n "$dns2" ]; then
tmp_resolv="/tmp/.banger-resolv.conf.$$"
: > "$tmp_resolv"
if [ -n "$dns1" ]; then
printf 'nameserver %s\n' "$dns1" >> "$tmp_resolv"
fi
if [ -n "$dns2" ]; then
printf 'nameserver %s\n' "$dns2" >> "$tmp_resolv"
fi
if [ -s "$tmp_resolv" ]; then
cat "$tmp_resolv" > /etc/resolv.conf
fi
rm -f "$tmp_resolv"
fi

View file

@ -0,0 +1,13 @@
[Unit]
Description=Banger guest network bootstrap
After=local-fs.target
Before=network.target network-online.target
ConditionPathExists=/proc/cmdline
[Service]
Type=oneshot
ExecStart=/usr/local/libexec/banger-network-bootstrap
RemainAfterExit=yes
[Install]
WantedBy=multi-user.target

View file

@ -0,0 +1,4 @@
#!/bin/sh
if [ -x /usr/local/libexec/banger-network-bootstrap ]; then
/usr/local/libexec/banger-network-bootstrap
fi

View file

@ -0,0 +1,30 @@
package guestnet
import _ "embed"
const (
GuestScriptPath = "/usr/local/libexec/banger-network-bootstrap"
SystemdServiceName = "banger-network.service"
VoidCoreServicePath = "/etc/runit/core-services/20-banger-network.sh"
)
var (
//go:embed assets/bootstrap.sh
bootstrapScript string
//go:embed assets/systemd.service
systemdService string
//go:embed assets/void-core-service.sh
voidCoreService string
)
func BootstrapScript() string {
return bootstrapScript
}
func SystemdServiceUnit() string {
return systemdService
}
func VoidCoreService() string {
return voidCoreService
}