#!/bin/bash # # hyperbola-bootstrap: Bootstrap a base Hyperbola GNU+Linux-libre system using any GNU distribution. # # Dependencies: bash >= 4, coreutils, wget, sed, gawk, tar, gzip, chroot, xz, zstd. # Project: https://git.sr.ht/~heckyel/hyperbola-bootstrap # # Usage: # # # ./hyper-bootstrap.sh destination # # ./hyper-bootstrap.sh -a x86_64 -r https://repo.hyperbola.info:50011/gnu-plus-linux-libre/testing destination-64 # # Example: # # # ./hyper-bootstrap.sh -a x86_64 -r "https://mirror.fsf.org/hyperbola/gnu-plus-linux-libre/testing" myhyper # # ./hyper-bootstrap.sh myhyper # # And then you can chroot to the destination directory (user: root, password: root): # # # chroot destination # # Note that some packages require some system directories to be mounted. Some of the commands you can try: # # # mount --bind /proc myhyper/proc # # mount --bind /sys myhyper/sys # # mount --bind /dev myhyper/dev # # mount --bind /dev/pts myhyper/dev/pts # set -e -u -o pipefail # Packages needed by pacman (see get-pacman-dependencies.sh) PACMAN_PACKAGES=( bash acl hyperbola-keyring attr bzip2 curl expat glibc gpgme libarchive lzip grep sed coreutils libassuan libgpg-error libnghttp2 libssh2 lzo libressl pacman pacman-mirrorlist xz zlib libffi krb5 e2fsprogs keyutils libidn2 gcc-libs lz4 libpsl icu zstd readline libunistring findutils ncurses pinentry-curses lsb-release ca-certificates ca-certificates-utils p11-kit libtasn1 libcap shadow pcre gzip gettext-tiny ) BASIC_PACKAGES=(${PACMAN_PACKAGES[*]} filesystem) EXTRA_PACKAGES=(gawk file tar hyperrc) DEFAULT_REPO_URL="https://mirror.fsf.org/hyperbola/gnu-plus-linux-libre/testing" stderr() { echo "$@" >&2 } debug() { echo -e "\e[1;32m==>\e[0m\033[1m $* \e[m" } extract_href() { sed -n '/]*href="\([^\"]*\)".*$/\1/p' } fetch() { curl -L -s "$@" } fetch_file() { local FILEPATH=$1 shift if [[ -e "$FILEPATH" ]]; then curl -L -z "$FILEPATH" -o "$FILEPATH" "$@" else curl -L -o "$FILEPATH" "$@" fi } uncompress() { local FILEPATH=$1 DEST=$2 case "$FILEPATH" in *.gz) tar xzf "$FILEPATH" -C "$DEST";; *.xz) xz -dc "$FILEPATH" | tar x -C "$DEST";; *.lz) tar xf "$FILEPATH" -C "$DEST";; *.zst) zstd -dc "$FILEPATH" | tar x -C "$DEST";; *) debug "Error: unknown package format: $FILEPATH" return 1;; esac } ### get_default_repo() { local ARCH=$1 if [[ "$ARCH" == x86* || "$ARCH" == i686 ]]; then echo $DEFAULT_REPO_URL fi } get_core_repo_url() { local REPO_URL=$1 ARCH=$2 if [[ "$ARCH" == x86* || "$ARCH" == i686 ]]; then echo "${REPO_URL%/}/core/os/$ARCH" fi } get_template_repo_url() { local REPO_URL=$1 ARCH=$2 if [[ "$ARCH" == x86* || "$ARCH" == i686 ]]; then echo "${REPO_URL%/}/\$repo/os/$ARCH" fi } configure_pacman() { local DEST=$1 ARCH=$2 debug "Configuring SERVER" SERVER=$(get_template_repo_url "$REPO_URL" "$ARCH") echo "Server = $SERVER" > "$DEST/etc/pacman.d/mirrorlist" debug "Configuring CERT" cp -fv certs/1.pem "$DEST/etc/ca-certificates/extracted/tls-ca-bundle.pem" } clean_chroot() { local DEST=$1 debug "Clean Chroot" rm -rf "$DEST/.BUILDINFO" "$DEST/.INSTALL" "$DEST/.MTREE" "$DEST/.PKGINFO" || true for i in $DEST/usr/share/{doc,man,info}; do rm -rf "$i" && install -d "$i" done truncate -s 0 "$DEST/var/log/pacman.log" } configure_minimal_system() { local DEST=$1 mkdir -p "$DEST/dev" sed -ie 's|^root:.*$|root:$1$GT9AUpJe$oXANVIjIzcnmOpY07iaGi/:14657::::::|' "$DEST/etc/shadow" touch "$DEST/etc/group" echo "nameserver 9.9.9.9" > "$DEST/etc/resolv.conf" echo "bootstrap" > "$DEST/etc/hostname" rm -f "$DEST/etc/mtab" echo "rootfs / rootfs rw 0 0" > "$DEST/etc/mtab" test -e "$DEST/dev/null" || mknod "$DEST/dev/null" c 1 3 test -e "$DEST/dev/random" || mknod -m 0644 "$DEST/dev/random" c 1 8 test -e "$DEST/dev/urandom" || mknod -m 0644 "$DEST/dev/urandom" c 1 9 sed -i "s|^[[:space:]]*\(CheckSpace\)|# \1|" "$DEST/etc/pacman.conf" sed -i "s|^[[:space:]]*SigLevel[[:space:]]*=.*$|SigLevel = Never|" "$DEST/etc/pacman.conf" } configure_locale() { local DEST=$1 sed -e 's/^#en_US.UTF-8 UTF-8/en_US.UTF-8 UTF-8/g' -i "$DEST/etc/locale.gen" echo LANG=en_US.UTF-8 > "$DEST/etc/locale.conf" LC_ALL=C chroot "$DEST" \ locale-gen } fetch_packages_list() { local REPO=$1 debug "Fetch packages list: $REPO/" fetch "$REPO/" | extract_href | awk -F"/" '{print $NF}' | sort -rn || { debug "Error: cannot fetch packages list: $REPO"; return 1; } } install_pacman_packages() { local BASIC_PACKAGES=$1 DEST=$2 LIST=$3 DOWNLOAD_DIR=$4 debug "pacman package and dependencies: $BASIC_PACKAGES" for PACKAGE in $BASIC_PACKAGES; do local FILE=$(echo "$LIST" | grep -m1 "^$PACKAGE-[[:digit:]].*\(\.gz\|\.xz\|\.lz\|\.zst\)$") test "$FILE" || { debug "Error: cannot find package: $PACKAGE"; return 1; } local FILEPATH="$DOWNLOAD_DIR/$FILE" debug "Download package: $REPO/$FILE" fetch_file "$FILEPATH" "$REPO/$FILE" debug "Uncompress package: $FILEPATH" uncompress "$FILEPATH" "$DEST" done } configure_static_qemu() { local ARCH=$1 DEST=$2 [[ "$ARCH" == arm* ]] && ARCH=arm QEMU_STATIC_BIN=$(command -v qemu-$ARCH-static || echo ) [[ -e "$QEMU_STATIC_BIN" ]] ||\ { debug "No static qemu for $ARCH, ignoring"; return 0; } cp "$QEMU_STATIC_BIN" "$DEST/usr/bin" } install_packages() { local ARCH=$1 DEST=$2 PACKAGES=$3 debug "Install packages: $PACKAGES" LC_ALL=C chroot "$DEST" \ /usr/bin/pacman --noconfirm --noprogressbar --quiet --arch $ARCH -Syy --force $PACKAGES \ && /usr/bin/pacman --noconfirm --noprogressbar --quiet -Scc } configure_keyring() { local DEST=$1 sed -i 's|SigLevel = Never|SigLevel = Required DatabaseOptional|' "$DEST/etc/pacman.conf" LC_ALL=C chroot "$DEST" \ /usr/bin/pacman-key --init && /usr/bin/pacman-key --populate hyperbola \ && /usr/bin/pacman-key --keyserver pgp.rediris.es --refresh-keys } show_usage() { stderr "Usage: $(basename "$0") [-q] [-a i686|x86_64|arm] [-r REPO_URL] [-d DOWNLOAD_DIR] DESTDIR" } main() { # Process arguments and options test $# -eq 0 && set -- "-h" local ARCH= local REPO_URL= local USE_QEMU= local DOWNLOAD_DIR= local PRESERVE_DOWNLOAD_DIR= while getopts "qa:r:d:h" ARG; do case "$ARG" in a) ARCH=$OPTARG;; r) REPO_URL=$OPTARG;; q) USE_QEMU=true;; d) DOWNLOAD_DIR=$OPTARG PRESERVE_DOWNLOAD_DIR=true;; *) show_usage; return 1;; esac done shift $(($OPTIND-1)) test $# -eq 1 || { show_usage; return 1; } [[ -z "$ARCH" ]] && ARCH=$(uname -m) [[ -z "$REPO_URL" ]] && REPO_URL=$(get_default_repo "$ARCH") local DEST=$1 local REPO=$(get_core_repo_url "$REPO_URL" "$ARCH") [[ -z "$DOWNLOAD_DIR" ]] && DOWNLOAD_DIR=$(mktemp -d) mkdir -p "$DOWNLOAD_DIR" [[ -z "$PRESERVE_DOWNLOAD_DIR" ]] && trap "rm -rf '$DOWNLOAD_DIR'" TERM EXIT debug "Destination directory: $DEST" debug "Core repository: $REPO" debug "Temporary directory: $DOWNLOAD_DIR" # Fetch packages, install system and do a minimal configuration mkdir -p "$DEST" local LIST=$(fetch_packages_list $REPO) install_pacman_packages "${BASIC_PACKAGES[*]}" "$DEST" "$LIST" "$DOWNLOAD_DIR" configure_pacman "$DEST" "$ARCH" configure_minimal_system "$DEST" [[ -n "$USE_QEMU" ]] && configure_static_qemu "$ARCH" "$DEST" install_packages "$ARCH" "$DEST" "${BASIC_PACKAGES[*]} ${EXTRA_PACKAGES[*]}" configure_keyring "$DEST" configure_locale "$DEST" clean_chroot "$DEST" # clean [[ -z "$PRESERVE_DOWNLOAD_DIR" ]] && rm -rf "$DOWNLOAD_DIR" debug "Done!" debug debug "You may now chroot or arch-chroot from package arch-install-scripts:" debug "$ doas arch-chroot $DEST" } main "$@"