-
Notifications
You must be signed in to change notification settings - Fork 118
167 lines (147 loc) · 6.73 KB
/
Copy pathmainnet-monitor.yml
File metadata and controls
167 lines (147 loc) · 6.73 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
name: Mainnet Health Monitor
on:
schedule:
- cron: "30 */6 * * *"
workflow_dispatch:
inputs:
contracts:
description: 'Optional: JSON array of {id, name, category, expected_admin} to monitor. Overrides LIVE_MAINNET.md / .deployment-manifest.json.'
required: false
default: ""
permissions:
contents: write
issues: write
env:
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true
jobs:
monitor:
name: Check deployed mainnet contracts
runs-on: ubuntu-latest
env:
XDG_CONFIG_HOME: /tmp/stellar-xdg
MAINNET_MONITOR_CONTRACTS: ${{ github.event.inputs.contracts || '' }}
steps:
- name: Checkout repository
uses: actions/checkout@v6
with:
fetch-depth: 0
- name: Install Stellar CLI
uses: stellar/stellar-cli@v23.0.1
- name: Register mainnet network + monitor identity
run: |
stellar network add mainnet \
--rpc-url https://mainnet.sorobanrpc.com \
--network-passphrase "Public Global Stellar Network ; September 2015"
stellar keys generate mainnet-monitor --network mainnet --overwrite
stellar keys public-key mainnet-monitor
- name: Run contract health checks
id: health
run: node .github/scripts/mainnet-monitor.js
- name: Open tracking issues for failures
if: steps.health.outputs.healthy != 'true'
uses: actions/github-script@v8
with:
script: |
const fs = require("node:fs");
const failures = JSON.parse(fs.readFileSync("monitor/failures.json", "utf8"));
for (const failure of failures) {
const query = `repo:${context.repo.owner}/${context.repo.repo} is:issue is:open label:mainnet label:bug "${failure.id}"`;
const existing = await github.rest.search.issuesAndPullRequests({ q: query, per_page: 1 });
const title = `Mainnet health check failed: ${failure.name}`;
const body = [
"The scheduled mainnet health monitor could not verify a deployed contract.",
"",
`- Contract: ${failure.name}`,
`- Address: \`${failure.id}\``,
`- Health: ${failure.health}`,
`- Paused: ${failure.paused}`,
`- Checked at: ${failure.checkedAt}`,
`- Workflow run: ${context.serverUrl}/${context.repo.owner}/${context.repo.repo}/actions/runs/${context.runId}`,
"",
"```text",
failure.output || "no diagnostics captured",
"```",
"",
failure.paused
? "## Triage\nA circuit breaker (`is_paused`) reported paused. Follow ROLLBACK_PROCEDURE.md immediately and confirm no guarded execution path is active."
: "",
].join("\n");
if (existing.data.items.length === 0) {
await github.rest.issues.create({
owner: context.repo.owner,
repo: context.repo.repo,
title,
body,
labels: ["mainnet", "bug"],
});
} else {
await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: existing.data.items[0].number,
body,
});
}
}
- name: Send alert notification (health failure)
if: steps.health.outputs.healthy != 'true'
continue-on-error: true
env:
ALERT_WEBHOOK_URLS: ${{ secrets.ALERT_WEBHOOK_URLS }}
ALERT_WEBHOOK_SECRET: ${{ secrets.ALERT_WEBHOOK_SECRET }}
run: |
node .github/scripts/alert.js \
--event "mainnet.health.failed" \
--title "Mainnet health check failed" \
--message "One or more deployed mainnet contracts failed their scheduled health check. Tracking issues have been opened." \
--severity high \
--field "failed=${{ steps.health.outputs.failures }}/${{ steps.health.outputs.total }}" \
--field "degraded=${{ steps.health.outputs.degraded }}" \
--field "paused=${{ steps.health.outputs.paused }}" \
--field "trigger=mainnet-monitor" \
--run-url "${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}"
- name: Send alert notification (circuit breaker paused)
if: steps.health.outputs.paused == 'true'
continue-on-error: true
env:
ALERT_WEBHOOK_URLS: ${{ secrets.ALERT_WEBHOOK_URLS }}
ALERT_WEBHOOK_SECRET: ${{ secrets.ALERT_WEBHOOK_SECRET }}
run: |
node .github/scripts/alert.js \
--event "mainnet.breaker.paused" \
--title "Mainnet circuit breaker PAUSED" \
--message "At least one monitored mainnet contract reports is_paused=true (circuit breaker tripped). Treat as an incident — follow ROLLBACK_PROCEDURE.md and do NOT resume traffic before the pause is resolved." \
--severity critical \
--field "paused=${{ steps.health.outputs.paused }}" \
--field "trigger=mainnet-monitor" \
--run-url "${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}"
- name: Publish status badge JSON
if: always() && steps.health.outputs.total != ''
run: |
set -euo pipefail
git config user.name "github-actions[bot]"
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
worktree="$(mktemp -d)"
if git ls-remote --exit-code --heads origin monitor >/dev/null 2>&1; then
git fetch origin monitor
git worktree add "$worktree" origin/monitor
else
git worktree add --detach "$worktree"
git -C "$worktree" checkout --orphan monitor
git -C "$worktree" rm -rf . >/dev/null 2>&1 || true
fi
mkdir -p "$worktree/badges"
cp monitor/status.json "$worktree/badges/mainnet-monitor.json"
git -C "$worktree" add badges/mainnet-monitor.json
if git -C "$worktree" diff --cached --quiet; then
echo "Badge JSON already up to date."
exit 0
fi
git -C "$worktree" commit -m "chore: update mainnet monitor badge"
git -C "$worktree" push origin HEAD:monitor
- name: Report unhealthy contracts
if: steps.health.outputs.healthy != 'true'
continue-on-error: true
run: |
echo "::warning::One or more mainnet contracts failed their health check. See the opened GitHub issues and alerts for details."
exit 1