This repository has been archived on 2026-09-17. You can view files and clone it, but you cannot make any changes to its state, such as pushing and creating new issues, pull requests or comments.
nixos-config/hosts/pinix/localServices/calendar
pijukka ec6d335aff docs: prune completed Herdr task papers, retire session log
Deletes 13 docs/intent/ files whose tasks are already shipped (verified
against git log, not just their own "Status" headers) plus docs/log.md
and docs/notiz-hardware-architektur.md, whose content is superseded or
fully absorbed elsewhere. Two still-open intent docs
(forgejo-umzug-blunix, ipv6-mango-connector-poc) get folded into
docs/roadmap.md as visible open items instead of sitting unlinked in
docs/intent/.

Cross-checked every candidate against the rest of the repo first:
agents/design/herdr-self-close-entwurf.md and
docs/intent/sdlc-playbook-prozessergaenzungen.md are read at runtime by
agents/tests/roles.sh (would have broken the test suite), and
docs/hdm-klon-plan.md, docs/herdr-eskalation-review-entwurf.md,
docs/graphify-dashboard-plan.md and docs/intent/kanban-board-admin-trigger.md
are actively cited by live service code or explicitly declared to remain
elsewhere — all six were kept out of the original consolidation list for
that reason.

Cleaned up dangling docs/log.md references left behind in README.md,
operator-prompt.md, memory/INDEX.md, memory/project-context.md,
docs/repos.md, dex.nix, radicale.nix and finance-agent/README.md.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QYZodsX5a4ksWdrwKXSExX
2026-09-05 16:57:19 +02:00
..
default.nix host(pinix): replace OWC with calendar frontend 2026-07-28 14:50:35 +02:00
H+9wX expose public calendar 2025-09-20 09:08:10 +02:00
radicale.nix docs: prune completed Herdr task papers, retire session log 2026-09-05 16:57:19 +02:00
README.md docs: write pinix/blunix/user READMEs, reorganize blunix's traefik-*.nix 2026-07-31 12:05:17 +02:00
web.nix calendar: add public Lukas calendar frontend 2026-07-30 11:14:02 +02:00

Calendar setup

The calendar stack runs on pinix:

  • Radicale provides CalDAV on the pinix Tailnet address, port 5002.
  • Nginx serves a small read-only FullCalendar frontend on the pinix Tailnet address, port 5001.
  • LLDAP authenticates users who edit calendars through CalDAV.
  • Each authenticated LLDAP user owns a private calendar namespace below their username and can create calendars there.
  • All authenticated LLDAP users can edit calendar/public, but cannot delete or overwrite the shared collection itself. Anonymous access remains read-only.

Access

Changed 29.07.2026: cal.0ff.space and Radicale used to share the same hostname, resolving differently depending on network (frontend externally, Radicale directly inside the Tailnet) because two separate Traefik routers matched it. That ambiguity was the likely cause of changes not showing up consistently (Vikunja #125). Now:

  • cal.0ff.space is always the read-only frontend, on both networks.

  • Radicale moved to its own internal-only hostname:

    https://calendar.0ff.space
    

    Only resolvable inside the Tailnet (AdGuardHome rewrite in hosts/blunix/localServices/adguard.nix); there is no public route to Radicale directly. Point CalDAV clients at calendar.0ff.space while on the Tailnet.

On the public Traefik entrypoint, two hostnames serve the same read-only calendar frontend:

https://cal.0ff.space/calendar.ics
https://cal.lukasrosier.com/lukas.ics

cal.0ff.space only shows the shared calendar/public collection; cal.lukasrosier.com only shows Lukas's existing personal calendar collection. Nginx maps only the corresponding exact feed path to Radicale. The other feed path returns an empty valid calendar because the shared frontend bundle requests both paths. Other public paths are never proxied to Radicale. Both feed proxies remove credentials and cookies, all feed locations permit only GET and HEAD, and Radicale's rights keep anonymous access read-only.

The HTML served on cal.lukasrosier.com gets a host-specific title and subscription link through Nginx's sub_filter; both hosts still use the same build from lukas/calendar-web.

Changed 30.07.2026: the frontend's source moved out of this flake into its own Forgejo repo, lukas/calendar-web. nginx's root for cal.${domain} now points at /var/www/calendar-web/dist on pinix, kept current by the gitpull-calendar-web webhook hook (hosts/pinix/localServices/webhook.nix): push to the repo's dev branch, the hook git pulls, runs npm ci && npm run build (esbuild bundles FullCalendar + ical.js), done -- no nixos-rebuild needed for frontend-only changes. This flake still owns the nginx vhost, firewall ports, and the CSP/proxy config around it.

Network boundaries

Radicale and the frontend listen only on the pinix Tailnet address. The NixOS firewall permits ports 5001 and 5002 only on tailscale0. Public traffic passes through Traefik on blunix.

Boot-time gotcha, fixed 31.07.2026: because Radicale binds directly to the tailnet address, it can fail once at boot with "Cannot assign requested address" if it starts before tailscaled-autoconnect.service has actually assigned that address -- found after a real pinix reboot, where it stayed down instead of recovering on its own. radicale.nix now depends on tailscaled-autoconnect.service and disables systemd's restart-limit (StartLimitIntervalSec = 0), the same fix already in place for gotosocial.nix/webdav.nix.

Verification

After deployment:

  1. Check that Radicale and Nginx are active.
  2. Confirm that ports 5001 and 5002 listen only on the pinix Tailnet IP.
  3. Verify that https://cal.0ff.space/calendar.ics is anonymously readable and rejects methods other than GET and HEAD.
  4. Verify that https://cal.lukasrosier.com/lukas.ics is anonymously readable and rejects methods other than GET and HEAD.
  5. Confirm that each frontend displays only its corresponding feed and that neither hostname exposes a direct Radicale path.
  6. Verify an authenticated LLDAP CalDAV login.
  7. Create and delete a temporary event in calendar/public with two different authenticated accounts.
  8. Create a personal calendar and verify that another account cannot read it.