From: Max Vilimpoc Date: Thu, 6 Aug 2026 12:40:08 +0000 (+0200) Subject: dotfiles: Linux dev-box provisioning script X-Git-Url: https://vilimpoc.org/repos/dotfiles/commitdiff_plain/HEAD?ds=inline dotfiles: Linux dev-box provisioning script --- diff --git a/setup-linux.sh b/setup-linux.sh new file mode 100644 index 0000000..afb36a7 --- /dev/null +++ b/setup-linux.sh @@ -0,0 +1,269 @@ +#!/bin/bash +# +# setup-linux.sh -- install everything needed to build rsync on Ubuntu. +# +# Tested on Ubuntu 22.04.5 LTS and 26.04 LTS. Covers both build systems: +# the autoconf one (./configure && make) and the CMake one (CMakeLists.txt), +# plus the optional feature libraries and the manpage generator. +# +# Usage: +# ./setup-linux.sh # install required + optional +# ./setup-linux.sh --minimal # required only (no ACLs/xattrs/zstd/...) +# ./setup-linux.sh --dry-run # show what would be installed +# +# Safe to re-run: apt skips anything already present. + +set -uo pipefail + +MINIMAL=0 +DRY_RUN=0 + +for arg in "$@"; do + case "$arg" in + --minimal) MINIMAL=1 ;; + --dry-run) DRY_RUN=1 ;; + -h|--help) + sed -n '2,17p' "$0" | sed 's/^# \{0,1\}//' + exit 0 ;; + *) + echo "setup-linux.sh: unknown option '$arg' (try --help)" >&2 + exit 2 ;; + esac +done + +# ---------------------------------------------------------------- helpers + +red() { printf '\033[31m%s\033[0m\n' "$*"; } +green() { printf '\033[32m%s\033[0m\n' "$*"; } +bold() { printf '\033[1m%s\033[0m\n' "$*"; } + +die() { red "ERROR: $*"; exit 1; } + +# ------------------------------------------------------- sanity checks + +command -v apt-get >/dev/null 2>&1 \ + || die "no apt-get found -- this script is for Debian/Ubuntu systems." + +DISTRO="unknown" +RELEASE="unknown" +if [ -r /etc/os-release ]; then + # shellcheck disable=SC1091 # runtime file, not available to the linter + . /etc/os-release + DISTRO="${ID:-unknown}" + RELEASE="${VERSION_ID:-unknown}" +fi + +bold "rsync build dependencies" +echo " distro ......... ${PRETTY_NAME:-$DISTRO $RELEASE}" +echo " architecture ... $(uname -m)" + +case "$DISTRO" in +ubuntu|debian|linuxmint|pop) ;; +*) echo + red "Warning: this script targets Ubuntu/Debian; '$DISTRO' is untested." + echo "Continuing anyway -- package names may differ." + ;; +esac + +# apt needs root. Re-exec under sudo rather than sprinkling it around, so +# that a single password prompt covers the whole run. +if [ "$(id -u)" -ne 0 ] && [ "$DRY_RUN" -eq 0 ]; then + command -v sudo >/dev/null 2>&1 \ + || die "not running as root and sudo is not installed." + echo + echo "Re-running under sudo..." + # Plain sudo, not "sudo -E": some sudoers configs refuse to preserve the + # environment, and the exports below happen in the root instance anyway. + exec sudo "$0" "$@" +fi + +export DEBIAN_FRONTEND=noninteractive +export NEEDRESTART_MODE=a # don't prompt about restarting services + +# ------------------------------------------------------------- packages + +# Needed to build rsync at all. +REQUIRED=( + build-essential # gcc, g++, make, libc headers + gawk # rsync needs a modern awk for its code generators + autoconf # ./configure is generated from configure.ac + automake + python3 # manpage + header generators, and the test suite + cmake # the CMake build + ninja-build # ...and its default generator + git # version stamping (git-version.h) + pkg-config +) + +# Optional: each one switches on an rsync feature. Missing ones only mean +# a smaller feature set, never a failed build. +OPTIONAL=( + acl libacl1-dev # --acls (helper tools also used by the tests) + attr libattr1-dev # --xattrs (likewise) + libxxhash-dev # xxhash checksums (becomes the default) + libzstd-dev # zstd compression (becomes the default) + liblz4-dev # lz4 compression + libssl-dev # OpenSSL MD4/MD5 + zlib1g-dev # for cmake -DRSYNC_EXTERNAL_ZLIB=ON + libpopt-dev # for ./configure --with-included-popt=no +) + +# Development tooling: not needed to build rsync, but useful when working on +# its shell scripts -- including this one, which shellcheck keeps honest. +DEVTOOLS=( + shellcheck +) +OPTIONAL+=("${DEVTOOLS[@]}") + +# The manpages need one of two python3 markdown libraries; upstream prefers +# cmarkgfm. Pick whichever this release actually offers. +MARKDOWN_PKG="" +for p in python3-cmarkgfm python3-commonmark; do + cand=$(apt-cache policy "$p" 2>/dev/null | awk '/Candidate:/{print $2}') + if [ -n "$cand" ] && [ "$cand" != "(none)" ]; then + MARKDOWN_PKG="$p" + break + fi +done +if [ -n "$MARKDOWN_PKG" ]; then + OPTIONAL+=("$MARKDOWN_PKG") +fi + +if [ "$MINIMAL" -eq 1 ]; then + OPTIONAL=() +fi + +if [ "$DRY_RUN" -eq 1 ]; then + echo + bold "Would install (required):" + printf ' %s\n' "${REQUIRED[@]}" + if [ ${#OPTIONAL[@]} -gt 0 ]; then + bold "Would install (optional):" + printf ' %s\n' "${OPTIONAL[@]}" + fi + exit 0 +fi + +# ------------------------------------------------------------- install + +echo +bold "Updating package lists..." +if ! apt-get update -qq; then + red "apt-get update failed -- continuing with the cached lists." +fi + +echo +bold "Installing required packages..." +if ! apt-get install -y "${REQUIRED[@]}"; then + # Fall back to one-at-a-time so the failing package is obvious. + red "Bulk install failed; retrying individually to find the culprit..." + FAILED=() + for p in "${REQUIRED[@]}"; do + apt-get install -y "$p" || FAILED+=("$p") + done + [ ${#FAILED[@]} -eq 0 ] || die "could not install: ${FAILED[*]}" +fi + +OPT_FAILED=() +if [ ${#OPTIONAL[@]} -gt 0 ]; then + echo + bold "Installing optional packages..." + if ! apt-get install -y "${OPTIONAL[@]}"; then + red "Bulk install failed; retrying individually..." + for p in "${OPTIONAL[@]}"; do + apt-get install -y "$p" || OPT_FAILED+=("$p") + done + fi +fi + +# --------------------------------------------------------------- verify + +echo +bold "Verifying the toolchain..." + +MISSING=() +check_cmd() { + if command -v "$1" >/dev/null 2>&1; then + printf ' %-16s %s\n' "$1" "$(command -v "$1")" + else + printf ' %-16s %s\n' "$1" "MISSING" + MISSING+=("$1") + fi +} + +for c in gcc g++ make gawk autoconf automake cmake ninja python3 git; do + check_cmd "$c" +done + +# Not required to build, so report it without failing the run. +if command -v shellcheck >/dev/null 2>&1; then + printf ' %-16s %s\n' "shellcheck" "$(command -v shellcheck)" +else + printf ' %-16s %s\n' "shellcheck" "not installed (optional)" +fi + +echo +bold "Verifying optional libraries..." +check_header() { + # $1 = human name, $2 = header path + if [ -e "/usr/include/$2" ] || \ + find /usr/include -maxdepth 3 -name "$(basename "$2")" -print -quit \ + 2>/dev/null | grep -q .; then + printf ' %-16s yes\n' "$1" + else + printf ' %-16s no\n' "$1" + fi +} +check_header acl sys/acl.h +check_header xattr sys/xattr.h +check_header xxhash xxhash.h +check_header zstd zstd.h +check_header lz4 lz4.h +check_header openssl openssl/md5.h +check_header zlib zlib.h + +echo +bold "Verifying the manpage generator..." +MD_OK=0 +for m in cmarkgfm commonmark; do + if python3 -c "import $m" >/dev/null 2>&1; then + echo " python3 module '$m' importable" + MD_OK=1 + break + fi +done +if [ "$MD_OK" -eq 0 ]; then + if [ "$MINIMAL" -eq 1 ]; then + echo " skipped (--minimal); build with ./configure --disable-md2man" + else + red " neither cmarkgfm nor commonmark is importable." + echo " Install one with: python3 -mpip install --user commonmark" + echo " ...or build with: ./configure --disable-md2man" + fi +fi + +# ---------------------------------------------------------------- done + +echo +if [ ${#MISSING[@]} -ne 0 ]; then + red "Missing required tools: ${MISSING[*]}" + exit 1 +fi + +if [ ${#OPT_FAILED[@]} -ne 0 ]; then + red "Optional packages that failed to install: ${OPT_FAILED[*]}" + echo "The build will still work, with those features disabled." +fi + +green "All required build dependencies are installed." +cat <<'EOF' + +Build rsync with either build system: + + autoconf: ./configure && make + CMake: cmake -B build -G Ninja && cmake --build build + +Run the test suite (needs the autoconf build's helper programs): + + make check +EOF