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:
parent
9f09b0d25c
commit
30f0c0b54a
37 changed files with 2334 additions and 99 deletions
132
internal/guestnet/assets/bootstrap.sh
Normal file
132
internal/guestnet/assets/bootstrap.sh
Normal 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
|
||||
13
internal/guestnet/assets/systemd.service
Normal file
13
internal/guestnet/assets/systemd.service
Normal 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
|
||||
4
internal/guestnet/assets/void-core-service.sh
Normal file
4
internal/guestnet/assets/void-core-service.sh
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
#!/bin/sh
|
||||
if [ -x /usr/local/libexec/banger-network-bootstrap ]; then
|
||||
/usr/local/libexec/banger-network-bootstrap
|
||||
fi
|
||||
30
internal/guestnet/guestnet.go
Normal file
30
internal/guestnet/guestnet.go
Normal 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
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue