hyper-bootstrap_v0.4.1.sh 7.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260
  1. #!/bin/bash
  2. #
  3. # hyperbola-bootstrap: Bootstrap a base Hyperbola GNU+Linux-libre system using any GNU distribution.
  4. #
  5. # Dependencies: bash >= 4, coreutils, wget, sed, gawk, tar, gzip, chroot, xz, zstd.
  6. # Project: https://git.sr.ht/~heckyel/hyperbola-bootstrap
  7. #
  8. # Usage:
  9. #
  10. # # ./hyper-bootstrap.sh destination
  11. # # ./hyper-bootstrap.sh -a x86_64 -r https://repo.hyperbola.info:50011/gnu-plus-linux-libre/testing destination-64
  12. #
  13. # Example:
  14. #
  15. # # ./hyper-bootstrap.sh -a x86_64 -r "https://mirror.fsf.org/hyperbola/gnu-plus-linux-libre/testing" myhyper
  16. # # ./hyper-bootstrap.sh myhyper
  17. #
  18. # And then you can chroot to the destination directory (user: root, password: root):
  19. #
  20. # # chroot destination
  21. #
  22. # Note that some packages require some system directories to be mounted. Some of the commands you can try:
  23. #
  24. # # mount --bind /proc myhyper/proc
  25. # # mount --bind /sys myhyper/sys
  26. # # mount --bind /dev myhyper/dev
  27. # # mount --bind /dev/pts myhyper/dev/pts
  28. #
  29. set -e -u -o pipefail
  30. # Packages needed by pacman (see get-pacman-dependencies.sh)
  31. PACMAN_PACKAGES=(
  32. bash acl hyperbola-keyring attr bzip2 curl expat glibc gpgme libarchive lzip grep sed coreutils
  33. libassuan libgpg-error libnghttp2 libssh2 lzo libressl pacman pacman-mirrorlist xz zlib libffi
  34. krb5 e2fsprogs keyutils libidn2 gcc-libs lz4 libpsl icu zstd readline libunistring findutils
  35. ncurses pinentry-curses lsb-release ca-certificates ca-certificates-mozilla p11-kit libtasn1
  36. libcap shadow pcre gzip gettext-tiny
  37. )
  38. BASIC_PACKAGES=(${PACMAN_PACKAGES[*]} filesystem)
  39. EXTRA_PACKAGES=(gawk file tar hyperrc)
  40. DEFAULT_REPO_URL="https://mirror.fsf.org/hyperbola/gnu-plus-linux-libre/testing"
  41. stderr() {
  42. echo "$@" >&2
  43. }
  44. debug() {
  45. echo -e "\e[1;32m==>\e[0m\033[1m $* \e[m"
  46. }
  47. extract_href() {
  48. sed -n '/<a / s/^.*<a [^>]*href="\([^\"]*\)".*$/\1/p'
  49. }
  50. fetch() {
  51. curl -L -s "$@"
  52. }
  53. fetch_file() {
  54. local FILEPATH=$1
  55. shift
  56. if [[ -e "$FILEPATH" ]]; then
  57. curl -L -s -z "$FILEPATH" -o "$FILEPATH" "$@"
  58. else
  59. curl -L -s -o "$FILEPATH" "$@"
  60. fi
  61. }
  62. uncompress() {
  63. local FILEPATH=$1 DEST=$2
  64. case "$FILEPATH" in
  65. *.gz)
  66. tar xzf "$FILEPATH" -C "$DEST";;
  67. *.xz)
  68. xz -dc "$FILEPATH" | tar x -C "$DEST";;
  69. *.lz)
  70. tar xf "$FILEPATH" -C "$DEST";;
  71. *.zst)
  72. zstd -dc "$FILEPATH" | tar x -C "$DEST";;
  73. *)
  74. debug "Error: unknown package format: $FILEPATH"
  75. return 1;;
  76. esac
  77. }
  78. ###
  79. get_default_repo() {
  80. local ARCH=$1
  81. if [[ "$ARCH" == x86* || "$ARCH" == i686 ]]; then
  82. echo $DEFAULT_REPO_URL
  83. fi
  84. }
  85. get_core_repo_url() {
  86. local REPO_URL=$1 ARCH=$2
  87. if [[ "$ARCH" == x86* || "$ARCH" == i686 ]]; then
  88. echo "${REPO_URL%/}/core/os/$ARCH"
  89. fi
  90. }
  91. get_template_repo_url() {
  92. local REPO_URL=$1 ARCH=$2
  93. if [[ "$ARCH" == x86* || "$ARCH" == i686 ]]; then
  94. echo "${REPO_URL%/}/\$repo/os/$ARCH"
  95. fi
  96. }
  97. configure_pacman() {
  98. local DEST=$1 ARCH=$2
  99. debug "Configuring SERVER"
  100. SERVER=$(get_template_repo_url "$REPO_URL" "$ARCH")
  101. echo "Server = $SERVER" > "$DEST/etc/pacman.d/mirrorlist"
  102. debug "Configuring CERT"
  103. cp -fv certs/1.pem "$DEST/etc/ca-certificates/extracted/tls-ca-bundle.pem"
  104. }
  105. clean_chroot() {
  106. local DEST=$1
  107. debug "Clean Chroot"
  108. rm -rf "$DEST/.BUILDINFO" "$DEST/.INSTALL" "$DEST/.MTREE" "$DEST/.PKGINFO" || true
  109. for i in $DEST/usr/share/{doc,man,info}; do
  110. rm -rf "$i" && install -d "$i"
  111. done
  112. truncate -s 0 "$DEST/var/log/pacman.log"
  113. }
  114. configure_minimal_system() {
  115. local DEST=$1
  116. mkdir -p "$DEST/dev"
  117. sed -ie 's|^root:.*$|root:$1$GT9AUpJe$oXANVIjIzcnmOpY07iaGi/:14657::::::|' "$DEST/etc/shadow"
  118. touch "$DEST/etc/group"
  119. echo "nameserver 9.9.9.9" > "$DEST/etc/resolv.conf"
  120. echo "bootstrap" > "$DEST/etc/hostname"
  121. rm -f "$DEST/etc/mtab"
  122. echo "rootfs / rootfs rw 0 0" > "$DEST/etc/mtab"
  123. test -e "$DEST/dev/null" || mknod "$DEST/dev/null" c 1 3
  124. test -e "$DEST/dev/random" || mknod -m 0644 "$DEST/dev/random" c 1 8
  125. test -e "$DEST/dev/urandom" || mknod -m 0644 "$DEST/dev/urandom" c 1 9
  126. sed -i "s|^[[:space:]]*\(CheckSpace\)|# \1|" "$DEST/etc/pacman.conf"
  127. sed -i "s|^[[:space:]]*SigLevel[[:space:]]*=.*$|SigLevel = Never|" "$DEST/etc/pacman.conf"
  128. }
  129. restore_signature() {
  130. local DEST=$1
  131. sed -i "s|SigLevel = Never|SigLevel = Required DatabaseOptional|" "$DEST/etc/pacman.conf"
  132. }
  133. configure_locale() {
  134. local DEST=$1
  135. sed -e 's/^#en_US.UTF-8 UTF-8/en_US.UTF-8 UTF-8/g' -i "$DEST/etc/locale.gen"
  136. echo LANG=en_US.UTF-8 > "$DEST/etc/locale.conf"
  137. LC_ALL=C chroot "$DEST" \
  138. locale-gen
  139. }
  140. fetch_packages_list() {
  141. local REPO=$1
  142. debug "Fetch packages list: $REPO/"
  143. fetch "$REPO/" | extract_href | awk -F"/" '{print $NF}' | sort -rn ||
  144. { debug "Error: cannot fetch packages list: $REPO"; return 1; }
  145. }
  146. install_pacman_packages() {
  147. local BASIC_PACKAGES=$1 DEST=$2 LIST=$3 DOWNLOAD_DIR=$4
  148. debug "pacman package and dependencies: $BASIC_PACKAGES"
  149. for PACKAGE in $BASIC_PACKAGES; do
  150. local FILE=$(echo "$LIST" | grep -m1 "^$PACKAGE-[[:digit:]].*\(\.gz\|\.xz\|\.lz\|\.zst\)$")
  151. test "$FILE" || { debug "Error: cannot find package: $PACKAGE"; return 1; }
  152. local FILEPATH="$DOWNLOAD_DIR/$FILE"
  153. debug "Download package: $REPO/$FILE"
  154. fetch_file "$FILEPATH" "$REPO/$FILE"
  155. debug "Uncompress package: $FILEPATH"
  156. uncompress "$FILEPATH" "$DEST"
  157. done
  158. }
  159. configure_static_qemu() {
  160. local ARCH=$1 DEST=$2
  161. [[ "$ARCH" == arm* ]] && ARCH=arm
  162. QEMU_STATIC_BIN=$(command -v qemu-$ARCH-static || echo )
  163. [[ -e "$QEMU_STATIC_BIN" ]] ||\
  164. { debug "No static qemu for $ARCH, ignoring"; return 0; }
  165. cp "$QEMU_STATIC_BIN" "$DEST/usr/bin"
  166. }
  167. install_packages() {
  168. local ARCH=$1 DEST=$2 PACKAGES=$3
  169. debug "Install packages: $PACKAGES"
  170. LC_ALL=C chroot "$DEST" \
  171. /usr/bin/pacman --noconfirm --noprogressbar --quiet --arch $ARCH -Syy --force $PACKAGES \
  172. && /usr/bin/pacman --noconfirm --noprogressbar --quiet -Scc
  173. }
  174. show_usage() {
  175. stderr "Usage: $(basename "$0") [-q] [-a i686|x86_64|arm] [-r REPO_URL] [-d DOWNLOAD_DIR] DESTDIR"
  176. }
  177. main() {
  178. # Process arguments and options
  179. test $# -eq 0 && set -- "-h"
  180. local ARCH=
  181. local REPO_URL=
  182. local USE_QEMU=
  183. local DOWNLOAD_DIR=
  184. local PRESERVE_DOWNLOAD_DIR=
  185. while getopts "qa:r:d:h" ARG; do
  186. case "$ARG" in
  187. a) ARCH=$OPTARG;;
  188. r) REPO_URL=$OPTARG;;
  189. q) USE_QEMU=true;;
  190. d) DOWNLOAD_DIR=$OPTARG
  191. PRESERVE_DOWNLOAD_DIR=true;;
  192. *) show_usage; return 1;;
  193. esac
  194. done
  195. shift $(($OPTIND-1))
  196. test $# -eq 1 || { show_usage; return 1; }
  197. [[ -z "$ARCH" ]] && ARCH=$(uname -m)
  198. [[ -z "$REPO_URL" ]] && REPO_URL=$(get_default_repo "$ARCH")
  199. local DEST=$1
  200. local REPO=$(get_core_repo_url "$REPO_URL" "$ARCH")
  201. [[ -z "$DOWNLOAD_DIR" ]] && DOWNLOAD_DIR=$(mktemp -d)
  202. mkdir -p "$DOWNLOAD_DIR"
  203. [[ -z "$PRESERVE_DOWNLOAD_DIR" ]] && trap "rm -rf '$DOWNLOAD_DIR'" TERM EXIT
  204. debug "Destination directory: $DEST"
  205. debug "Core repository: $REPO"
  206. debug "Temporary directory: $DOWNLOAD_DIR"
  207. # Fetch packages, install system and do a minimal configuration
  208. mkdir -p "$DEST"
  209. local LIST=$(fetch_packages_list $REPO)
  210. install_pacman_packages "${BASIC_PACKAGES[*]}" "$DEST" "$LIST" "$DOWNLOAD_DIR"
  211. configure_pacman "$DEST" "$ARCH"
  212. configure_minimal_system "$DEST"
  213. [[ -n "$USE_QEMU" ]] && configure_static_qemu "$ARCH" "$DEST"
  214. install_packages "$ARCH" "$DEST" "${BASIC_PACKAGES[*]} ${EXTRA_PACKAGES[*]}"
  215. configure_locale "$DEST"
  216. clean_chroot "$DEST" # clean
  217. [[ -z "$PRESERVE_DOWNLOAD_DIR" ]] && rm -rf "$DOWNLOAD_DIR"
  218. restore_signature "$DEST"
  219. debug "Done!"
  220. debug
  221. debug "You may now chroot or arch-chroot from package arch-install-scripts:"
  222. debug "$ doas arch-chroot $DEST"
  223. }
  224. main "$@"