From 965e80da28ff4075eeb4c13c5a6b9c17ab195bb0 Mon Sep 17 00:00:00 2001 From: Andrew Cady Date: Mon, 28 Aug 2017 16:04:22 -0400 Subject: debootstrap.sh: debootstrap wrapper --- debootstrap.sh | 207 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 207 insertions(+) create mode 100755 debootstrap.sh diff --git a/debootstrap.sh b/debootstrap.sh new file mode 100755 index 0000000..e0bbc4b --- /dev/null +++ b/debootstrap.sh @@ -0,0 +1,207 @@ +#!/bin/sh +imgdir=./debootstrap +warn() { printf 'Warning: %s\n' "$*" >&2; } +die() { printf 'Error: %s\n' "$*" >&2; exit 1; } +[ -d "$imgdir" ] || die "directory does not exist: $imgdir" + +usage() +{ + cat <&2 +Usage: + $0 init + $0 new + $0 clone + $0 chroot + $0 list + +EOF + list + exit 1 +} + +list() +{ + local f suite name header_printed= + for f in $imgdir/*.btrfs; do + [ -e "$f" ] || continue + f=${f##*/} + f=${f%.btrfs} + case "$f" in *.*) continue ;; esac + suite=${f%%-*} + [ "$header_printed" ] || echo 'Existing initialized suites:' + printf ' %s\n' "$suite" + header_printed=y + done + header_printed= + for f in $imgdir/*.*.btrfs; do + [ -e "$f" ] || continue + f=${f##*/} + f=${f%.btrfs} + suite=${f%%-*} + name=${f##*.} + [ "$header_printed" ] || echo 'Existing images:' + printf ' %s %s\n' "$suite" "$name" + header_printed=y + done +} + +validate_suite() +{ + case "$1" in + jessie|stretch|sid) return 0 ;; + *) return 1 ;; + esac +} + +i_am_root() { [ "$(id -u)" = 0 ]; } + +btrfs_show_subvolume_id() +{ + local result path="$1" + result=$(btrfs subvolume show "$path" | sed -n -e 's/^[ \t]*Subvolume ID:[ \t]*//p; s/.*is toplevel subvolume/5/p') + if [ "$result" ] + then printf '%s\n' "$result" + else false + fi +} + +mkfs_btrfs() +{ + local target="$1" + mountpoint="$2" # can't be local because of trap + + mkfs.btrfs "$target" || die "mkfs.btrfs failed" + mount -t btrfs "$target" "$mountpoint" || die "mount failed" + trap 'umount "$mountpoint"' EXIT + + btrfs subvolume create "$mountpoint"/root || die "command 'btrfs subvolume create' failed" + mkdir -p "$mountpoint"/root/var/cache/apt + btrfs subvolume create "$mountpoint"/root/var/cache/apt/archives || die "command 'btrfs subvolume create' failed" + subvol_id=$(btrfs_show_subvolume_id "$mountpoint"/root) || die "could not find btrfs subvolume name" + btrfs subvolume set-default "$subvol_id" "$mountpoint" || die "command 'btrfs subvolume set-default' failed" + + trap - EXIT + umount "$mountpoint" +} + +suite_to_basename() +{ + suite=$1 + variant=minbase + arch=$(dpkg-architecture -q DEB_HOST_ARCH) || die "command 'dpkg-architecture' failed" + + printf '%s\n' "$suite-$variant-$arch.btrfs" +} + +suite_name_to_imagename() +{ + suite=$1 + name=$2 + variant=minbase + arch=$(dpkg-architecture -q DEB_HOST_ARCH) || die "command 'dpkg-architecture' failed" + + printf '%s/%s-%s-%s.%s.btrfs\n' "$imgdir" "$suite" "$variant" "$arch" "$name" +} + +chroot_image() +{ + suite=$1 + name=$2 + [ "$suite" -a "$name" ] || usage + imagename=$(suite_name_to_imagename "$suite" "$name") + [ -e "$imagename" ] || die "no such file: $imagename" + [ -d "$imagename".mnt ] || mkdir "$imagename".mnt || die "mkdir" + mountpoint -q "$imagename".mnt || mount "$imagename" "$imagename".mnt || die "mount" + + unshare -f -m -p \ + chroot "$imagename".mnt \ + /bin/sh -c 'mount -t proc proc /proc; mount -t devpts devpts /dev/pts; exec /bin/bash' + + r=$? + umount "$imagename".mnt + rmdir "$imagename".mnt + return $r +} + +clone() +{ + suite=$1 + source_name=$2 + name=$3 + [ "$suite" -a "$name" -a "$source_name" ] || usage + image_basename=$imgdir/$(suite_to_basename "$suite") || die 'suite_to_basename' + + source=${image_basename%.btrfs}.$source_name.btrfs + dest=${image_basename%.btrfs}.$name.btrfs + + [ -e "$source" ] || die "source image not found -- run '$0 new $suite $source_name' to create it" + [ ! -e "$dest" ] || die "file exists: $dest" + cp --reflink=always "$source" "$dest" +} + +new() +{ + suite=$1 + name=$2 + [ "$suite" -a "$name" ] || usage + image_basename=$imgdir/$(suite_to_basename "$suite") || die 'suite_to_basename' + + source=$image_basename + dest=${source%.btrfs}.$name.btrfs + + [ -e "$source" ] || die "source image not found -- run '$0 init $suite' to create it" + [ ! -e "$dest" ] || die "file exists: $dest" + cp --reflink=always "$source" "$dest" +} + +init() +{ + suite=$1 + variant=minbase + arch=$(dpkg-architecture -q DEB_HOST_ARCH) || die "command 'dpkg-architecture' failed" + + size=1G # truncate(1) format + + i_am_root || die 'you are not root' + + validate_suite "$suite" || die "invalid suite: '$suite'" + + target=$imgdir/$suite-$variant-$arch.btrfs + [ -e "$target" ] && return + [ -e "$target".tmp ] && die "refusing to overwrite existing temporary file: $target.tmp" + + [ -d "$target".mnt ] || mkdir "$target".mnt || die "could not create directory $target.mnt" + + truncate -s "$size" "$target".tmp || die "truncate failed" + mkfs_btrfs "$target".tmp "$target".mnt || die "btrfs filesystem creation failed" + mount -t btrfs "$target".tmp "$target".mnt || die "mount failed" + trap 'umount "$target.mnt"' EXIT + + debootstrap_efficiently "$arch" "$variant" "$suite" \ + "$target".mnt "${target%.btrfs}".debs.txt || die "debootstrap failed" + + if umount "$target".mnt + then trap - EXIT + else warn "umount failed" + fi + + mv "$target".tmp "$target" +} + +debootstrap_efficiently() +{ + local arch="$1" variant="$2" suite="$3" target="$4" savedebs="$5" + set -- --arch "$arch" --variant "$variant" "$suite" "$target" + debs=$(debootstrap --print-debs --keep-debootstrap-dir "$@" | tee "$savedebs") || die "debootstrap failed" + for deb in $debs; do + cp /var/cache/apt/archives/${deb}_* $target/var/cache/apt/archives/ + done + + debootstrap "$@" || die "debootstrap failed" +} + +case "$1" in + init|new|clone|list) cmd=$1; shift; $cmd "$@" ;; + chroot) cmd=chroot_image; shift; $cmd "$@" ;; + *) usage ;; +esac -- cgit v1.2.3