Commit 9dd3f26ae5
9dd3f26ae58c3dd2da05cbe2ae8e9a6ce13e14c3
parent: d206b14ff8
Verified · cmc ci/build: success ci/lint: success ci/test: success
cmc <hello@cleberg.net> · 2026-09-11 14:38 UTC
Check listed instances daily
scripts/check-instances.py fetches /api/instance from every clearnet
instance in instances.json and exits 1 if any is down; a scheduled
gitbay job runs it each morning.
Closes #32
Layout: unified · split
.gitbay/ci.yml
+7
| @@ -17,3 +17,10 @@ jobs: |
| 17 | 17 | image: localhost/gitbay-ci:2 |
| 18 | 18 | steps: |
| 19 | 19 | - go run github.com/golangci/golangci-lint/v2/cmd/golangci-lint@v2.13.2 run ./... |
| 20 | # Daily check that every listed instance answers, so INSTANCES.md does not |
| 21 | # keep dead links. `build trigger krz/skunky-art instances` runs it now. |
| 22 | instances: |
| 23 | image: localhost/gitbay-ci:2 |
| 24 | schedule: "0 6 * * *" |
| 25 | steps: |
| 26 | - python3 scripts/check-instances.py |
README.org
+2 −1
| @@ -85,5 +85,6 @@ go run github.com/golangci/golangci-lint/v2/cmd/golangci-lint@v2.13.2 run ./... |
| 85 | 85 | ci runs both on every push. the roadmap is [[file:ROADMAP.md][ROADMAP.md]]. |
| 86 | 86 | |
| 87 | 87 | * instances |
| 88 | | add yours by merge request to [[file:instances.json][instances.json]] and [[file:INSTANCES.md][INSTANCES.md]] (or use |
| 88 | instances are checked daily by ci (~scripts/check-instances.py~); a dead one |
| 89 | turns the build red. add yours by merge request to [[file:instances.json][instances.json]] and [[file:INSTANCES.md][INSTANCES.md]] (or use |
| 89 | 90 | ~--add-instance~ to write both), or open an issue. |
scripts/check-instances.py
added
+55
| @@ -0,0 +1,55 @@ |
| 1 | #!/usr/bin/env python3 |
| 2 | """Check every clearnet instance in instances.json. |
| 3 | |
| 4 | Fetches /api/instance from each and prints one line per instance. Exits 1 if |
| 5 | any is down, so a scheduled CI job turns red and INSTANCES.md does not keep |
| 6 | listing dead links. Tor, I2P and Yggdrasil addresses are skipped: the runner |
| 7 | cannot reach them. |
| 8 | """ |
| 9 | |
| 10 | import json |
| 11 | import sys |
| 12 | import urllib.error |
| 13 | import urllib.request |
| 14 | |
| 15 | TIMEOUT = 15 |
| 16 | |
| 17 | |
| 18 | def check(url): |
| 19 | req = urllib.request.Request( |
| 20 | url.rstrip("/") + "/api/instance", |
| 21 | headers={"User-Agent": "skunkyart-instance-check"}, |
| 22 | ) |
| 23 | try: |
| 24 | with urllib.request.urlopen(req, timeout=TIMEOUT) as resp: |
| 25 | info = json.load(resp) |
| 26 | except (urllib.error.URLError, json.JSONDecodeError, TimeoutError) as err: |
| 27 | return None, str(err) |
| 28 | return info.get("version", "?"), None |
| 29 | |
| 30 | |
| 31 | def main(): |
| 32 | with open("instances.json", encoding="utf-8") as f: |
| 33 | instances = json.load(f)["instances"] |
| 34 | |
| 35 | failed = 0 |
| 36 | for inst in instances: |
| 37 | url = inst.get("urls", {}).get("clearnet") |
| 38 | if not url: |
| 39 | print(f"{inst['title']}: no clearnet url, skipped") |
| 40 | continue |
| 41 | version, err = check(url) |
| 42 | if err: |
| 43 | failed += 1 |
| 44 | print(f"{inst['title']}: DOWN {url} ({err})") |
| 45 | else: |
| 46 | print(f"{inst['title']}: ok {url} v{version}") |
| 47 | |
| 48 | if failed: |
| 49 | print(f"{failed} instance(s) down") |
| 50 | return 1 |
| 51 | return 0 |
| 52 | |
| 53 | |
| 54 | if __name__ == "__main__": |
| 55 | sys.exit(main()) |