diff --git a/.github/workflows/build-netbsd10.yml b/.github/workflows/build-netbsd10.yml new file mode 100644 index 0000000..528b1d6 --- /dev/null +++ b/.github/workflows/build-netbsd10.yml @@ -0,0 +1,67 @@ +name: Build NetBSD 10.1 Vagrant Box + +on: + workflow_dispatch: + push: + paths: + - 'netbsd10/**' + - '.github/workflows/build-netbsd10.yml' + +jobs: + build: + runs-on: ubuntu-latest + defaults: + run: + working-directory: netbsd10 + + steps: + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd #v6.0.2 + + - name: Install dependencies + run: | + curl -fsSL https://apt.releases.hashicorp.com/gpg | sudo gpg --dearmor -o /usr/share/keyrings/hashicorp-archive-keyring.gpg + echo "deb [signed-by=/usr/share/keyrings/hashicorp-archive-keyring.gpg] https://apt.releases.hashicorp.com $(lsb_release -cs) main" | sudo tee /etc/apt/sources.list.d/hashicorp.list + sudo apt-get update && sudo apt-get install -y qemu-system-x86 qemu-utils vagrant packer hcp python3 python3-pexpect genisoimage + sudo chmod 666 /dev/kvm + # Anita's pypi namespace is owned by an unrelated package, and its + # setup.py uses distutils (gone in py3.12). Clone and fix the py2 + # shebang instead of pip-installing. + git clone --depth 1 https://github.com/gson1703/anita.git "$HOME/anita" + sed -i '1c#!/usr/bin/env python3' "$HOME/anita/anita" + echo "$HOME/anita" >> "$GITHUB_PATH" + + - name: Init Packer plugins + run: packer init netbsd10.pkr.hcl + + - name: Cache anita workdir + uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae #v5.0.5 + with: + path: netbsd10/anita-work + key: netbsd-10.1-anita-v2 + + - name: Run anita install + run: ./scripts/anita-install.sh + + - name: Build box + run: PACKER_LOG=1 packer build netbsd10.pkr.hcl + + - name: Upload box as artifact + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a #v7.0.1 + with: + name: netbsd10-libvirt + path: netbsd10/netbsd10-libvirt.box + + - name: Publish to HCP Vagrant Registry + if: github.ref == 'refs/heads/main' + env: + HCP_CLIENT_ID: ${{ secrets.HCP_CLIENT_ID }} + HCP_CLIENT_SECRET: ${{ secrets.HCP_CLIENT_SECRET }} + run: | + hcp auth login --client-id="$HCP_CLIENT_ID" --client-secret="$HCP_CLIENT_SECRET" + vagrant cloud publish \ + --force \ + --release \ + DefinedNet/netbsd10 \ + 1.0.${{ github.run_number }} \ + libvirt \ + netbsd10-libvirt.box diff --git a/README.md b/README.md index 5bc2281..d17889b 100644 --- a/README.md +++ b/README.md @@ -16,6 +16,22 @@ packer build openbsd78.pkr.hcl Produces `openbsd78-libvirt.box`. +### NetBSD 10.1 (amd64, libvirt) + +Minimal NetBSD 10.1 box with vagrant user, sudo, and SSH configured. + +NetBSD has no native install autoresponder, so install is driven by [anita](https://www.gson.org/netbsd/anita/) (the canonical NetBSD test-install tool). `anita-install.sh` produces a base disk image and bootstraps SSH access for root; packer then takes over to configure the vagrant user. + +```sh +cd netbsd10 +pip install anita +packer init netbsd10.pkr.hcl +./scripts/anita-install.sh +packer build netbsd10.pkr.hcl +``` + +Produces `netbsd10-libvirt.box`. + ## Building locally Requires KVM. Both boxes use QEMU with KVM acceleration. diff --git a/netbsd10/netbsd10.pkr.hcl b/netbsd10/netbsd10.pkr.hcl new file mode 100644 index 0000000..35e9c72 --- /dev/null +++ b/netbsd10/netbsd10.pkr.hcl @@ -0,0 +1,64 @@ +packer { + required_plugins { + qemu = { + version = ">= 1.1.0" + source = "github.com/hashicorp/qemu" + } + vagrant = { + version = ">= 1.1.0" + source = "github.com/hashicorp/vagrant" + } + } +} + +variable "version" { + type = string + default = "10.1" +} + +# Disk image produced by scripts/anita-install.sh. Anita drives sysinst over +# the console to install NetBSD into wd0.img, then boots the result once with +# --persist to install the vagrant insecure public key for root and enable +# sshd. Packer takes over from there over SSH. +variable "base_image" { + type = string + default = "anita-work/wd0.qcow2" +} + +variable "ssh_private_key_file" { + type = string + default = "anita-work/vagrant" +} + +source "qemu" "netbsd10" { + iso_url = var.base_image + iso_checksum = "none" + disk_image = true + use_backing_file = true + ssh_username = "root" + ssh_private_key_file = var.ssh_private_key_file + ssh_timeout = "15m" + shutdown_command = "/sbin/shutdown -p now" + disk_size = "20G" + memory = 2048 + cpus = 2 + headless = true + vnc_port_min = 5950 + vnc_port_max = 5950 + accelerator = "kvm" + format = "qcow2" + + boot_wait = "30s" +} + +build { + sources = ["source.qemu.netbsd10"] + + provisioner "shell" { + script = "scripts/provision.sh" + } + + post-processor "vagrant" { + output = "netbsd10-libvirt.box" + } +} diff --git a/netbsd10/scripts/anita-install.sh b/netbsd10/scripts/anita-install.sh new file mode 100755 index 0000000..2087cf8 --- /dev/null +++ b/netbsd10/scripts/anita-install.sh @@ -0,0 +1,58 @@ +#!/bin/sh +# Drive anita to produce a NetBSD disk image that packer can boot and SSH into. +# +# Two stages: +# 1. anita install: runs sysinst over the QEMU console to lay down a base +# install at $WORKDIR/wd0.img. +# 2. anita boot --persist: boots the install once, drops the vagrant insecure +# public key into /root/.ssh/authorized_keys and enables sshd, then halts. +# +# After this script finishes, packer can use $WORKDIR/wd0.img as a base disk +# and SSH in as root with $WORKDIR/vagrant to run scripts/provision.sh. + +set -eux + +WORKDIR="${WORKDIR:-anita-work}" +RELEASE_URL="${RELEASE_URL:-https://cdn.netbsd.org/pub/NetBSD/NetBSD-10.1/amd64/}" + +mkdir -p "$WORKDIR" + +# Vagrant's insecure ssh keypair. Public on github, baked into every vagrant +# install. We need both halves: the public key to trust on the box, the private +# key for packer to ssh in with. +if [ ! -f "$WORKDIR/vagrant.pub" ]; then + curl -fsSL -o "$WORKDIR/vagrant.pub" \ + https://raw.githubusercontent.com/hashicorp/vagrant/main/keys/vagrant.pub +fi +if [ ! -f "$WORKDIR/vagrant" ]; then + curl -fsSL -o "$WORKDIR/vagrant" \ + https://raw.githubusercontent.com/hashicorp/vagrant/main/keys/vagrant + chmod 600 "$WORKDIR/vagrant" +fi + +if [ ! -f "$WORKDIR/wd0.img" ]; then + anita --workdir="$WORKDIR" install "$RELEASE_URL" +fi + +if [ ! -f "$WORKDIR/.bootstrapped" ]; then + PUBKEY=$(cat "$WORKDIR/vagrant.pub") + BOOTSTRAP=" +mkdir -p /root/.ssh +chmod 700 /root/.ssh +echo '$PUBKEY' > /root/.ssh/authorized_keys +chmod 600 /root/.ssh/authorized_keys +echo sshd=YES >> /etc/rc.conf +echo dhcpcd=YES >> /etc/rc.conf +echo 'PermitRootLogin yes' >> /etc/ssh/sshd_config +" + anita --workdir="$WORKDIR" boot --no-install --persist \ + --run "$BOOTSTRAP" "$RELEASE_URL" + touch "$WORKDIR/.bootstrapped" +fi + +# Anita writes a raw image, but packer's qemu builder uses -F qcow2 when +# use_backing_file is set. Produce a qcow2 copy for packer to back from. +if [ ! -f "$WORKDIR/wd0.qcow2" ] || [ "$WORKDIR/wd0.img" -nt "$WORKDIR/wd0.qcow2" ]; then + qemu-img convert -O qcow2 "$WORKDIR/wd0.img" "$WORKDIR/wd0.qcow2.tmp" + mv "$WORKDIR/wd0.qcow2.tmp" "$WORKDIR/wd0.qcow2" +fi diff --git a/netbsd10/scripts/provision.sh b/netbsd10/scripts/provision.sh new file mode 100755 index 0000000..be4071c --- /dev/null +++ b/netbsd10/scripts/provision.sh @@ -0,0 +1,58 @@ +#!/bin/sh +set -eux + +# Non-interactive SSH skips /etc/profile, so PATH is sshd's minimal default. +# Set it explicitly so we find pkg_add, useradd, chown, etc. +export PATH=/sbin:/usr/sbin:/bin:/usr/bin:/usr/pkg/sbin:/usr/pkg/bin:/usr/local/sbin:/usr/local/bin + +# QEMU user-mode advertises an IPv6 prefix that doesn't actually route to the +# public internet. libfetch tries v6 first, waits through retries, then falls +# back to v4 -- compounded across pkg_add's dep chain that's minutes of waste. +# Strip non-link-local v6 addresses and the v6 default route. Don't bring the +# interface down, that kills SSH. +for a in $(ifconfig vioif0 | awk '/inet6/ && !/fe80::/ {sub(/\/.*/,"",$2); print $2}'); do + ifconfig vioif0 inet6 "$a" delete || true +done +route delete -inet6 default 2>/dev/null || true + +# Pull binary packages from the matching NetBSD release repo. ftp.netbsd.org no +# longer carries 9.x packages, which is what triggered building this box in the +# first place. +export PKG_PATH="https://cdn.netbsd.org/pub/pkgsrc/packages/NetBSD/amd64/10.1/All/" + +# Network sanity. Hard-timeout each probe so we never silently hang. +echo "=== sanity ===" +ifconfig vioif0 | grep -E 'inet |status' || true +cat /etc/resolv.conf || true +echo "--- https probe ---" +timeout 20 ftp -V -o - "https://cdn.netbsd.org/pub/pkgsrc/packages/NetBSD/amd64/10.1/All/" 2>&1 | head -10 || echo "https probe exit $?" +echo "--- http probe ---" +timeout 20 ftp -V -o - "http://cdn.netbsd.org/pub/pkgsrc/packages/NetBSD/amd64/10.1/All/" 2>&1 | head -10 || echo "http probe exit $?" +echo "=== /sanity ===" + +# Install rsync (the reason this box exists) plus the usual vagrant box bits. +pkg_add -v sudo rsync bash curl + +# Sudo for the vagrant user. +cat >> /usr/pkg/etc/sudoers <<'EOF' + +Defaults:vagrant !requiretty +vagrant ALL=(ALL) NOPASSWD: ALL +EOF + +# Vagrant user. No password set, key-only login. NetBSD's useradd defaults +# primary group to the username, so the group has to exist first. +groupadd vagrant +useradd -m -g vagrant -G wheel -s /usr/pkg/bin/bash vagrant + +mkdir -p /home/vagrant/.ssh +chmod 700 /home/vagrant/.ssh +ftp -o /home/vagrant/.ssh/authorized_keys \ + https://raw.githubusercontent.com/hashicorp/vagrant/main/keys/vagrant.pub +chmod 600 /home/vagrant/.ssh/authorized_keys +chown -R vagrant:vagrant /home/vagrant/.ssh + +# Shrink the disk image post-processor will see. +rm -rf /tmp/* /var/tmp/* +dd if=/dev/zero of=/tmp/zero bs=1m 2>/dev/null || true +rm -f /tmp/zero