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:
1717 image: localhost/gitbay-ci:2
1818 steps:
1919 - 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 ./...
8585ci runs both on every push. the roadmap is [[file:ROADMAP.md][ROADMAP.md]].
8686
8787* instances
88add yours by merge request to [[file:instances.json][instances.json]] and [[file:INSTANCES.md][INSTANCES.md]] (or use
88instances are checked daily by ci (~scripts/check-instances.py~); a dead one
89turns the build red. add yours by merge request to [[file:instances.json][instances.json]] and [[file:INSTANCES.md][INSTANCES.md]] (or use
8990~--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
4Fetches /api/instance from each and prints one line per instance. Exits 1 if
5any is down, so a scheduled CI job turns red and INSTANCES.md does not keep
6listing dead links. Tor, I2P and Yggdrasil addresses are skipped: the runner
7cannot reach them.
8"""
9
10import json
11import sys
12import urllib.error
13import urllib.request
14
15TIMEOUT = 15
16
17
18def 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
31def 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
54if __name__ == "__main__":
55 sys.exit(main())