diff options
-rw-r--r-- | .gitignore | 3 | ||||
-rwxr-xr-x | bootstrap | 344 | ||||
-rwxr-xr-x | init | 49 |
3 files changed, 396 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..9921eec --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +*.cpio.gz +buildos-initrd +buildos-image diff --git a/bootstrap b/bootstrap new file mode 100755 index 0000000..5ac3b46 --- /dev/null +++ b/bootstrap @@ -0,0 +1,344 @@ +#! /usr/bin/env bash + +# Bootstrap build2 buildos. +# +# Assumptions/expectations: +# +# - host debootstrap/debian-archive-keyring matching release +# +# - /btrfs/<user> is a btrfs directory where the current user can create +# snapshots +# +# - sudo is passwordless (used to run debootstrap, systemd-nspawn, etc) +# +# Options: +# +# --stage <num> +# Jump straigh to stage <num> by clonning the previous stage snapshot. +# Stages are: +# +# 1 - bootstrap phase 1 +# 2 - bootstrap phase 2 +# 3 - setup +# 4 - create footfs +# 5 - create kernel image and initrd +# +usage="usage: $0" + +id="$(id -un)" +btrfs=/btrfs +release="unstable" +mirror="https://deb.debian.org/debian/" +passwd="123" #@@ TMP root passwd. + +root="$btrfs/$id/buildos" + +owd="$(pwd)" +trap "{ cd '$owd'; exit 1; }" ERR +set -o errtrace # Trap in functions. + +function info () { echo "$*" 1>&2; } +function error () { info "$*"; exit 1; } + +stage="1" +stage_max="5" + +while [ "$#" -gt 0 ]; do + case "$1" in + --stage) + shift + stage="$1" + shift + ;; + -*) + error "unknown option: $1" + ;; + *) + break + ;; + esac +done + +if [ "$stage" -lt "1" -o "$stage" -gt "$stage_max" ]; then + error "invalid stage number $stage" +fi + +# Btrfs subvolume manipulation. +# +function subvol_delete () # <path> +{ + # The subvol show (just as list) needs root. + # + if sudo btrfs subvol show "$1" 1>/dev/null 2>&1; then + btrfs property set -ts "$1" ro false + btrfs subvol delete "$1" + fi +} + +function subvol_create () # <path> +{ + btrfs subvol create "$@" +} + +function subvol_snapshot () # [-r] <src-path> <dst-path> +{ + btrfs subvol snapshot "$@" +} + +# Clean up the working subvolume and all the snapshots starting from the +# requested stage. Also, if stage is not 1, then restore the working subvolume +# from the previous stage snapshot. +# +subvol_delete "$root" + +for i in $(seq "$stage" "$stage_max"); do + subvol_delete "$root-$i" +done + +if [ "$stage" -gt "1" ]; then + i="$(($stage - 1))" + info "restoring working subvolume from stage $i snapshot" + subvol_snapshot "$root-$i" "$root" +fi + +# Spawn a systemd namespace container (systemd-nspawn) +# +function nspawn () # <systemd-nspawn-args> +{ + sudo systemd-nspawn --register=no -D "$root" "$@" + + # systemd-nspawn may create the /var/lib/machines subvolume which prevents + # the deletion of the containing submodule. So we clean it up. + # + if sudo btrfs subvol show "$root/var/lib/machines" 1>/dev/null 2>&1; then + sudo btrfs subvol delete "$root/var/lib/machines" + fi +} + +# (Over)write or append to a file in the installation root, for example: +# +# write <<<'unknown' /etc/hostname +# +function write () # <path> +{ + sudo tee "$root$1" >/dev/null +} + +function append () # <path> +{ + sudo tee -a "$root$1" >/dev/null +} + +# Stage 1: debootstrap, phase 1. +# +if [ "$stage" -eq "1" ]; then + + subvol_create "$root" + + # Notes: + # + # - systemd-container seems to be required by host systemd-nspawn. + # + pkgs="locales,systemd-container" + pkgs+=",net-tools,iproute2,isc-dhcp-client,wget" + pkgs+=",linux-image-amd64" + + sudo debootstrap \ + --foreign \ + --arch=amd64 \ + --merged-usr \ + --variant=minbase \ + --include="$pkgs" \ + "$release" "$root" "$mirror" + + # Post-phase 1 fixups. + # + write <<<'unknown' /etc/hostname + + # Set timezone to UTC (picked up by tzdata package during stage 2). + # + write <<<'Etc/UTC' /etc/timezone + + subvol_snapshot -r "$root" "$root-1" +fi + +# Stage 2: debootstrap, phase 2. +# +if [ "$stage" -le "2" ]; then + + # Create a bootstrap script that will finish the bootstrap from within the + # installation via systemd-nspawn. + # + sudo mkdir "$root/bootstrap" + + write <<EOF /bootstrap/bootstrap +#!/bin/bash + +trap "exit 1" ERR + +set -x + +# Hack around systemd bug#79306 (changes /etc/localtime) by removing it now +# and making readonly below. +# +rm /etc/localtime + +# Both nspawn and debootstrap try to mount /proc /sys (Debian bug#840372). +# +mkdir /tmp/proc /tmp/sys +mount --move /proc /tmp/proc +mount --move /sys /tmp/sys + +# Run second stage of debootstrap. +# +/debootstrap/debootstrap --second-stage + +rm -f /etc/localtime +cp /usr/share/zoneinfo/UTC /etc/localtime +chattr +i /etc/localtime + +# Set root password. +# +chpasswd <<<'root:$passwd' + +# Setup locale. We only support en_US.UTF-8. +# +sed -i 's/^# *\(en_US.UTF-8\)/\1/' /etc/locale.gen +locale-gen --purge + +cat <<EOF1 >/etc/default/locale +LANG="en_US.UTF-8" +LANGUAGE="en_US:en" +EOF1 + +EOF + sudo chmod u+x "$root/bootstrap/bootstrap" + + # Notes: + # + # - Failed to create directory .../sys/fs/selinux: Read-only file system is + # harmless and fixed upstream (systemd issue#3748). + # + nspawn /bootstrap/bootstrap + + subvol_snapshot -r "$root" "$root-2" +fi + +# Stage 3: setup. +# +if [ "$stage" -le "3" ]; then + + # Create the setup script/service that will finish the setup from within the + # installation via systemd-nspawn --boot. + # + sudo mkdir -p "$root/bootstrap" + write <<EOF /bootstrap/setup +#!/bin/bash + +trap "exit 1" ERR + +set -x + +# Clean up package cache. +# +apt-get clean + +# Clean up /bootstrap. +# +rm /usr/lib/systemd/system/default.target.wants/setup.service +rm /usr/lib/systemd/system/setup.service +rm -r /bootstrap + +# Shutdown the container from within. +# +systemctl poweroff + +EOF + sudo chmod u+x "$root/bootstrap/setup" + + # Note that when started via systemd-nspawn, we get /dev/console, not + # /dev/tty0. + # + write <<EOF /usr/lib/systemd/system/setup.service +[Unit] +Description=Setup Service +After=default.target +Conflicts=console-getty.service + +[Service] +Type=idle +TimeoutStartSec=infinity +RemainAfterExit=true +ExecStart=/bootstrap/setup +StandardInput=tty-force +StandardOutput=inherit +StandardError=inherit +TTYPath=/dev/console +TTYReset=yes +TTYVHangup=yes + +[Install] +WantedBy=default.target +EOF + + sudo mkdir -p "$root/usr/lib/systemd/system/default.target.wants" + sudo ln -sf "$root/usr/lib/systemd/system/setup.service" \ + "$root/usr/lib/systemd/system/default.target.wants/setup.service" + + nspawn --boot + + subvol_snapshot -r "$root" "$root-3" +fi + +# Stage 4: generate rootfs. +# +if [ "$stage" -le "4" ]; then + + # Note that there is also initramfs image that is embedded into kernel. In + # Debian it contains just /dev/console and /root/. + # + # Quite a few files/directories are only accessible by root (e.g., /root) so + # we run under sudo. + # + cd "$root" + + root_dirs="dev etc mnt root usr var" + root_links="bin sbin lib lib32 lib64" + + info "generating buildos-rootfs.cpio.gz..." + sudo find $root_dirs $root_links -print0 | \ + sudo cpio --null -o -H newc | \ + gzip -9 > "$owd/buildos-rootfs.cpio.gz" + + cd "$owd" + + subvol_snapshot -r "$root" "$root-4" +fi + +# Stage 5: generate initrd. +# +if [ "$stage" -le "5" ]; then + + # @@ TODO: init location + # + sudo cp -f ./init "$root/" + + info "generating buildos-init.cpio.gz..." + sudo echo 'init' | \ + sudo cpio -o -H newc | \ + gzip -9 > "$owd/buildos-init.cpio.gz" + + cat buildos-rootfs.cpio.gz buildos-init.cpio.gz >buildos-initrd + + # Copy the kernel image next to the initramfs for convenience. + # + cp "$root/vmlinuz" buildos-image + + subvol_snapshot -r "$root" "$root-5" +fi + +# Test. +# +sudo kvm \ + -m 8G \ + -kernel buildos-image -initrd buildos-initrd @@ -0,0 +1,49 @@ +#!/bin/bash + +# Init script for build2 buildos. +# +# Loosely based on the one that comes in Debian initrd.img (since we are +# using its kernel image as is). +# +trap "exit 1" ERR +set -o errtrace # Trap in functions. + +# Note: diagnostics goes to stdout. +# +function info () { echo "$*"; } +function error () { info "$*"; exit 1; } + +export PATH=/sbin:/usr/sbin:/bin:/usr/bin + +# One would expect rootflags=size=1g to work but it doesn't (perhaps init +# is expected to interpret it)? +# +mount -o remount,size=1G / + +mkdir -p /sys /proc +mount -t sysfs -o nodev,noexec,nosuid sysfs /sys +mount -t proc -o nodev,noexec,nosuid proc /proc + +info "init starting up..." + +mount -t devtmpfs -o nosuid,mode=0755 udev /dev +mkdir -p /dev/pts +mount -t devpts -o noexec,nosuid,gid=5,mode=0620 devpts /dev/pts || true + +mkdir -p /run +mount -t tmpfs -o "noexec,nosuid,size=10%,mode=0755" tmpfs /run + +mkdir -p /tmp +mount -t tmpfs -o "nodev,nosuid,size=10%,mode=1777" tmpfs /tmp + +cmdline="$(cat /proc/cmdline)" + +info "boot cmdline: $cmdline" + +sleep 2 + +# --machine-id +# +#exec /lib/systemd/systemd #</dev/console >/dev/console 2>&1 + +exec /bin/bash |