#!/usr/bin/env bash set -euo pipefail ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" export ANSIBLE_CONFIG="${ROOT_DIR}/ansible.cfg" # macOS: prevent Objective-C runtime from aborting forked worker processes. # Required for WinRM connections (pywinrm/requests uses Foundation framework # internally; macOS kills forked children that load ObjC before fork()). export OBJC_DISABLE_INITIALIZE_FORK_SAFETY=YES # Defaults MODE="fg" # fg|bg PLAYBOOK="infrastructure/playbooks/site.yml" VAULT_PASS_FILE="" if [[ -f "${ROOT_DIR}/.vault_pass" ]]; then VAULT_PASS_FILE="${ROOT_DIR}/.vault_pass" fi LIMIT="" # optional TAGS="" # optional EXTRA_ARGS=() # passthrough # Use local logs if PROLE_LOGS is not writable or looks like a remote path LOG_BASE="${ROOT_DIR}/.ansible/logs" if [[ -n "${PROLE_LOGS:-}" ]]; then # If it's a relative path, or it exists and is writable, or its parent is writable if [[ ! "${PROLE_LOGS}" =~ ^/ ]] || [[ -w "${PROLE_LOGS}" ]] || [[ -w "$(dirname "${PROLE_LOGS}" 2>/dev/null)" ]]; then LOG_BASE="${PROLE_LOGS}" fi fi LOG_BASE="${LOG_BASE%/}" LOG_DIR="${LOG_BASE}/ansible" SYSLOG_HOST="" # e.g. loghost.prole.org SYSLOG_PORT="514" SYSLOG_TAG="ansible" usage() { cat <] Options: -p, --playbook PATH Playbook path (default: ${PLAYBOOK}) -l, --limit HOSTS Limit hosts -t, --tags TAGS Tags -v, --vault-pass-file FILE Vault password file -m, --mode fg|bg Run in foreground or background (default: fg) --syslog-host HOST Send start/end markers via UDP syslog to HOST --syslog-port PORT Syslog UDP port (default: 514) --syslog-tag TAG Syslog tag (default: ansible) -h, --help Show help Examples: ./ansible.sh -l pi.prole.org -t iscsi -v .vault_pass ./ansible.sh -m bg -p infrastructure/playbooks/site.yml -v .vault_pass ./ansible.sh --syslog-host loghost.prole.org -m bg -v .vault_pass -- -vv EOF } send_syslog() { local msg="$1" if [[ -n "${SYSLOG_HOST}" ]]; then # -d = UDP, -n host, -P port logger -d -n "${SYSLOG_HOST}" -P "${SYSLOG_PORT}" -t "${SYSLOG_TAG}" -- "${msg}" || true fi } while [[ $# -gt 0 ]]; do case "$1" in -p|--playbook) PLAYBOOK="$2"; shift 2 ;; -l|--limit) LIMIT="$2"; shift 2 ;; -t|--tags) TAGS="$2"; shift 2 ;; -v|--vault-pass-file) VAULT_PASS_FILE="$2"; shift 2 ;; -m|--mode) MODE="$2"; shift 2 ;; --syslog-host) SYSLOG_HOST="$2"; shift 2 ;; --syslog-port) SYSLOG_PORT="$2"; shift 2 ;; --syslog-tag) SYSLOG_TAG="$2"; shift 2 ;; --) shift; EXTRA_ARGS+=("$@"); break ;; -h|--help) usage; exit 0 ;; *) EXTRA_ARGS+=("$1"); shift ;; esac done # Default to k3s hosts for the main site run (avoid touching non-k3s Linux/Pi hosts unless explicitly requested) if [[ -z "${LIMIT}" ]]; then if [[ "${PLAYBOOK}" =~ (^|/)infrastructure/playbooks/site\.yml$ ]]; then LIMIT="k3s_hosts" fi fi mkdir -p "${LOG_DIR}" ts="$(date +%Y%m%d-%H%M%S)" logfile="${LOG_DIR}/ansible-${ts}.log" cmd=(ansible-playbook "${PLAYBOOK}") [[ -n "${LIMIT}" ]] && cmd+=("--limit" "${LIMIT}") [[ -n "${TAGS}" ]] && cmd+=("--tags" "${TAGS}") [[ -n "${VAULT_PASS_FILE}" ]] && cmd+=("--vault-password-file" "${VAULT_PASS_FILE}") cmd+=("${EXTRA_ARGS[@]}") send_syslog "START playbook=${PLAYBOOK} limit=${LIMIT:-} tags=${TAGS:-} log=${logfile}" echo "ANSIBLE_CONFIG=${ANSIBLE_CONFIG}" echo "LOGFILE=${logfile}" echo "CMD: ${cmd[*]}" if [[ "${MODE}" == "fg" ]]; then # Stream to terminal and file color_env=() use_pty=false if [[ -z "${NO_COLOR:-}" ]] && [[ -z "${ANSIBLE_NOCOLOR:-}" ]]; then [[ -z "${ANSIBLE_FORCE_COLOR:-}" ]] && color_env+=("ANSIBLE_FORCE_COLOR=true") [[ -z "${PY_COLORS:-}" ]] && color_env+=("PY_COLORS=1") # Ensure a useful terminal type for ANSI colors when invoked from wrappers. if [[ -z "${TERM:-}" ]] || [[ "${TERM}" == "dumb" ]]; then color_env+=("TERM=xterm-256color") fi if command -v script >/dev/null 2>&1; then use_pty=true fi fi run_cmd=("${cmd[@]}") if [[ "${use_pty}" == "true" ]]; then # `tee` breaks TTY detection; wrap in a pseudo-tty so Ansible keeps colors. run_cmd=(script -qF /dev/null "${cmd[@]}") fi set +e env "${color_env[@]}" "${run_cmd[@]}" 2>&1 | tee "${logfile}" rc=${PIPESTATUS[0]} set -e else # Background: nohup to logfile nohup "${cmd[@]}" >"${logfile}" 2>&1 & rc=0 echo "Started in background (pid $!)" fi if [[ "${MODE}" == "fg" ]]; then if [[ $rc -eq 0 ]]; then send_syslog "END OK playbook=${PLAYBOOK} limit=${LIMIT:-} tags=${TAGS:-} log=${logfile}" else send_syslog "END FAIL rc=${rc} playbook=${PLAYBOOK} limit=${LIMIT:-} tags=${TAGS:-} log=${logfile}" fi exit $rc fi