#!/usr/bin/env bash # Native Debian baseline; Docker packages are prepared and pinned by our controller. set -euo pipefail export LC_ALL=C fail() { printf 'ERROR: %s\n' "$*" >&2; return 1; } ensure_baseline_packages() { local package missing=() for package in ca-certificates curl jq git openssl openssh-client openssh-server iproute2 nftables procps kmod util-linux; do if [[ $(dpkg-query -W -f='${db:Status-Status}' "$package" 2>/dev/null) != installed ]]; then missing+=("$package") fi done if ((${#missing[@]})); then apt-get -o DPkg::Lock::Timeout=60 update apt-get -o DPkg::Lock::Timeout=60 install -y --no-install-recommends "${missing[@]}" else echo 'Baseline packages already installed; APT refresh skipped.' fi } check_existing_binding() { local state_dir=$1 expected=$2 bound='' local binding="$state_dir/binding" [[ ! -L "$state_dir" && ! -L "$binding" ]] || { fail 'Symlink in bootstrap binding'; return 1; } [[ ! -e "$binding" ]] && return 0 [[ -f "$binding" && $(stat -c %u "$binding") == 0 && $(stat -c %a "$binding") == 600 && $(stat -c %h "$binding") == 1 ]] || { fail 'Existing bootstrap binding is not a trusted 0600 regular file'; return 1; } IFS= read -r bound < "$binding" || { fail 'Cannot read bootstrap binding'; return 1; } [[ "$bound" == "$expected" ]] || { fail 'Existing bootstrap binding belongs to another hostname'; return 1; } } main() { local action='' name='' packages='' public_key='' argument docker_mode=required firewall_policy='' firewall_script='' while (($#)); do case "$1" in --help|-h) echo 'Usage: bash host-init.sh --check|--apply --hostname NAME [--docker-dir VERIFIED_DEB_DIRECTORY] [--ssh-key-file SHARED_PUBLIC_KEY] [--without-docker] [--firewall-policy ROOT_0600_JSON --firewall-script VERIFIED_RELEASE_FIREWALL_SH]'; return 0;; --without-docker) docker_mode=none; shift;; --check|--apply) [[ -z "$action" ]] || return 1; action=$1; shift;; --hostname|--docker-dir|--ssh-key-file|--firewall-policy|--firewall-script) (($# >= 2)) || return 1 case "$1" in --hostname) name=$2;; --docker-dir) packages=$2;; --ssh-key-file) public_key=$2;; --firewall-policy) firewall_policy=$2;; --firewall-script) firewall_script=$2;; esac; shift 2;; *) fail "Unknown argument: $1"; return 1;; esac done [[ "$action" == --check || "$action" == --apply ]] || { fail 'Choose check or apply'; return 1; } local word='[a-z][a-z0-9]{0,15}' [[ "$name" =~ ^(dev|test|stage|prod)-$word-$word(-$word)?-(0[1-9]|[1-9][0-9])$ && ${#name} -le 63 ]] || { fail 'Explicit short hostname required'; return 1; } [[ $EUID == 0 && -d /run/systemd/system && ! -e /.dockerenv ]] || { fail 'Native root systemd host required'; return 1; } [[ $(uname -m) == x86_64 ]] || { fail 'The published Periphery and Docker package set currently supports x86_64 only'; return 1; } grep -Eq '^ID="?debian"?$' /etc/os-release && grep -Eq '^VERSION_ID="?13"?$' /etc/os-release || { fail 'Debian 13 required; upgrade is a separate human-run task'; return 1; } check_existing_binding /var/lib/platform-bootstrap "$name" local debs=() file package version architecture installed seen=' ' sha extra [[ "$docker_mode" != none || -z "$packages" ]] || { fail 'Docker directory conflicts with --without-docker'; return 1; } if [[ -n "$packages" ]]; then [[ "$packages" == /* && -d "$packages" && ! -L "$packages" && -f "$packages/SHA256SUMS" && ! -L "$packages/SHA256SUMS" ]] || { fail 'Prepared package directory and manifest required'; return 1; } while read -r sha file extra; do [[ "$sha" =~ ^[a-f0-9]{64}$ && "$file" =~ ^[A-Za-z0-9][A-Za-z0-9._+%:~-]*\.deb$ && -z "$extra" && -f "$packages/$file" && ! -L "$packages/$file" ]] || { fail 'Invalid package manifest'; return 1; } printf '%s %s\n' "$sha" "$packages/$file" | sha256sum -c - >/dev/null package=$(dpkg-deb -f "$packages/$file" Package); version=$(dpkg-deb -f "$packages/$file" Version); architecture=$(dpkg-deb -f "$packages/$file" Architecture) [[ "$architecture" == amd64 ]] || { fail 'Prepared Docker packages must be amd64'; return 1; } case "$package" in docker-ce|docker-ce-cli|containerd.io|docker-buildx-plugin|docker-compose-plugin) :;; *) fail 'Unexpected Docker package'; return 1;; esac [[ "$seen" != *" $package "* ]] || { fail 'Duplicate package'; return 1; }; seen+="$package " installed=$(dpkg-query -W -f='${Version}' "$package" 2>/dev/null) || installed='' [[ -z "$installed" || "$installed" == "$version" ]] || { fail 'Existing Docker version differs; plan explicit maintenance'; return 1; } debs+=("$packages/$file") done < "$packages/SHA256SUMS" [[ ${#debs[@]} == 5 ]] || { fail 'All five pinned official Docker packages required'; return 1; } elif [[ "$docker_mode" == required ]]; then command -v docker >/dev/null && docker compose version >/dev/null || { fail 'Prepare the five pinned Docker DEBs through our resource publication first'; return 1; } fi if [[ -n "$public_key" ]]; then [[ -f "$public_key" && ! -L "$public_key" && $(wc -l < "$public_key") == 1 ]] || { fail 'One shared public key line required'; return 1; } grep -Eq '^(ssh-ed25519|ssh-rsa|ecdsa-sha2-nistp(256|384|521)) [A-Za-z0-9+/=]+( .*)?$' "$public_key" || { fail 'Public key format invalid'; return 1; } ssh-keygen -l -f "$public_key" >/dev/null [[ ! -L /root/.ssh && ! -L /root/.ssh/authorized_keys ]] || { fail 'Symlink in SSH destination'; return 1; } fi if [[ -n "$firewall_policy" || -n "$firewall_script" ]]; then [[ "$firewall_policy" == /* && "$firewall_script" == /* && -f "$firewall_script" && ! -L "$firewall_script" && $(stat -c %u "$firewall_script") == 0 && $(stat -c %h "$firewall_script") == 1 ]] || { fail 'Use the verified root-owned release firewall script and explicit policy'; return 1; } [[ -f "$firewall_policy" && ! -L "$firewall_policy" && $(stat -c %u "$firewall_policy") == 0 && $(stat -c %a "$firewall_policy") == 600 ]] || { fail 'Firewall policy must be a root-owned 0600 regular file'; return 1; } bash "$firewall_script" --render --policy "$firewall_policy" >/dev/null fi [[ "$action" == --apply ]] || { echo 'Baseline inputs checked; no packages, hostname or SSH configuration changed.'; return 0; } umask 077 exec 9>>/run/lock/platform-host-init.lock; flock -n 9 || { fail 'Host initialization is already running'; return 1; } check_existing_binding /var/lib/platform-bootstrap "$name" ensure_baseline_packages if ((${#debs[@]})); then apt-get -o DPkg::Lock::Timeout=60 install -y "${debs[@]}"; fi hostnamectl set-hostname "$name" if [[ -n "$public_key" ]]; then install -d -m 0700 /root/.ssh touch /root/.ssh/authorized_keys; chmod 0600 /root/.ssh/authorized_keys if ! grep -Fqx -- "$(cat "$public_key")" /root/.ssh/authorized_keys; then printf '\n' >> /root/.ssh/authorized_keys; cat "$public_key" >> /root/.ssh/authorized_keys; fi fi if [[ -n "$firewall_policy" ]]; then # Same native transaction as normal maintenance: all private sources allowed, # public recovery inputs remain explicit; a fresh external SSH must confirm. bash "$firewall_script" --check --policy "$firewall_policy" bash "$firewall_script" --apply --policy "$firewall_policy" echo 'Firewall pending: confirm its exact digest from a NEW SSH connection within 180 seconds.' fi if [[ "$docker_mode" == required ]]; then systemctl enable --now docker docker --host unix:///var/run/docker.sock info >/dev/null docker --host unix:///var/run/docker.sock compose version fi [[ $(hostname) == "$name" ]] echo 'Prerequisites prepared; full host baseline NOT verified. Continue host-baseline.sh and the SSH/firewall transaction; verify a fresh external SSH connection.' } if [[ ${BASH_SOURCE[0]:-} == "$0" || -z ${BASH_SOURCE[0]:-} ]]; then main "$@"; fi