oficina / calendar Public

Clone
git clone https://amadan.net/oficina/calendar

Plain git — no account needed to clone.

Download

Download this file

Calendar

A calendar for one team, on that team's own instance. Part of the oficina suite, alongside docs and sheets.

Live at https://calendar.bds.oncarlos.com.

What it does

Day, week, month and agenda views. Events that repeat. Reminders by email. Import and export as .ics, and subscription links you can paste into Google or Apple Calendar.

Every screen works with JavaScript switched off — including the week grid, whose overlapping events are laid out on the server, and click-a-time-to-create, which is an ordinary link for every half hour of the day.

What is sealed, and what is not

Titles, places and notes are encrypted at rest. The database file this instance keeps — the one the platform replicates to object storage — never holds a readable one. There is a test that greps the raw bytes.

Times are not. They cannot be: every view is a range query over them, the reminder scheduler reads them before the app has opened anything, and the platform's own timer registry holds every reminder instant in plaintext regardless. So:

> Publish this database and the worst anyone learns is the shape of > the team's time — when people are busy, for how long, how often — > and who is on the team. Never what any event is, where it is, or what > was written about it.

That is a real leak, and a larger one than it first sounds: a team's free/busy says a lot. It is stated here rather than left for somebody to discover.

Three more things sit outside the seal:

  • A reminder email carries the title. That is the product working, but the mail relay's logs are not ours.
  • A subscription link puts this calendar's plaintext into whatever account you paste it into — Google, Apple, a phone. The share page says so before it offers you the button.
  • A compromised running host reads everything. The instance holds the key while it runs. We never claim we cannot read your data.

Time zones

An event's record is the wall-clock time you typed plus the zone you meant it in. The UTC instant is derived from those, and only ever used as an index.

That matters when a government moves a daylight-saving boundary and a new time zone database ships. A calendar that stored the instant would now have your 09:00 standup at 08:00, with nothing able to say what was originally meant. Here the standup is still at 09:00 and only the index moves.

Your zone is a setting, never guessed from your browser. A calendar that silently re-times itself because you opened it on holiday is a calendar that lies to you.

Repeating events

The repeat picker offers six choices, and they are labelled against the event you are editing — "Every Wednesday", not "Weekly".

A calendar you import may use a rule more complicated than those six. It is kept exactly as it arrived and exported unchanged; its future occurrences are not drawn on the grid, and the event's page shows you the rule. Nothing is thrown away.

Reminders

The instance sleeps when nobody is using it, which is what makes it cheap to run and what makes reminders interesting. An ordinary timer inside the process would not fire, because the process is not there.

So the app tells the platform when it next has work, and the platform wakes it then. A six-hourly check is the backstop, and any page view opportunistically sends anything overdue.

A reminder can still arrive late — a machine has to wake up first. When that happens the email says so, rather than pretending it was on time.

Exports and subscriptions

.ics export carries IANA time zone identifiers and no VTIMEZONE block. Google Calendar, Apple Calendar and Thunderbird resolve zones from their own databases and are fine. Outlook desktop may not be — it has historically resolved only the zones it finds inside the file. If you import one of our files into Outlook and see a wrong time, that is why, and it is worth telling us.

A subscription link is a capability: anyone holding it can read that calendar, because a calendar app cannot sign in. Only its hash is stored, so it is shown once when you create it and never again. Revoke it and it stops working.

Subscribed clients poll constantly — Apple roughly every fifteen minutes — and each poll wakes this instance. The share page shows you how many times each link has been polled, because a feed is not free.

Running it

make ci        # vet, gofmt, tests, migration check — the one gate
make release   # the stripped linux/arm64 binary

Locally:

go run ./cmd/calendar -env development -addr localhost:8099 -db /tmp/calendar.db

-env development is the only way to get development behaviour: it is never honoured from the environment, because platform config is environment variables and a stray CALENDAR_ENV=development in production would open the allowlist and put sign-in links in the log.

In production the app refuses to start without CALENDAR_ORIGIN, CALENDAR_INSTANCE_KEY, CALENDAR_DEK, CALENDAR_ALLOWED_ADDRESSES and a mail relay. Each refusal names what to set. A misconfigured instance is down rather than quietly wrong.

Deploying

make release
carlos deploy --account oficina --app calendar --channel edge \
  --timeout 5m releases/calendar-linux-arm64

Then check the process, not the edge:

curl -sS https://calendar.bds.oncarlos.com/api/version

X-Carlos-Version is the router's adopted release and flips the moment the promote lands. The process takes about another 110 seconds to cycle.

stable needs a passkey step-up in the console, and so does moving an instance between stable and edge. This app runs on edge.

Reading further

  • AGENTS.md — the binding rules, and the reasoning behind each
  • PRODUCT.md — what this is for and what it deliberately is not
  • docs/superpowers/specs/ — the design
  • docs/superpowers/brainstorms/ — the raw material, including the alternatives that were rejected and why