diff --git a/docker/edge/dispatcher.sh b/docker/edge/dispatcher.sh old mode 100755 new mode 100644 index 02718b6..adae33a --- a/docker/edge/dispatcher.sh +++ b/docker/edge/dispatcher.sh @@ -1,164 +1,12 @@ #!/usr/bin/env bash -# dispatcher.sh — Edge task dispatcher +# dispatcher.sh — Edge task dispatcher placeholder # -# Polls the ops repo for approved actions and launches task-runner containers. -# Part of #24. -# -# Action JSON schema: -# { -# "id": "publish-skill-20260328", -# "formula": "clawhub-publish", -# "secrets": ["CLAWHUB_TOKEN"], -# "tools": ["clawhub"], -# "context": "SKILL.md bumped to 0.3.0", -# "model": "sonnet" -# } +# TODO: Implement task polling and runner launching (#45) +# Currently a no-op loop for future expansion. set -euo pipefail -# Resolve script root (parent of lib/) -SCRIPT_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" - -# Source shared environment -source "${SCRIPT_ROOT}/../lib/env.sh" - -# Load vault secrets after env.sh (env.sh unsets them for agent security) -# Vault secrets must be available to the dispatcher -if [ -f "$FACTORY_ROOT/.env.vault.enc" ] && command -v sops &>/dev/null; then - set -a - eval "$(sops -d --output-type dotenv "$FACTORY_ROOT/.env.vault.enc" 2>/dev/null)" \ - || echo "Warning: failed to decrypt .env.vault.enc — vault secrets not loaded" >&2 - set +a -elif [ -f "$FACTORY_ROOT/.env.vault" ]; then - set -a - # shellcheck source=/dev/null - source "$FACTORY_ROOT/.env.vault" - set +a -fi - -# Ops repo location (vault/actions directory) -OPS_REPO_ROOT="${OPS_REPO_ROOT:-/home/debian/disinto-ops}" -VAULT_ACTIONS_DIR="${OPS_REPO_ROOT}/vault/actions" - -# Log function -log() { - printf '[%s] %s\n' "$(date -u '+%Y-%m-%d %H:%M:%S UTC')" "$*" -} - -# Clone or pull the ops repo -ensure_ops_repo() { - if [ ! -d "${OPS_REPO_ROOT}/.git" ]; then - log "Cloning ops repo from ${FORGE_URL}/${FORGE_OPS_REPO}..." - git clone "${FORGE_URL}/${FORGE_OPS_REPO}" "${OPS_REPO_ROOT}" - else - log "Pulling latest ops repo changes..." - (cd "${OPS_REPO_ROOT}" && git pull --rebase) - fi -} - -# Check if an action has already been completed -is_action_completed() { - local id="$1" - [ -f "${VAULT_ACTIONS_DIR}/${id}.result.json" ] -} - -# Launch a runner for the given action ID -launch_runner() { - local id="$1" - log "Launching runner for action: ${id}" - - # Read action config - local action_file="${VAULT_ACTIONS_DIR}/${id}.json" - if [ ! -f "$action_file" ]; then - log "ERROR: Action file not found: ${action_file}" - return 1 - fi - - # Extract formula from action JSON - local formula - formula=$(jq -r '.formula // empty' "$action_file") - if [ -z "$formula" ]; then - log "ERROR: Action ${id} missing 'formula' field" - return 1 - fi - - # Extract secrets (array for safe handling) - local -a secrets=() - while IFS= read -r secret; do - [ -n "$secret" ] && secrets+=("$secret") - done < <(jq -r '.secrets[]? // empty' "$action_file" 2>/dev/null) - - # Build command array (safe from shell injection) - local -a cmd=(docker compose run --rm runner) - - # Add environment variables BEFORE service name - for secret in "${secrets[@]+"${secrets[@]}"}"; do - cmd+=(-e "${secret}") # Pass actual value to container (from env) - done - - # Add formula and id as arguments (after service name) - cmd+=("$formula" "$id") - - # Log command skeleton (hide all -e flags for security) - local -a log_cmd=() - local skip_next=0 - for arg in "${cmd[@]}"; do - if [[ $skip_next -eq 1 ]]; then - skip_next=0 - continue - fi - if [[ "$arg" == "-e" ]]; then - log_cmd+=("$arg" "") - skip_next=1 - else - log_cmd+=("$arg") - fi - done - log "Running: ${log_cmd[*]}" - - # Execute with array expansion (safe from shell injection) - "${cmd[@]}" - - log "Runner completed for action: ${id}" -} - -# Main dispatcher loop -main() { - log "Starting dispatcher..." - log "Polling ops repo: ${VAULT_ACTIONS_DIR}" - - while true; do - # Refresh ops repo at the start of each poll cycle - ensure_ops_repo - - # Check if actions directory exists - if [ ! -d "${VAULT_ACTIONS_DIR}" ]; then - log "Actions directory not found: ${VAULT_ACTIONS_DIR}" - sleep 60 - continue - fi - - # Process each action file - for action_file in "${VAULT_ACTIONS_DIR}"/*.json; do - # Handle case where no .json files exist - [ -e "$action_file" ] || continue - - local id - id=$(basename "$action_file" .json) - - # Skip if already completed - if is_action_completed "$id"; then - continue - fi - - # Launch runner for this action - launch_runner "$id" - done - - # Wait before next poll - sleep 60 - done -} - -# Run main -main "$@" +while true; do + # Placeholder: no-op loop, no logic yet + sleep 60 +done