#!/usr/bin/env bash # br-deploy - BlackRoad OS Deployment Workflow Engine # Custom deployment workflows for fleet-wide or per-node deployments # Usage: br-deploy [args] set -euo pipefail source "$HOME/.blackroad/config/nodes.sh" 2>/dev/null || true WORKFLOWS_DIR="$HOME/.blackroad/workflows" DEPLOY_LOG="$HOME/.blackroad/logs/deploy.log" mkdir -p "$WORKFLOWS_DIR" "$(dirname "$DEPLOY_LOG")" usage() { cat < [node] Run a deployment workflow br-deploy list List available workflows br-deploy create Create a new workflow br-deploy status Show last deployment status br-deploy rollback Rollback last deployment ${AMBER}WORKFLOWS:${RESET} Workflows are shell scripts in ~/.blackroad/workflows/ They receive NODE, IP, USER as environment variables. Use hooks: pre-deploy, deploy, post-deploy, verify ${GREEN}EXAMPLES:${RESET} br-deploy run update-ollama cecilia br-deploy run sync-configs br-deploy create my-workflow EOF } log_deploy() { echo "[$(date '+%Y-%m-%d %H:%M:%S')] $*" | tee -a "$DEPLOY_LOG" } cmd_list() { printf '%b%-24s %s%b\n' "$BLUE" "WORKFLOW" "DESCRIPTION" "$RESET" printf '%-24s %s\n' "────────" "───────────" for f in "$WORKFLOWS_DIR/"*.sh; do [[ -f "$f" ]] || continue name=$(basename "$f" .sh) desc=$(head -5 "$f" | grep -m1 '^#[^!]' | sed 's/^# *//') printf '%-24s %s\n' "$name" "${desc:-—}" done } cmd_create() { local name="$1" local wf="$WORKFLOWS_DIR/${name}.sh" if [[ -f "$wf" ]]; then echo "Workflow '$name' already exists." return 1 fi cat > "$wf" <<'WORKFLOW' #!/bin/bash # Custom deployment workflow # Receives: $NODE (name), $IP (address), $USER (ssh user), $SSH_TARGET (user@ip) set -euo pipefail pre_deploy() { echo "Pre-deploy checks on $NODE ($IP)..." # Add pre-flight checks here # Return non-zero to abort deployment } deploy() { echo "Deploying to $NODE ($IP)..." # Add deployment steps here # Example: # scp -q ./my-app "$SSH_TARGET:/opt/blackroad/bin/" # ssh "$SSH_TARGET" "sudo systemctl restart my-app" } post_deploy() { echo "Post-deploy verification on $NODE..." # Add verification steps here } verify() { echo "Verifying deployment on $NODE..." # Return non-zero if verification fails # Example: # ssh "$SSH_TARGET" "curl -sf http://localhost:8080/health" || return 1 } # Workflow execution pre_deploy && deploy && post_deploy && verify WORKFLOW chmod +x "$wf" printf '%bCreated workflow: %s%b\n' "$GREEN" "$wf" "$RESET" } cmd_run() { local workflow_name="$1" local target_node="${2:-}" local wf="$WORKFLOWS_DIR/${workflow_name}.sh" if [[ ! -f "$wf" ]]; then printf '%bWorkflow %s not found%b\n' "$RED" "$workflow_name" "$RESET" return 1 fi local targets=() if [[ -n "$target_node" ]]; then targets=("$target_node") else targets=("${PI_NODES[@]}") fi log_deploy "=== DEPLOY START: $workflow_name ===" local success=0 fail=0 for node in "${targets[@]}"; do local ip="${NODE_IP[$node]:-}" local user="${NODE_USER[$node]:-}" if [[ -z "$ip" ]]; then log_deploy "SKIP $node: unknown node" continue fi printf '\n%b>>> Deploying %s to %s (%s)%b\n' "$AMBER" "$workflow_name" "$node" "$ip" "$RESET" if ! br_ping "$node" 2>/dev/null; then log_deploy "SKIP $node: offline" printf '%b %s offline — skipping%b\n' "$RED" "$node" "$RESET" ((fail++)) || true continue fi # Export node context for the workflow export NODE="$node" export IP="$ip" export USER="$user" export SSH_TARGET="${user}@${ip}" if bash "$wf" 2>&1 | tee -a "$DEPLOY_LOG"; then log_deploy "OK $node" printf '%b %s: deployed%b\n' "$GREEN" "$node" "$RESET" ((success++)) || true else log_deploy "FAIL $node" printf '%b %s: FAILED%b\n' "$RED" "$node" "$RESET" ((fail++)) || true fi done log_deploy "=== DEPLOY END: $workflow_name (ok=$success fail=$fail) ===" printf '\n%bDeployment complete: %d success, %d failed%b\n' "$PINK" "$success" "$fail" "$RESET" } cmd_status() { if [[ ! -f "$DEPLOY_LOG" ]]; then echo "No deployments yet." return fi printf '%bRecent deployments:%b\n' "$BLUE" "$RESET" tail -20 "$DEPLOY_LOG" } # Main case "${1:-}" in run) cmd_run "${2:?workflow name required}" "${3:-}" ;; list) cmd_list ;; create) cmd_create "${2:?workflow name required}" ;; status) cmd_status ;; -h|--help|help|"") usage ;; *) echo "Unknown: $1"; usage; exit 1 ;; esac