Files
challenge_status_tracker/README.md
T
2026-10-20 17:11:31 +02:00

159 lines
6.0 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# ctf_status.py
Reads the `README.md` of every repo in the GitLab challenges group (REST API, no
clone) and shows which categories have challenges in which state, at which
difficulty — in the terminal and as a HedgeDoc note.
## Setup
```sh
cp ctf_status.env.example ctf_status.env && chmod 600 ctf_status.env
$EDITOR ctf_status.env # GitLab group URL + token, HedgeDoc note
```
## Use
```sh
./ctf_status.py # terminal summary
./ctf_status.py -o overview.md # + HedgeDoc markdown to a file
./ctf_status.py --stdout | wl-copy # straight to clipboard, paste into HedgeDoc
./ctf_status.py --update # overwrite the note in HEDGEDOC_NOTE
./ctf_status.py --update <note-url> # ... or an explicit one
```
The token is a fine-grained personal access token on the challenges group with
**Project: Read** (to list the repos) and **Repository: Read** (to download the
READMEs). A plain `.env` with `GL_PAT=` / `GITLAB_URL=` is read as well.
## Keeping a HedgeDoc note up to date
`--update` overwrites an **existing** note in place, so the URL you share stays the
same. Every run rewrites the whole body, starting with
```
---
tags: Logistics, h4tum CTF 2026
---
```
and a `**Last updated:** <date> <time> <tz>` line under the heading, so a stale note
is obvious at a glance. Tags come from `HEDGEDOC_TAGS` (or `--tags`).
The note's permission has to be **freely** (Menu → Permission), because the run is
anonymous. HedgeDoc 1.x has no REST API for editing a note, so the script talks the
editor's own engine.io/socket.io + OT protocol over long-polling — it reads the
current text, sends one "delete everything, insert this" operation, waits for the
server's `ack`, and reads the note back to confirm. Anyone with the note open sees
the change live. Still `requests`-only, no new dependencies.
`--hedgedoc <instance>` (POST a *new* note) is unrelated and only works where
anonymous note creation is enabled — on `hedgedoc.cit.tum.de` it is not.
## challs.txt for the deployment repo
`--challs` commits the clone URL of every challenge at **Testing Ready or beyond**
to `CHALLS_PATH` (default `2026/challs.txt`) in the deployment repo
`CHALLS_REPO_URL`, one per line, the same format as last year's:
```
git@gitlab.lrz.de:h4tum-ctf/h4tum-ctf-2026/challenges/baby_rsa.git
...
```
It only commits when the list actually changed, with a message naming what was
added and removed (`Update 2026/challs.txt from challenge status: +jwt -safe`).
A challenge that falls back below Testing Ready drops out again. Preview without
committing:
```sh
./ctf_status.py -q --challs-out -
```
The token additionally needs **Repository: Read** and **Repository: Write** on the
deployment project. If the branch is protected, the token's user must be allowed
to push to it. Without `CHALLS_BRANCH`, the default branch is looked up, which
also needs **Project: Read** there.
## Every 30 minutes, on a server
Copy the repo to the server, create `ctf_status.env` or `.env` there (both are gitignored, so they
do not travel with the code), then:
```sh
./install-timer.sh
```
It writes systemd **user** units into `~/.config/systemd/user/`, substituting
whatever directory the repo actually sits in, enables the timer, turns on lingering
so it keeps firing when you are not logged in, and does one run to prove it works.
Re-run it any time; it just overwrites the units.
```sh
systemctl --user list-timers ctf-status.timer # when it next fires
journalctl --user -u ctf-status.service -n 50 # what it did
systemctl --user start ctf-status.service # run it now
systemctl --user disable --now ctf-status.timer # stop it
```
The timer fires at `:00` and `:30` with up to a minute of jitter, and
`Persistent=true` catches up a run missed while the machine was down. Plain cron
works too, if you would rather:
```
*/30 * * * * /path/to/planner/ctf_status.py --update --challs --quiet
```
`--quiet` keeps a successful run silent, so you only hear about it when the note
changed or something broke. Overlapping runs are prevented by a lock file, and the
HedgeDoc write is retried (`--retries`, default 3) before the run gives up with a
one-line error and a non-zero exit.
## What the note shows
1. **✅ Ready for infra testing** — first thing in the note: every challenge at
Testing Ready, Tested or Ready, linked to its repo, so infra knows what it
can deploy and test against right now.
2. **Status by category** — how many challenges each category has per stage
(⚪ Planning → 🟠 PoC Draft → 🟡 Testing Ready → 🔵 Tested → 🟢 Ready).
3. **Category × difficulty** — one dot per challenge, coloured by its status, so
gaps and stragglers show up at a glance.
4. **Challenges** — one table per category, linked to the repo, with event,
author, tester and the repo's last activity.
5. **Needs attention** — see below.
## Reading the README
Only the template's header block is parsed:
```
### Challenge Name
- Category: crypto
- Difficulty: medium
- Event: TUM student CTF
- Author: ...
- Tester: ...
- Status: Testing Ready
```
People fill it in loosely, so values are normalised:
- **Status** is matched against the five stages ("Tested with …" → Tested,
"PoC" → PoC Draft). If it names several ("Planning to PoC Draft", a half-deleted
option list) the *least* advanced one counts and it is flagged as unclear.
- **Difficulty** ranges written as `medium-hard`, `medium/hard` or
`easy to medium` become their own column; otherwise the first level named wins.
- **Category**: the first one named counts ("rev, misc" → rev); the full value
is shown next to the challenge. Categories outside the usual list (e.g. `ai`)
get their own row.
## What it flags
- challenge at Testing Ready or beyond with no tester (`[Tester Name]`, `??`,
`None`, `not found yet` all count as none)
- status / difficulty / category missing or unclear
- no challenge name in the README
- repos whose README is missing or still the untouched template — these are left
out of the counts
Repos GitLab has scheduled for deletion, and archived repos, are skipped.