Radicale: the first new app through the checklist — template, record, fixture, first ladder step 3.8.0 -> 3.8.1
gates / gates (push) Successful in 2s
gates / gates (push) Successful in 2s
Calendar and contacts (CalDAV/CardDAV), ghcr.io/kozea/radicale:3.8.1. Login file written from the generated password on the first start only (R-765: rewriting it at every start lost the household's login on a restore). onboarding/radicale.md complete; bench + 9202 proven; FIRST-ADMIN, README, Hungarian freeze. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0159rPz1ZhFKsS53msqPYxtS
This commit is contained in:
@@ -1,3 +1,16 @@
|
||||
## Radicale — the first app through the new-app checklist (2026-10-01, evening)
|
||||
|
||||
- **`templates/radicale/`** — calendar and contacts (CalDAV/CardDAV), the Radicale project's own image
|
||||
`ghcr.io/kozea/radicale:3.8.1`. The image ships no config and Radicale 3 lets nobody in by default (`denyall`), so the
|
||||
start command writes a bcrypt login file from the box's generated password on the FIRST start only, then runs
|
||||
Radicale with htpasswd auth. First admin class 1; no setup gate (401 from the first answer). Its first ladder step
|
||||
3.8.0 -> 3.8.1 proven on the bench (swap 0) and on 9202, written by `upgrade-test.py --write-ladder`.
|
||||
- **Found and fixed before publishing (R-765):** the first template rewrote the login file at EVERY start; a remove +
|
||||
restore then left the household with a password no page shows (a restore regenerates `type: password` values by
|
||||
design, expecting the app's own login to come back with its data). Written once, it does — measured.
|
||||
- `onboarding/radicale.md` (every id done or n/a), fixture `Radicale` in `upgrade_fixtures_box.py` (CalDAV seed,
|
||||
three negative controls), FIRST-ADMIN row, README tables, Hungarian freeze (`--add-app`).
|
||||
|
||||
## wger hidden until R-762 and R-763 are fixed (2026-10-01, late afternoon)
|
||||
|
||||
- Operator ruling 2026-10-01: `templates/wger/.felhom.yml` gets `lifecycle: hidden` — not offered for new installs; an
|
||||
|
||||
@@ -70,6 +70,7 @@ asks the probe first where there is one. Open sign-up is closed by the box after
|
||||
| plex | 2 | the household's plex.tv claim token | – | fine | R |
|
||||
| privatebin | 5 | anonymous pastes by design | – | fine | R |
|
||||
| **radarr** | 4 | first visitor sets auth | **setup gate**, opened by the household's press („Kész, beállítottam", confirm first); no sign-up to close (single user / no accounts); `/initialize.json` hands anyone the API key BEFORE the setup — the gate hides it; after, it needs a login | **GATED** — catalog, 2026-09-29 (decisions 46–47) | **M 9202**: stranger → gate page / 401; household reached the first-setup screen; gate opened after the setup; the app answered after (`felhom.eu/documentation/audits/gate-rollout-2026-09-29/`) |
|
||||
| **radicale** | 1 | the box generates the password at install; the container writes Radicale's login file from it on the FIRST start only (Radicale 3 ships with `auth = denyall`) | — (no first-run screen, no sign-up route) | **NEW 2026-10-01** — catalog, onboarding record `onboarding/radicale.md` | **M 9202** (drill catalog): no login 401 from the first answer; wrong 401; right 207; 15 wrong tries → right at once (no lock, ~1.3 s delay each); remove + restore → the household's login comes back with the data (`felhom.eu/documentation/audits/new-apps-2026-10-01/box/radicale/`) |
|
||||
| **rallly** | 4 | magic link to any e-mail | **setup gate**, the household's press; magic link by e-mail — not provable on 9202 | **GATED** — catalog, 2026-09-29; the opening NOT proven | **M 9202**: stranger → gate page / 401, household reached the first-setup screen (`felhom.eu/documentation/audits/gate-rollout-2026-09-29/`) |
|
||||
| **recipe-importer** | 4 | our own image, open until set | **setup gate**, opened by the household's press („Kész, beállítottam", confirm first); no sign-up to close (single user / no accounts); our own app: open until a password is set in its settings — the confirm says so | **GATED** — catalog, 2026-09-29 (decisions 46–47) | **M 9202**: stranger → gate page / 401; household reached the first-setup screen; gate opened after the setup; the app answered after (`felhom.eu/documentation/audits/gate-rollout-2026-09-29/`) |
|
||||
| **romm** | 4 | first unauthenticated `POST /api/users` creates the user (harness); the stale `admin / admin` note is **removed** (2026-09-29) | **setup gate**, opened by probe `/api/heartbeat` → `SYSTEM.SHOW_SETUP_WIZARD` = false; the app itself refuses a stranger's second first-admin / sign-up call | **GATED** — catalog, 2026-09-29 (decisions 46–47) | **M 9202**: stranger → gate page / 401; household reached the first-setup screen; gate opened after the setup; the app answered after (`felhom.eu/documentation/audits/gate-rollout-2026-09-29/`) |
|
||||
|
||||
@@ -320,6 +320,7 @@ block + the matching compose `${VAR}` lines.
|
||||
| Plex | None (file) | 512M / 2048M | no | `${HDD_PATH}/media/` | plex.* |
|
||||
| PrivateBin | None (file) | 30M / 128M | yes | -- | paste.* |
|
||||
| Radarr | None (file) | 150M / 512M | yes | `${HDD_PATH}/media/` | radarr.* |
|
||||
| Radicale | None (file) | 40M / 128M | yes | -- | calendar.* |
|
||||
| Rallly | PostgreSQL | 50M / 256M | yes | -- | poll.* |
|
||||
| RomM | MariaDB + Redis | 300M / 1024M | no | `${HDD_PATH}/storage/romm/` | arcade.* |
|
||||
| Jellyseerr | None (file) | 100M / 384M | yes | -- | requests.* |
|
||||
@@ -378,6 +379,7 @@ block + the matching compose `${VAR}` lines.
|
||||
| Plex | yes | yes | PLEX_CLAIM |
|
||||
| PrivateBin | yes | -- | -- |
|
||||
| Radarr | yes | yes | -- |
|
||||
| Radicale | yes | -- | RADICALE_PASSWORD (password) |
|
||||
| Rallly | yes | -- | SECRET_PASSWORD, DB_PASSWORD |
|
||||
| RomM | yes | yes | DB_PASSWORD, MYSQL_ROOT_PASSWORD, ROMM_AUTH_SECRET_KEY |
|
||||
| Jellyseerr | yes | -- | -- |
|
||||
|
||||
@@ -0,0 +1,73 @@
|
||||
# Onboarding record — radicale
|
||||
|
||||
app: radicale
|
||||
opened: 2026-10-01
|
||||
template_at: the commit that publishes this record (ghcr.io/kozea/radicale:3.8.1; the step 3.8.0 -> 3.8.1 proven on both venues)
|
||||
|
||||
<!--
|
||||
Evidence root: felhom.eu/documentation/audits/new-apps-2026-10-01/ — FIT.md (group 0), bench/radicale/ (bench 9401,
|
||||
swap 0), box/radicale/ (scratch guest 9202, drill catalog), undo/box/radicale/ (the forced-fail case), shots/ (8.3).
|
||||
box/radicale-attempt1/ is the FIRST template (login file rewritten at every start): a remove + restore locked the
|
||||
household out (2.5) — the template was changed, and every box and bench row below was measured again on the second.
|
||||
-->
|
||||
|
||||
0.1 | done | felhom.eu/documentation/audits/new-apps-2026-10-01/A/A1-github-facts.txt — GPL-3.0; the project's own image, pulled, never redistributed
|
||||
0.2 | done | felhom.eu/documentation/audits/new-apps-2026-10-01/A/A1-github-facts.txt — v3.8.1 on 2026-09-24, 13 releases in 2026, 10 open issues
|
||||
0.3 | done | felhom.eu/documentation/audits/new-apps-2026-10-01/FIT.md — `3.8.0`, `3.8.1` version tags; amd64, arm64, arm/v7
|
||||
0.4 | done | felhom.eu/documentation/audits/new-apps-2026-10-01/box/radicale/checks.txt — no start-time network job (the start command is ours, read in the compose); no phone-home found in the image's config or code
|
||||
0.5 | done | felhom.eu/documentation/audits/new-apps-2026-10-01/box/radicale/install.txt — healthy 22 s after the press with nothing fetched but the image
|
||||
0.6 | n/a | Radicale serves CalDAV and CardDAV over plain HTTP on one port
|
||||
0.7 | done | felhom.eu/documentation/audits/new-apps-2026-10-01/box/radicale/checks.txt — a phone client's discovery through traefik over https: well-known 301, PROPFIND 207, the principal and the calendar list; no setup gate stands in the way
|
||||
0.8 | done | app-catalog-felhom.eu/templates/radicale/.felhom.yml — tagline + use_cases: the family calendar and contacts on its own server
|
||||
0.9 | done | felhom.eu/documentation/audits/new-apps-2026-10-01/bench/radicale/evidence/MV-radicale/verdict.json — runs on the bench; no public name is read
|
||||
1.1 | done | felhom.eu/documentation/audits/new-apps-2026-10-01/G/gates-radicale.txt — image-pins and image-resolvable exit 0
|
||||
1.2 | n/a | Radicale stores files, no database engine anywhere
|
||||
1.3 | n/a | no MariaDB or PostgreSQL service in this template
|
||||
1.4 | done | felhom.eu/documentation/audits/new-apps-2026-10-01/bench/radicale/evidence/MV-radicale/verdict.json ; felhom.eu/documentation/audits/new-apps-2026-10-01/box/radicale/step.txt — the previous release 3.8.0 seeded, stepped INTO the pin 3.8.1, read back on both venues (file storage; no migration switch exists)
|
||||
1.5 | done | felhom.eu/documentation/audits/new-apps-2026-10-01/box/radicale/checks.txt — PID 1 is `radicale` itself, the project's own server
|
||||
1.6 | done | felhom.eu/documentation/audits/new-apps-2026-10-01/S/S1-radicale-reads.txt — the only secret is the login; no image config ships and auth defaults to `denyall`; the template sets htpasswd + bcrypt from the generated password
|
||||
1.7 | done | felhom.eu/documentation/audits/new-apps-2026-10-01/S/S1-radicale-reads.txt — the image's entrypoint is `radicale --hosts …` with no switches; the template's own start command is the whole start-up
|
||||
1.8 | done | felhom.eu/documentation/audits/new-apps-2026-10-01/box/radicale/checks.txt — an unknown page answers 401 with no debug output
|
||||
1.9 | done | app-catalog-felhom.eu/templates/radicale/docker-compose.yml — the password is NOT a data_key: the login file is written once and lives on the data volume, so a restore brings the old login back with the calendars (measured, 2.5); marking it would make a restore after a remove refuse
|
||||
2.1 | done | felhom.eu/documentation/audits/new-apps-2026-10-01/box/radicale/restore.txt — the seed came back from the volume backup alone after the app and its volume were removed (the persistence question answered by a restore)
|
||||
2.2 | done | app-catalog-felhom.eu/templates/radicale/docker-compose.yml — one named volume (NVMe): `collections/` + the login file
|
||||
2.3 | n/a | needs_hdd false: no drive path to classify; the tier-1 unit holds the one volume
|
||||
2.4 | done | felhom.eu/documentation/audits/new-apps-2026-10-01/box/radicale/checks.txt — runs as uid 1000 `radicale`; the volume and the 0600 login file are its own
|
||||
2.5 | done | felhom.eu/documentation/audits/new-apps-2026-10-01/box/radicale/restore.txt — the update's per-app backup, remove keeping backups, the household's restore button, the event read back with the ORIGINAL login (attempt 1 failed here: box/radicale-attempt1/restore.txt)
|
||||
2.6 | done | felhom.eu/documentation/audits/new-apps-2026-10-01/box/radicale/restore.txt ; felhom.eu/documentation/audits/new-apps-2026-10-01/box/radicale/remove.txt — keep-backups and with-data both delete the volume; no drive data exists
|
||||
2.7 | done | felhom.eu/documentation/audits/new-apps-2026-10-01/box/radicale/checks.txt — 92 KB after the seed; a family's calendars stay in megabytes
|
||||
2.8 | n/a | Radicale stores no uploaded files: events and contacts are text, read back over CalDAV/CardDAV (2.5)
|
||||
3.1 | done | felhom.eu/documentation/audits/new-apps-2026-10-01/box/radicale/install.txt — class 1: the box generates the password, the login works on a fresh install (C1)
|
||||
3.2 | n/a | no known default login: without the template's config Radicale lets nobody in (`denyall`)
|
||||
3.3 | n/a | no first-run screen: every request needs the login from the first answer
|
||||
3.4 | done | felhom.eu/documentation/audits/new-apps-2026-10-01/box/radicale/checks.txt — no sign-up route; a stranger's MKCOL on a new principal answers 401
|
||||
3.5 | done | felhom.eu/documentation/audits/new-apps-2026-10-01/box/radicale/poll.txt — 25 stranger polls from the press: 404 until routed, then 401; never in
|
||||
3.6 | done | felhom.eu/documentation/audits/new-apps-2026-10-01/box/radicale/checks.txt — 15 wrong tries: no lock, ~1.3 s delay each (Radicale's own); the right password works at once
|
||||
3.7 | done | app-catalog-felhom.eu/templates/radicale/.felhom.yml — one shared login (no user admin in Radicale); `add_people` says each family member uses it and makes their own calendar. Changing the password is not offered in Radicale
|
||||
3.8 | done | app-catalog-felhom.eu/templates/radicale/docker-compose.yml — the password is read from the environment inside the program, never pasted into code
|
||||
3.9 | done | felhom.eu/documentation/audits/new-apps-2026-10-01/box/radicale/checks.txt — CalDAV and CardDAV clients' calls through traefik: right 207/201, wrong 401; the browser UI logs in with the same pair (S2 screenshots)
|
||||
4.1 | done | felhom.eu/documentation/audits/new-apps-2026-10-01/S/S1-radicale-reads.txt — wget is in the image (checked one tool per run); `127.0.0.1:5232/.web/`
|
||||
4.2 | done | felhom.eu/documentation/audits/new-apps-2026-10-01/G/gates-radicale.txt — probe-matches-compose exit 0
|
||||
4.3 | done | felhom.eu/documentation/audits/new-apps-2026-10-01/box/radicale/install.txt — healthy 22 s after the press, 0 restarts
|
||||
4.4 | done | felhom.eu/documentation/audits/new-apps-2026-10-01/box/radicale/checks.txt — paused: the controller read `stopped`, then `running` after unpause
|
||||
4.5 | done | app-catalog-felhom.eu/templates/radicale/docker-compose.yml — `container_name: radicale`, the only service
|
||||
5.1 | done | felhom.eu/documentation/audits/new-apps-2026-10-01/bench/radicale/mem-radicale.csv — bench, swap 0, from birth: peak anon ~26 MiB of 128M, 0 kills
|
||||
5.2 | done | felhom.eu/documentation/audits/new-apps-2026-10-01/bench/radicale/evidence/MV-radicale/verdict.json — 10-min watch: anon peak ~20 %, 0 kills, 0 restarts
|
||||
5.3 | done | app-catalog-felhom.eu/templates/radicale/.felhom.yml — mem_limit 128M = the one service's 128M
|
||||
5.4 | n/a | Radicale is a Python app, no self-sizing heap
|
||||
5.5 | done | felhom.eu/documentation/audits/new-apps-2026-10-01/S/S1-radicale-reads.txt — pi_compatible true (arm64 + arm/v7); the image is ~97 MB
|
||||
6.1 | done | app-catalog-felhom.eu/scripts/upgrade_fixtures_box.py — `Radicale`: a calendar and an event over CalDAV, with three negative controls
|
||||
6.2 | done | app-catalog-felhom.eu/templates/radicale/.felhom.yml — the first ladder step 3.8.0 -> 3.8.1, written by `upgrade-test.py --write-ladder` from both verdicts
|
||||
6.3 | done | felhom.eu/documentation/audits/new-apps-2026-10-01/undo/box/radicale/step.txt — a drill step to an image that exits: verifying 305 s, the box undid it by itself, 3.8.1 back, the event read back (on attempt 1's template; the undo is the box's, the start command played no part)
|
||||
6.4 | done | felhom.eu/documentation/audits/new-apps-2026-10-01/bench/radicale/evidence/MV-radicale/verdict.json — no file changes at start (no mark)
|
||||
6.5 | done | felhom.eu/documentation/audits/new-apps-2026-10-01/A/A1-github-facts.txt — plain `x.y.z` from v3.0 to v3.8.1
|
||||
7.1 | n/a | Radicale sends no mail at all; there is nothing to map
|
||||
8.1 | done | felhom.eu/documentation/audits/new-apps-2026-10-01/G/gates-radicale.txt — copy-i18n exit 0 (frozen with --add-app)
|
||||
8.2 | done | app-catalog-felhom.eu/templates/radicale/.felhom.yml — tagline, use_cases, first_steps, add_people; each step followed on 9202 (the web login, MKCALENDAR, the phone discovery calls)
|
||||
8.3 | done | felhom.eu/documentation/audits/new-apps-2026-10-01/S/S3-assets.txt — radicale-logo.svg and three screenshots answer 200 on felhom.eu; boxes get them with the next hub release (R-766)
|
||||
8.4 | done | app-catalog-felhom.eu/README.md — both tables; FIRST-ADMIN row; category productivity; catalog_since
|
||||
8.5 | done | felhom.eu/documentation/audits/new-apps-2026-10-01/S/S3-assets.txt — the website's count, read
|
||||
9.1 | done | felhom.eu/documentation/audits/new-apps-2026-10-01/G/gates-radicale.txt — every gate exit 0, the runtime volume gate included
|
||||
9.2 | done | felhom.eu/documentation/audits/new-apps-2026-10-01/box/radicale/install.txt ; felhom.eu/documentation/audits/new-apps-2026-10-01/box/radicale/checks.txt — a fresh install from the drill catalog, as household and stranger
|
||||
9.3 | done | felhom.eu/documentation/audits/new-apps-2026-10-01/README.md — every row done or n/a; the one defect found (attempt 1, 2.5) was fixed before publishing
|
||||
9.4 | done | app-catalog-felhom.eu/onboarding/radicale.md — published in one commit with this record (the onboarding gate)
|
||||
@@ -832,6 +832,27 @@
|
||||
"deploy_fields[SUBDOMAIN].label": "Aldomain",
|
||||
"description": "Automatikus film letöltő és rendszerező"
|
||||
},
|
||||
"radicale": {
|
||||
"app_info.add_people": "A Radicale-ban egy közös bejelentkezés van. A családtagod ugyanezzel a névvel és jelszóval veszi fel a telefonjára, és saját naptárat hoz létre magának.",
|
||||
"app_info.first_steps[0]": "Nyisd meg a calendar.DOMAIN címet, és jelentkezz be a Beállítások oldalon látható névvel és jelszóval",
|
||||
"app_info.first_steps[1]": "Hozz létre egy naptárat és egy címjegyzéket (\"Create new addressbook or calendar\")",
|
||||
"app_info.first_steps[2]": "Androidon telepítsd a DAVx5-öt, és add meg: https://calendar.DOMAIN, a nevet és a jelszót",
|
||||
"app_info.first_steps[3]": "iPhone-on: Beállítások, Naptár, Fiókok, Új fiók, Egyéb, CalDAV-fiók - szerver: calendar.DOMAIN",
|
||||
"app_info.tagline": "Naptár és névjegyek a saját szervereden - a telefonod szinkronizál vele",
|
||||
"app_info.use_cases[0]": "A telefonod naptára és névjegyei a saját szervereden, nem a Google-nél vagy az Apple-nél",
|
||||
"app_info.use_cases[1]": "Közös családi naptár, amit mindenki lát a telefonján",
|
||||
"app_info.use_cases[2]": "Androidon a DAVx5, iPhone-on és Macen a beépített naptár szinkronizál vele",
|
||||
"app_info.use_cases[3]": "Thunderbird és más asztali naptárak is használhatják",
|
||||
"deploy_fields[DOMAIN].description": "A szerver domain neve",
|
||||
"deploy_fields[DOMAIN].label": "Domain",
|
||||
"deploy_fields[RADICALE_PASSWORD].description": "Telepítéskor generált jelszó. A telefonod naptára és névjegyei is ezzel jelentkeznek be.",
|
||||
"deploy_fields[RADICALE_PASSWORD].label": "Jelszó",
|
||||
"deploy_fields[RADICALE_USER].description": "Ezzel a névvel jelentkezel be a telefonodon és a böngészőben",
|
||||
"deploy_fields[RADICALE_USER].label": "Felhasználónév",
|
||||
"deploy_fields[SUBDOMAIN].description": "Az alkalmazás aldomainje",
|
||||
"deploy_fields[SUBDOMAIN].label": "Aldomain",
|
||||
"description": "Naptár és névjegyek a saját szervereden (CalDAV / CardDAV)"
|
||||
},
|
||||
"rallly": {
|
||||
"app_info.first_steps[0]": "Nyisd meg a poll.DOMAIN címet a böngészőben",
|
||||
"app_info.first_steps[1]": "Hozz létre egy új szavazást",
|
||||
@@ -1162,5 +1183,7 @@
|
||||
"description": "ShareX/Flameshot szerver - screenshot és fájlmegosztás"
|
||||
}
|
||||
},
|
||||
"reasons": {}
|
||||
"reasons": {
|
||||
"radicale": "new app 2026-10-01 through NEW-APP-CHECKLIST.md: te-form, no kérjük; reviewed by CC against the operator rules"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1875,6 +1875,61 @@ class UptimeKuma:
|
||||
return found
|
||||
|
||||
|
||||
# =============================================================================================
|
||||
class Radicale:
|
||||
"""Radicale (2026-10-01, new app through NEW-APP-CHECKLIST.md). THE FRONT DOOR is CalDAV itself, the route every
|
||||
phone and desktop calendar uses: HTTP basic auth with the box's generated login (`RADICALE_USER`, default
|
||||
"csalad"; `RADICALE_PASSWORD`). Seed: MKCALENDAR a calendar, PUT one event (a unique UID and SUMMARY); read back
|
||||
with GET. Negative controls on every readback: no credentials 401, a wrong password 401, an event never created
|
||||
404 — so a read that says "found" cannot be an open door or a catch-all."""
|
||||
sub = "calendar"
|
||||
|
||||
@staticmethod
|
||||
def _creds(w):
|
||||
g = w.GENERATED.get("radicale") or {}
|
||||
return g.get("RADICALE_USER") or "csalad", g.get("RADICALE_PASSWORD") or ""
|
||||
|
||||
def seed(self, w, sub, say):
|
||||
if not w.wait_app(sub, "/.web/", want=("200",), tries=60):
|
||||
self.tried = "/.web/ never answered 200"
|
||||
return None
|
||||
user, pw = self._creds(w)
|
||||
cal = "felhom-" + secrets.token_hex(4)
|
||||
uid = "ev-" + secrets.token_hex(6) + "@felhom"
|
||||
summary = "Felhom teszt " + secrets.token_hex(3)
|
||||
rc, code, _ = w.app_curl(sub, f"/{user}/{cal}/", "-u", f"{user}:{pw}", method="MKCALENDAR")
|
||||
say(f" radicale: MKCALENDAR /{user}/{cal}/ http={code}")
|
||||
if code != "201":
|
||||
self.tried = f"MKCALENDAR -> {code}"
|
||||
return None
|
||||
ics = ("BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//felhom//fixture//EN\r\nBEGIN:VEVENT\r\n"
|
||||
f"UID:{uid}\r\nDTSTAMP:20261001T100000Z\r\nDTSTART:20261002T100000Z\r\nSUMMARY:{summary}\r\n"
|
||||
"END:VEVENT\r\nEND:VCALENDAR\r\n")
|
||||
rc, code, _ = w.app_curl(sub, f"/{user}/{cal}/{uid}.ics", "-u", f"{user}:{pw}", "-H", "Content-Type: text/calendar",
|
||||
data=ics, method="PUT")
|
||||
say(f" radicale: PUT event http={code}")
|
||||
if code != "201":
|
||||
self.tried = f"PUT event -> {code}"
|
||||
return None
|
||||
return {"user": user, "pw": pw, "cal": cal, "uid": uid, "summary": summary}
|
||||
|
||||
def verify(self, w, sub, t, say):
|
||||
if not w.wait_app(sub, "/.web/", want=("200",), tries=60):
|
||||
say(" radicale: /.web/ never answered")
|
||||
return False
|
||||
path = f"/{t['user']}/{t['cal']}/{t['uid']}.ics"
|
||||
rc, c_none, _ = w.app_curl(sub, path)
|
||||
rc, c_wrong, _ = w.app_curl(sub, path, "-u", f"{t['user']}:{t['pw']}x")
|
||||
rc, c_absent, _ = w.app_curl(sub, f"/{t['user']}/{t['cal']}/never-{secrets.token_hex(3)}.ics", "-u", f"{t['user']}:{t['pw']}")
|
||||
if c_none != "401" or c_wrong != "401" or c_absent != "404":
|
||||
say(f" radicale: READBACK UNUSABLE — no auth {c_none}, wrong password {c_wrong}, absent event {c_absent}")
|
||||
return False
|
||||
rc, code, out = w.app_curl(sub, path, "-u", f"{t['user']}:{t['pw']}")
|
||||
ok = code == "200" and f"SUMMARY:{t['summary']}" in (out or "")
|
||||
say(f" radicale: readback http={code} found={ok} (controls: no auth {c_none}, wrong {c_wrong}, absent {c_absent})")
|
||||
return ok
|
||||
|
||||
|
||||
FIXTURES = {
|
||||
"uptime-kuma": UptimeKuma(),
|
||||
"crafty-controller": Crafty(),
|
||||
@@ -1911,4 +1966,5 @@ FIXTURES = {
|
||||
"wishlist": Wishlist(),
|
||||
"claper": Claper(),
|
||||
"calcom": Calcom(),
|
||||
"radicale": Radicale(),
|
||||
}
|
||||
|
||||
@@ -0,0 +1,116 @@
|
||||
# =============================================================================
|
||||
# .felhom.yml - App metadata for felhom-controller
|
||||
# =============================================================================
|
||||
# Radicale — the Radicale project's own image (ghcr.io/kozea/radicale). Onboarding record: onboarding/radicale.md
|
||||
# (NEW-APP-CHECKLIST.md). First admin: CLASS 1 — the box generates the password at install; the container writes the
|
||||
# login file from it on the first start only (see the compose header). No setup gate: there is no first-run screen, and a
|
||||
# request without the right login answers 401 from the first second.
|
||||
|
||||
# --- Display info (shown on dashboard) ---
|
||||
display_name: "Radicale"
|
||||
description: "Naptár és névjegyek a saját szervereden (CalDAV / CardDAV)"
|
||||
category: "productivity"
|
||||
subdomain: "calendar"
|
||||
slug: "radicale"
|
||||
# catalog_since: the date THIS repo last changed this app's pinned images. Any commit that
|
||||
# changes an image: line must set this to the same day (see CLAUDE.md).
|
||||
catalog_since: "2026-10-01"
|
||||
|
||||
# --- Resource hints (displayed on deploy screen) ---
|
||||
resources:
|
||||
mem_request: "40M"
|
||||
mem_limit: "128M" # one service: radicale 128M
|
||||
pi_compatible: true
|
||||
needs_hdd: false
|
||||
|
||||
# --- Deploy wizard fields ---
|
||||
deploy_fields:
|
||||
- env_var: DOMAIN
|
||||
label: "Domain"
|
||||
type: domain
|
||||
description: "A szerver domain neve"
|
||||
locked_after_deploy: true
|
||||
|
||||
- env_var: SUBDOMAIN
|
||||
label: "Aldomain"
|
||||
type: subdomain
|
||||
default: "calendar"
|
||||
required: true
|
||||
locked_after_deploy: true
|
||||
description: "Az alkalmazás aldomainje"
|
||||
|
||||
- env_var: RADICALE_USER
|
||||
label: "Felhasználónév"
|
||||
type: text
|
||||
default: "csalad"
|
||||
required: true
|
||||
locked_after_deploy: true
|
||||
description: "Ezzel a névvel jelentkezel be a telefonodon és a böngészőben"
|
||||
|
||||
- env_var: RADICALE_PASSWORD
|
||||
label: "Jelszó"
|
||||
type: password
|
||||
generate: "password:24"
|
||||
locked_after_deploy: true
|
||||
description: "Telepítéskor generált jelszó. A telefonod naptára és névjegyei is ezzel jelentkeznek be."
|
||||
|
||||
# --- Customer-facing info ---
|
||||
app_info:
|
||||
tagline: "Naptár és névjegyek a saját szervereden - a telefonod szinkronizál vele"
|
||||
add_people: "A Radicale-ban egy közös bejelentkezés van. A családtagod ugyanezzel a névvel és jelszóval veszi fel a telefonjára, és saját naptárat hoz létre magának."
|
||||
docs_url: "https://radicale.org/v3.html"
|
||||
use_cases:
|
||||
- 'A telefonod naptára és névjegyei a saját szervereden, nem a Google-nél vagy az Apple-nél'
|
||||
- 'Közös családi naptár, amit mindenki lát a telefonján'
|
||||
- 'Androidon a DAVx5, iPhone-on és Macen a beépített naptár szinkronizál vele'
|
||||
- 'Thunderbird és más asztali naptárak is használhatják'
|
||||
first_steps:
|
||||
- 'Nyisd meg a calendar.DOMAIN címet, és jelentkezz be a Beállítások oldalon látható névvel és jelszóval'
|
||||
- 'Hozz létre egy naptárat és egy címjegyzéket ("Create new addressbook or calendar")'
|
||||
- 'Androidon telepítsd a DAVx5-öt, és add meg: https://calendar.DOMAIN, a nevet és a jelszót'
|
||||
- 'iPhone-on: Beállítások, Naptár, Fiókok, Új fiók, Egyéb, CalDAV-fiók - szerver: calendar.DOMAIN'
|
||||
|
||||
# --- Controller health probe (dials what the compose healthcheck dials) ---
|
||||
healthcheck:
|
||||
checks:
|
||||
- type: api
|
||||
port: 5232
|
||||
path: "/.web/"
|
||||
expect:
|
||||
status: 200
|
||||
|
||||
i18n:
|
||||
en:
|
||||
description: 'Calendar and contacts on your own server (CalDAV / CardDAV)'
|
||||
app_info:
|
||||
tagline: 'Calendar and contacts on your own server - your phone syncs with it'
|
||||
add_people: "Radicale has one shared login. Your family member adds the same name and password on their phone and makes their own calendar."
|
||||
use_cases:
|
||||
- 'Your phone calendar and contacts on your own server, not with Google or Apple'
|
||||
- 'A shared family calendar that everyone sees on their phone'
|
||||
- 'On Android DAVx5, on iPhone and Mac the built-in calendar syncs with it'
|
||||
- 'Thunderbird and other desktop calendars can use it too'
|
||||
first_steps:
|
||||
- 'Open calendar.DOMAIN and sign in with the name and password shown on the settings page'
|
||||
- 'Create a calendar and an address book ("Create new addressbook or calendar")'
|
||||
- 'On Android install DAVx5 and enter: https://calendar.DOMAIN, the name and the password'
|
||||
- 'On iPhone: Settings, Calendar, Accounts, Add Account, Other, CalDAV account - server: calendar.DOMAIN'
|
||||
deploy_fields:
|
||||
- env_var: DOMAIN
|
||||
label: 'Domain'
|
||||
description: 'The server domain name'
|
||||
- env_var: SUBDOMAIN
|
||||
label: 'Subdomain'
|
||||
description: 'The subdomain this app answers on'
|
||||
- env_var: RADICALE_USER
|
||||
label: 'User name'
|
||||
description: 'You sign in with this name on your phone and in the browser'
|
||||
- env_var: RADICALE_PASSWORD
|
||||
label: 'Password'
|
||||
description: 'Generated at install. Your phone calendar and contacts sign in with it too.'
|
||||
|
||||
# update_ladder — the test record: one tested step per line, oldest first (JSON flow mappings,
|
||||
# `09-update-architecture.md` §6.4 part 4). WRITTEN BY scripts/upgrade-test.py, never by hand;
|
||||
# gated by scripts/check-test-record.py. An image: move without a proven entry here is refused.
|
||||
update_ladder:
|
||||
- {"from": {"radicale": "ghcr.io/kozea/radicale:3.8.0"}, "to": {"radicale": "ghcr.io/kozea/radicale:3.8.1"}, "digest": {"radicale": "sha256:54d9406cd30f9e206a9dd6d61dfac48da26b9afc37200e14f23beb8fafa7d11c"}, "verdict": "proven", "tested_at": "2026-10-01T14:47:56Z", "harness_version": 4, "evidence": "felhom.eu/documentation/audits/new-apps-2026-10-01/bench/radicale/evidence/MV-radicale/verdict.json", "box_evidence": "felhom.eu/documentation/audits/new-apps-2026-10-01/box/radicale/step.txt", "memory_peak_pct": 20.0, "marks": {"files_may_change": false, "needs_person": null, "memory_tight": false}, "memory_basis": "anon", "memory_cgroup_peak_pct": 23.7}
|
||||
@@ -0,0 +1,64 @@
|
||||
# Radicale - Naptár és névjegyek (CalDAV / CardDAV szerver)
|
||||
# Domain: ${SUBDOMAIN}.${DOMAIN}
|
||||
# Database: None (file-based — every calendar and address book is a folder of .ics/.vcf files)
|
||||
# RAM: ~40M (mem_limit: 128M) | Pi-compatible: Yes (amd64, arm64, arm/v7)
|
||||
#
|
||||
# Environment variables:
|
||||
# DOMAIN - Your domain (e.g., demo-felhom.eu)
|
||||
# RADICALE_USER - A bejelentkezési név (az űrlapon megadható, alapból "csalad")
|
||||
# RADICALE_PASSWORD - A jelszó (telepítéskor generálva, az alkalmazás oldalán látható)
|
||||
#
|
||||
# The upstream image (ghcr.io/kozea/radicale, the Radicale project's own) ships NO config, and Radicale 3's
|
||||
# default `auth type` is `denyall` — nobody can log in. So the start command writes the login file from the
|
||||
# box's generated password (bcrypt, 0600, on the data volume) ON THE FIRST START ONLY, then starts Radicale with
|
||||
# htpasswd auth. There is never a moment without a login (no install window, checklist 3.5). The password is
|
||||
# read from the environment, never pasted into program code (checklist 3.8).
|
||||
# FIRST START ONLY, measured 2026-10-01 on 9202: the box never puts a `type: password` value into a backup (an
|
||||
# internet-reachable login; controller `PortableSecretEnvVars`) and makes a NEW one on a restore after a remove,
|
||||
# expecting the app's own login to come back WITH ITS DATA. The first version rewrote the file at every start —
|
||||
# a remove + restore then replaced the household's login with a value no page shows, and every phone lost its
|
||||
# calendar. Written once, the login file lives on the data volume and comes back with the calendars.
|
||||
|
||||
services:
|
||||
radicale:
|
||||
image: ghcr.io/kozea/radicale:3.8.1
|
||||
container_name: radicale
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
- TZ=Europe/Budapest
|
||||
- RADICALE_USER=${RADICALE_USER}
|
||||
- RADICALE_PASSWORD=${RADICALE_PASSWORD}
|
||||
entrypoint:
|
||||
- /bin/sh
|
||||
- -c
|
||||
- |
|
||||
[ -s /var/lib/radicale/users ] || /app/bin/python -c 'import os, bcrypt; f = "/var/lib/radicale/users"; u = os.environ["RADICALE_USER"]; p = os.environ["RADICALE_PASSWORD"].encode(); open(f, "w").write(u + ":" + bcrypt.hashpw(p, bcrypt.gensalt()).decode() + "\n"); os.chmod(f, 0o600)' || exit 1
|
||||
exec /app/bin/python /app/bin/radicale --hosts 0.0.0.0:5232 --auth-type htpasswd --auth-htpasswd-filename /var/lib/radicale/users --auth-htpasswd-encryption bcrypt --storage-filesystem-folder /var/lib/radicale/collections
|
||||
volumes:
|
||||
- radicale_data:/var/lib/radicale
|
||||
networks:
|
||||
- traefik-public
|
||||
deploy:
|
||||
resources:
|
||||
limits:
|
||||
memory: 128M
|
||||
healthcheck:
|
||||
test: ["CMD", "wget", "--spider", "-q", "http://127.0.0.1:5232/.web/"]
|
||||
interval: 30s
|
||||
timeout: 5s
|
||||
retries: 3
|
||||
start_period: 20s
|
||||
labels:
|
||||
- "traefik.enable=true"
|
||||
- "traefik.http.routers.radicale.rule=Host(`${SUBDOMAIN}.${DOMAIN}`)"
|
||||
- "traefik.http.routers.radicale.entrypoints=websecure"
|
||||
- "traefik.http.routers.radicale.tls=true"
|
||||
- "traefik.http.routers.radicale.tls.certresolver=letsencrypt"
|
||||
- "traefik.http.services.radicale.loadbalancer.server.port=5232"
|
||||
|
||||
volumes:
|
||||
radicale_data:
|
||||
|
||||
networks:
|
||||
traefik-public:
|
||||
external: true
|
||||
Reference in New Issue
Block a user