1
0
forked from wrenn/wrenn
Co-authored-by: Tasnim Kabir Sadik <tksadik@omukk.dev>

Reviewed-on: wrenn/wrenn#50
This commit is contained in:
2026-05-24 21:10:37 +00:00
parent 4707f16c76
commit 05ddf62399
203 changed files with 15815 additions and 9344 deletions

17
images/build-alpine.sh Executable file
View File

@ -0,0 +1,17 @@
#!/usr/bin/env bash
#
# build-alpine.sh — Build the minimal-alpine system base rootfs (template id 1).
#
# Usage: bash images/build-alpine.sh
set -euo pipefail
source "$(cd "$(dirname "$0")" && pwd)/build-common.sh"
# Alpine is musl-based: the static envd + static tini run fine. bash is added so
# wrenn-user has a familiar login shell; wrenn-init itself only needs /bin/sh.
PREP="set -e
apk add --no-cache socat chrony sudo wget curl ca-certificates git iproute2 tini bash
adduser -D wrenn-user
${WRENN_SUDOERS_SETUP}"
build_system_rootfs "alpine:3.22" 1 "${PREP}"

20
images/build-arch.sh Executable file
View File

@ -0,0 +1,20 @@
#!/usr/bin/env bash
#
# build-arch.sh — Build the minimal-arch system base rootfs (template id 2).
#
# Arch is rolling-release; archlinux:base is the minimal base group.
#
# Usage: bash images/build-arch.sh
set -euo pipefail
source "$(cd "$(dirname "$0")" && pwd)/build-common.sh"
# tini is AUR-only on Arch (not in core/extra), so it is not installed here —
# rootfs-from-container.sh injects the static tini binary instead.
PREP="set -e
pacman -Sy --noconfirm --needed socat chrony sudo wget curl ca-certificates git iproute2 inetutils
useradd -m -s /bin/bash wrenn-user
${WRENN_SUDOERS_SETUP}
pacman -Scc --noconfirm || true"
build_system_rootfs "archlinux:base" 2 "${PREP}"

59
images/build-common.sh Executable file
View File

@ -0,0 +1,59 @@
#!/usr/bin/env bash
#
# build-common.sh — shared helpers for building the system base rootfs images.
#
# Sourced by images/build-{ubuntu,alpine,arch,fedora}.sh. Each caller defines
# the distro base image, reserved template ID, and the in-container prep snippet
# (install packages + create wrenn-user), then calls build_system_rootfs.
#
# The same statically-linked envd + tini run on every distro; the per-OS prep
# only differs in the package manager and the user-creation command.
set -euo pipefail
# base36(all-zeros UUID) = the platform team that owns every system base
# template. Must match id.PlatformTeamID / id.UUIDToBase36 on the Go side.
PLATFORM_TEAM_B36="0000000000000000000000000"
# WRENN_SUDOERS_SETUP grants wrenn-user passwordless sudo. Identical on every
# distro; appended to each prep snippet after the user is created.
WRENN_SUDOERS_SETUP='echo "wrenn-user ALL=(ALL) NOPASSWD:ALL" > /etc/sudoers.d/wrenn-user && chmod 0440 /etc/sudoers.d/wrenn-user'
# build_system_rootfs <base_image> <template_id_int> <prep_snippet>
#
# Spawns a throwaway container from base_image, runs prep_snippet inside it,
# then exports it to the system base template's on-disk path
# (images/teams/<platform>/<base36(id)>/rootfs.ext4) via rootfs-from-container.sh.
build_system_rootfs() {
local base_image="$1" template_id="$2" prep="$3"
local script_dir project_root container dest tmpl_b36
script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
project_root="$(cd "${script_dir}/.." && pwd)"
container="wrenn-build-${template_id}-$$"
# base36(template_id). System IDs are single-digit (0-3), so base36 equals
# the decimal digit and the 25-char zero-padded decimal matches what
# id.UUIDToBase36 produces for these well-known IDs.
tmpl_b36="$(printf '%025d' "${template_id}")"
dest="teams/${PLATFORM_TEAM_B36}/${tmpl_b36}"
echo "==> Pulling ${base_image}..."
docker pull "${base_image}"
echo "==> Preparing container ${container}..."
docker rm -f "${container}" >/dev/null 2>&1 || true
# Arm cleanup before starting the container so a failed run still removes it.
# Expand the name into the trap now: it must survive after this function's
# locals go out of scope (set -u would error on a stale reference otherwise).
trap "docker rm -f '${container}' >/dev/null 2>&1 || true" EXIT
docker run --name "${container}" "${base_image}" /bin/sh -c "${prep}"
# Run the exporter as the normal user, NOT under sudo: it builds envd via
# `make build-envd` (needs cargo on the user's PATH) and uses sudo itself
# for the privileged mount/mkfs/copy steps.
echo "==> Exporting to images/${dest}/rootfs.ext4..."
bash "${project_root}/scripts/rootfs-from-container.sh" "${container}" "${dest}"
}

