aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBoris Kolpackov <boris@codesynthesis.com>2017-03-23 10:29:27 +0200
committerBoris Kolpackov <boris@codesynthesis.com>2017-03-23 10:29:27 +0200
commitb8f3927e48467ecf196e91a95dd29c4f409709d8 (patch)
treeeea5c59b5e1ad81b9cd03d9fb3b5faa1dc09ff82
Initial bootstrap script
-rw-r--r--.gitignore3
-rwxr-xr-xbootstrap344
-rwxr-xr-xinit49
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
diff --git a/init b/init
new file mode 100755
index 0000000..fdcb318
--- /dev/null
+++ b/init
@@ -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