111 lines
3.9 KiB
Markdown
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`.
|