Files
challenge_status_tracker/README.md
T
2026-08-30 14:25:56 +02:00

111 lines
3.9 KiB
Markdown

# ctf_status.py
Reads the per-author challenge folders straight off the Nextcloud share (WebDAV,
no sync, no mount) and tells you who to scream at.
## Setup
```sh
cp ctf_status.env.example ctf_status.env && chmod 600 ctf_status.env
$EDITOR ctf_status.env # app password from Nextcloud → Settings → Security
```
## 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
```
## 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.
## Every 30 minutes, on a server
Copy the repo to the server, create `ctf_status.env` there (it is gitignored, so it
does 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 --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 it flags
| verdict | meaning |
| --- | --- |
| 🔴 SCREAM | nothing submitted, **or** they filled out `template.md` in place |
| 🟡 NAG | submitted, but placeholders / empty sections remain |
| 🟢 OK | every challenge fully filled out |
A `template.md` that is still pristine is ignored. One that has real content in it
is counted as a challenge *and* flagged — so nobody's work is lost, they just get
yelled at.
Also flagged per challenge: unnamed challenge, `[placeholder]` values left in any
of Category/Difficulty/Event/Author/Tester/Status, empty prose sections, and
category/difficulty values outside the allowed set.
## Layout it expects
```
<NC_ROOT>/
alice/
README.md # ignored
template.md # ignored while pristine
baby-rsa.md # ← a challenge, any filename
bob/
```
Any `.md` that isn't `README.md` is parsed as a challenge, so the file naming
convention doesn't have to be enforced by the script. If you want it enforced,
add the check in `Challenge.problems`.