#! /bin/bash # # gentoostrap # Build a Gentoo 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 _optconfigure declare -i _optchroot declare -i _optbootloader declare _optdest="/mnt" declare _optprofile="desktop" declare -i _opthelp declare -i _optverbose # 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]... -h Print this help text -b Perform the host half of installation. Should invoke as root -c Perform the Gentoo half of installation. Must be invoked from a chroot -d Perform the second Gentoo half of installation from a chroot -v Print more status messages. Stacks Copyright (c) 2020 rehashedsalt@cock.li Licensed under the MIT license EOF } build-gentoo() { # Build Gentoo # First, get a link to a stage3 tarball stage3="http://distfiles.gentoo.org/releases/amd64/autobuilds/current-stage3-amd64-systemd/$( curl -s http://distfiles.gentoo.org/releases/amd64/autobuilds/current-stage3-amd64-systemd/ \ | grep -oe 'stage3-amd64-systemd-20.*\.tar\.xz' \ | sed 's/^stage3.*">//g' \ | sort \ | uniq \ | head -n 1 )" stage3file="/tmp/tmp.$USER.$_name.stage3.tar.xz" stage3sig="/tmp/tmp.$USER.$_name.stage3.tar.xz.DIGESTS" for file in "$stage3file" "$stage3sig"; do touch "$file" chmod 0600 "$file" done # We grab the checksums first to verify the integrity of any tarball leftover from a previous run local shouldextract=1 [ -f "$_optdest/bin/bash" ] && unset shouldextract log "Downloading stage3 signature" curl -s "$stage3.DIGESTS" -o "$stage3sig" if ! [ -f "$stage3file" ]; then log "Downloading stage3" curl -s "$stage3" -o "$stage3file" elif ! grep "$stage3sig" -e "$(sha512sum "$stage3file" | awk '{print $1}')" > /dev/null 2>&1; then log "Checksum verification failed; downloading new stage3" curl -s "$stage3" -o "$stage3file" fi # At this point, we should have a new stage3 that matches our signature; die if not if ! grep "$stage3sig" -e "$(sha512sum "$stage3file" | awk '{print $1}')" > /dev/null 2>&1; then error "stage3 checksum verification failed" 50 fi if [ -n "$shouldextract" ]; then log "Decompressing tarball; this will prompt for root privileges" sudo tar xf "$stage3file" -C "$_optdest" fi unset shouldextract configure-gentoo } configure-gentoo() { # Now we do some pre-chroot configuration pushd "$_optdest" > /dev/null 2>&1 # These is a very generic make.conf to get the system up and running # We use march of x86-64 to ensure compatibility if we're bootstrapping on a host other than the target log "Building make.conf" cat <<-EOF > "$_optdest/etc/portage/make.conf" # ABI_X86 is used to control multilib support ABI_X86="64 32" ACCEPT_LICENSE="*" COMMON_FLAGS="-march=x86-64 -mtune=generic -O2 -pipe" CFLAGS="\${COMMON_FLAGS}" CXXFLAGS="\${COMMON_FLAGS}" FEATURES="-network-sandbox" GENTOO_MIRRORS="http://distfiles.gentoo.org" GRUB_PLATFORMS="emu efi-32 efi-64 pc" MAKEOPTS="-j$(nproc)" USE="networkmanager systemd -elogind -test" EOF # This is required on >=17.1 to unroll some circular dependencies log "Setting package.use settings for gentoostrap" cat <<-EOF > "$_optdest/etc/portage/package.use/gentoostrap" dev-lang/python -bluetooth -sqlite net-misc/networkmanager dhclient sys-libs/ncurses -gpm EOF # Configure default ebuild repositories log "Syncing ebuild repos" mkdir -p "$_optdest/etc/portage/repos.conf" cp "$_optdest/usr/share/portage/config/repos.conf" \ "$_optdest/etc/portage/repos.conf/gentoo.conf" # DNS log "Configuring DNS" cat <<-EOF > "$_optdest/etc/resolv.conf" nameserver 8.8.8.8 nameserver 8.8.4.4 EOF # Hostname if [ -f "$_optdest/etc/conf.d/hostname" ]; then hostname="$(<"$_optdest/etc/conf.d/hostname")" log "Detected hostname $hostname" else read -p "Enter system hostname: " hostname echo "$hostname" > "$_optdest/etc/conf.d/hostname" fi # hosts log "Configuring hosts" cat <<-EOF > "$_optdest/etc/hosts" 127.0.0.1 $hostname ::1 $hostname EOF # Timezone log "Configuring timezone" cp /etc/timezone "$_optdest/etc/timezone" # Locale log "Configuring locale" cat <<-EOF > "$_optdest/etc/locale.gen" en_US ISO-8859-1 en_US.UTF-8 UTF-8 EOF # fstab if grep "$_optdest/etc/fstab" -ie '^[[:blank:]]*[^[:blank:]#]' > /dev/null 2>&1; then log "Configured fstab detected" else log "Dropping to editor to configure fstab" cat <<-EOF > "$_optdest/etc/fstab" # fstab - Filesystem table # See fstab(5) for syntax and configuration information # Example: #LABEL=gentoo / ext4 defaults,noatime,errors=remount-ro 0 1 EOF "${EDITOR:-vim}" "$_optdest/etc/fstab" fi # Now we need to pivot into a chroot and finish configuration natively popd > /dev/null 2>&1 log "Pivoting to container for more setup" cp "$0" "$_optdest/gentoostrap.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 "$_optdest/$dir" fi done chroot "$_optdest" /gentoostrap.sh -c chroot "$_optdest" /gentoostrap.sh -d log "Configuration complete! You should be good to reboot now." } build-gentoo-chroot() { # Build Gentoo, but from within the chroot environment log "Building Gentoo from within chroot" # Sync repositories if ! grep /etc/portage/repos.conf/gentoo.conf -ie 'git' > /dev/null 2>&1; then log "Getting ebuild repositories" # Only do this rsync if we're actually rsyncing # As part of installation, we switch to git, so this prevents us from hammering upstream, too emerge-webrsync fi # Get the stable plasma/systemd profile and select it profile=$( eselect profile list \ | grep -ie 'stable' \ | grep -ie "$_optprofile" \ | head -n 1 \ | grep -oP '\[\K[^\]]+' ) || error "Error finding profile: $profile" 51 [ -z "$profile" ] && error "Could not find profile: $profile" 52 log "Setting profile $profile" eselect profile set "$profile" # BEHOLD! emerge -DNUu @world --jobs --quiet-build y --with-bdeps y # Configure timezone emerge --config sys-libs/timezone-data # Locales locale-gen eselect locale set en_US.utf8 env-update . /etc/profile # Extra packages emerge -DNUu --jobs --quiet-build y\ app-admin/sudo \ app-editors/vim \ dev-vcs/git \ net-misc/networkmanager \ sys-boot/grub:2 \ sys-fs/{e2fs,xfs}progs \ sys-process/cronie # Configure sudoers cat <<-EOF >> "/etc/sudoers" %wheel ALL=(ALL) ALL ansible ALL=(ALL) NOPASSWD:ALL EOF # Unnghhh... COLONEL... # Dude distribution kernels are awesome emerge -DNUu --jobs --quiet-build y sys-kernel/installkernel-gentoo sys-kernel/linux-firmware emerge -DNUu --jobs --quiet-build y sys-kernel/gentoo-kernel # Rebuilding modules shouldn't be necessary since the kernel was our last step #emerge --jobs --quiet-build y @module-rebuild # Reconfigure repo to clone over git from here on out log "Configuring gentoo repo" cat <<-EOF > "/etc/portage/repos.conf/gentoo.conf" [DEFAULT] main-repo = gentoo [gentoo] location = /usr/portage #sync-depth = 1 sync-type = git sync-uri = https://github.com/gentoo-mirror/gentoo.git sync-git-verify-commit-signature = true auto-sync = yes EOF # Install the bootloader mkdir -p /boot/efi chmod 0700 /boot/efi # Configure a new user, add him to sudoers 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 audio cdrom floppy games usb video wheel; do usermod -G "$group" -a salt || warn "Failed to add salt to group: $group" done # Configure Ansible user, his sudo rights are specifically in sudoers 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 # Configure services systemctl enable NetworkManager sshd # We've made it this far and now the user has to do just a liiiiiitle bit of configuration clear cat <<-EOF Initial system configuration is complete. There are now just a few tasks left: * Mount the ESP under /boot/efi * Verify the status of fstab * Make sure everything looks good before we install GRUB Dumping to a shell now. Have fun! EOF exec bash } build-bootloader() { # Last few chroot steps, mostly involving GRUB read -p "Enter device to install GRUB to: " grubdev if [ -z "$grubdev" ]; then log "Skipping" return 0 fi log "Configuring GRUB" [ -f /boot/grub ] && rm /boot/grub -f [ -d /boot/grub ] || mkdir /boot/grub -p grub-mkconfig -o /boot/grub/grub.cfg log "Installing GRUB" grub-install "$grubdev" cat <<-EOF EOF } # Main main() { # Parse out arguments while [ -n "$1" ]; do # Parse out flags while getopts ":bcdhv" opt; do case $opt in b) _optconfigure=1 ;; c) _optchroot=1 ;; d) _optbootloader=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 chroot curl sha512sum tar; then error "Failed to find program: $_return" 1 fi # Do the do if [ -n "$_optbootloader" ]; then build-bootloader elif [ -n "$_optchroot" ]; then build-gentoo-chroot elif [ -n "$_optconfigure" ]; then configure-gentoo else build-gentoo fi exit 0 } main "$@"