10 Commits
Author SHA1 Message Date
Esa Kataja 2974ffc621 feat!: drop the built-in auth in favour of Authelia
check / check (push) Successful in 2m14s
BREAKING CHANGE: PASSWORD is gone and AUTH and CERTRESOLVER are required.
The server's compose.yaml and .env must be updated in the same deploy — the
new image ignores PASSWORD, and the old one refuses to start without it.

Authelia now sits in front of Traefik, so the app was asking for a second
password at the same door. Two prompts, and the weaker of the two was the one
holding a single shared secret with no sessions, no MFA and no revocation.
Deleting it is the whole change: Authelia already does this properly, once,
for every service on the host.

Gone: auth(), challenge(), the whole of throttle.go and its tests, and
golang.org/x/time with them. routes() returns the bare mux, /healthz is an
ordinary route on it, and the smoke script drops sixty -u flags. Roughly 230
lines removed and nothing written to replace them.

What holds the app up now, both asserted in compose.yaml:

- The router names the Authelia middleware through AUTH. Traefik takes a
  router out of service when its middleware does not resolve, so a typo or an
  unset variable fails shut rather than serving the app open.
- The container still publishes no ports, so the proxy is the only thing that
  can reach it. Publishing 8080 would now bypass authentication outright, not
  merely TLS — the comment there says so.

certresolver replaces the bare tls=true, parameterised as CERTRESOLVER: the
server had been carrying that label by hand since the first deploy. Naming a
resolver implies tls=true, so it stays one label.

TestAuth and TestHealthzSkipsAuth are replaced by one test asserting every
route answers without credentials — a 401 from here would now mean auth had
crept back in.
2026-09-06 13:31:17 +03:00
Esa Kataja 6d5f3e3de3 ci: retrigger after runner cache config
check / check (push) Successful in 2m13s
2026-09-06 11:16:02 +03:00
Esa Kataja 6590c18412 build: move releases to CI and trim the Makefile
check / check (push) Successful in 6m23s
Merging a pull request into main is now the whole release. A Gitea
Actions workflow derives the CalVer tag, builds the image and pushes
it with :latest, so nothing is built locally any more.

That made image/push/release redundant, and with them the .release-tag
state file and the main-branch guard — the workflow only runs on main,
which is protected, so the guard had nothing left to catch. The digest
verification went too: it guarded a `make -j` race between image and
push that cannot happen in a single CI job.

seed, icons and vendor ran a few times a year and are written out in
the README instead. Makefile: 151 lines to 71.

Docs referenced the removed targets in sixteen places, including a
CONTRIBUTING note claiming the branch check "has to be local".
2026-09-06 10:54:22 +03:00
Esa Kataja c0b48ee71a ci: run make check on every dev push
check / check (push) Successful in 6m27s
2026-09-06 10:12:32 +03:00
Esa Kataja f2ac6c8373 ci: skip TLS verification until the registry has a real cert
demo / check (push) Successful in 2s
2026-09-06 10:09:20 +03:00
Esa Kataja 5e3650deea ci: add checkout to the demo workflow
demo / check (push) Failing after 33s
2026-09-06 10:07:46 +03:00
Esa Kataja 00b737cf8d ci: add a demo workflow to verify the Gitea runner
demo / check (push) Successful in 37s
2026-09-06 10:06:15 +03:00
Esa Kataja 0f0f04240a fix(kirjaa): open days older than the first entry ever logged
Picking a date from before the oldest log entry rendered nothing at all.
history() truncates its window at min(date) — correct, there is no history
before the first entry to be missing — and loadDays widened the window by
inflating the day count, which that truncation then undid. No row for the
selected day meant no board to log it in, so the one thing you would want an
old empty day for was the one thing you could not do. The same happened past
maxHistoryDays, reachable straight from the date picker.

history() now takes the selected day as an explicit floor instead of the
caller guessing a day count, and date() clamps the past at maxHistoryDays the
way it already clamped the future — the list runs unbroken from today down to
the selection, so a picker set to 1994 would otherwise ask for eleven thousand
rows.

While in there:

- entriesBetween replaces the day-at-a-time entryFor loop. Two queries for
  the whole window rather than two per day; the widest window a URL can ask
  for was 3,600 round trips through a pool of exactly one connection.
- softDeleteMain and updateMain filter on special = 0. The catalog never
  lists Tähteet, but a stale tab or a hand-made POST could still have removed
  the row migration 0002 guarantees.
- WriteTimeout and IdleTimeout on the server. With one database connection, a
  reader stalling on a long history response blocks everything behind it.

Two tests, both of which fail on the old code: a day 100 back with only today
logged, and sides landing on their own day now that they arrive in one query.
2026-09-06 00:23:10 +03:00
Esa Kataja 2695b8c01b docs: note that the server's compose and env are copied by hand
The variable rename landed in the repository but not on the server, which
keeps its own compose.yaml and .env. Neither is pulled from here, so the
container came up against the old names. A release touching either now has to
say so in its notes.
2026-09-06 00:08:49 +03:00
Esa Kataja 85bf5e1a25 test: make the smoke script use relative dates
The script hardcoded 2026-09-05, so at the next midnight that became "some
day in the past" and the assertions quietly changed meaning: today turned
into an unlogged row, and a refute on "Ei merkintää" started matching it
instead of the day under test. Dates are now computed at run time.

That refute was wrong regardless. It denied the string across the whole page,
but every unlogged day legitimately renders one. It now asserts positively
that the entry is still shown while the delete is being confirmed.
2026-09-06 00:04:32 +03:00

Diff Content Not Available