#!/usr/bin/env bash set -euo pipefail TOKEN="" REPO_FULL="" WORKFLOW_FILE="" TARGET_REF="" HEAD_SHA="" SOURCE_WORKFLOW="" BASE_NEEDED="" BASE_HASH="" TRACE_ID="" MAX_ROUNDS=3 declare -a API_BASES=() declare -a EXTRA_INPUTS=() usage() { cat <<'EOF' Usage: dispatch-workflow.sh \ --token \ --repo \ --workflow \ --ref \ --head-sha \ --source-workflow \ [--api-base ]... \ [--base-needed ] \ [--base-hash ] \ [--trace-id ] \ [--input ]... \ [--max-rounds ] EOF } ensure_curl() { if command -v curl >/dev/null 2>&1; then return 0 fi if command -v apt-get >/dev/null 2>&1; then export DEBIAN_FRONTEND=noninteractive apt-get update -qq apt-get install -y -qq curl ca-certificates fi if command -v curl >/dev/null 2>&1; then return 0 fi echo "curl is required for dispatch and could not be installed" >&2 return 1 } build_default_api_bases() { if [[ -n "${GITHUB_SERVER_URL:-}" ]]; then API_BASES+=("${GITHUB_SERVER_URL%/}/api/v1") fi if [[ -n "${GITEA_SERVER_URL:-}" ]]; then API_BASES+=("${GITEA_SERVER_URL%/}/api/v1") fi if [[ -n "${GITEA_SSH_HOST:-}" ]]; then API_BASES+=("http://${GITEA_SSH_HOST}:3001/api/v1") fi if [[ -n "${GITEA_REGISTRY_HOST:-}" ]]; then API_BASES+=("http://${GITEA_REGISTRY_HOST}:3001/api/v1") fi if [[ -n "${GITEA_REGISTRY_IP:-}" ]]; then API_BASES+=("http://${GITEA_REGISTRY_IP}:3001/api/v1") fi if [[ ${#API_BASES[@]} -eq 0 ]]; then API_BASES+=("http://kankali.darkhelm.lan:3001/api/v1") fi } append_input_field() { local key="$1" local value="$2" [[ -z "$value" ]] && return 0 PAYLOAD_INPUTS+=$',\n' PAYLOAD_INPUTS+=" \"${key}\": \"${value}\"" } while [[ $# -gt 0 ]]; do case "$1" in --api-base) API_BASES+=("$2") shift 2 ;; --token) TOKEN="$2" shift 2 ;; --repo) REPO_FULL="$2" shift 2 ;; --workflow) WORKFLOW_FILE="$2" shift 2 ;; --ref) TARGET_REF="$2" shift 2 ;; --head-sha) HEAD_SHA="$2" shift 2 ;; --source-workflow) SOURCE_WORKFLOW="$2" shift 2 ;; --base-needed) BASE_NEEDED="$2" shift 2 ;; --base-hash) BASE_HASH="$2" shift 2 ;; --trace-id) TRACE_ID="$2" shift 2 ;; --input) EXTRA_INPUTS+=("$2") shift 2 ;; --max-rounds) MAX_ROUNDS="$2" shift 2 ;; --help|-h) usage exit 0 ;; *) echo "Unknown argument: $1" >&2 usage exit 2 ;; esac done if [[ -z "$TOKEN" || -z "$REPO_FULL" || -z "$WORKFLOW_FILE" || -z "$TARGET_REF" || -z "$HEAD_SHA" || -z "$SOURCE_WORKFLOW" ]]; then echo "Missing required arguments" >&2 usage exit 2 fi if ! [[ "$MAX_ROUNDS" =~ ^[0-9]+$ ]] || [[ "$MAX_ROUNDS" -lt 1 ]]; then echo "--max-rounds must be a positive integer" >&2 exit 2 fi REPO_OWNER="${REPO_FULL%/*}" REPO_NAME="${REPO_FULL#*/}" if [[ ${#API_BASES[@]} -eq 0 ]]; then build_default_api_bases fi if [[ -z "$TRACE_ID" ]]; then TRACE_ID="dispatch-${GITHUB_RUN_ID:-local}-${GITHUB_RUN_ATTEMPT:-1}-${HEAD_SHA:0:8}" fi PAYLOAD_INPUTS=$' "head_sha": "'"${HEAD_SHA}"$'",\n "source_workflow": "'"${SOURCE_WORKFLOW}"$'"' append_input_field "trace_id" "$TRACE_ID" append_input_field "base_needed" "$BASE_NEEDED" append_input_field "base_hash" "$BASE_HASH" for kv in "${EXTRA_INPUTS[@]}"; do if [[ "$kv" != *=* ]]; then echo "Invalid --input value: ${kv} (expected key=value)" >&2 exit 2 fi key="${kv%%=*}" value="${kv#*=}" append_input_field "$key" "$value" done PAYLOAD=$(cat < "${response_file}" echo "dispatch_attempt=${attempt}/${total_attempts}" echo "dispatch_round=${round}/${MAX_ROUNDS}" echo "dispatch_api_base=${api_base}" dispatch_start_ms=$(date +%s%3N) http_code=$(curl -sS --connect-timeout 5 --max-time 20 -o "${response_file}" -w "%{http_code}" -X POST \ -H "Authorization: token ${TOKEN}" \ -H "Content-Type: application/json" \ "${dispatch_url}" \ -d "${PAYLOAD}" || true) dispatch_end_ms=$(date +%s%3N) dispatch_elapsed_ms=$((dispatch_end_ms - dispatch_start_ms)) echo "dispatch_elapsed_ms=${dispatch_elapsed_ms}" echo "dispatch_http_code=${http_code}" if [[ "${http_code}" =~ ^[0-9]+$ ]] && [[ "${http_code}" -ge 200 ]] && [[ "${http_code}" -lt 300 ]]; then echo "Dispatch succeeded" dispatched=true break 2 fi echo "Dispatch attempt failed via ${api_base} (HTTP ${http_code})" if [[ -s "${response_file}" ]]; then echo "Response body:" cat "${response_file}" || true fi done sleep_seconds=$((2 ** round)) echo "Dispatch round ${round} failed; backing off for ${sleep_seconds}s" sleep "${sleep_seconds}" done if [[ "${dispatched}" != "true" ]]; then echo "Dispatch failed on all endpoints after ${MAX_ROUNDS} rounds" >&2 exit 1 fi