Two bugs in the grep+sed fallback (triggered when yq is unavailable): 1. Greedy colon match: `s/.*: *//` consumed the *last* `: ` in the line, so values containing a colon (e.g. `systemd:magicmirror.service`) were silently truncated to the portion after the last colon. Fix: `s/^[[:space:]]*[^:]*:[[:space:]]*//' — anchored at line start, key chars are `[^:]*` (no colons), so only the first `: ` separator is removed. 2. Inline YAML comment not stripped: `first_contact: pi@pimirror2.local # ...` returned the full tail including `#`, breaking callers like ssh-copy-id. Fix: add `s/[[:space:]]\+#.*$//` — requires at least one space before `#` to preserve bare `#` characters inside a value. Also add leading/trailing whitespace trim as a separate pass. Both bugs affect any node.yaml field that has an inline comment or a colon in its value; all ten fields in hosts/lustro/node.yaml now parse correctly. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
85 lines
4.1 KiB
Bash
85 lines
4.1 KiB
Bash
#!/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
|