19
images/build-fedora.sh Executable file
View File

@ -0,0 +1,19 @@
#!/usr/bin/env bash
#
# build-fedora.sh — Build the minimal-fedora system base rootfs (template id 3).
#
# Usage: bash images/build-fedora.sh
set -euo pipefail
source "$(cd "$(dirname "$0")" && pwd)/build-common.sh"
# Fedora's iproute package provides `ip` (no "2" suffix, unlike Debian/Arch).
PREP="set -e
# install_weak_deps=False keeps the image lean. The guest never runs systemd:
# PID 1 is wrenn-init -> tini -> envd.
dnf install -y --setopt=install_weak_deps=False socat chrony sudo wget curl ca-certificates git iproute hostname tini
useradd -m -s /bin/bash wrenn-user
${WRENN_SUDOERS_SETUP}
dnf clean all"
build_system_rootfs "fedora:45" 3 "${PREP}"

View File

25
images/build-ubuntu.sh Executable file
View File

@ -0,0 +1,25 @@
#!/usr/bin/env bash
#
# build-ubuntu.sh — Build the minimal-ubuntu system base rootfs (template id 0).
#
# Usage: bash images/build-ubuntu.sh
set -euo pipefail
source "$(cd "$(dirname "$0")" && pwd)/build-common.sh"
PREP="set -e
export DEBIAN_FRONTEND=noninteractive
apt-get update
# --no-install-recommends keeps the image lean (avoids pulling systemd-adjacent
# recommends). The guest never runs systemd: PID 1 is wrenn-init -> tini -> envd.
apt-get install -y --no-install-recommends socat chrony sudo wget curl ca-certificates git iproute2 hostname tini
# Remove the stock 'ubuntu' user (uid 1000) shipped by the base image; it is
# replaced by wrenn-user. Also drop its cloud-init sudoers drop-in.
userdel -r ubuntu 2>/dev/null || true
rm -f /etc/sudoers.d/90-cloud-init-users
useradd -m -s /bin/bash wrenn-user
${WRENN_SUDOERS_SETUP}
apt-get clean
rm -rf /var/lib/apt/lists/*"
build_system_rootfs "ubuntu:26.04" 0 "${PREP}"

View File

@ -1,5 +1,5 @@
#!/bin/sh
# wrenn-init: minimal PID 1 init for Firecracker microVMs.
# wrenn-init: minimal PID 1 init for Cloud Hypervisor microVMs.
# Mounts virtual filesystems, starts chronyd for time sync, then execs tini + envd.
set -e
@ -17,9 +17,17 @@ mkdir -p /sys/fs/cgroup
mount -t cgroup2 cgroup2 /sys/fs/cgroup 2>/dev/null || true
echo "+cpu +memory +io" > /sys/fs/cgroup/cgroup.subtree_control 2>/dev/null || true
# Set hostname and make it resolvable (sudo requires this).
hostname capsule
echo "127.0.0.1 capsule" >> /etc/hosts
# Disable write_zeroes and discard on rootfs — dm-snapshot doesn't support
# these ops, but CH advertises them anyway. Suppress at block queue level.
# sysfs attributes are read-only on some kernels, so failures are expected.
{ echo 0 > /sys/block/vda/queue/write_zeroes_max_bytes; } 2>/dev/null || true
{ echo 0 > /sys/block/vda/queue/discard_max_bytes; } 2>/dev/null || true
# Set hostname and make it resolvable (sudo requires this). Use the kernel knob
# directly so we don't depend on the `hostname` binary, which is absent from
# minimal Arch/Fedora images. Guard so a failure never aborts init under set -e.
echo capsule > /proc/sys/kernel/hostname 2>/dev/null || hostname capsule 2>/dev/null || true
echo "127.0.0.1 capsule" >> /etc/hosts 2>/dev/null || true
# Configure networking if the kernel ip= boot arg did not already set it up.
if ! ip addr show eth0 2>/dev/null | grep -q "169.254.0.21"; then
@ -29,9 +37,14 @@ if ! ip addr show eth0 2>/dev/null | grep -q "169.254.0.21"; then
ip route add default via 169.254.0.22 2>/dev/null || true
fi
# Configure DNS resolver.
echo "nameserver 8.8.8.8" > /etc/resolv.conf
echo "nameserver 8.8.4.4" >> /etc/resolv.conf
# Configure DNS resolver. Drop any existing symlink first — on some distros
# (e.g. Fedora) /etc/resolv.conf is a dangling symlink into systemd-resolved,
# and writing through it would fail and abort init under set -e.
rm -f /etc/resolv.conf 2>/dev/null || true
{
echo "nameserver 8.8.8.8"
echo "nameserver 8.8.4.4"
} > /etc/resolv.conf 2>/dev/null || true
# Set a standard PATH so envd and all child processes can find common binaries.
export PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games