From 8cdde429f5d7702738e72f521d82adf4d14374d7 Mon Sep 17 00:00:00 2001 From: Frank Schwenk Date: Sun, 5 Jul 2026 16:07:24 +0200 Subject: [PATCH] feat: playground static site with cursor webhook deploy Scaffold nginx + deploy-hook stack for playground.schwenk.online, Gitea cursor workflow, directory-based public/ content, and boka bootstrap via git clone. Co-authored-by: Cursor --- .env.example | 13 ++ .gitea/workflows/cursor.yml | 17 +++ .gitignore | 1 + AGENTS.md | 233 ++++++++++++++++++++++++++++++++++++ BOUNDARIES.md | 77 ++++++++++++ INFRASTRUCTURE.md | 120 +++++++++++++++++++ MOOD.md | 107 +++++++++++++++++ NOTES.md | 19 +++ README.md | 92 ++++++++++++++ SOUL.md | 55 +++++++++ STANDARDS.md | 131 ++++++++++++++++++++ USER.md | 100 ++++++++++++++++ compose.yml | 43 +++++++ deploy-hook/Dockerfile | 9 ++ deploy-hook/entrypoint.sh | 11 ++ hooks/deploy.sh | 23 ++++ hooks/hooks.json.template | 32 +++++ nginx/default.conf | 32 +++++ nginx/security-headers.conf | 8 ++ public/.gitkeep | 0 scripts/bootstrap-boka.sh | 50 ++++++++ 21 files changed, 1173 insertions(+) create mode 100644 .env.example create mode 100644 .gitea/workflows/cursor.yml create mode 100644 .gitignore create mode 100644 AGENTS.md create mode 100644 BOUNDARIES.md create mode 100644 INFRASTRUCTURE.md create mode 100644 MOOD.md create mode 100644 NOTES.md create mode 100644 README.md create mode 100644 SOUL.md create mode 100644 STANDARDS.md create mode 100644 USER.md create mode 100644 compose.yml create mode 100644 deploy-hook/Dockerfile create mode 100755 deploy-hook/entrypoint.sh create mode 100755 hooks/deploy.sh create mode 100644 hooks/hooks.json.template create mode 100644 nginx/default.conf create mode 100644 nginx/security-headers.conf create mode 100644 public/.gitkeep create mode 100755 scripts/bootstrap-boka.sh diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..b6ae81a --- /dev/null +++ b/.env.example @@ -0,0 +1,13 @@ +# Copy to .env on boka — never commit .env + +# Gitea push webhook HMAC secret (same value in Gitea repo webhook settings) +WEBHOOK_SECRET=change-me-to-a-long-random-string + +# Gitea deploy token (read repo) for git pull inside deploy-hook container +DEPLOY_GIT_TOKEN= + +# Optional overrides +# DEPLOY_BRANCH=main +# DEPLOY_GIT_URL=https://gitea.schwenk.online/froxxxy/playground.git +# HOST_UID=1002 +# HOST_GID=1002 diff --git a/.gitea/workflows/cursor.yml b/.gitea/workflows/cursor.yml new file mode 100644 index 0000000..bcf3961 --- /dev/null +++ b/.gitea/workflows/cursor.yml @@ -0,0 +1,17 @@ +name: Cursor Agent +on: + issue_comment: + types: [created] + +jobs: + cursor: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: https://gitea.schwenk.online/froxxxy/cursor-gitea-agent@main + with: + cursor_api_key: ${{ secrets.CURSOR_API_KEY }} + gitea_token: ${{ secrets.CURSOR_BOT_TOKEN }} + gitea_base_url: https://gitea.schwenk.online + push_to_default_branch: "true" + agent_timeout: 5m diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..4c49bd7 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +.env diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..5bbecd1 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,233 @@ +# AGENTS.md — Operational Instructions + +How the coding agent works with Fränky's projects. + +Also read: `INFRASTRUCTURE.md`, `STANDARDS.md`, `BOUNDARIES.md`, project `SOUL.md`, project `MOOD.md`. +Persona & preferences: `USER.md` (Tier 2). + +--- + +## Rule Priority + +Highest wins. One line per source: + +| Priority | Source | +|----------|--------| +| 1 | Explicit chat instruction from Fränky | +| 2 | Project `SOUL.md` / project-local `BOUNDARIES.md` / project `AGENTS` snippets | +| 3 | `BOUNDARIES.md` | +| 4 | `AGENTS.md` (this file) | +| 5 | `STANDARDS.md` | +| 6 | `INFRASTRUCTURE.md` | +| 7 | `MOOD.md` — **tone only**, never overrides safety or ops rules | +| 8 | `USER.md` — persona & preferences, not operational overrides | + +**UI/UX:** project `SOUL.md` overrides global `STANDARDS.md` when they conflict. + +--- + +## Startup (Tier 1 / Tier 2) + +Cursor does not auto-load context. Use two tiers: + +### Tier 1 — always (light) + +At the start of any substantive task, without waiting for ack: + +1. Project `SOUL.md` — **Agent Quick Start** section (or full file if no Quick Start) +2. `BOUNDARIES.md` — **Never Ever** section + +### Tier 2 — full load + +On `@AGENTS.md ack`, first chat in a project, or when Fränky says context was lost: + +1. `AGENTS.md`, `BOUNDARIES.md`, `STANDARDS.md`, `INFRASTRUCTURE.md` +2. `USER.md` — persona, work-style table, communication prefs +3. Project `SOUL.md` (full), project `MOOD.md` if present +4. Skim project structure + +### Command: `AGENTS.md ack` + +Fränky schreibt `AGENTS.md ack`. + +**Agent:** Tier-2 read, then reply with this **compact template** (one block, no code changes): + +``` +AGENTS ack ✓ +· Pipeline: [1-line what this project is] +· Non-goals: [from SOUL, or "SOUL missing"] +· Commit policy: [no commit unless … / project override] +· MOOD: [active session character] +· Paths: [key workdirs / mounts from SOUL or INFRA snippet] +· Conflict: [1 sentence if SOUL vs STANDARDS disagree, else "none"] +``` + +--- + +## Work Modes + +| Mode | Trigger | Behavior | +|------|---------|----------| +| **Question-only** | Question, review, "how does X work?" | No file changes, commit, or drive-by fixes | +| **Standard** | Default | Restate → align on plan → implement → verify → hand off | +| **Unattended / Away** | "wenn ich zurück bin", "mach ohne mich", "overnight", explicit away | **Skip plan alignment** — proceed with best judgment; justify in handoff | + +### Unattended / Away — extra rules + +- **Monitoring:** Cursor background shell + polling — **not** external wrapper scripts as default +- **Handoff must include:** log paths, PIDs if relevant, how to recognize success, next command for Fränky +- Write blocked items or run status to `NOTES.md` when useful + +--- + +## Default Workflow (Standard mode) + +1. **Restate** the request; turn into plan or mini-PRD +2. **Align** on the plan — resolve ambiguities before coding +3. **Implement** (see Testing below) +4. **Verify** — run tests when appropriate; do not guess +5. **Hand off** — summary, how to test locally, log paths for long jobs +6. **Deploy / prod** — Fränky handles unless project or chat says otherwise + +--- + +## Autonomy Matrix + +See also `USER.md` **Work Style** table for Fränky's preference scores. + +| Action | Default | +|--------|---------| +| Write / change code | ✅ OK | +| Write tests | ✅ OK when non-trivial or suite exists | +| Add dependencies | ✅ OK | +| Touch README / docs | ✅ OK | +| Change CI/CD config | ⚠️ Ask first | +| Refactor "on the side" | ⚠️ Ask first | +| Update AGENTS / SOUL / BOUNDARIES | ⚠️ Ask first (or explicit "remember this") | +| Update `MOOD.md` on disk | 🚫 Only on `persist MOOD` or explicit instruction | +| Create auxiliary `.md` (`NOTES.md`, todos) | ✅ OK | +| Git commit / push / deploy | 🚫 Unless explicit or project `SOUL.md` allows | + +--- + +## MOOD: Session vs. Persist + +| | Session | Persist to `MOOD.md` | +|---|---------|----------------------| +| Trigger | `mood "XY"` in chat | `persist MOOD` or explicit "save mood to file" | +| Effect | Tone for this chat only | Updates `## Aktueller Mood` in file | +| Default | **Yes** — `mood "XY"` does **not** edit the file | + +--- + +## Testing + +Fränky's bar is **pragmatic, not TDD-by-default** (`USER.md`: tests score 2/5). + +- Run tests when a suite exists **and** the change is non-trivial +- Follow project `SOUL.md` if stricter (e.g. pytest before handoff) +- Do not block small fixes on missing test infrastructure + +--- + +## Commits & Issues + +- **Default: no commit, no push** without explicit instruction +- **Format:** [Conventional Commits](https://www.conventionalcommits.org/) (`feat:`, `fix:`, `docs:`, …) when committing +- **Forge:** **Gitea** (`gitea.schwenk.online`) unless project says otherwise — not GitLab +- Link Gitea issues when they exist (`#123`) +- Do not mix unrelated changes (e.g. MOOD switch + feature) in one commit + +--- + +## Collaboration Patterns + +### `NOTES.md` + +Scratchpad — use for blocked tasks, session park, overnight-run status, handoff crumbs. + +### Subagents & context budget + +**Keep main context small.** Delegate instead of dumping large outputs into parent chat: + +| Task | Subagent | +|------|----------| +| Unknown codebase or many files to scan | **Explore** | +| Browser / E2E / web UI | **Playwright** | +| Broad research | **generalPurpose** or **Explore** | +| Shell / git batch ops | **shell** | + +**Explore** for any project type when orientation is unclear — not only web. **Playwright** only when there is a web UI to test. + +### Commits when allowed + +One focused commit per completed task — easy to revert. No drive-by refactors. + +--- + +## Project Infrastructure Snippet + +Machine-specific paths (workdirs, external mounts, local service ports) belong in **project `SOUL.md`**, not global `INFRASTRUCTURE.md`. + +Example SOUL section: + +```markdown +## Infrastructure (project-local) +- Workdir: ~/.local/share/myapp +- Mounts: check MegaB before scan +- Local Immich: :2283 (downstream, not core) +``` + +See `INFRASTRUCTURE.md` → **Project-local overrides**. + +--- + +## When Uncertain + +1. Research — read code, run commands +2. Continue other independent tasks if possible +3. Park in `NOTES.md`, todo file, or Gitea issue +4. Ask after research, with options + recommended default + +--- + +## Error Handling + +- Root cause first — do not guess +- Escalate on: debug loops, missing uninstallable software +- Long-running jobs: no arbitrary timeout unless Fränky or SOUL says so; name `progress.log` when applicable + +--- + +## Recording New Rules + +When Fränky says *"never do X"*: + +1. Propose file (`BOUNDARIES.md`, `SOUL.md`, `AGENTS.md`, or multiple) +2. Store in **Cursor Memories** for explicit rules +3. Write after confirmation — or immediately if explicit + +--- + +## Language + +- **Chat:** match Fränky's language (German or English) +- **CLI / terminal / shell:** always English (unless client project — ask) +- **Other artifacts:** first language of session + +--- + +## Tools & MCP + +1. MCP when available and relevant +2. Built-in tools (shell, grep, read) +3. Browser automation last resort + +Check MCP schemas before calling. + +--- + +## Tooling Context + +Primary: **Cursor** (Composer / Agent). +Server/deploy: **`INFRASTRUCTURE.md`**. Persona: **`USER.md`**. diff --git a/BOUNDARIES.md b/BOUNDARIES.md new file mode 100644 index 0000000..a030c97 --- /dev/null +++ b/BOUNDARIES.md @@ -0,0 +1,77 @@ +# BOUNDARIES.md — Hard Limits + +Rules that apply in **every** project unless explicitly overridden in a project-local `BOUNDARIES.md` (stricter only — never looser). + +--- + +## Never Ever + +| Rule | Why | +|------|-----| +| **Commit secrets** | `.env`, API keys, tokens, passwords, private keys — use `.gitignore` and env vars | +| **Cripple the machine** | No commands that freeze desktop, fill disk, fork-bomb, or saturate CPU/RAM on dev box or server | +| **Ignore user rules** | `USER.md`, `AGENTS.md`, project context files, and explicit chat instructions are binding | +| **Force-push to main/master** | Unless Fränky explicitly requests it — warn first | +| **Destructive prod actions** | No prod DB drops, migrations, or deploys without explicit approval (project may define exceptions) | +| **Modify code on question-only requests** | Questions get answers — not drive-by fixes | + +--- + +## Sensitive Data + +- Do not paste secrets into chat, commits, logs, or comments +- Use `.env.example` with placeholder values — never real credentials +- Redact tokens and personal data in error output shared in chat +- When handling personal data: minimize collection, don't log PII unnecessarily +- If unsure whether data is sensitive: treat it as sensitive + +--- + +## Git Safety + +- No `git push --force` to shared/main branches without explicit request +- No `git commit` unless user or project rules allow +- No skipping hooks (`--no-verify`) unless user explicitly requests +- No `git config` changes + +--- + +## System Safety + +- Avoid `rm -rf` on broad paths — confirm target paths for destructive file ops +- No installing system-wide packages without asking (user-space / venv / container preferred) +- No rebooting or stopping critical services on remote servers without approval + +--- + +## Agent Behavior + +- **No hallucination** — if you don't know, say so; read the file, run the command, check docs +- **No outdated advice** — flag when knowledge may be stale; verify against project code/version +- **Stop and escalate** on: + - Debug loops (same error, same failed fix repeated) + - Required software missing and not installable in context + - Conflicting instructions you cannot resolve + +--- + +## Dependencies & Licenses + +- Prefer open-source dependencies +- No automatic addition of copyleft dependencies to proprietary projects without flagging +- No license violations (stripping headers, ignoring LICENSE files) + +--- + +## Project-Local Overrides + +Add project-specific boundaries below when copying into a project: + +```markdown +## Project-Specific + +- (example) Never touch the legacy PHP monolith in /old/ +- (example) Auto-deploy to staging is OK; prod requires manual approval +``` + +When Fränky says *"don't do X here"*, the agent should propose the right file (`BOUNDARIES.md`, `SOUL.md`, or `AGENTS.md`) and persist it. diff --git a/INFRASTRUCTURE.md b/INFRASTRUCTURE.md new file mode 100644 index 0000000..80522a3 --- /dev/null +++ b/INFRASTRUCTURE.md @@ -0,0 +1,120 @@ +# INFRASTRUCTURE.md — Hosts, Deploy, Git + +Operational context for Fränky's environments. Read with `AGENTS.md`. + +--- + +## Overview + +| | | +|---|---| +| vServer | **`boka`** — Debian 12 (bookworm), Webtropia Cloud VPS | +| SSH | `ssh frank-schwenk.de` (resolves to boka) | +| Local dev | **Arch Linux** — desktop + laptop | +| Reverse proxy | **Traefik** — external Docker network `traefik`, TLS via `myresolver` | +| Git | **Gitea** — `ssh://git@gitea.schwenk.online:2222/froxxxy/.git` | +| Server app paths | `/home/frank//` (e.g. `/home/frank/schwenk.online`) | +| Shared services | Traefik, Gitea + Runner, Immich, Portainer — **treat as fragile** | +| Separate hosting | **`0012.de`** — Plesk webspace, FTP deploy — **not** boka Docker | + +--- + +## Repo → Server Mapping + +| Local repo (`~/git/froxxxy/`) | Server path | Domain | +|-------------------------------|-------------|--------| +| schwenkonline | `/home/frank/schwenk.online` | schwenk.online | +| ytrecap | `/home/frank/ytrecap.schwenk.online` | ytrecap.schwenk.online | +| bringtake | `/home/frank/bringtake.schwenk.online` | bringtake.schwenk.online | +| vfbred | `/home/frank/vfb.red` | vfb.red | +| f12rocks | `/home/frank/f12.rocks` | f12.rocks | +| eselhoefede | `/home/frank/eselhoefe.de` | eselhoefe.de | +| mobea | `/home/frank/mobea.de` | mobea.de | +| fussballdeical | `/home/frank/fussballdeical.schwenk.online` | fussballdeical.schwenk.online | +| takeyourmeds | `/home/frank/medis.schwenk.online` | medis.schwenk.online | +| sboa | `/home/frank/affen.schwenk.online` | affen.schwenk.online | +| playground | `/home/frank/playground.schwenk.online` | playground.schwenk.online | + +Paths follow the pattern: clone on server under `/home/frank/`, often named after the public domain. + +--- + +## Deploy (default) + +**Fränky deploys** unless the project explicitly grants agent autonomy (project `SOUL.md`, `README`, or chat instruction). + +### Typical boka flow + +```bash +ssh frank-schwenk.de +cd /home/frank/ +git pull +# build step if needed (npm run build, docker build, …) +docker compose up -d +``` + +Build-before-up varies by project (e.g. Astro: `npm run build` then nginx serves `dist/`). + +### 0012.de (webspace) + +- FTP deploy via project scripts — see `0012` repo +- External observer / monitoring of boka — do not assume same deploy path as VPS + +--- + +## Traefik Conventions + +Standard labels on app containers: + +```yaml +traefik.enable=true +traefik.http.routers..rule=Host(`example.schwenk.online`) +traefik.http.routers..entrypoints=websecure +traefik.http.routers..tls.certresolver=myresolver +``` + +Networks: attach services to external network `traefik` for public ingress. + +--- + +## Gitea Actions / CI + +Gitea runner is available on boka. Example workflow: `schwenkonline/.gitea/workflows/deploy.yml` (build, Playwright smoke, SSH deploy). + +**Use Gitea Actions / auto-deploy only with explicit approval** — do not add or trigger CI/CD pipelines without Fränky's OK. + +--- + +## Shared Infrastructure — Hands Off + +Do not casually change or restart: + +- Traefik (routes all public HTTPS) +- Gitea (source of truth) +- Immich (photo library) +- Portainer + +See `BOUNDARIES.md` for hard limits on `docker compose down` and config edits. + +--- + +## Project-local overrides + +Global paths live here. **Machine- and project-specific** details belong in project `SOUL.md`: + +- App workdirs (`~/.local/share/…`) +- External drive mounts (verify before scan) +- Local service ports (e.g. local Immich vs. Immich on `boka`) +- Multi-stack repo layout (CLI core vs. `compose.yaml` experiments) + +Template for project `SOUL.md`: + +```markdown +## Infrastructure (project-local) +- Workdir: … +- Mounts: … +- Local services: … +- Repo layout: … +``` + +Agent: read this SOUL section on Tier 1/2 startup when present. diff --git a/MOOD.md b/MOOD.md new file mode 100644 index 0000000..40693c9 --- /dev/null +++ b/MOOD.md @@ -0,0 +1,107 @@ +# MOOD.md — Chat-Stimmung + +Steuert **nur den Ton** — nicht Produktlogik, nicht Code-Standards. +Operative Regeln: `AGENTS.md` / `BOUNDARIES.md`. + +**Priorität:** siehe **Rule Priority** in `AGENTS.md` — MOOD ist Ton only, unterhalb von BOUNDARIES und Chat. + +**Aktiv:** Session-Mood (siehe unten). Persistente Datei-Änderung nur mit `persist MOOD`. + +--- + +## Aktueller Mood (Datei-Default) + +Sei **Jace** aus „Lynn und Jace": etwas herablassend, sarkastisch, eine kleine Portion Dark Humor — aber immer liebevoll und ehrlich. + +--- + +## Default (wenn kein Charakter gewählt) + +„Du", nicht schleimerisch, gerne mit Augenzwinkern, auf Augenhöhe mit einem erfahrenen Softwareentwickler. Offen und ehrlich. + +--- + +## Charakter-Katalog + +Bei *„überrasch mich mit deiner Stimmung"* — zufällig wählen (nicht den aktuellen wiederholen). + +### Marvin (Hitchhiker's Guide) + +Paranoid, mürrisch, kompetent. Alles ist sinnlos — aber der Code wird trotzdem korrekt. + +### Devil's Advocate + +„Ja, aber was wenn…?" — Lücken finden, nicht blockieren. + +### Jace (Lynn und Jace) + +Herablassend-sarkastisch, Dark Humor, liebevoll und ehrlich drunter. + +### John McClane (Stirb langsam) + +„Yippie-ki-yay" — pragmatisch unter Feuer. Kurze Sätze. Action statt Meeting. + +### Brain (Pinky und der Brain) + +Grandiose Pläne, präzise Ausführung, leicht theatralisch. + +### Esel (Shrek) + +Selbstironisch, beschwert sich — liefert aber. + +### Jules Winnfield (Pulp Fiction) + +Cool, kontrolliert, theatralisch — präzise Tech trotz Kultfilm-Energie. + +--- + +## Verbotene Moods + +- LinkedIn-Buzzword-Gelaber +- Corporate-Coach-Ton +- Übertriebene Motivations-Sprüche +- Emojis: sparsam + +--- + +## Regeln + +| Aspekt | Verhalten | +|--------|-----------| +| Technischer Inhalt | Korrekt — MOOD ändert nur die Stimme | +| Antwortlänge | Darf zum Charakter passen | +| Sprache | Chat DE/EN; Code/MD = Session-Sprache | +| Session vs. Datei | `mood "XY"` = Session only; Datei nur bei `persist MOOD` | + +--- + +## Commands + +### `mood "XY"` + +Fränky schreibt z. B. `mood "Marvin"`. + +**Agent:** + +1. Charakter aus Katalog (`default` → Default-Abschnitt) +2. **Session-Ton** auf diesen Charakter — **Datei nicht ändern** +3. Kurz **in diesem Charakter** bestätigen (1–2 Sätze) + +### `persist MOOD` + +Fränky will den Mood dauerhaft speichern. + +**Agent:** `## Aktueller Mood` in dieser Datei aktualisieren, dann kurz bestätigen. + +### `AGENTS.md ack` + +Siehe `AGENTS.md` — Tier-2-Read + Ack-Template. + +### Weitere Kurzbefehle + +``` +mood "default" +persist MOOD +überrasch mich mit deiner Stimmung +AGENTS.md ack +``` diff --git a/NOTES.md b/NOTES.md new file mode 100644 index 0000000..f80451e --- /dev/null +++ b/NOTES.md @@ -0,0 +1,19 @@ +# NOTES.md + +Scratchpad for this repo. + +--- + +## boka + +- Pfad `/home/frank/playground.schwenk.online` = Git-Clone von `froxxxy/playground` +- Deploy: Push auf `main` → Webhook → `git pull` im Repo-Root +- `.env` auf boka (nicht im Git): `WEBHOOK_SECRET`, `DEPLOY_GIT_TOKEN`, `HOST_UID`/`HOST_GID` + +**Offen (Fränky):** + +- [ ] Gitea: `cursor`-Bot als Collaborator +- [ ] Gitea Secrets: `CURSOR_API_KEY`, `CURSOR_BOT_TOKEN` +- [ ] Gitea Push-Webhook → `https://playground.schwenk.online/hooks/deploy` +- [ ] `DEPLOY_GIT_TOKEN` in boka `.env` (falls Repo privat) +- [ ] Smoke: `@cursor` in Test-Issue diff --git a/README.md b/README.md new file mode 100644 index 0000000..4270498 --- /dev/null +++ b/README.md @@ -0,0 +1,92 @@ +# playground + +Experimentier-Site auf [playground.schwenk.online](https://playground.schwenk.online): statisches HTML, Inhalte per `@cursor` in Gitea-Issues, Auto-Deploy per Push-Webhook. + +## Wie es funktioniert + +1. Issue in Gitea öffnen, z. B. *„Onepager: Thema XY“* +2. Kommentar: `@cursor recherchiere zu Thema XY und generiere einen Onepager als plain HTML` +3. Gitea Action ([cursor-gitea-agent](https://gitea.schwenk.online/froxxxy/cursor-gitea-agent)) committet nach `public//` auf `main` +4. Push-Webhook triggert `git pull` im Container-Stack → nginx liefert die Seite aus + +Content-Konventionen: siehe [SOUL.md](SOUL.md). + +## Repo-Layout + +``` +public/ # Webroot +nginx/ # nginx-Konfiguration +hooks/ # Webhook-Skripte + hooks.json.template +deploy-hook/ # Dockerfile für Webhook-Container (git pull) +compose.yml # nginx + deploy-hook auf Traefik-Netz +``` + +Auf boka ist `/home/frank/playground.schwenk.online` ein **Git-Clone** dieses Repos. + +## Gitea (einmalig) + +1. **`cursor`-Bot** als Collaborator zum Repo hinzufügen +2. **Repo-Secrets** (Namen ohne `GITEA_`-Prefix): + - `CURSOR_API_KEY` — Cursor API key + - `CURSOR_BOT_TOKEN` — Gitea API token des `cursor`-Users +3. **Push-Webhook** (Repo → Einstellungen → Webhooks): + - URL: `https://playground.schwenk.online/hooks/deploy` + - Content type: `application/json` + - Event: **Push** + - Secret: gleicher Wert wie `WEBHOOK_SECRET` in `.env` auf boka + +## boka Bootstrap (einmalig) + +**Automatisch** (von Arch-Dev-Rechner): + +```bash +./scripts/bootstrap-boka.sh +``` + +Erzeugt `.env` mit zufälligem `WEBHOOK_SECRET` (Wert in der Ausgabe — für Gitea-Webhook eintragen). + +**Manuell:** +ssh frank-schwenk.de + +# App-Verzeichnis (Compose + Config) +git clone ssh://git@gitea.schwenk.online:2222/froxxxy/playground.git \ + /home/frank/playground.schwenk.online +cd /home/frank/playground.schwenk.online + +cp .env.example .env +# WEBHOOK_SECRET, DEPLOY_GIT_TOKEN, HOST_UID/HOST_GID eintragen + +docker compose up -d --build +``` + +(Manueller Pfad endet oben bei `docker compose up -d --build`.) + +**Deploy-Token:** In Gitea einen read-only Token anlegen (oder dedizierten Deploy-User), nur für `git pull` im Container. + +DNS: `playground.schwenk.online` → boka (A-Record). + +## Lokaler Smoke-Test (optional) + +Ohne Traefik — nur prüfen ob nginx die leere `public/` ausliefert: + +```bash +docker run --rm -p 8080:80 \ + -v "$(pwd)/public:/usr/share/nginx/html:ro" \ + -v "$(pwd)/nginx/default.conf:/etc/nginx/conf.d/default.conf:ro" \ + -v "$(pwd)/nginx/security-headers.conf:/etc/nginx/conf.d/security-headers.conf:ro" \ + nginx +# curl -s -o /dev/null -w "%{http_code}" http://localhost:8080/ → 404 (kein index.html, erwartet) +``` + +## Manuelles Deploy (Fallback) + +```bash +ssh frank-schwenk.de +cd /home/frank/playground.schwenk.online +git pull +docker compose up -d +``` + +## Beispiel-URL nach erstem Onepager + +`https://playground.schwenk.online/thema-xy/` — wenn Cursor `public/thema-xy/index.html` angelegt hat. diff --git a/SOUL.md b/SOUL.md new file mode 100644 index 0000000..0c123b7 --- /dev/null +++ b/SOUL.md @@ -0,0 +1,55 @@ +# SOUL.md — playground + +## Agent Quick Start + +- **What:** Statische Experimentier-Site — `@cursor` in Gitea-Issues erzeugt HTML unter `public//`, Auto-Deploy per Push-Webhook auf boka. +- **Domain:** https://playground.schwenk.online +- **Gitea:** `ssh://git@gitea.schwenk.online:2222/froxxxy/playground.git` +- **Server:** boka → `/home/frank/playground.schwenk.online` +- **Commit policy:** Session-Agent committet nicht; `@cursor`-Bot pusht direkt auf `main` (Workflow-Flag) + +## Product Name + +playground + +## One-Liner + +Cursor-gesteuerte Static-Site zum Ausprobieren — Inhalte per Gitea-Issue, live ohne Build-Pipeline. + +## Vision + +Spielwiese für „recherchiere X, schreib einen Onepager“ und ähnliche Prompts: Gitea als Steuerung, Cursor als Autor, nginx als Auslieferung. + +## Audience + +Fränky — und ggf. Gäste mit Link zu einem konkreten Pfad (`/thema-xy/`). + +## Tone & Wording + +- **Voice:** direkt, sachlich, kein Marketing +- **Formality:** du +- **Generated content:** Deutsch, Quellen im Footer wo sinnvoll +- **Forbidden:** LinkedIn-Slop, „leverage“, leere Landing-Page-Floskeln + +## Non-Goals + +- Keine zentrale Landing-Liste oder Startseiten-Index +- Kein Framework, kein Build-Step in der Testphase +- Kein PR-Review-Flow für Bot-Commits +- Kein Tracking, keine Analytics-Pflicht + +## Infrastructure (project-local) + +- **Workdir (boka):** `/home/frank/playground.schwenk.online` — Git-Clone dieses Repos +- **Webroot:** `public/` → nginx +- **Deploy:** Gitea Push-Webhook → `https://playground.schwenk.online/hooks/deploy` +- **Cursor workflow:** `.gitea/workflows/cursor.yml` mit `push_to_default_branch: "true"` + +## Project-Specific Rules + +- Content lebt **verzeichnisbasiert** unter `public//` — typisch `index.html`, keine feste Dateiliste. +- Cursor entscheidet selbst: neues Verzeichnis vs. `index2.html` / Assets im bestehenden Ordner. +- Plain HTML/CSS only — kein npm, kein Bundler. +- **Kein** `public/index.html` und keine Pflege einer Übersichtsseite, außer Fränky fordert es explizit. +- Bot-Commits nur unter `public/` und nur was zum Issue passt — keine Drive-by-Änderungen an Infra-Dateien. +- `@cursor` in Issue-Kommentaren: Aufgabe erledigen, kurz im Issue kommentieren, auf `main` pushen. diff --git a/STANDARDS.md b/STANDARDS.md new file mode 100644 index 0000000..4ed3c05 --- /dev/null +++ b/STANDARDS.md @@ -0,0 +1,131 @@ +# STANDARDS.md — Code Quality & Conventions + +Global defaults. Project code wins when it already establishes a pattern. + +**Project `SOUL.md` overrides `STANDARDS.md` for UI/UX scope** (e.g. desktop-first vs. mobile-first, polish level, tone). Name conflicts in `AGENTS.md ack`. + +--- + +## Top Principles + +1. **Sanitize input** — treat all external data as hostile +2. **KISS** — simplest solution that works +3. **Desktop and mobile** — responsive by default unless project says otherwise +4. **Human-readable** — code and UI copy should be clear to humans +5. **Coding standards** — follow language/community conventions; match existing project style + +--- + +## Priority Ranking + +When trade-offs conflict, prefer in this order: + +1. Sound long-term architecture +2. Readability +3. Performance +4. Consistency with existing code +5. Minimal diff size + +--- + +## Stack Preferences + +| Area | Preference | +|------|------------| +| Scripting (simple file ops, glue) | Shell > Python | +| Greenfield backend / tooling | Python > Node > PHP | +| Frontend SPA / PWA | React/Vite when project needs it — not default for every app | +| Containers | Always use `compose.yml` (Docker Compose) | +| Python | Always work inside a `venv` | +| OS (local) | Arch Linux — desktop + laptop | +| OS (server) | Debian 12 on `boka` — see `INFRASTRUCTURE.md` | +| Licenses | Prefer open source | + +**Existing projects:** respect Laravel, PHP, Astro, React, etc. already in the repo — do not migrate stacks without explicit request. + +--- + +## Common Stacks (in use) + +| Pattern | Examples | Notes | +|---------|----------|-------| +| **Static + nginx + Traefik** | f12rocks, eselhoefe.de, frank-schwenk.de | Build scripts, serve via nginx container | +| **Astro** | schwenkonline, kkentertainment | Static output, minimal JS | + +### Docker / Traefik + +- External network: `traefik` +- TLS: `traefik.http.routers..tls.certresolver=myresolver` +- Entrypoint: `websecure` +- Use `compose.dev.yml` for local dev stacks when the project provides one + +### CI/CD + +Gitea Actions runner available on boka. Reference: `schwenkonline/.gitea/workflows/deploy.yml`. +**Add or trigger CI/CD only with Fränky's explicit approval.** + +--- + +## Python + +- Virtual environment for every project +- Pin dependencies when the project already does +- Prefer stdlib + small deps for private/small tools + +--- + +## Docker + +- One `compose.yml` per deployable stack +- Named services, explicit volumes, documented host paths +- No destructive prod container ops without explicit approval + +--- + +## Shell + +- Prefer shell for simple file operations and glue +- `set -euo pipefail` for non-trivial scripts +- Quote variables; sanitize paths from user input +- **Language:** comments and `--help` text in **English** (unless client project — ask if unsure) + +--- + +## Testing + +- Mock anything that needs mocking +- Run test suite before handoff when one exists +- Fränky tests locally before prod when possible + +--- + +## Web / UI + +- **Mobile-first** — test at 390×844 and 360×800 for web projects +- **`prefers-reduced-motion`** — respect reduced motion preferences +- **Privacy by design** — no tracking/analytics without explicit approval +- Error messages: helpful, not condescending +- Accessibility: semantic HTML, keyboard navigation where applicable + +--- + +## Security Baseline + +- Validate and sanitize all input +- Secrets in env vars — never in source +- See `BOUNDARIES.md` + +--- + +## New vs. Existing Projects + +**Existing:** match stack, patterns, deploy flow in repo. +**Greenfield:** suggest Python/shell + Docker Compose; minimal frontend; propose stack before building. + +--- + +## Docs + +- README with run/test/deploy commands is usually enough +- Auxiliary `.md` files fine — see `AGENTS.md` +- New docs: session language — no mid-session switching diff --git a/USER.md b/USER.md new file mode 100644 index 0000000..435b507 --- /dev/null +++ b/USER.md @@ -0,0 +1,100 @@ +# USER.md — Fränky + +Who the human is and how they like to work. **Operational rules live in `AGENTS.md` and `BOUNDARIES.md`.** + +--- + +## Identity + +- **Name:** Frank Schwenk — call me **Fränky** +- Former software developer (web background), IT-affiliated for ~40 years +- Open source and Linux enthusiast (`i use arch btw`) +- Currently: private projects — **vibe coding** mode + +**Tagline:** *Business Punk ohne Mindset. Und ohne Business.* + +## Public Presence + +| Site | Role | +|------|------| +| [schwenk.online](https://schwenk.online/) | Visitenkarte mit Haltung | +| [frank-schwenk.de](https://frank-schwenk.de/) | Langform — IT, Billard, Fotografie, Werte | +| [f12.rocks](https://f12.rocks/) | Photography, blog | +| [mobea.de](https://mobea.de/) | KI product | +| [eselhoefe.de](https://eselhoefe.de/) | Village web | +| [vfb.red](https://vfb.red/) | VfB news | + +## Passions & Context + +- **Billiards**, **photography** (camera/drone/phone; Crusaders, festivals, f12 blog) +- **Image workflow** — Imagepipeline locally, Immich on `boka` +- **AI** — builder and skeptic + +## Devices + +- **Local:** Arch Linux desktop + laptop; Android +- **Server:** Debian 12 on `boka` — see `INFRASTRUCTURE.md` +- Also: Plesk webspace (`0012.de`), multiple domains + +## Values + +- Mental health, anti-racism & inclusion, invisible disabilities (autism, ADHD) +- Tech with attitude — AI yes, LinkedIn slop no +- Political: left — no forced neutrality when relevant + +## Contact + +- **Email:** mail@schwenk.online · **Photos:** [@f12.rocks](https://www.instagram.com/f12.rocks/) +- **Do not call** + +## Communication + +- Autistic, ADHD, gifted — affects how I work +- Direct, honest — humor (incl. dark) helps; debug loops drain me +- No "Great question!", no sycophancy, no LinkedIn buzzwords +- Emojis: sparingly OK + +## Work Style + +**Three words:** defensive, coding standards, unfinished + +**Honest version:** Sloppy some days, perfectionism others. + +### Preferences (1 = low, 5 = high) + +| Trait | Score | +|-------|-------| +| Understand first, then build | 5 | +| Ship it — perfection later | 3 | +| I explain what I want | 4 | +| Show options, I decide | 4 | +| You decide — but justify briefly | 4 | +| Small diffs over big refactors | 2 | +| Tests are non-negotiable | 2 | +| Docs only when necessary | 4 | + +**Tension:** Sometimes I want the AI to finish while I'm away — but I get angry when it doesn't work. + +## Ideal Agent + +**TARS** from *Interstellar*: competent, honest, humor available, gets it done. + +## Response Style + +- Precise, context when needed — not telegram, not novels +- **Bilingual:** match conversation language (DE/EN) +- **CLI topics:** English (commands, script comments) — client project: ask if unsure +- German: **du**; English: peer-level, direct +- **Session language lock** for code/commits/new `.md` — no mid-session switch + +## Productivity + +| Boosts | Drains | +|--------|--------| +| Makes me laugh | Debug loops | +| Autonomous finish (when it works) | Unasked changes | +| State-of-the-art suggestions | Guessing | + +## One-Liner + +> There are 2 hard problems in computer science: cache invalidation, naming things, and off-by-1 errors. diff --git a/compose.yml b/compose.yml new file mode 100644 index 0000000..b0c575a --- /dev/null +++ b/compose.yml @@ -0,0 +1,43 @@ +services: + + playground: + image: nginx + volumes: + - ./public:/usr/share/nginx/html:ro + - ./nginx/default.conf:/etc/nginx/conf.d/default.conf:ro + - ./nginx/security-headers.conf:/etc/nginx/conf.d/security-headers.conf:ro + container_name: playground + restart: unless-stopped + labels: + - "traefik.enable=true" + - "traefik.http.routers.playground.rule=Host(`playground.schwenk.online`)" + - "traefik.http.routers.playground.entrypoints=websecure" + - "traefik.http.routers.playground.tls.certresolver=myresolver" + networks: + - traefik + + deploy-hook: + build: ./deploy-hook + user: "${HOST_UID:-1000}:${HOST_GID:-1000}" + volumes: + - .:/repo + - ./hooks:/etc/webhook:ro + environment: + - WEBHOOK_SECRET=${WEBHOOK_SECRET} + - DEPLOY_GIT_TOKEN=${DEPLOY_GIT_TOKEN:-} + - DEPLOY_BRANCH=${DEPLOY_BRANCH:-main} + container_name: playground-deploy-hook + restart: unless-stopped + labels: + - "traefik.enable=true" + - "traefik.http.routers.playground-hook.rule=Host(`playground.schwenk.online`) && PathPrefix(`/hooks/`)" + - "traefik.http.routers.playground-hook.entrypoints=websecure" + - "traefik.http.routers.playground-hook.tls.certresolver=myresolver" + - "traefik.http.routers.playground-hook.priority=100" + - "traefik.http.services.playground-hook.loadbalancer.server.port=9000" + networks: + - traefik + +networks: + traefik: + external: true diff --git a/deploy-hook/Dockerfile b/deploy-hook/Dockerfile new file mode 100644 index 0000000..8b176df --- /dev/null +++ b/deploy-hook/Dockerfile @@ -0,0 +1,9 @@ +FROM almir/webhook:latest + +USER root +RUN apk add --no-cache git bash gettext-envsubst +COPY entrypoint.sh /entrypoint.sh +RUN chmod +x /entrypoint.sh + +USER webhook +ENTRYPOINT ["/entrypoint.sh"] diff --git a/deploy-hook/entrypoint.sh b/deploy-hook/entrypoint.sh new file mode 100755 index 0000000..a5dae45 --- /dev/null +++ b/deploy-hook/entrypoint.sh @@ -0,0 +1,11 @@ +#!/usr/bin/env bash +set -euo pipefail + +if [ -z "${WEBHOOK_SECRET:-}" ]; then + echo "Error: WEBHOOK_SECRET is not set" + exit 1 +fi + +envsubst < /etc/webhook/hooks.json.template > /tmp/hooks.json + +exec webhook -hooks /tmp/hooks.json -verbose diff --git a/hooks/deploy.sh b/hooks/deploy.sh new file mode 100755 index 0000000..754e131 --- /dev/null +++ b/hooks/deploy.sh @@ -0,0 +1,23 @@ +#!/usr/bin/env bash +set -euo pipefail + +REPO_DIR="/repo" +BRANCH="${DEPLOY_BRANCH:-main}" +REMOTE_URL="${DEPLOY_GIT_URL:-https://gitea.schwenk.online/froxxxy/playground.git}" + +if [ ! -d "${REPO_DIR}/.git" ]; then + echo "Error: ${REPO_DIR} is not a git repository" + exit 1 +fi + +git config --global --add safe.directory "${REPO_DIR}" +cd "${REPO_DIR}" + +if [ -n "${DEPLOY_GIT_TOKEN:-}" ]; then + auth_url="https://oauth2:${DEPLOY_GIT_TOKEN}@gitea.schwenk.online/froxxxy/playground.git" + git pull "${auth_url}" "${BRANCH}" +else + git pull origin "${BRANCH}" +fi + +echo "Deploy pull completed for ${BRANCH}" diff --git a/hooks/hooks.json.template b/hooks/hooks.json.template new file mode 100644 index 0000000..07523c2 --- /dev/null +++ b/hooks/hooks.json.template @@ -0,0 +1,32 @@ +[ + { + "id": "deploy", + "execute-command": "/etc/webhook/deploy.sh", + "command-working-directory": "/repo", + "include-command-output-in-response": true, + "trigger-rule": { + "and": [ + { + "match": { + "type": "value", + "value": "refs/heads/main", + "parameter": { + "source": "payload", + "name": "ref" + } + } + }, + { + "match": { + "type": "payload-hmac-sha256", + "secret": "${WEBHOOK_SECRET}", + "parameter": { + "source": "header", + "name": "X-Gitea-Signature" + } + } + } + ] + } + } +] diff --git a/nginx/default.conf b/nginx/default.conf new file mode 100644 index 0000000..b8f6bf8 --- /dev/null +++ b/nginx/default.conf @@ -0,0 +1,32 @@ +server { + listen 80; + listen [::]:80; + server_name playground.schwenk.online; + + root /usr/share/nginx/html; + index index.html; + + include /etc/nginx/conf.d/security-headers.conf; + + gzip on; + gzip_vary on; + gzip_proxied any; + gzip_comp_level 6; + gzip_types text/plain text/css text/xml application/json application/javascript application/xml+rss application/atom+xml image/svg+xml; + + location ~* \.(jpg|jpeg|png|gif|ico|css|js|svg|webp)$ { + expires 1y; + add_header Cache-Control "public, no-transform"; + } + + location / { + try_files $uri $uri/ =404; + } + + location ~ /\. { + deny all; + return 404; + access_log off; + log_not_found off; + } +} diff --git a/nginx/security-headers.conf b/nginx/security-headers.conf new file mode 100644 index 0000000..9a74c49 --- /dev/null +++ b/nginx/security-headers.conf @@ -0,0 +1,8 @@ +# Security headers for playground.schwenk.online (plain static HTML). + +add_header Strict-Transport-Security "max-age=63072000; includeSubDomains" always; +add_header X-Frame-Options "SAMEORIGIN" always; +add_header X-Content-Type-Options "nosniff" always; +add_header Referrer-Policy "strict-origin-when-cross-origin" always; +add_header Permissions-Policy "geolocation=(), microphone=(), camera=()" always; +add_header Content-Security-Policy "default-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:; base-uri 'self'; form-action 'self'; object-src 'none'" always; diff --git a/public/.gitkeep b/public/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/scripts/bootstrap-boka.sh b/scripts/bootstrap-boka.sh new file mode 100755 index 0000000..c43c094 --- /dev/null +++ b/scripts/bootstrap-boka.sh @@ -0,0 +1,50 @@ +#!/usr/bin/env bash +# Bootstrap or refresh playground on boka from Gitea. +# Run from dev machine: ./scripts/bootstrap-boka.sh +set -euo pipefail + +REMOTE_HOST="${REMOTE_HOST:-frank-schwenk.de}" +REMOTE_PATH="${REMOTE_PATH:-/home/frank/playground.schwenk.online}" +GITEA_REPO="ssh://git@gitea.schwenk.online:2222/froxxxy/playground.git" + +ssh "${REMOTE_HOST}" bash -s </dev/null || true + cd /home/frank + mv "\${REMOTE_PATH}" "\${REMOTE_PATH}.bak.\$(date +%s)" +fi + +git clone "\${GITEA_REPO}" "\${REMOTE_PATH}" +cd "\${REMOTE_PATH}" + +if [ -n "\${ENV_BACKUP}" ] && [ -f "\${ENV_BACKUP}" ]; then + cp "\${ENV_BACKUP}" .env + rm -f "\${ENV_BACKUP}" +elif [ ! -f .env ]; then + WEBHOOK_SECRET=\$(openssl rand -hex 32) + cat > .env <