Skip to content

Instantly share code, notes, and snippets.

@sfan5
Last active October 28, 2024 14:14
Show Gist options
  • Save sfan5/52aa53f5dca06ac3af30455b203d3404 to your computer and use it in GitHub Desktop.
Save sfan5/52aa53f5dca06ac3af30455b203d3404 to your computer and use it in GitHub Desktop.
Create bootable systemd-nspawn containers with Alpine, Arch Linux or Ubuntu
#!/bin/bash -e
# Creates a systemd-nspawn container with Alpine
MIRROR=http://dl-cdn.alpinelinux.org/alpine
VERSION=${VERSION:-v3.20}
APKTOOLS_VERSION=2.14.4-r1
wget_or_curl () {
if command -v wget >/dev/null; then
wget -qO- "$1"
elif command -v curl >/dev/null; then
curl -Ls "$1"
else
echo "missing either curl or wget" >&2
return 1
fi
}
if [ $UID -ne 0 ]; then
echo "run this script as root" >&2
exit 1
fi
dest="$1"
if [ -z "$dest" ]; then
echo "Usage: $0 <destination>" >&2
exit 0
fi
if [ -e "$dest/usr/bin" ]; then
echo "destination already seems to contain a root file system" >&2
exit 1
fi
if [[ "$(uname -m)" =~ ^i[3456]86|x86 ]]; then
toolarch=x86
guestarch=$toolarch
[ "$(uname -m)" = x86_64 ] && guestarch=x86_64
elif [[ "$(uname -m)" =~ ^arm|aarch64 ]]; then
toolarch=armv7
guestarch=$toolarch
[ "$(uname -m)" = aarch64 ] && guestarch=aarch64
else
echo "unsupported architecture" >&2
exit 1
fi
apkdir=$(mktemp -d)
trap 'rm -rf $apkdir' EXIT
wget_or_curl "$MIRROR/latest-stable/main/$toolarch/apk-tools-static-$APKTOOLS_VERSION.apk" \
| tar -xz -C $apkdir || \
{ echo "couldn't download apk-tools, the version might have changed..." >&2; exit 1; }
$apkdir/sbin/apk.static \
-X $MIRROR/$VERSION/main -U --arch $guestarch \
--allow-untrusted --root "$dest" \
--initdb add alpine-base
mkdir -p "$dest"/{etc/apk,root}
# configure mirror
printf '%s/%s/main\n%s/%s/community\n' "$MIRROR" $VERSION "$MIRROR" $VERSION >"$dest"/etc/apk/repositories
for i in $(seq 0 10); do # https://github.com/systemd/systemd/issues/852
echo "pts/$i" >>"$dest/etc/securetty"
done
# make console work
sed '/tty[0-9]:/ s/^/#/' -i "$dest"/etc/inittab
printf 'console::respawn:/sbin/getty 38400 console\n' >>"$dest"/etc/inittab
# minimal boot services
for s in hostname bootmisc syslog; do
ln -s /etc/init.d/$s "$dest"/etc/runlevels/boot/$s
done
for s in killprocs savecache; do
ln -s /etc/init.d/$s "$dest"/etc/runlevels/shutdown/$s
done
echo ""
echo "Alpine $VERSION $guestarch container was created successfully."
#!/bin/bash -e
# Creates a systemd-nspawn container with Arch Linux
MIRROR=http://mirror.fra10.de.leaseweb.net/archlinux
ISO_DATE=latest
PKG_GROUPS="base"
wget_or_curl () {
if command -v wget >/dev/null; then
wget "$1" -O "$2"
elif command -v curl >/dev/null; then
curl -L "$1" -o "$2"
else
echo "missing either curl or wget" >&2
return 1
fi
}
if [ $UID -ne 0 ]; then
echo "run this script as root" >&2
exit 1
fi
dest="$1"
if [ -z "$dest" ]; then
echo "Usage: $0 <destination>" >&2
exit 0
fi
if [ -e "$dest/usr/bin" ]; then
echo "destination already seems to contain a root file system" >&2
exit 1
fi
[ "$(uname -m)" = x86_64 ] || { echo "unsupported architecture" >&2; exit 1; }
tarfile=$(mktemp)
trap 'rm $tarfile' EXIT
wget_or_curl "$MIRROR/iso/$ISO_DATE/archlinux-bootstrap-x86_64.tar.zst" $tarfile
mkdir -p "$dest"
tar -xaf $tarfile -C "$dest" --strip-components=1 --numeric-owner
# configure mirror
printf 'Server = %s/$repo/os/$arch\n' "$MIRROR" >"$dest"/etc/pacman.d/mirrorlist
sed '/^root:/ s|\*||' -i "$dest/etc/shadow" # passwordless login
rm "$dest/etc/resolv.conf" # systemd configures this
# https://github.com/systemd/systemd/issues/852
[ -f "$dest/etc/securetty" ] && \
printf 'pts/%d\n' $(seq 0 10) >>"$dest/etc/securetty"
# seems to be this bug https://github.com/systemd/systemd/issues/3611
systemd-machine-id-setup --root="$dest"
# install the packages
systemd-nspawn -q -D "$dest" sh -c "
pacman-key --init && pacman-key --populate
pacman -Sy --noconfirm --needed ${PKG_GROUPS}
"
echo ""
echo "Arch Linux container was created successfully (bootstrapped from $ISO_DATE)"
#!/bin/bash -e
# Creates a systemd-nspawn container with Ubuntu
CODENAME=${CODENAME:-noble}
wget_or_curl () {
if command -v wget >/dev/null; then
wget "$1" -O "$2"
elif command -v curl >/dev/null; then
curl -L "$1" -o "$2"
else
echo "missing either curl or wget" >&2
return 1
fi
}
if [ $UID -ne 0 ]; then
echo "run this script as root" >&2
exit 1
fi
dest="$1"
if [ -z "$dest" ]; then
echo "Usage: $0 <destination>" >&2
exit 0
fi
if [ -e "$dest/usr/bin" ]; then
echo "destination already seems to contain a root file system" >&2
exit 1
fi
if [ "$(uname -m)" = x86_64 ]; then
guestarch=amd64
elif [ "$(uname -m)" = aarch64 ]; then
guestarch=arm64
else
echo "unsupported architecture" >&2
exit 1
fi
rootfs=$(mktemp)
trap 'rm $rootfs' EXIT
wget_or_curl "http://cloud-images.ubuntu.com/${CODENAME}/current/${CODENAME}-server-cloudimg-${guestarch}-root.tar.xz" $rootfs
mkdir -p "$dest"
tar -xaf $rootfs -C "$dest" --numeric-owner
sed '/^root:/ s|\*||' -i "$dest/etc/shadow" # passwordless login
rm "$dest/etc/resolv.conf" # systemd configures this
# https://github.com/systemd/systemd/issues/852
[ -f "$dest/etc/securetty" ] && \
printf 'pts/%d\n' $(seq 0 10) >>"$dest/etc/securetty"
# container needs no mounts
>"$dest/etc/fstab"
# disable services and uninstall packages
systemd-nspawn -q -D "$dest" sh -c '
[ -s /etc/environment ] && . /etc/environment
for unit in ssh.service ssh.socket systemd-timesyncd systemd-networkd-wait-online systemd-resolved; do
systemctl is-enabled "$unit" && systemctl disable "$unit"
done
apt-get -qq satisfy -y --purge "Conflicts: lxcfs, lxd, snapd, cloud-init" || \
apt-get -qq purge --autoremove snapd lxcfs lxd cloud-init
'
echo ""
echo "Ubuntu $CODENAME $guestarch container was created successfully"
@smekkley
Copy link

