#!/usr/bin/env bash # scripts/onboard/lib/common.sh — shared helpers for the onboarding tool set -euo pipefail # ── colour codes (disabled when not a tty) ────────────────────────────────── if [[ -t 1 ]]; then _C_RESET='\033[0m' _C_GREEN='\033[0;32m' _C_YELLOW='\033[1;33m' _C_RED='\033[0;31m' _C_CYAN='\033[0;36m' _C_BOLD='\033[1m' else _C_RESET='' _C_GREEN='' _C_YELLOW='' _C_RED='' _C_CYAN='' _C_BOLD='' fi # ── logging ────────────────────────────────────────────────────────────────── log() { echo -e "${_C_GREEN}[onboard]${_C_RESET} $(date +'%H:%M:%S') ${*}"; } warn() { echo -e "${_C_YELLOW}[WARN]${_C_RESET} $(date +'%H:%M:%S') ${*}" >&2; } die() { echo -e "${_C_RED}[ERROR]${_C_RESET} $(date +'%H:%M:%S') ${*}" >&2; exit 1; } step() { echo -e "${_C_CYAN}${_C_BOLD}==> ${*}${_C_RESET}"; } dryrun() { echo -e "${_C_YELLOW}[dry-run]${_C_RESET} ${*}"; } # ── command detection ───────────────────────────────────────────────────────── have_cmd() { command -v "$1" >/dev/null 2>&1; } # ── dry-run execution wrapper ───────────────────────────────────────────────── # run CMD [ARGS…] — executes CMD in live mode; prints intent in dry-run. # Wrap MUTATIONS with this. Read-only probes (SSH BatchMode tests, status # queries, command -v checks) must run unconditionally — never wrap them. run() { if [ "${DRY_RUN:-0}" = 1 ]; then echo "[dry-run] would: $*" else "$@" fi } export -f run # ── file helpers ────────────────────────────────────────────────────────────── # ensure_line FILE LINE — appends LINE to FILE if it is not already present (idempotent) ensure_line() { local file="$1" line="$2" [[ -f "$file" ]] || touch "$file" grep -qxF "$line" "$file" || echo "$line" >> "$file" } # ── node.yaml parsing ───────────────────────────────────────────────────────── # require_node_yaml NODE — sets NODE_YAML; exits if not found require_node_yaml() { local node="$1" NODE_YAML="${REPO_ROOT}/hosts/${node,,}/node.yaml" [[ -f "$NODE_YAML" ]] || die "node.yaml not found: $NODE_YAML" export NODE_YAML } # yaml_get NODE_YAML KEY — read a scalar value from a YAML file # Uses yq when available; falls back to grep/sed for simple key: value pairs. # Supports dot-separated paths (e.g. tailscale.hostname) only in yq mode; # the grep fallback handles only the last path component. yaml_get() { local file="$1" key="$2" if have_cmd yq; then yq -r ".${key} // empty" "$file" 2>/dev/null else # fallback: extract last segment of key, match " key: value" # Strip inline YAML comment (space(s)+'#'+rest) and surrounding whitespace. # Pattern uses \+ (BRE one-or-more) so a bare '#' inside a value is preserved. local leaf="${key##*.}" grep -E "^\s*${leaf}:" "$file" | head -1 \ | sed -e 's/^[[:space:]]*[^:]*:[[:space:]]*//' \ -e 's/[[:space:]]\+#.*$//' \ -e 's/^[[:space:]]*//' \ -e 's/[[:space:]]*$//' \ | tr -d '"' | tr -d "'" fi } # ── git wrapper ──────────────────────────────────────────────────────────────── # All git calls from onboarding scripts must go through this so --no-pager is # always set and there is no interactive output. git() { command git --no-pager "$@"; } export -f git