#!/bin/bash # install-fleet_base.sh — taskserv installer for fleet base node setup. # Runs on the target Debian/Ubuntu host as root. Idempotent. # # Inputs (env file scp'd alongside this script): # FLEET_AGE_KEY — age private key content # FLEET_KEY_INSTALL_PATH — target path (default /etc/fleet/age.key) # FLEET_REGISTRY_COUNT — number of registries # FLEET_REGISTRY_N_NAME — registry name (N = 0-based index) # FLEET_REGISTRY_N_ENDPOINT — registry endpoint # FLEET_REGISTRY_N_USERNAME — registry username # FLEET_REGISTRY_N_PASSWORD — registry password # SCCACHE_ENDPOINT — (optional) S3 endpoint # SCCACHE_BUCKET — (optional) S3 bucket # SCCACHE_REGION — (optional) S3 region # SCCACHE_DISK_PATH — (optional) local disk cache path # SCCACHE_ACCESS_KEY — (optional) S3 access key id # SCCACHE_SECRET_KEY — (optional) S3 secret access key set -euo pipefail SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" for f in env-fleet_base _credentials.env; do [ -f "${SCRIPT_DIR}/${f}" ] && source "${SCRIPT_DIR}/${f}" || true done : "${FLEET_AGE_KEY:?FLEET_AGE_KEY must be set}" FLEET_KEY_INSTALL_PATH="${FLEET_KEY_INSTALL_PATH:-/etc/fleet/age.key}" FLEET_REGISTRY_COUNT="${FLEET_REGISTRY_COUNT:-0}" bold() { printf "\033[1m%s\033[0m\n" "$*"; } fatal() { printf "\033[31mFATAL:\033[0m %s\n" "$*" >&2; exit 1; } [ "$(id -u)" -eq 0 ] || fatal "must run as root" [ "$FLEET_AGE_KEY" != "UNSET" ] || fatal "FLEET_AGE_KEY is sentinel 'UNSET'" ACTION="${1:-install}" cmd_install() { bold "==> [1/4] System packages" export DEBIAN_FRONTEND=noninteractive apt-get update -qq apt-get install -y -qq openssl openssh-server systemctl enable --now ssh bold "==> [2/4] Fleet age key → ${FLEET_KEY_INSTALL_PATH}" install -d -m 0700 "$(dirname "${FLEET_KEY_INSTALL_PATH}")" printf '%s\n' "${FLEET_AGE_KEY}" > "${FLEET_KEY_INSTALL_PATH}" chmod 0600 "${FLEET_KEY_INSTALL_PATH}" echo " installed age key at ${FLEET_KEY_INSTALL_PATH}" bold "==> [3/4] OCI registry auth (${FLEET_REGISTRY_COUNT} registries)" if [ "${FLEET_REGISTRY_COUNT}" -gt 0 ]; then AUTH_DIR="/etc/containers" install -d -m 0755 "${AUTH_DIR}" # Build auths JSON by iterating indexed env vars — no jq required AUTHS_JSON="{" SEP="" for i in $(seq 0 $((FLEET_REGISTRY_COUNT - 1))); do name_var="FLEET_REGISTRY_${i}_NAME" ep_var="FLEET_REGISTRY_${i}_ENDPOINT" user_var="FLEET_REGISTRY_${i}_USERNAME" pass_var="FLEET_REGISTRY_${i}_PASSWORD" name="${!name_var}" endpoint="${!ep_var}" username="${!user_var:-}" password="${!pass_var:-}" [ -n "${username}" ] || { echo " WARN: ${name} has no username, skipping"; continue; } b64=$(printf '%s:%s' "${username}" "${password}" | openssl base64 -A) AUTHS_JSON="${AUTHS_JSON}${SEP}\"${endpoint}\":{\"auth\":\"${b64}\"}" SEP="," echo " configured registry ${name} (${endpoint})" done AUTHS_JSON="${AUTHS_JSON}}" # Merge with any existing auth.json in pure bash — no extra tooling on the node. # This manager writes compact JSON and auth values are flat objects # ("ep":{"auth":"b64"[,"identitytoken":"…"]}), so each entry is matched by a # single grep; existing entries the incoming set overrides are dropped. if [ -f "${AUTH_DIR}/auth.json" ]; then incoming_body="${AUTHS_JSON#\{}"; incoming_body="${incoming_body%\}}" incoming_keys=" " while IFS= read -r _e; do [ -z "$_e" ] && continue _k="${_e%%\":*}"; _k="${_k#\"}" incoming_keys="${incoming_keys}${_k} " done < <(printf '%s' "${incoming_body}" | grep -oE '"[^"]+":\{[^}]*\}') existing_compact="$(tr -d ' \t\n' < "${AUTH_DIR}/auth.json")" existing_entries="${existing_compact#\{\"auths\":\{}" existing_entries="${existing_entries%\}\}}" kept="" while IFS= read -r _e; do [ -z "$_e" ] && continue _k="${_e%%\":*}"; _k="${_k#\"}" case "${incoming_keys}" in *" ${_k} "*) continue ;; esac kept="${kept},${_e}" done < <(printf '%s' "${existing_entries}" | grep -oE '"[^"]+":\{[^}]*\}') merged="${incoming_body}${kept}"; merged="${merged#,}" printf '{"auths":{%s}}\n' "${merged}" > "${AUTH_DIR}/auth.json.new" mv "${AUTH_DIR}/auth.json.new" "${AUTH_DIR}/auth.json" else printf '{"auths":%s}\n' "${AUTHS_JSON}" > "${AUTH_DIR}/auth.json" fi chmod 0600 "${AUTH_DIR}/auth.json" echo " wrote ${AUTH_DIR}/auth.json" else echo " no registries configured" fi bold "==> [4/4] sccache env" if [ -n "${SCCACHE_ENDPOINT:-}" ]; then SCCACHE_DIR="/etc/sccache" install -d -m 0755 "${SCCACHE_DIR}" DISK_PATH="${SCCACHE_DISK_PATH:-/var/cache/sccache}" install -d -m 1777 "${DISK_PATH}" cat > "${SCCACHE_DIR}/sccache.env" < Done" } cmd_status() { echo "=== Fleet age key ===" if [ -f "${FLEET_KEY_INSTALL_PATH}" ]; then stat "${FLEET_KEY_INSTALL_PATH}" head -1 "${FLEET_KEY_INSTALL_PATH}" | grep -o '^AGE-SECRET-KEY-1' || echo " (present, header not AGE-SECRET-KEY-1)" else echo " NOT installed at ${FLEET_KEY_INSTALL_PATH}" fi echo "" echo "=== OCI registry auth ===" if [ -f /etc/containers/auth.json ]; then # list endpoints without exposing credentials grep -o '"[^"]*":{"auth"' /etc/containers/auth.json | sed 's/:{"auth"//' || echo " (empty)" else echo " /etc/containers/auth.json not present" fi echo "" echo "=== sccache env ===" if [ -f /etc/sccache/sccache.env ]; then grep -v 'SECRET\|KEY' /etc/sccache/sccache.env || true else echo " /etc/sccache/sccache.env not present" fi } cmd_uninstall() { rm -f "${FLEET_KEY_INSTALL_PATH}" rmdir --ignore-fail-on-non-empty "$(dirname "${FLEET_KEY_INSTALL_PATH}")" 2>/dev/null || true echo "uninstalled (auth.json and sccache.env preserved)" } case "$ACTION" in install) cmd_install ;; status) cmd_status ;; uninstall) cmd_uninstall ;; *) fatal "unknown action: $ACTION (install|status|uninstall)" ;; esac