diff --git a/action.yml b/action.yml index d5a5793..5f18615 100644 --- a/action.yml +++ b/action.yml @@ -111,6 +111,45 @@ runs: echo "Testbox initialized: testbox_id=${TESTBOX_ID}" + - name: Start heartbeat + if: steps.metadata.outputs.available == 'true' && inputs.testbox_id + shell: bash + run: | + STATE=/tmp/.testbox + if [ ! -f "$STATE/testbox_id" ]; then + echo "Warning: state directory not initialized, skipping heartbeat" + exit 0 + fi + + # Launch a background heartbeat loop that POSTs to the backend + # every 30 seconds. The backend uses this to detect dead VMs + # whose phone-home (completed) never arrived. + # + # nohup + fd redirects + disown fully detaches the process + # from this step's shell. Without this, GitHub Actions may + # wait on or SIGTERM child processes between composite action + # steps, killing the heartbeat before run-testbox starts. + # + # The state files are read inside the loop so the script is + # self-contained and does not depend on shell variable scope. + nohup bash -c 'STATE=/tmp/.testbox + API_URL=$(cat "$STATE/api_url") + AUTH_TOKEN=$(cat "$STATE/auth_token") + TESTBOX_ID=$(cat "$STATE/testbox_id") + INSTALLATION_MODEL_ID=$(cat "$STATE/installation_model_id") + while true; do + sleep 30 + curl -s -f --max-time 10 -X POST "${API_URL}/api/testbox/heartbeat" \ + -H "Content-Type: application/json" \ + -H "Authorization: Bearer ${AUTH_TOKEN}" \ + -d "{\"testbox_id\":\"${TESTBOX_ID}\",\"installation_model_id\":${INSTALLATION_MODEL_ID}}" \ + >/dev/null 2>&1 || true + done' /dev/null 2>&1 & + HEARTBEAT_PID=$! + echo "$HEARTBEAT_PID" > "$STATE/heartbeat_pid" + disown "$HEARTBEAT_PID" + echo "Heartbeat started (PID $HEARTBEAT_PID, every 30s, disowned)" + - name: Install SSH public key if: steps.metadata.outputs.available == 'true' && inputs.testbox_id shell: bash