diff --git a/archstrap.sh b/archstrap.sh new file mode 100755 index 0000000..6acfa15 --- /dev/null +++ b/archstrap.sh @@ -0,0 +1,208 @@ +#! /bin/bash +# +# archstrap +# Build an Arch system in /mnt +# Copyright (C) 2020 Vintage Salt +# +# Distributed under terms of the MIT license. +# +set -e + +# Read-only set-once variables +declare -r _name="$(basename -- "$0")" +# Options +declare -i _optchroot +declare -i _opthelp +declare -i _optverbose +declare _optdest="/mnt" +# Working variables +declare -a _args +declare _return + +# Helper functions +log() { + # Print a line to the terminal if _optverbose is greater than $2 + # $2 defaults to 0 + # loglevel 0: Daily-use messages + # loglevel 1: Detailed but not quite debugging + # loglevel 2: Definitely debugging + [ -z "$1" ] && return 1 + if (( _optverbose >= ${2:-0} )); then + printf "%s\\n" "$1" + fi +} +warn() { + # Print a yellow line to the terminal, respecting _optverbose + [ -z "$1" ] && return 1 + if (( _optverbose >= ${2:-0} )); then + if [ -t 1 ]; then + printf "\\e[33m%s\\e[0m\\n" "$1" + else + printf "WARN: %s\\n" "$1" + fi + fi +} +error() { + # Print a red line to the terminal, exit if $2 is specified + [ -z "$1" ] && return 1 + if [ -t 2 ]; then + printf "\\e[31m%s\\e[0m\\n" "$1" 1>&2 + else + printf "ERROR: %s\\n" "$1" 1>&2 + fi + [ -z "$2" ] && return + exit "${2:-1}" +} +has() { + # Parse out all arguments and try to find them in path + # If an argument cannot be found, set _return and fail + for prog in "$@"; do + if ! command -v "$prog" > /dev/null 2>&1; then + _return="$prog" + return 1 + fi + done + return 0 +} + +# Helper functions +cleanup() { + [ -z "$1" ] && warn "No argument passed to cleanup" + rm -rf "$1" || warn "failed to remove $1: $?" +} + +# Core program functions +printhelp() { + cat << EOF +Usage: $_name [OPTION]... + + -c [FILE] Load the given file in place of the usual config file + -h Print this help text + -v Print more status messages. Stacks + +Copyright (c) 2020 rehashedsalt@cock.li +Licensed under the MIT license +EOF +} +arch-build() { + # Download and unpack our minimal Arch image. Or pacstrap it or whatever + log "Bootstrapping an Arch installation" + script="https://raw.githubusercontent.com/tokland/arch-bootstrap/master/arch-bootstrap.sh" + bootstrap="$(mktemp)" + for file in "$bootstrap"; do + touch "$file" + chmod 0700 "$file" + trap EXIT cleanup "$file" + done + "$bootstrap" "$_optdest" +} +arch-chroot() { + # Jump to our new Arch chroot + log "Pivoting to chroot" + cp "$0" "$_optdest/archstrap.sh" + if ! grep -qs "$_optdest/proc " /proc/mounts; then + log "Mounting proc" + mount --types proc /proc "$_optdest/proc" + fi + for dir in sys dev; do + log "Mounting $dir" + if ! grep -qs "$_optdest/$dir " /proc/mounts; then + mount --rbind /$dir "$_optdest/$dir" + mount --make-rslave /$dir "$_optdest/$dir" + fi + done + chroot "$_optdest" /archstrap.sh -c +} +arch-chroot-configure() { + # Configure the Arch system from within the chroot + # Required packages + pacman -S linux linux-firmware grub + # Secondary packages + pacman -S sudo + cat <<-EOF >> "/etc/sudoers" + %wheel ALL=(ALL) ALL + ansible ALL=(ALL) NOPASSWD:ALL + EOF + # Configure my user + if ! id salt > /dev/null 2>&1; then + useradd salt -m -G wheel -s /bin/bash -u 1000 -U + passwd salt + fi + for group in wheel; do + usermod -G "$group" -a salt || warn "Failed to add salt to group: $group" + done + # Configure Ansible user + if ! id ansible > /dev/null 2>&1; then + useradd ansible -m -s /bin/bash -u 1001 -U + fi + sudo -u ansible -i mkdir .ssh + sudo -u ansible -i chmod 0700 .ssh + sudo -u ansible -i echo "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQDfXVgMHeD2wtCAIVoDYQ+R19vKfhmR2FgUTkHhAzE2156fB/+IMB+6Qc4X3aFRIcUp+Ls8Vm8JQ3d0jvbcGQkgbAjRExQa71XGBmhxJCxzlCLBoQzBmTSnryL09LExoMynzVgrso8TQP92vZBGJFI/lLGAaop2l9pu+3cgM3sRaK+A11lcRCrS25C3hqPQhKC44zjzOt7sIoaG6RqG3CQ8jhE35bthQdBySOZVDgDKfjDyPuDzVxiKjsuNm4Ojzm0QW5gq6GkLOg2B8OSQ1TGQgBHQu4b8zsKBOUOdbZb0JLM8NdpH1cMntC0QBofy3DzqR/CFaSaBzUx+dnkBH0/pjBOrhHzzqZGOJayfC1igYki67HqzFV5IjhAVa+c4S9L/zbFk0+YZYdgMoKNlMU2LgzrSEastuXHD7NUy3fMP4BZbqg37SjQzFRXoUp5+ctVs9tCoy/qvvjT3UVGcn312eJrRRfWrYagU2nWKGyqbTOpsuOJ5OLlhopy6eP9+yRM= ansible" > ~ansible/.ssh/authorized_keys + sudo -u ansible -i chmod 0600 .ssh/authorized_keys + + cat <<-EOF + Initial system configuration is comiplete. Just a few tasks remain: + * Mount the ESP under /boot/efi + * Verify fstab + * Install GRUB + + Dumping to a shell now. Have fun! + EOF + exec bash +} + +# Main +main() { + # Parse out arguments + while [ -n "$1" ]; do + # Parse out flags + while getopts ":chv" opt; do + case $opt in + c) + _optchroot=1 + ;; + h) + _opthelp=1 + ;; + v) + _optverbose+=1 + ;; + :) + error "Option requires argument: -$OPTARG" 2 + ;; + *) + error "Invalid option: -$OPTARG" 2 + ;; + esac + done + # Store arguments + shift $((OPTIND - 1)) + if [ -n "$1" ]; then + _args+=("$1") + shift + fi + unset OPTIND + done + # Early hook for help + [ -n "$_opthelp" ] && printhelp && exit 0 + # Validate critical options + # TODO: That + # Validate core program dependencies + log "Validating dependencies" 2 + if ! has basename curl zstd; then + error "Failed to find program: $_return" 1 + fi + + # Do the do + if [ -n "$_optchroot" ]; then + # We're doing in-chroot configuration + arch-chroot-configure + else + arch-build + arch-chroot + fi + exit 0 +} + +main "$@" +