The tailnet-only Hungarian community “top kép” voting system on telep-mainframe — people vote favourite Frigate person-detection snapshots per day (/nap), which roll up into weekly (/het) then monthly (/honap) rounds. Voted pics are saved locally on the mainframe (survive Frigate’s ~14-day event purge); Telegram reminders fire on a schedule. LIVE as of 2026-08-10. This is the canonical “how it works / where it lives” doc. It REPLACES the Google-Drive top-pics workflow (2026-08-10-top-kepek-drive-curation-print-pipeline).
For Agents
Everything runs on telep-mainframe. Identity is the
Tailscale-User-Loginrequest header — one vote per tailnet identity. No secrets belong in this note — the Telegram bot token is referenced by file path only and read at runtime. The design spec is 2026-08-10-top-kep-community-voting-spec; the task-by-task implementation plan is 2026-08-10-top-kep-community-voting-plan.top4-web.pyis a stdlibhttp.serverapp, NOT Flask (Flask is not installed on the box) — earlier vault text calling it “Flask” is wrong.
Quick Reference
- Web app:
/home/levander/top4-web.py, systemdtop4-web.service(User=levander), binds127.0.0.1:8090→ exposedhttps://telep-mainframe.taild4189d.ts.net:8443- Routes (new):
GET /nap/het/honap(Hungarian voting grids) ·GET /pic/<event_id>(local saved jpg if present, else proxies Frigate) ·POST /vote(formevent_id/camera/captured_ts→ JSON{votes};403withoutTailscale-User-Login)- Data/logic module:
/home/levander/top_kep_store.py(Python 3 stdlib only, no deps)- SQLite DB:
/srv/top-kep/votes.db· Saved images:/srv/top-kep/img/YYYY-MM-DD/<event_id>.jpg- Reminder CLI:
/home/levander/top_kep_remind.py(symlink~/top-kep-remind.py) —--daily|--weekly|--monthly [--dry]- Timers:
top-kep-remind-daily.timer(20:00) ·top-kep-remind-weekly.timer(Sun 19:30) ·top-kep-remind-monthly.timer(daily 21:00, guarded)- Frigate API:
http://127.0.0.1:5000· Telegram group:-1004475187307(『Telephely biztonsági riasztások』)- Bot token (runtime read, NOT in source):
/home/levander/nvr/frigate-notify/config.yml- Tests:
/home/levander/test_top_kep.py(stdlibunittest), 12/12 pass- Home portal tile:
~/home-portal/index.html(:8093) “Top képek” tile →…:8443/nap
What it does
Replaces the Drive-based top-pics flow: instead of an agent/user pushing curated frames to Google Drive, the community browses and votes favourite camera pics themselves, tailnet-only, in Hungarian. Daily votes roll up — the week and month re-rank the pics already voted in that window. Picked images are stored on the mainframe (not Drive) until further instruction.
Web routes (added to top4-web.py)
Added to the existing stdlib http.server app (class H(BaseHTTPRequestHandler) + ThreadingHTTPServer), reusing the existing :8443 tailnet exposure — no new service.
| Route | Method | Behaviour |
|---|---|---|
/nap | GET | Today’s deduped person candidates as a Hungarian grid + ❤ buttons, chronological |
/het | GET | Pics with ≥1 vote in the current ISO week (Mon–Sun), vote-count desc |
/honap | GET | Pics with ≥1 vote in the current calendar month, vote-count desc |
/pic/<event_id> | GET | Serves the locally saved jpg if it exists, else proxies the snapshot from Frigate |
/vote | POST | Form event_id/camera/captured_ts → toggles the caller’s ❤, returns JSON {votes}; 403 without Tailscale-User-Login |
/vote resolves the voter from the Tailscale-User-Login header. If a (event_id, voter) vote row exists it is deleted (un-vote), else inserted. On the pic’s first-ever vote its full-res clean snapshot (bbox=0&quality=100) is fetched from Frigate and persisted locally (idempotent — never re-fetched). Week = ISO week containing “now”; month = calendar month; all Europe/Budapest.
Data & logic — top_kep_store.py
Pure Python 3 stdlib (sqlite3 + urllib), zero deps, unit-testable in isolation. SQLite at /srv/top-kep/votes.db:
pics(event_id PK, camera, captured_ts, saved_path, first_voted_ts)
votes(event_id, voter, voted_ts, PRIMARY KEY(event_id, voter))
reminder_state(kind PK, last_event_ts)- Candidates = Frigate
label=personevents for the day, deduped to one per (camera, whole-second) keeping the highesttop_score(mirrors a de-noised/explore?labels=person). - Persist-on-first-vote: the clean full-res snapshot is written to
/srv/top-kep/img/YYYY-MM-DD/<event_id>.jpg(date = capture date) so votes survive Frigate’s ~14-day event purge. Unvoted pics are never stored. event_idis the unique key — NOT the_scorethat appears in the old Drive-flow filenames (ties share a score; see the gotcha).
Reminders — top_kep_remind.py + systemd timers
CLI /home/levander/top_kep_remind.py (symlink ~/top-kep-remind.py): --daily | --weekly | --monthly [--dry]. Reads the bot token at runtime from /home/levander/nvr/frigate-notify/config.yml (no secret in source), sends via the Telegram Bot API to group -1004475187307. --dry is fully side-effect-free.
| Timer | Schedule | Guard |
|---|---|---|
top-kep-remind-daily.timer | OnCalendar=*-*-* 20:00 | Sends only if today’s newest candidate is newer than the stored watermark (reminder_state.daily.last_event_ts); watermark advances only after a confirmed non-dry send |
top-kep-remind-weekly.timer | OnCalendar=Sun *-*-* 19:30 | none |
top-kep-remind-monthly.timer | OnCalendar=*-*-* 21:00 (daily) | CLI sends only when tomorrow is the 1st — a single daily timer covers 28/30/31-day months |
All units User=levander, WorkingDirectory=/home/levander, Persistent=true (a reminder missed during a power outage fires on the next boot).
Drive removal
- The new voting flow writes nothing to Drive.
- The weekly Drive export
top4-export.timer(Mon 08:00) was DISABLED. - The old picker’s Drive buttons/endpoints (
/save-full-to-drive,/save-crop-to-drive,~/top-kep-drive.py,~/top-kep-id.py) and the 4-slot camwall flow (/srv/frigate/top4/{1..4}.jpg) were left intact and working — a full Drive rip-out is deferred until the user asks. See 2026-08-10-top-kepek-drive-curation-print-pipeline for that (now-superseded) flow.
Home portal
The “Top képek” tile in ~/home-portal/index.html (served :8093, node home.taild4189d.ts.net — the home portal) was repointed from the old picker to …:8443/nap.
Testing
/home/levander/test_top_kep.py (stdlib unittest, no framework) — 12/12 pass. Covers dedup collapse, per-identity vote toggle netting to zero, persist-once (saved_path set on first vote, unchanged after), and the month-end reminder guard across 28/30/31-day months.
Known follow-ups / deferred
- Cosmetic: the ❤ button doesn’t pre-highlight pics the current user already voted on after a page reload (the server-side tally stays correct).
- Deferred: full Drive rip-out; a print/export path off the local
/srv/top-kep/imgstore (“stored on mainframe until further instruction”).- Resolution ceiling: pics are 720p (detect stream). >720p for printing would need pulling from the record/main stream — same limitation as the old pipeline.
Review-pass hardening (2026-08-10)
A two-lens adversarial review pass — correctness/concurrency + security/data-integrity — was run over the voting system. Findings were fixed and verified on telep-mainframe.
CRITICAL: client-side voting had never worked (pre-existing, missed by earlier reviews)
GRID_TAILwas concatenated raw into the served HTML, but had been written with doubled{{/}}braces (as if it were going through.format()likeGRID_HEAD/GRID_CARD). The doubled braces leaked literally into the<script>, making it invalid JS — sovote()was never defined and clicking the ❤ did nothing. Fixed:GRID_TAILnow uses single braces. Verified: the served/napscript passesnode --check. Why earlier reviews missed it: theycurl’d the JSON endpoint and never rendered the page in a browser. Lesson: verify web UIs by rendering the page / checking the served JS, not only by curling the JSON endpoint.
Confirmed SAFE (verified)
- Identity spoofing not possible.
top4-webbinds127.0.0.1only, fronted bytailscale serve(HTTP-proxy) which overwrites theTailscale-User-Loginheader from the verified tailnet peer — a client cannot forge it. - Telegram bot token cannot leak via exceptions.
str(e)on urllib errors omits the URL, and the web process never touches the token (token is read only by the reminder CLI).
Fixed and verified
- Stored XSS —
camerawas client-supplied in the/votePOST and rendered unescaped in the grid.render_gridnow HTML-escapesevent_id/camera/title(html.escape,quote=True). Verified:/hetrenders the payload escaped. - Concurrent toggle race — a double-tap fired two
/votePOSTs →IntegrityError→ HTTP 500 (or a lost vote).toggle_voterewritten atomic:DELETE … rowcountdetects an un-vote,INSERT OR IGNOREfor the vote. - Disk reclaim — un-voting never freed the saved jpg. Now when a pic drops to 0 votes its jpg and
picsrow are removed. Verified. - Client error handling — the
vote()JS treated any non-403 error as success (showedundefined); added anif(!r.ok)guard. - CSRF hardening —
/votenow403s whenSec-Fetch-Site: cross-site. Verified. - DB perms —
/srv/top-kep/votes.dbwas mode644(exposed voter emails), now600. Verified.
Open product decision (NOT yet done)
The paired physical cameras (telep_cam1/telep_cam2 = south, telep_cam3/telep_cam4 = north) are different Frigate camera names, so the same physical moment can appear twice in /nap (dedup keys on camera-name + second). Decide: dedup by physical-camera group, or keep both angles.
Deferred minors
- Derive
cameraserver-side from Frigate instead of trusting the client. - Validate
event_idagainst Frigate’s id charset. - Per-handler sqlite conn close (benign — CPython closes at scope exit).
Update — 2026-08-11: mobile UI, lightbox, person/car filter, HD recording + HD upgrade
A second build pass on the voting system, all DONE and verified live in a real (headless Playwright) browser at phone width on telep-mainframe. The headline: pics can now be served at real HD (2304×1296), not just the 720p detect stream — but only for events captured after a Frigate recording reconfig done in this pass.
For Agents
New routes/params on
~/top4-web.py:GET /pic/<id>?full=1(full-res persisted snapshot for the lightbox),GET /pic/<id>?hq=1(HD recording-snapshot, cached to/srv/top-kep/hq/<id>.jpg), and a?label=person|carquery param on/nap/het/honap(defaultperson, validated againstLABELS=("person","car")). Alabel TEXTcolumn was added to thepicstable (ALTER-TABLE migration ininit_db). HD only works for events recorded AFTER the reconfig below; older pics stay 720p.
Mobile-first UI + tap-to-zoom lightbox
render_grid/templates in top4-web.py reworked for phones: bigger 16:9 cards, full-width Nap/Hét/Hónap tabs, 44px+ touch targets, responsive grid repeat(auto-fill, minmax(min(100%, 340px), 1fr)). Tapping an image opens a full-screen lightbox (#lb) showing the full-res snapshot (/pic/<id>?full=1) with:
- a “Nagyítás” zoom toggle (native-size scrollable + mobile pinch-zoom),
- a vote ❤ button,
- a “Magasabb minőség” (HD) button (see below),
- “Bezár” to close.
Person / Car filter
_frigate_events / day_candidates / votes_for_window now take a label param; the routes read ?label=person|car (default person, validated against LABELS=("person","car")). A label TEXT column was added to the pics table (ALTER-TABLE migration in init_db), stored on persist and threaded through toggle_vote / _vote. A Személy / Autó filter bar in the UI persists the chosen label across the day/week/month tiers. Verified live: today person=189, car=36; a car vote stored pics.label='car'.
HD recording — Frigate reconfig (the real enabler)
Root cause of the 720p ceiling
Previously every camera both detected and recorded the 720p sub stream, so no HD frame ever existed for past events — the “pics are 720p” limitation was baked in at the recording layer, not just the snapshot layer.
Reconfig: each telep_camN now detects on camN_sub (720p, role: detect) and RECORDS a new camN_main go2rtc stream (role: record) at 2304×1296. Lens mapping (verified by still comparison):
| Frigate camera | Physical lens | main stream (record) |
|---|---|---|
telep_cam1 | DÉL fix | .119 / stream1 |
telep_cam2 | DÉL PTZ | .119 / stream6 |
telep_cam3 | ÉSZAK fix | .139 / stream1 |
telep_cam4 | ÉSZAK PTZ | .139 / stream6 |
- Config backup on the box:
/home/levander/nvr/frigate/config.yml.bak-hqrec-1786445779. - Detection fps unaffected (~5 on the subs).
- Storage impact ~3–4× recordings, on a disk that’s 6% used (3.2T free) — fine.
Caveat to watch — camera-side RTSP connection limits
Each Tapo now serves 4 concurrent RTSP pulls (2 lenses × sub+main) + ONVIF. Watch for camera-side connection-limit refusals if anything else starts pulling from
.119/.139.
”Magasabb minőség” (HD) button
The lightbox HD button hits /pic/<id>?hq=1, which fetches the main-stream recording-snapshot at the event’s timestamp (/api/<camera>/recordings/<start_time>/snapshot.jpg, now HD), caches it to /srv/top-kep/hq/<id>.jpg, and serves 2304×1296. Fallback chain (never 500s):
- recording-snapshot (HD), else
- event-clip ffmpeg frame, else
- 720p detect snapshot.
event_id is charset-validated (^[0-9.]+-[A-Za-z0-9]+$) before any subprocess / URL / path use — this also closes the earlier “validate event_id charset” deferred item. HD works only for events captured AFTER the recording reconfig; older pics stay 720p.
Invariants maintained (for future agents)
Don't break these when editing
top4-web.py
- All interpolated values in
render_gridarehtml.escaped — the stored-XSS fix stays.GRID_HEAD/GRID_CARDare.format()ed (braces doubled);GRID_TAILis raw-concatenated (single braces) — the served<script>must contain zero{{(this was the prior critical GRID_TAIL bug).event_idis charset-validated before any subprocess/URL/path use.
Backups on the box from this pass: top4-web.py.bak-ui-*, .bak-hq-*, .bak-lbl-*, .bak-hq2-*; top_kep_store.py.bak-lbl-*.
Verification (2026-08-11)
Confirmed live via a headless real browser (Playwright) at phone width, with the Tailscale identity injected by tailscale serve:
- filter toggles work (car page = 36 cards,
data-label=car), - the lightbox opens with the full-res image,
- “Magasabb minőség” upgrades the image 1280 → 2304×1296 (button → “HD ✓”),
- the vote → persist → un-vote → reclaim path works end-to-end.
Related
- 2026-08-10-top-kep-community-voting-spec — the design spec (decisions, data model, routes)
- 2026-08-10-top-kep-community-voting-plan — the task-by-task implementation plan
- 2026-08-10-top-kepek-drive-curation-print-pipeline — the Drive curation/print pipeline this supersedes
- telep-mainframe — the host running the web app, Frigate, and the timers
- telep-mainframe-handover — session handover / living runbook for the host
- 2026-07-24-global-dashboard — the home portal with the “Top képek” tile
- LOG
- TOPICS