#!/bin/sh -eu # vim: ts=4 et: [ -z "$DEBUG" ] || [ "$DEBUG" = 0 ] || set -x export \ DEVICE=/dev/vda \ TARGET=/mnt \ SETUP=/tmp/setup.d die() { printf '\033[1;7;31m FATAL: %s \033[0m\n' "$@" >&2 # bold reversed red exit 1 } einfo() { printf '\n\033[1;7;36m> %s <\033[0m\n' "$@" >&2 # bold reversed cyan } # set up the builder's environment setup_builder() { einfo "Setting up Builder Instance" setup-apkrepos -1 # main repo via dl-cdn # ODO? also uncomment community repo? # Always use latest versions within the release, security patches etc. apk upgrade --no-cache --available apk --no-cache add \ e2fsprogs \ dosfstools \ gettext \ lsblk \ parted } make_filesystem() { einfo "Making the Filesystem" root_dev=$DEVICE # make sure we're using a blank block device lsblk -P --fs "$DEVICE" >/dev/null 2>&1 || \ die "'$DEVICE' is not a valid block device" if lsblk -P --fs "$DEVICE" | grep -vq 'FSTYPE=""'; then die "Block device '$DEVICE' is not blank" fi if [ "$FIRMWARE" = uefi ]; then # EFI partition isn't optimally aligned, but is rarely used after boot parted "$DEVICE" -- \ mklabel gpt \ mkpart EFI fat32 512KiB 1MiB \ mkpart / ext4 1MiB 100% \ set 1 esp on \ unit MiB print root_dev="${DEVICE}2" mkfs.fat -n EFI "${DEVICE}1" fi mkfs.ext4 -O ^64bit -L / "$root_dev" mkdir -p "$TARGET" mount -t ext4 "$root_dev" "$TARGET" if [ "$FIRMWARE" = uefi ]; then mkdir -p "$TARGET/boot/efi" mount -t vfat "${DEVICE}1" "$TARGET/boot/efi" fi } install_base() { einfo "Installing Alpine Base" mkdir -p "$TARGET/etc/apk" echo "$REPOS" > "$TARGET/etc/apk/repositories" cp -a /etc/apk/keys "$TARGET/etc/apk" # shellcheck disable=SC2086 for key in $REPO_KEYS; do wget -q $key -P "$TARGET/etc/apk/keys" done # shellcheck disable=SC2086 apk --root "$TARGET" --initdb --no-cache add $PACKAGES_ADD # shellcheck disable=SC2086 [ -z "$PACKAGES_NOSCRIPTS" ] || \ apk --root "$TARGET" --no-cache --no-scripts add $PACKAGES_NOSCRIPTS # shellcheck disable=SC2086 [ -z "$PACKAGES_DEL" ] || \ apk --root "$TARGET" --no-cache del $PACKAGES_DEL } setup_chroot() { mount -t proc none "$TARGET/proc" mount --bind /dev "$TARGET/dev" mount --bind /sys "$TARGET/sys" # Needed for bootstrap, will be removed in the cleanup stage. install -Dm644 /etc/resolv.conf "$TARGET/etc/resolv.conf" } install_bootloader() { einfo "Installing Bootloader" # create initfs # shellcheck disable=SC2046 kernel=$(basename $(find "$TARGET/lib/modules/"* -maxdepth 0)) # ensure features can be found by mkinitfs for FEATURE in $INITFS_FEATURES; do # already taken care of? [ -f "$TARGET/etc/mkinitfs/features.d/$FEATURE.modules" ] || \ [ -f "$TARGET/etc/mkinitfs/features.d/$FEATURE.files" ] && continue # find the kernel module directory module=$(chroot "$TARGET" /sbin/modinfo -k "$kernel" -n "$FEATURE") [ -z "$module" ] && die "initfs_feature '$FEATURE' kernel module not found" # replace everything after .ko with a * echo "$module" | cut -d/ -f5- | sed -e 's/\.ko.*/.ko*/' \ > "$TARGET/etc/mkinitfs/features.d/$FEATURE.modules" done # TODO? this appends INITFS_FEATURES, we may want to allow removal someday? sed -Ei "s/^features=\"([^\"]+)\"/features=\"\1 $INITFS_FEATURES\"/" \ "$TARGET/etc/mkinitfs/mkinitfs.conf" chroot "$TARGET" /sbin/mkinitfs "$kernel" if [ "$FIRMWARE" = uefi ]; then install_grub_efi else install_extlinux fi } install_extlinux() { # Use disk labels instead of UUID or devices paths so that this works across # instance familes. UUID works for many instances but breaks on the NVME # ones because EBS volumes are hidden behind NVME devices. # # Shorten timeout (1/10s), eliminating delays for instance launches. # # ttyS0 is for EC2 Console "Get system log" and "EC2 Serial Console" # features, whereas tty0 is for "Get Instance screenshot" feature. Enabling # the port early in extlinux gives the most complete output in the log. # # TODO: review for other clouds -- this may need to be cloud-specific. sed -Ei -e "s|^[# ]*(root)=.*|\1=LABEL=/|" \ -e "s|^[# ]*(default_kernel_opts)=.*|\1=\"$KERNEL_OPTIONS\"|" \ -e "s|^[# ]*(serial_port)=.*|\1=ttyS0|" \ -e "s|^[# ]*(modules)=.*|\1=$KERNEL_MODULES|" \ -e "s|^[# ]*(default)=.*|\1=virt|" \ -e "s|^[# ]*(timeout)=.*|\1=1|" \ "$TARGET/etc/update-extlinux.conf" chroot "$TARGET" /sbin/extlinux --install /boot # TODO: is this really necessary? can we set all this stuff during --install? chroot "$TARGET" /sbin/update-extlinux --warn-only } install_grub_efi() { [ -d "/sys/firmware/efi" ] || die "/sys/firmware/efi does not exist" case "$ARCH" in x86_64) grub_target=x86_64-efi ; fwa=x64 ;; aarch64) grub_target=arm64-efi ; fwa=aa64 ;; *) die "ARCH=$ARCH is currently unsupported" ;; esac # disable nvram so grub doesn't call efibootmgr chroot "$TARGET" /usr/sbin/grub-install --target="$grub_target" --efi-directory=/boot/efi \ --bootloader-id=alpine --boot-directory=/boot --no-nvram # fallback mode install -D "$TARGET/boot/efi/EFI/alpine/grub$fwa.efi" "$TARGET/boot/efi/EFI/boot/boot$fwa.efi" # install default grub config envsubst < "$SETUP/grub.template" > "$SETUP/grub" install -o root -g root -Dm644 -t "$TARGET/etc/default" \ "$SETUP/grub" # generate/install new config chroot "$TARGET" grub-mkconfig -o /boot/grub/grub.cfg } configure_system() { einfo "Configuring System" # default network configuration install -o root -g root -Dm644 -t "$TARGET/etc/network" "$SETUP/interfaces" # configure NTP server, if specified [ -n "$NTP_SERVER" ] && \ sed -e 's/^pool /server /' -e "s/pool.ntp.org/$NTP_SERVER/g" \ -i "$TARGET/etc/chrony/chrony.conf" # setup fstab install -o root -g root -Dm644 -t "$TARGET/etc" "$SETUP/fstab" # if we're using an EFI bootloader, add extra line for EFI partition if [ "$FIRMWARE" = uefi ]; then cat "$SETUP/fstab.grub-efi" >> "$TARGET/etc/fstab" fi # Disable getty for physical ttys, enable getty for serial ttyS0. sed -Ei -e '/^tty[0-9]/s/^/#/' -e '/^#ttyS0:/s/^#//' "$TARGET/etc/inittab" # setup sudo and/or doas if grep -q '^sudo$' "$TARGET/etc/apk/world"; then echo '%wheel ALL=(ALL) NOPASSWD: ALL' > "$TARGET/etc/sudoers.d/wheel" fi if grep -q '^doas$' "$TARGET/etc/apk/world"; then echo 'permit nopass :wheel' > "$TARGET/etc/doas.d/wheel.conf" fi # explicitly lock the root account chroot "$TARGET" /bin/sh -c "/bin/echo 'root:*' | /usr/sbin/chpasswd -e" chroot "$TARGET" /usr/bin/passwd -l root # set up image user user="${IMAGE_LOGIN:-alpine}" chroot "$TARGET" /usr/sbin/addgroup "$user" chroot "$TARGET" /usr/sbin/adduser -h "/home/$user" -s /bin/sh -G "$user" -D "$user" chroot "$TARGET" /usr/sbin/addgroup "$user" wheel chroot "$TARGET" /bin/sh -c "echo '$user:*' | /usr/sbin/chpasswd -e" # modify PS1s in /etc/profile to add user sed -Ei \ -e "s/(^PS1=')(\\$\\{HOSTNAME%)/\\1\\$\\USER@\\2/" \ -e "s/( PS1=')(\\\\h:)/\\1\\\\u@\\2/" \ -e "s/( PS1=')(%m:)/\\1%n@\\2/" \ "$TARGET"/etc/profile # write /etc/motd echo "$MOTD" > "$TARGET"/etc/motd setup_services } # shellcheck disable=SC2046 setup_services() { for lvl_svcs in $SERVICES_ENABLE; do rc add $(echo "$lvl_svcs" | tr '=,' ' ') done for lvl_svcs in $SERVICES_DISABLE; do rc del $(echo "$lvl_svcs" | tr '=,' ' ') done } rc() { op="$1" # add or del runlevel="$2" # runlevel name shift 2 services="$*" # names of services for svc in $services; do chroot "$TARGET" rc-update "$op" "$svc" "$runlevel" done } setup_builder make_filesystem install_base setup_chroot install_bootloader configure_system