#! /usr/bin/env bash # Bootstrap build2 Build OS. # # Assumptions/expectations: # # - host debootstrap/debian-archive-keyring matching release # # - /btrfs/ is a btrfs directory where the current user can create # snapshots # # - sudo is passwordless (used to run debootstrap, systemd-nspawn, etc) # # Options: # # --stage # Jump straigh to stage 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. macaddr="de:ad:be:ef:b8:da" # Mac address for testing. 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 () # { # 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 () # { btrfs subvol create "$@" } function subvol_snapshot () # [-r] { 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 () # { 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 <<<'localhost' /etc/hostname # function write () # { sudo tee "$root$1" >/dev/null } function append () # { 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,klibc-utils,sudo,systemd-container" pkgs+=",linux-image-amd64,irqbalance,pciutils,usbutils" pkgs+=",hdparm,btrfs-progs" pkgs+=",net-tools,iproute2,iptables,isc-dhcp-client" pkgs+=",ifupdown,bridge-utils,dnsmasq,ntp,postfix" pkgs+=",iputils-ping,wget,curl" pkgs+=",openssh-client,openssh-server" pkgs+=",tftp-hpa,tftpd-hpa" pkgs+=",bzip2,xz-utils" pkgs+=",less,nano" pkgs+=",qemu-kvm,qemu-utils,socat" pkgs+=",g++,pkg-config" sudo debootstrap \ --foreign \ --arch=amd64 \ --merged-usr \ --variant=minbase \ --include="$pkgs" \ "$release" "$root" "$mirror" # Post-phase 1 fixups. # # Set the initial hostname to '(none)'. This value is detected and # overriden by /sbin/dhclient-script if the DHCP server sends host-name. # write <<<'(none)' /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 </etc/os-release NAME="Build OS" VERSION="0.5.0" ID=buildos ID_LIKE=debian PRETTY_NAME="Build OS 0.5.0 (Based on Debian)" VERSION_ID="0.5" HOME_URL="https://build2.org/" SUPPORT_URL="https://lists.build2.org/" BUG_REPORT_URL="https://lists.build2.org/" EOF1 cat </etc/issue Build OS 0.5.0 (Based on Debian) \n \l EOF1 cat </etc/motd Welcome to Build OS 0.5.0 (https://build2.org)! EOF1 # Set root password. # chpasswd <<<'root:$passwd' # Enable IPv4 forwarding (used for private bridge NAT). # sed -i -e 's/^# *\(net.ipv4.ip_forward\).*/\1=1/' /etc/sysctl.conf # Setup locale. We only support en_US.UTF-8. # sed -i -e 's/^# *\(en_US.UTF-8\)/\1/' /etc/locale.gen locale-gen --purge cat </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 </etc/sudoers.d/build echo "Defaults:build !syslog" >>/etc/sudoers.d/build chmod 0440 /etc/sudoers.d/build # Clean up package cache. # apt-get clean # Clean up /bootstrap. # rm /usr/lib/systemd/system/default.target.wants/buildos-setup.service rm /usr/lib/systemd/system/buildos-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 < "$owd/buildos-rootfs.cpio.gz" cd "$owd" subvol_snapshot -r "$root" "$root-4" fi # Stage 5: generate initrd. # if [ "$stage" -le "5" ]; then # Generate buildid and store it in /etc/os-release and in buildos-buildid. # These are used by the monitor to detect when it's time to reboot. # # Note that /etc/os-release is a symlink to /usr/lib/os-release. # buildid="$(uuidgen)" sudo tee -a "$root/etc/os-release" >/dev/null <<<"BUILD_ID=\"$buildid\"" # Install init and buildos monitor. # sudo install -m 755 ./init "$root/" sudo install -m 755 ./buildos "$root/usr/sbin/" sudo install -m 755 ./buildos.service "$root/usr/lib/systemd/system/" sudo ln -sf "$root/usr/lib/systemd/system/buildos.service" \ "$root/usr/lib/systemd/system/default.target.wants/buildos.service" info "generating buildos-init.cpio.gz..." cd "$root" sudo cpio -o -H newc < "$owd/buildos-init.cpio.gz" usr/lib/os-release init usr/sbin/buildos usr/lib/systemd/system/buildos.service usr/lib/systemd/system/default.target.wants/buildos.service EOF cd "$owd" 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 echo "$buildid" >buildos-buildid subvol_snapshot -r "$root" "$root-5" fi exit 0 # Test. # if [ ! -e /tmp/buildos-state ]; then qemu-img create -f raw /tmp/buildos-state 20M fi if [ ! -e /tmp/buildos-machines ]; then qemu-img create -f raw /tmp/buildos-machines 100M fi # To test PXE boot, replace -kernel/-initrd/-append with '-boot n'. # sudo kvm \ -m 16G \ -cpu host -smp "sockets=1,cores=4,threads=2" \ -device "e1000,netdev=net0,mac=$macaddr" \ -netdev "tap,id=net0,script=./qemu-ifup" \ -device "virtio-scsi-pci,id=scsi" \ -device "scsi-hd,drive=disk1" \ -drive "if=none,id=disk1,file=/tmp/buildos-state,format=raw" \ -device "scsi-hd,drive=disk2" \ -drive "if=none,id=disk2,file=/tmp/buildos-machines,format=raw" \ -boot n # -kernel buildos-image -initrd buildos-initrd \ # -append "buildos.smtp_relay=build2.org buildos.admin_email=admin@build2.org"