Static status page for Reactome production services, published at https://status.reactome.org. Each production host runs a small collector every 5 minutes that uploads a JSON snapshot to S3; CloudFront serves the bucket. Because the page is static and the data lives in S3, it stays up when a production server does not, and a missing report is the outage signal.
Alpha. Not yet an official Reactome service; numbers may be incomplete or wrong.
| Path | What |
|---|---|
collector/collector.py |
Stdlib-only Python 3 collector. systemd units, HTTP/TCP health checks, Apache mod_status, access-log aggregates, host load/memory/disk, restart detection. Uploads with the AWS CLI. Containers are checked over HTTP/TCP; the collector deliberately has no Docker socket access. |
collector/config/<host>.json |
Per-host configuration (what to check, log path, S3 prefix). |
collector/*.service, *.timer, install.sh |
systemd units and the sudo install script. |
site/ |
The static page: index.html, app.js, style.css, vendored uPlot, hosts.json (list of hosts shown). |
infra/status-site.yaml |
CloudFormation: private S3 bucket, CloudFront (OAC, HTTPS), lifecycle rule, IAM upload policy. |
infra/deploy.sh |
Certificate request, stack deploy, site upload. |
docs/PLAN.md |
Original design notes. |
docs/BACKLOG.md |
Pointer to the backlog, which lives in GitHub issues. |
data/<host>/latest.json current snapshot (Cache-Control 60 s)
data/<host>/series/24h.json 5-min points, last 24 h
data/<host>/series/7d.json 30-min points, last 7 d
data/<host>/series/90d.json 6-h points, last 90 d
data/<host>/events.json the 500 most recent restart / outage / recovery events
raw/<host>/YYYY/MM/DD/HHMM.json every snapshot, expired after 90 d by S3 lifecycle
site/ the page itself (served via a CloudFront origin path; hosts cannot write here)
Only aggregates leave a host: request counts, status-class counts and latency percentiles per URL group. No client IPs, user agents or full URLs are uploaded. The page is public, so nothing in a snapshot should be sensitive.
- The 24 h / 7 d / 90 d buttons set the range for the whole page; the choice is remembered per browser.
- Click the ⤢ icon on any chart (or double-click the plot) to enlarge it. The enlarged view has its own range control plus a custom From/To window; it uses the finest history that covers the window and says which resolution it is showing. "Download CSV" exports the shown window; "Copy link" copies a URL that reopens exactly that chart and window (the address bar always reflects the current view).
- The ◐ button cycles the colour theme: follow the system, light, dark.
infra/deploy.sh cert # request ACM cert; add the printed CNAME in Cloudflare (DNS only)
infra/deploy.sh stack # once the cert is ISSUED: bucket + CloudFront + IAM policy
infra/deploy.sh outputs # DistributionDomainName -> Cloudflare CNAME for status.reactome.org (DNS only)
infra/deploy.sh site # upload site/ (re-run after any page change)The stack attaches an upload policy to the production hosts' existing instance role
(EC2CloudwatchAgentRole), so the collector needs no access keys.
git clone https://github.com/reactome/reactome-status.git
sudo reactome-status/collector/install.sh reactome-status/collector/config/reactome.org.jsonThis creates a reactome-status system user with its own group, grants it read access to the
Apache log directory with a narrow ACL (not group membership, which would also expose other
group-readable files such as CMS credentials; deliberately not docker either, which would be
root-equivalent), installs the script under /opt/reactome-status, the config under
/etc/reactome-status, state under /var/lib/reactome-status, and enables a timer that runs at
minutes 1, 6, 11, … past the hour (deliberately off the :00/:30 marks that cron jobs use). Check with:
systemctl list-timers reactome-status-collector.timer
journalctl -u reactome-status-collector.service -n 20Stop it with sudo systemctl disable --now reactome-status-collector.timer, or remove it
entirely with sudo collector/uninstall.sh. The unit is capped at half a CPU and 512 MB
and runs with a read-only view of the filesystem.
To try it without uploading (any user in the reactome group), point it at a private state
directory so it does not touch the installed collector's history:
python3 collector/collector.py -c collector/config/reactome.org.json --state-dir /tmp/status-test --no-upload --print- Copy
collector/config/reactome.org.jsontocollector/config/curator.reactome.org.json, sethost,s3_prefix(data/curator.reactome.org), and adjust units, probes and log path. - Add the host to
site/hosts.jsonand runinfra/deploy.sh site. - Make sure the host's instance role has the upload policy (stack output
CollectorUploadPolicyArn). - Run
install.shon the host.
The repo uses Spec Kit. .specify/memory/constitution.md
records the project's principles (independence from what it monitors, missing data is downtime,
only aggregates leave a host, least privilege, inert uploads, …); every non-trivial change is
checked against it. Features live under specs/:
specs/000-status-page-baseline/describes the system as delivered, for/speckit-converge.specs/001-multi-host-monitoring/is the next feature (curator, Plant Reactome, CPWS): spec, plan, research, data model, operator quickstart and task list.
Workflow in Claude Code: /speckit-specify (new feature) → /speckit-clarify (optional) →
/speckit-plan → /speckit-tasks → /speckit-implement; /speckit-analyze checks the
artifacts against each other and the constitution; /speckit-converge compares the codebase
with a spec and appends remaining work as tasks. The commands act on the feature named in the
machine-local .specify/feature.json (not committed); set SPECIFY_FEATURE_DIRECTORY or edit
that file to point at another feature, e.g. specs/000-status-page-baseline. The constitution
says which changes need a spec; small fixes skip the ceremony.
collector/selftest.sh runs the collector against a fake access log in a scratch directory and
checks first-run behaviour, grouping, rotation carry-over, the unreadable-log path and the
root/unsafe-run refusals. Run it before committing collector changes; the constitution's
"verified before deployed" principle relies on it together with a --state-dir dry run on a host.
# pull real snapshots from a host into site/data/ (git-ignored) then serve
cd site && python3 -m http.server 8765A restart is detected when a service's start time (systemd ExecMainStartTimestamp
or Docker StartedAt) changes between runs. The collector then records when the
service's health check first passes again, giving a time-to-healthy with 5-minute
resolution. Events appear in events.json and in the "Recent service events" table.