aboutsummaryrefslogtreecommitdiff
path: root/bootstrap
diff options
context:
space:
mode:
Diffstat (limited to 'bootstrap')
-rwxr-xr-xbootstrap344
1 files changed, 344 insertions, 0 deletions
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