Awesome, thank you for sharing the script.
It's a little bit ugly but I change to use edge instead.

VERSION=edge
APKTOOLS_VERSION=$(curl -s $MIRROR/edge/main/$ARCH/ | awk -F'=' 'match($0, /href="apk-tools-static-(.*).apk">/, a) {print a[1]}')

@mikhailnov
Copy link

Thank you, very useful scripts, saved a lot of time.

@mikhailnov
Copy link

mikhailnov commented Apr 3, 2019

@amit-tewari
Copy link

Starting alpine image on Ubuntu 20 gives

Sep 07 02:53:14 Gelato systemd-nspawn[79402]: getty: console: TIOCSCTTY: Operation not permitted
Sep 07 02:53:24 Gelato systemd-nspawn[79402]: getty: console: TIOCSCTTY: Operation not permitted
Sep 07 02:53:34 Gelato systemd-nspawn[79402]: getty: console: TIOCSCTTY: Operation not permitted
Sep 07 02:53:44 Gelato systemd-nspawn[79402]: getty: console: TIOCSCTTY: Operation not permitted
Sep 07 02:53:54 Gelato systemd-nspawn[79402]: getty: console: TIOCSCTTY: Operation not permitted

but

systemd-nspawn -M alpine -- /sbin/getty -nl /bin/ash 0 /dev/console

works.

printf 'console::respawn:/sbin/getty 38400 console\n' >>"$dest"/etc/inittab

does not seem to work.

@X0RW3LL
Copy link

X0RW3LL commented Nov 17, 2022

Thank you so much for this! I hope you don't mind me using bits of your scripts here: https://github.com/X0RW3LL/XenSpawn
Linked to this gist in the references section <3

@Jip-Hop
Copy link

Jip-Hop commented Jan 2, 2023

Thanks for this script! Building my own based on yours :)

@mrstux
Copy link

mrstux commented Jul 23, 2024

Thanks for this script! Building my own based on yours :)

heh ;)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment