diff options
Diffstat (limited to 'straper/install-base.sh')
| -rwxr-xr-x | straper/install-base.sh | 375 |
1 files changed, 0 insertions, 375 deletions
diff --git a/straper/install-base.sh b/straper/install-base.sh deleted file mode 100755 index a37de64..0000000 --- a/straper/install-base.sh +++ /dev/null @@ -1,375 +0,0 @@ -#!/usr/bin/env bash -set -Eeuo pipefail - -SCRIPT_DIR="$(cd -- "$(dirname -- "$0")" && pwd)" -# shellcheck disable=SC1091 -source "${SCRIPT_DIR}/lib/common.sh" - -SCRIPT_NAME="install-base.sh" -USE_DB_APT_SOURCES=false -FROM_DB_MANUAL=false -HOSTNAME_WANTED="" -TIMEZONE_WANTED="Europe/Warsaw" -LOCALE_WANTED="en_US.UTF-8" - -usage() { - cat <<USAGE -Usage: sudo ./install-base.sh [options] - -Purpose: - Install a reproducible Debian base for sanctum/labunix recovery without - importing host-specific configs or identities. - -Options: - --db-dir PATH Capture DB root (default: ${DB_DIR}) - --role lab|hardware|replacement - --profile minimal|core|full - --hostname NAME Set hostname - --timezone TZ Set timezone (default: ${TIMEZONE_WANTED}) - --locale LOCALE Set locale (default: ${LOCALE_WANTED}) - --use-db-apt-sources Restore apt sources from db/public/packages/apt - --from-db-manual Install packages listed in apt-mark-manual.txt - --start-safe-services Enable/start low-risk services after install - --yes Non-interactive yes to prompts - --dry-run Print actions only - --verbose More logging - --help -USAGE -} - -while [[ $# -gt 0 ]]; do - case "$1" in - --db-dir) DB_DIR="$2"; PUBLIC_DIR="${DB_DIR}/db/public"; SECRET_DIR="${DB_DIR}/db/secret"; shift ;; - --role) ROLE="$2"; shift ;; - --profile) PROFILE="$2"; shift ;; - --hostname) HOSTNAME_WANTED="$2"; shift ;; - --timezone) TIMEZONE_WANTED="$2"; shift ;; - --locale) LOCALE_WANTED="$2"; shift ;; - --use-db-apt-sources) USE_DB_APT_SOURCES=true ;; - --from-db-manual) FROM_DB_MANUAL=true ;; - --start-safe-services) START_SERVICES=true ;; - --yes) ASSUME_YES=true ;; - --dry-run) DRY_RUN=true ;; - --verbose) VERBOSE=true ;; - --help) usage; exit 0 ;; - *) die "unknown option: $1" ;; - esac - shift -done - -ensure_root -load_optional_config -ensure_runtime_dirs -require_cmd apt-get apt-cache dpkg hostnamectl rsync - -mkdir -p -- /etc/labunix - -log "${SCRIPT_NAME}: role=${ROLE} profile=${PROFILE} db=${DB_DIR}" -report "${SCRIPT_NAME}" "run" "start" "init" "ok" "role=${ROLE} profile=${PROFILE}" "" - -case "${ROLE}" in - lab|hardware|replacement) ;; - *) die "invalid role: ${ROLE}" ;; -esac -case "${PROFILE}" in - minimal|core|full) ;; - *) die "invalid profile: ${PROFILE}" ;; -esac - -BASE_PACKAGES=( - ca-certificates curl wget gnupg2 jq rsync vim zsh sudo - locales tzdata lsb-release apt-transport-https - net-tools iproute2 iputils-ping dnsutils ethtool - openssh-server systemd-resolved nftables fail2ban -) - -CORE_PACKAGES=( - wireguard wireguard-tools dnsmasq unbound dns-root-data docker.io docker-compose - nginx mariadb-server postfix prosody - tor i2pd pygopherd mumble-server prometheus prometheus-node-exporter loki - apparmor apparmor-utils - grafana -) - -FULL_EXTRA_PACKAGES=( - git tmux htop build-essential pkg-config - grafana alloy -) - - -setup_third_party_repos() { - # Grafana apt repo - if ! [[ -f /etc/apt/keyrings/grafana.gpg ]]; then - log "setting up Grafana apt repo..." - mkdir -p /etc/apt/keyrings - if curl -fsSL https://apt.grafana.com/gpg.key | gpg --dearmor -o /etc/apt/keyrings/grafana.gpg 2>/dev/null; then - echo "deb [signed-by=/etc/apt/keyrings/grafana.gpg] https://apt.grafana.com stable main" > /etc/apt/sources.list.d/grafana.list - report "${SCRIPT_NAME}" "apt" "grafana-repo" "setup" "changed" "Grafana apt repo configured" "" - else - warn "Failed to set up Grafana repo — grafana will not be installable" - fi - fi -} - -bootstrap_dns_prepare() { - local test_host="${BOOTSTRAP_TEST_HOST:-deb.debian.org}" - local backup="/etc/resolv.conf.labunix-preinstall.bak" - - # Always write a static resolv.conf — breaks any symlink to systemd-resolved - # stub (127.0.0.53) which can disappear when services restart mid-install. - [[ -e /etc/resolv.conf && ! -e "${backup}" ]] && cp -a /etc/resolv.conf "${backup}" || true - - if getent ahostsv4 "${test_host}" >/dev/null 2>&1; then - # DNS works but may be via stub — write static file pointing at real resolver - local current_ns - current_ns="$(grep '^nameserver' /etc/resolv.conf 2>/dev/null | head -1 | awk '{print $2}')" - # Never use stub addresses — they won't survive service restarts - if [[ "$current_ns" == "127.0.0.53" || "$current_ns" == "127.0.0.54" || -z "$current_ns" ]]; then - current_ns="1.1.1.1" - fi - [[ -L /etc/resolv.conf ]] && rm -f /etc/resolv.conf - printf 'nameserver %s -nameserver 9.9.9.9 -' "$current_ns" > /etc/resolv.conf - chmod 644 /etc/resolv.conf - report "${SCRIPT_NAME}" "dns" "bootstrap" "check" "ok" "static resolv.conf written: ${current_ns}" "" - return 0 - fi - - if ! ping -c 1 -W 3 1.1.1.1 >/dev/null 2>&1; then - note_failure "${SCRIPT_NAME}" "dns" "bootstrap" "check" "no outbound IP connectivity" - die "bootstrap DNS check failed: no outbound IP connectivity" - fi - - [[ -L /etc/resolv.conf ]] && rm -f /etc/resolv.conf - printf 'nameserver 1.1.1.1 -nameserver 9.9.9.9 -' > /etc/resolv.conf - chmod 644 /etc/resolv.conf - - if getent ahostsv4 "${test_host}" >/dev/null 2>&1; then - report "${SCRIPT_NAME}" "dns" "bootstrap" "fallback" "changed" "static fallback resolv.conf applied" "${backup}" - return 0 - fi - - note_failure "${SCRIPT_NAME}" "dns" "bootstrap" "check" "DNS still broken after fallback attempt" - die "bootstrap DNS check failed" -} - -install_pkg_group() { - local group_name="$1"; shift - local pkgs=("$@") - log "installing package group: ${group_name}" - if apt_install_if_missing "${pkgs[@]}"; then - report "${SCRIPT_NAME}" "packages" "${group_name}" "install" "ok" "group processed" "" - else - note_failure "${SCRIPT_NAME}" "packages" "${group_name}" "install" "apt install failed" - die "package group failed: ${group_name}" - fi -} - -apply_hostname_locale_timezone() { - if [[ -n ${HOSTNAME_WANTED} ]]; then - if [[ ${DRY_RUN} == true ]]; then - printf '[dry] hostnamectl set-hostname %s\n' "${HOSTNAME_WANTED}" - else - hostnamectl set-hostname "${HOSTNAME_WANTED}" - printf '%s\n' "${HOSTNAME_WANTED}" > /etc/hostname - fi - report "${SCRIPT_NAME}" "system" "hostname" "set" "changed" "${HOSTNAME_WANTED}" "" - fi - - if [[ -f /etc/locale.gen ]] && grep -Eq "^#?\s*${LOCALE_WANTED}\b" /etc/locale.gen 2>/dev/null; then - if [[ ${DRY_RUN} == true ]]; then - printf '[dry] enable locale %s in /etc/locale.gen\n' "${LOCALE_WANTED}" - else - if have_cmd locale-gen && have_cmd update-locale; then - sed -i "s/^# *\(${LOCALE_WANTED}\b.*\)/\1/" /etc/locale.gen - locale-gen "${LOCALE_WANTED}" >/dev/null - update-locale LANG="${LOCALE_WANTED}" >/dev/null - else - report "${SCRIPT_NAME}" "system" "locale" "manual" "manual" "locale tools missing; install locales first or rerun after base packages" "" - return 0 - fi - fi - report "${SCRIPT_NAME}" "system" "locale" "set" "changed" "${LOCALE_WANTED}" "" - else - report "${SCRIPT_NAME}" "system" "locale" "manual" "manual" "locale missing in /etc/locale.gen: ${LOCALE_WANTED}" "" - fi - - if [[ ${DRY_RUN} == true ]]; then - printf '[dry] set timezone %s\n' "${TIMEZONE_WANTED}" - else - ln -sf "/usr/share/zoneinfo/${TIMEZONE_WANTED}" /etc/localtime - dpkg-reconfigure -f noninteractive tzdata >/dev/null - fi - report "${SCRIPT_NAME}" "system" "timezone" "set" "changed" "${TIMEZONE_WANTED}" "" -} - -restore_apt_sources_from_db() { - local src="${PUBLIC_DIR}/packages/apt" - [[ -d ${src} ]] || { mark_manual "${SCRIPT_NAME}" "apt" "sources" "db/public/packages/apt missing"; return 0; } - - if ! prompt_yes_no "Restore APT sources from DB now?" no; then - report "${SCRIPT_NAME}" "apt" "sources" "restore" "skipped" "operator skipped" "" - return 0 - fi - - restore_path "${SCRIPT_NAME}" "apt" "sources.list" "${src}/sources.list" "/etc/apt/sources.list" - restore_path "${SCRIPT_NAME}" "apt" "sources.list.d" "${src}/sources.list.d" "/etc/apt/sources.list.d" - restore_path "${SCRIPT_NAME}" "apt" "preferences" "${src}/preferences" "/etc/apt/preferences" - restore_path "${SCRIPT_NAME}" "apt" "preferences.d" "${src}/preferences.d" "/etc/apt/preferences.d" - restore_path "${SCRIPT_NAME}" "apt" "apt.conf.d" "${src}/apt.conf.d" "/etc/apt/apt.conf.d" - - if [[ ${DRY_RUN} == true ]]; then - printf '[dry] apt-get update\n' - else - if apt-get update; then - report "${SCRIPT_NAME}" "apt" "update-after-restore" "update" "ok" "apt metadata refreshed" "" - else - note_failure "${SCRIPT_NAME}" "apt" "update-after-restore" "update" "apt-get update failed after restoring sources" - fi - fi -} - -package_available() { - local pkg="$1" - apt-cache show -- "${pkg}" >/dev/null 2>&1 -} - -install_from_db_manual() { - local f="${PUBLIC_DIR}/packages/apt-mark-manual.txt" pkg installed=0 skipped=0 failed=0 - [[ -f ${f} ]] || { mark_manual "${SCRIPT_NAME}" "packages" "db-manual" "apt-mark-manual.txt missing"; return 0; } - - while IFS= read -r pkg; do - [[ -n ${pkg} ]] || continue - [[ ${pkg} =~ ^# ]] && continue - if dpkg -s -- "${pkg}" >/dev/null 2>&1; then - ((installed+=1)) - continue - fi - if ! package_available "${pkg}"; then - ((skipped+=1)) - report "${SCRIPT_NAME}" "packages" "${pkg}" "install" "skipped" "package not available in current apt sources" "" - continue - fi - if [[ ${DRY_RUN} == true ]]; then - printf '[dry] apt-get install -y --no-install-recommends %s\n' "${pkg}" - ((installed+=1)) - continue - fi - if DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends "${pkg}"; then - ((installed+=1)) - report "${SCRIPT_NAME}" "packages" "${pkg}" "install" "changed" "installed from apt-mark-manual" "" - else - ((failed+=1)) - note_failure "${SCRIPT_NAME}" "packages" "${pkg}" "install" "apt install failed" - fi - done < <(list_from_file "${f}") - - report "${SCRIPT_NAME}" "packages" "db-manual-summary" "install" "ok" "installed=${installed} skipped=${skipped} failed=${failed}" "" -} -normalize_service_baseline() { - local keep_active=( - ssh.service - systemd-resolved.service - fail2ban.service - ) - - local stop_disable=( - dnsmasq.service - unbound.service - unbound-resolvconf.service - nginx.service - mariadb.service - postfix.service - prosody.service - prometheus.service - tor.service - i2pd.service - mumble-server.service - pygopherd.service - ) - - local unit - - for unit in "${stop_disable[@]}"; do - if systemctl list-unit-files --type=service --no-legend --no-pager 2>/dev/null | awk '{print $1}' | grep -qx -- "${unit}"; then - systemctl disable --now "${unit}" >/dev/null 2>&1 || true - fi - done - - systemctl reset-failed "${stop_disable[@]}" >/dev/null 2>&1 || true - - for unit in "${keep_active[@]}"; do - if systemctl list-unit-files --type=service --no-legend --no-pager 2>/dev/null | awk '{print $1}' | grep -qx -- "${unit}"; then - systemctl enable "${unit}" >/dev/null 2>&1 || true - systemctl restart "${unit}" >/dev/null 2>&1 || true - fi - done - - report "${SCRIPT_NAME}" "services" "baseline" "normalize" "changed" "safe services kept; app/conflicting services stopped" "" -} -start_safe_services() { - local svc - for svc in ssh systemd-resolved fail2ban; do - systemctl_safe_enable "${svc}" || true - systemctl_safe_start "${svc}" || true - done - - case "${DNS_MODE}" in - resolved) - systemctl_safe_enable systemd-resolved || true - systemctl_safe_start systemd-resolved || true - ;; - esac - report "${SCRIPT_NAME}" "services" "safe-start" "start" "ok" "safe services processed" "" -} - -if [[ ${DRY_RUN} == false ]]; then - bootstrap_dns_prepare - if apt-get update; then - report "${SCRIPT_NAME}" "apt" "update" "update" "ok" "initial apt update" "" - else - note_failure "${SCRIPT_NAME}" "apt" "update" "update" "initial apt-get update failed" - die "initial apt-get update failed" - fi -else - report "${SCRIPT_NAME}" "apt" "update" "update" "ok" "initial apt update (dry-run)" "" -fi - -setup_third_party_repos -apt-get update -qq 2>/dev/null || true -install_pkg_group base "${BASE_PACKAGES[@]}" -case "${PROFILE}" in - minimal) ;; - core) install_pkg_group core "${CORE_PACKAGES[@]}" ;; - full) - install_pkg_group core "${CORE_PACKAGES[@]}" - install_pkg_group full-extra "${FULL_EXTRA_PACKAGES[@]}" - ;; -esac - -normalize_service_baseline -apply_hostname_locale_timezone - -if [[ ${USE_DB_APT_SOURCES} == true ]]; then - restore_apt_sources_from_db -fi - -if [[ ${FROM_DB_MANUAL} == true ]]; then - install_from_db_manual -fi - -install -d -m 0755 /srv /srv/www /srv/docs /etc/wireguard /etc/labunix /var/lib/labunix -report "${SCRIPT_NAME}" "filesystem" "base-dirs" "create" "ok" "/srv /etc/wireguard /etc/labunix prepared" "" - -if [[ ${START_SERVICES} == true ]]; then - start_safe_services -else - report "${SCRIPT_NAME}" "services" "safe-start" "start" "skipped" "--start-safe-services not requested" "" -fi - -set_state install_base done -report "${SCRIPT_NAME}" "run" "finish" "exit" "ok" "completed" "" -log "${SCRIPT_NAME}: done; report=${REPORT_FILE}" |
