Files
busyfeed/README.md
Lucas Orth 45d881f9a9 busyfeed: Initial commit mit Tag-Deployment
Bestehende App plus Gitea-Actions-Pipeline: Tag v* loest Build, Tests
und Container-Neustart auf dem VPS aus.

- .gitea/workflows/deploy.yml: npm ci/build/test als Gate, danach
  docker compose up -d --build. Die .env wird aus dem Repo-Secret DOTENV
  erzeugt, damit auf dem Host keine Secret-Datei gepflegt werden muss.
- docker-compose.yml: fester Projektname (der CI-Job hat ein anderes
  Arbeitsverzeichnis als der Host), Image mit Versionstag fuer Rollback,
  fester Volume-Name.
- README: Deployment- und Rollback-Abschnitt, Token-Rotation angepasst.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-19 19:56:54 +02:00

119 lines
4.6 KiB
Markdown

# busyfeed
Belegtzeiten aus dem Exchange-Kalender auf dem iPhone als ICS-Feed fuer Cal.com.
Nach aussen gehen **ausschliesslich Start- und Endzeitpunkte**. Keine Titel, keine
Orte, keine Teilnehmer, keine Notizen. Das ist keine Einstellung, die man falsch
setzen kann: Der Kurzbefehl liest diese Felder gar nicht erst aus, der Server kann
sie also weder speichern noch ausliefern.
```
iPhone (Kurzbefehl, 4x taeglich) -> POST /api/sync -> GET /feed/<token>.ics -> Cal.com
```
## Server einrichten
Zwei Tokens erzeugen:
```bash
node -e "console.log(require('crypto').randomBytes(32).toString('base64url'))" # SYNC_TOKEN
node -e "console.log(require('crypto').randomBytes(32).toString('base64url'))" # FEED_TOKEN
```
Dann in Gitea ein Repo-Secret `DOTENV` anlegen (Settings -> Actions -> Secrets)
mit dem kompletten `.env`-Inhalt - Vorlage ist `.env.example`. Auf dem Server
liegt keine `.env`; das Deployment erzeugt sie daraus, siehe
[Deployment](#deployment).
Im nginx proxy manager einen Proxy Host anlegen: `busy.example.de` -> `busyfeed:8080`,
Let's-Encrypt-Zertifikat ausstellen, "Force SSL" aktivieren. Das Compose-File hat
bewusst **kein** `ports:`-Mapping - NPM erreicht den Container ueber das gemeinsame
Netz `nginx-proxy-manager_default`, es liegt nichts offen am Host.
## Der Kurzbefehl
Kurzbefehle-App, neuer Kurzbefehl `Belegtzeiten senden`:
1. **Kalendereignisse suchen**
- Filter: `Kalender` ist *[dein Exchange-Kalender]*
- Filter: `Startdatum` ist nach `heute -1 Tag` **und** vor `heute +90 Tage`
- Limit: aus
2. **Wiederholen mit jedem Element**
- `Details von Kalendereignissen abrufen` -> *Startdatum* -> `Datum formatieren` -> ISO 8601
- dasselbe fuer *Enddatum*
- `Text`: `[Startdatum]|[Enddatum]`
3. **Text kombinieren**, Trennzeichen: neue Zeile
4. **Inhalt von URL abrufen**
- URL `https://busy.example.de/api/sync`, Methode `POST`
- Header `Authorization: Bearer <SYNC_TOKEN>`
- Request Body `JSON`, ein Feld `events` (Text) -> die kombinierte Textvariable
Ein einzelnes Textfeld statt eines echten JSON-Arrays ist Absicht: Arrays in
Shortcuts zu bauen ist muehsam und fehleranfaellig.
**Automationen:** Kurzbefehle -> Automation -> Tageszeit, vier Stueck
(z. B. 07:00 / 11:00 / 15:00 / 19:00), jeweils "Vor dem Ausfuehren fragen" **aus**.
Stuendlich geht nicht - Tageszeit-Automationen kennen nur taeglich/woechentlich/monatlich.
## Cal.com verbinden
Settings -> Calendars -> "Check for conflicts" -> +Add -> ICS Feed -> Feed-URL einfuegen.
## Verhalten im Fehlerfall
| Situation | Verhalten |
|---|---|
| Snapshot veraltet | Wird **weiter ausgeliefert**. Ein alter Belegt-Feed ist sicherer als ein leerer - leer hiesse "alles frei" und wuerde Doppelbuchungen verursachen. Das Alter steht im `X-WR-CALDESC`. |
| Sync liefert null Termine | Wird mit **409 abgelehnt**, der alte Snapshot bleibt. Sonst blankt ein kaputter Kurzbefehl den Feed. Bewusstes Leeren: `POST /api/sync?allowEmpty=1`. |
| Einzelne Zeilen unparsbar | Werden uebersprungen und in der Antwort als `skippedLines` zurueckgemeldet. |
| Falsches Feed-Token | `404`, nicht `401` - die Existenz des Feeds soll ohne Token nicht erkennbar sein. |
## Nicht verhandelbar
- **Alle Zeiten als UTC mit `Z`, niemals `TZID`.** Cal.com interpretiert `TZID` falsch
([calcom/cal.com#14664](https://github.com/calcom/cal.com/issues/14664), offen seit
April 2024). Termine landen sonst zur falschen Uhrzeit und es entstehen Doppelbuchungen.
- **Kein `RRULE`.** Der Kurzbefehl liefert Serientermine bereits als Einzelvorkommen.
Beides ist durch Tests abgesichert: `npm test`.
## Sicherheit
Die Feed-URL ist eine **Capability-URL** - ICS-Abos koennen in vielen Clients keine
Authentifizierung, der Schutz ist allein der unratbare Token im Pfad. Diese URL ist
faktisch ein Passwort und gehoert in keinen Chat und kein Ticket. Zum Rotieren:
`FEED_TOKEN` im Secret `DOTENV` aendern, neuen Tag pushen, Abo in Cal.com neu anlegen.
## Deployment
Deployt wird per Tag. Kein SCP, kein manuelles Neubauen.
```bash
git tag v1.0.1
git push origin v1.0.1
```
Der Gitea-Runner auf dem VPS baut, testet und startet den Container neu.
**Rote Tests brechen den Lauf ab, bevor irgendetwas deployt wird.**
Vorausgesetzt: das Repo-Secret `DOTENV` ist gesetzt und auf dem VPS laeuft ein
registrierter `act_runner` mit Zugriff auf den Docker-Socket.
Rollback auf eine aeltere Version, direkt auf dem VPS:
```bash
IMAGE_TAG=v1.0.0 docker compose up -d
```
Ohne `--build` - das alte Image liegt noch lokal.
## Lokal entwickeln
```bash
npm install
npm run build
npm test
SYNC_TOKEN=lokal-test-token-1234 FEED_TOKEN=lokal-feed-token-1234 \
DATA_DIR=./.local-data PORT=8099 node dist/server.js
```