# CheckPoint Ehrenamt — Deployment & Betrieb

> Flask/Gunicorn · Docker · cpe.bujour.info · Stand: Juni 2026

---

## Überblick

CheckPoint Ehrenamt läuft als Docker-Container auf dem EU-VPS hinter Caddy.
Das Image wird direkt auf dem VPS aus dem geklonten Forgejo-Repo gebaut —
kein Container-Registry, kein CI/CD.

**URL:** https://cpe.bujour.info
**Repo:** https://git.bujour.de/patsy/CP_EHRENAMT
**Code auf VPS:** `~/apps/checkpoint/`
**Stack:** `~/stacks/checkpoint/`

---

## Stack-Struktur

```
~/stacks/checkpoint/
├── compose.yaml
└── .env          ← SECRET_KEY (nicht im Git)

~/apps/checkpoint/
├── Dockerfile
├── wsgi.py
├── gunicorn.conf.py
├── seed.py
├── requirements.txt
├── app/
└── instance/     ← DB + Uploads (Docker Volume)
```

---

## compose.yaml

```yaml
services:
  checkpoint:
    build: /home/patsy/apps/checkpoint
    container_name: checkpoint
    restart: unless-stopped
    environment:
      - CP_ENV=production
      - SECRET_KEY=${SECRET_KEY}
    volumes:
      - checkpoint_data:/app/instance
    networks:
      - proxy
    expose:
      - "8000"

volumes:
  checkpoint_data:

networks:
  proxy:
    external: true
```

---

## Erstes Deployment (Einmalig)

```bash
# 1. Repo klonen
git clone ssh://git@git.bujour.de:2222/patsy/CP_EHRENAMT.git ~/apps/checkpoint

# 2. Secret generieren
python3 -c "import secrets; print('SECRET_KEY=' + secrets.token_urlsafe(48))" \
  > ~/stacks/checkpoint/.env

# 3. Image bauen und Container starten
cd ~/stacks/checkpoint
docker compose build
docker compose up -d

# 4. Datenbank seeden (einmalig)
docker exec checkpoint python seed.py

# 5. Ersten Admin anlegen (einmalig)
docker exec -e SECRET_KEY=$(grep SECRET_KEY ~/stacks/checkpoint/.env | cut -d= -f2) \
  checkpoint flask --app wsgi create-admin <nutzername> "<anzeigename>" "<passwort>"
```

---

## Update einspielen

```bash
cd ~/apps/checkpoint
git pull

cd ~/stacks/checkpoint
docker compose build
docker compose up -d
```

Neue Tabellen werden beim Start automatisch via `db.create_all()` angelegt.
**Achtung:** Bestehende Tabellen werden nicht migriert — bei Schema-Änderungen
vorher Backup ziehen und ggf. manuell migrieren.

---

## Weitere Admins anlegen

```bash
docker exec -e SECRET_KEY=$(grep SECRET_KEY ~/stacks/checkpoint/.env | cut -d= -f2) \
  checkpoint flask --app wsgi create-admin <nutzername> "<anzeigename>" "<passwort>"
```

---

## Datenbank-Backup (manuell)

```bash
docker exec checkpoint sqlite3 /app/instance/checkpoint.sqlite \
  ".backup '/app/instance/checkpoint-backup-$(date +%Y%m%d).sqlite'"
```

Das Backup liegt im Volume unter `/app/instance/` und kann von dort kopiert werden.

---

## Logs

```bash
# Live-Logs
docker logs checkpoint -f

# Letzte 100 Zeilen
docker logs checkpoint --tail 100
```

---

## Fehlersuche

| Problem | Ursache | Lösung |
|---|---|---|
| 500 nach Deploy | Fehlendes `SECRET_KEY` | `.env` prüfen, Container neu starten |
| Login funktioniert nicht | `CP_ENV` nicht gesetzt | `docker exec checkpoint env \| grep CP_ENV` |
| App nicht erreichbar | Caddy oder Container down | `docker ps`, dann Caddy-Reload |
| „database is locked" | Zu viele Worker für SQLite | `workers` in `gunicorn.conf.py` auf 2 senken |

---

## Technischer Stack

| Komponente | Entscheidung |
|---|---|
| Backend | Python 3.13 + Flask |
| WSGI | gunicorn (3 Worker, gthread) |
| Datenbank | SQLite mit WAL-Modus |
| Templating | Jinja2, server-rendered |
| CSS | Selbst gebaut, kein Framework |
| JavaScript | Minimal, Vanilla (nur Chat-Polling + Sidebar) |
| CSRF | Selbst gebaut (Session-Token, kein Flask-WTF) |
| Hosting | IONOS EU-VPS, Docker, Caddy (TLS automatisch) |
