#!/usr/bin/env bash # lib/common.sh — shared helpers for shell scripts and hooks in this repo. # # Source it from any script: # #!/usr/bin/env bash # set -euo pipefail # HERE="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" # # shellcheck source=lib/common.sh # source "$HERE/lib/common.sh" # or the appropriate relative path # # This library exists to drive a known cross-project inconsistency to zero: # every repo used to re-paste the ANSI color block, redefine log_* helpers, # pick one of three incompatible shebangs, and roll its own docker wrapper. # Import this once instead. # Do NOT set -euo pipefail here unconditionally — some callers (git hooks) # source this file and rely on controlling their own shell options. We only # guarantee the functions below are defined. ############################################################################### # Config — override via environment before sourcing if needed ############################################################################### : "${TEMPLATE_ROOT:=$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)}" export TEMPLATE_ROOT ############################################################################### # ANSI colors (defined once, used everywhere) ############################################################################### if [ -t 1 ] && [ -z "${NO_COLOR:-}" ]; then RED='\033[0;31m'; GREEN='\033[0;32m'; YELLOW='\033[1;33m' BLUE='\033[0;34m'; BOLD='\033[1m'; NC='\033[0m' else RED=''; GREEN=''; YELLOW=''; BLUE=''; BOLD=''; NC='' fi export RED GREEN YELLOW BLUE BOLD NC ############################################################################### # Logging ############################################################################### log_info() { printf "${BLUE}›${NC} %s\n" "$*"; } log_ok() { printf "${GREEN}✓${NC} %s\n" "$*"; } log_warn() { printf "${YELLOW}⚠${NC} %s\n" "$*" >&2; } log_error() { printf "${RED}✗${NC} %s\n" "$*" >&2; } log_step() { printf "\n${BOLD}== %s ==${NC}\n" "$*"; } die() { log_error "$*"; exit 1; } ############################################################################### # Predicates ############################################################################### # have — return 0 if is on PATH have() { command -v "$1" >/dev/null 2>&1; } ############################################################################### # Path helpers ############################################################################### repo_root() { # Prefer git's notion of the repo root, fall back to $TEMPLATE_ROOT, then pwd. if git rev-parse --show-toplevel >/dev/null 2>&1; then git rev-parse --show-toplevel else printf '%s\n' "${TEMPLATE_ROOT:-$(pwd)}" fi } ############################################################################### # Privilege helpers ############################################################################### # as_root — run the remaining args as root via sudo, or directly if already root. as_root() { if [ "$(id -u)" -eq 0 ]; then "$@"; else sudo "$@"; fi } ############################################################################### # Docker wrapper ############################################################################### # docker_run # Ephemeral container, host-uid ownership, repo mounted at /data, cwd /data. # Drives the "host stays clean; everything runs in containers" policy and # ensures output files are owned by the invoking user, not root. docker_run() { [ "$#" -ge 1 ] || die "docker_run: image required" local image="$1"; shift have docker || die "docker not found on PATH" local root root="$(repo_root)" docker run --rm \ --user "$(id -u):$(id -g)" \ -e HOME=/tmp \ -v "$root:/data" \ -w /data \ "$image" "$@" } ############################################################################### # Rule-audit accumulator (used by scripts/check-rules.sh) # Globals read/written: RULE_PASS RULE_WARN RULE_FAIL ############################################################################### init_counters() { RULE_PASS=0; RULE_WARN=0; RULE_FAIL=0; } # check check() { local desc="$1" result="$2" case "$result" in pass) RULE_PASS=$((RULE_PASS + 1)) if [ "${RULE_VERBOSE:-true}" = true ]; then printf " ${GREEN}PASS${NC} %s\n" "$desc"; fi ;; warn) RULE_WARN=$((RULE_WARN + 1)) if [ "${RULE_VERBOSE:-true}" = true ]; then printf " ${YELLOW}WARN${NC} %s\n" "$desc"; fi ;; fail) RULE_FAIL=$((RULE_FAIL + 1)) printf " ${RED}FAIL${NC} %s\n" "$desc" ;; *) die "check(): invalid result '$result' (use pass|warn|fail)" ;; esac } # print_summary_and_exit print_summary_and_exit() { if [ "${RULE_VERBOSE:-true}" = true ]; then printf "\n=== Summary ===\n PASS: %s\n WARN: %s\n FAIL: %s\n\n" \ "$RULE_PASS" "$RULE_WARN" "$RULE_FAIL" fi if [ "$RULE_FAIL" -gt 0 ]; then if [ "${RULE_VERBOSE:-true}" = true ]; then printf "AUDIT FAILED — %s rule(s) violated.\n" "$RULE_FAIL" fi exit 1 fi if [ "${RULE_VERBOSE:-true}" = true ]; then printf "AUDIT PASSED.\n"; fi exit 0 }