Lucas Orth 2d024a7f54
All checks were successful
Build & Deploy / deploy (push) Successful in 2m38s
Deploy gegen fehlendes Secret und Crash-Schleife absichern
Zwei Faelle, in denen der Lauf gruen geworden waere, obwohl nichts laeuft:

- Ist das Secret DOTENV nicht gesetzt, entstand eine leere .env. Der
  Server wirft dann beim Start (requireEnv), der Container startet
  endlos neu - der Workflow haette das nicht gemerkt. Jetzt bricht der
  Schritt vorher ab.
- "docker compose up -d" kehrt zurueck, sobald der Container erzeugt ist,
  nicht wenn er laeuft. Mit --wait wird der vorhandene Healthcheck zum
  Gate; --wait-timeout 180, weil das erste Health-Ergebnis beim
  Intervall von 60s entsprechend spaet kommt.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-20 18:58:45 +02:00

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:

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.

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, 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.

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:

IMAGE_TAG=v1.0.0 docker compose up -d

Ohne --build - das alte Image liegt noch lokal.

Lokal entwickeln

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
Description
No description provided
Readme 47 KiB
Languages
TypeScript 64.5%
JavaScript 32.6%
Dockerfile 2.9%