#!/bin/bash # ppf-common.sh -- shared library for PPF operations toolkit # Source this file; do not execute directly. set -eu # --------------------------------------------------------------------------- # Paths # --------------------------------------------------------------------------- PPF_DIR="${PPF_DIR:-$HOME/git/ppf}" ANSIBLE_DIR="/opt/ansible" ANSIBLE_VENV="${ANSIBLE_DIR}/venv/bin/activate" PPF_INVENTORY="${PPF_DIR}/tools/playbooks/inventory.ini" # --------------------------------------------------------------------------- # Host topology # --------------------------------------------------------------------------- MASTER="odin" WORKERS="cassius edge sentinel" ALL_HOSTS="odin cassius edge sentinel" # Container names per role MASTER_CONTAINER="ppf" WORKER_CONTAINER="ppf-worker" # --------------------------------------------------------------------------- # Colors (respects NO_COLOR -- https://no-color.org) # --------------------------------------------------------------------------- if [ -z "${NO_COLOR:-}" ] && [ -t 1 ]; then C_RST='\033[0m' C_DIM='\033[2m' C_BOLD='\033[1m' C_RED='\033[38;5;167m' C_GREEN='\033[38;5;114m' C_YELLOW='\033[38;5;180m' C_BLUE='\033[38;5;110m' C_CYAN='\033[38;5;116m' else C_RST='' C_DIM='' C_BOLD='' C_RED='' C_GREEN='' C_YELLOW='' C_BLUE='' C_CYAN='' fi # --------------------------------------------------------------------------- # Output helpers # --------------------------------------------------------------------------- log_ok() { printf "${C_GREEN} ✓${C_RST} %s\n" "$*"; } log_err() { printf "${C_RED} ✗${C_RST} %s\n" "$*" >&2; } log_warn() { printf "${C_YELLOW} ⚠${C_RST} %s\n" "$*"; } log_info() { printf "${C_BLUE} ●${C_RST} %s\n" "$*"; } log_dim() { printf "${C_DIM} %s${C_RST}\n" "$*"; } die() { log_err "$@"; exit 1; } # Section header section() { printf "\n${C_BOLD}${C_CYAN} %s${C_RST}\n" "$*" } # --------------------------------------------------------------------------- # Host resolution helpers # --------------------------------------------------------------------------- is_master() { [ "$1" = "$MASTER" ]; } is_worker() { local h for h in $WORKERS; do [ "$h" = "$1" ] && return 0; done return 1 } container_name() { if is_master "$1"; then echo "$MASTER_CONTAINER"; else echo "$WORKER_CONTAINER"; fi } # Expand target aliases into host list # "all" -> all hosts # "workers" -> worker hosts # "odin" -> just odin # Multiple args are concatenated with comma resolve_targets() { local targets="" local arg for arg in "$@"; do case "$arg" in all) targets="${targets:+$targets }$ALL_HOSTS" ;; workers) targets="${targets:+$targets }$WORKERS" ;; master) targets="${targets:+$targets }$MASTER" ;; *) targets="${targets:+$targets }$arg" ;; esac done # Deduplicate while preserving order echo "$targets" | tr ' ' '\n' | awk '!seen[$0]++' | tr '\n' ' ' | sed 's/ $//' } # Convert space-separated host list to comma-separated for ansible hosts_csv() { echo "$*" | tr ' ' ',' } # --------------------------------------------------------------------------- # Ansible wrapper # --------------------------------------------------------------------------- # Runs ansible with toolkit inventory via venv. # Usage: ansible_cmd ansible_cmd() { ( # shellcheck disable=SC1090 . "$ANSIBLE_VENV" cd "$ANSIBLE_DIR" ansible -i "$PPF_INVENTORY" "$@" ) } # Runs ansible-playbook with toolkit inventory via venv. # Usage: ansible_playbook_cmd ansible_playbook_cmd() { ( # shellcheck disable=SC1090 . "$ANSIBLE_VENV" cd "$ANSIBLE_DIR" ansible-playbook "$@" ) } # --------------------------------------------------------------------------- # Remote podman/compose wrappers # --------------------------------------------------------------------------- # Run a podman command on a remote host as the podman user. # Uses dynamic UID discovery. # Usage: podman_cmd HOST "podman subcommand..." podman_cmd() { local host="$1"; shift local cmd="$*" ansible_cmd "$host" -m raw -a \ "uid=\$(id -u podman) && sudo -u podman XDG_RUNTIME_DIR=/run/user/\$uid $cmd" } # Run a podman-compose subcommand on a remote host. # Usage: compose_cmd HOST "subcommand [args]" compose_cmd() { local host="$1"; shift local cmd="$*" ansible_cmd "$host" -m raw -a \ "uid=\$(id -u podman) && cd /home/podman/ppf && sudo -u podman XDG_RUNTIME_DIR=/run/user/\$uid podman-compose $cmd" } # --------------------------------------------------------------------------- # Validation # --------------------------------------------------------------------------- validate_syntax() { local errors=0 local f section "Validating Python syntax" for f in "$PPF_DIR"/*.py; do [ -f "$f" ] || continue if python3 -m py_compile "$f" 2>/dev/null; then log_dim "$(basename "$f")" else log_err "$(basename "$f")" errors=$((errors + 1)) fi done if [ "$errors" -gt 0 ]; then die "$errors file(s) failed syntax check" fi log_ok "All files valid" } # --------------------------------------------------------------------------- # Version # --------------------------------------------------------------------------- PPF_TOOLS_VERSION="1.0.0"