Files
digiserver-v2/docs/07-deployment.md
T

163 lines
5.7 KiB
Markdown

# 07 · Deployment
DigiServer v2 runs as a **Docker Compose** stack (app + Caddy) with optional remote SSH provisioning of players.
---
## 1. Architecture
```mermaid
flowchart LR
subgraph Host["Docker Host"]
subgraph Net["digiserver-network"]
APP["digiserver-app\nFlask + Gunicorn :5000"]
CAD["caddy:2-alpine\n:80 / :443"]
end
VOL1["./data/instance → /app/instance\n(SQLite DBs)"]
VOL2["./data/uploads → /app/app/static/uploads"]
VOL3["./data/Caddyfile → /etc/caddy/Caddyfile"]
VOL4["./data/caddy-data → /data\n./data/caddy-config → /config"]
end
Browser["Browser"] --> CAD
CAD --> APP
APP --> VOL1
APP --> VOL2
CAD --> VOL3
CAD --> VOL4
APP -.SSH/rsync.-> Players["Remote player hosts"]
```
---
## 2. `docker-compose.yml`
### Service `digiserver-app`
- **Build:** `.` (Dockerfile, `python:3.13-slim`)
- **Ports:** `5000:5000` (direct dev access; also `expose: 5000`)
- **Volumes:** `./data/instance:/app/instance`, `./data/uploads:/app/app/static/uploads`
- **Env:** `FLASK_ENV=production`, `SECRET_KEY`, `ADMIN_USERNAME`, `ADMIN_PASSWORD`
- **Healthcheck:** HTTP GET `http://localhost:5000/` (30s interval, 40s start)
- **Restart:** `unless-stopped`
### Service `caddy`
- **Image:** `caddy:2-alpine`
- **Ports:** `8080:80`, `8443:443`
- **Volumes:** `./data/Caddyfile:/etc/caddy/Caddyfile:rw`, `./data/caddy-data:/data`, `./data/caddy-config:/config`, `./data/caddy-logs:/var/log/caddy`
- **Depends on:** app (service started)
- **Healthcheck:** `wget` on port 80
---
## 3. Dockerfile
| Stage | Content |
|---|---|
| Base | `python:3.13-slim` |
| System deps | `poppler-utils`, `ffmpeg`, `libmagic1`, `sudo`, `fonts-noto-color-emoji`, `LibreOffice` (core/impress/writer), `sshpass`, `openssh-client`, `rsync`, `git` |
| Python | `COPY requirements.txt``pip install` (cached layer), then `COPY . .` |
| App config | `FLASK_APP=app.app:create_app`, `FLASK_ENV=production`, `EXPOSE 5000` |
| User | Non-root `appuser` (UID 1000) with **passwordless sudo** limited to `apt-get`, `install_libreoffice.sh`, `install_emoji_fonts.sh` |
| Runtime | `HEALTHCHECK` (HTTP 5000), `ENTRYPOINT /docker-entrypoint.sh` |
---
## 4. `docker-entrypoint.sh`
1. Create `/app/instance` and `/app/app/static/uploads`.
2. If `dashboard.db` is missing: create app + `db.create_all()`, then create/update the admin user from `ADMIN_USERNAME` / `ADMIN_PASSWORD`.
3. Start **Gunicorn**: `--bind 0.0.0.0:5000 --workers 4 --timeout 120 app.app:create_app('production')`.
---
## 5. `deploy.sh` (One-Shot Deployment)
```
1. Validate compose + project; create data/ subdirs; copy nginx configs
2. docker compose up -d + verify containers "Up"
3. Run migration scripts (add_https_config_table, add_player_user_table,
add_email_to_https_config, migrate_player_user_global,
add_original_filename_to_content)
4. Run /app/https_manager.py enable <hostname> <domain> <email> <ip> <port>
⚠ https_manager.py is NOT in the current repo — this step needs attention
5. Verify DB tables via SQLAlchemy inspector
6. caddy validate + https_manager.py status; print access URLs + default creds
```
---
## 6. HTTPS Setup (Caddy)
HTTPS is configured through the **Admin → HTTPS Configuration** page, which:
1. Saves `HTTPSConfig` (hostname, domain, IP, email, port, enabled).
2. `CaddyConfigGenerator.generate_caddyfile(config)` picks a template:
- **HTTP-only** → `:80` reverse proxy
- **Domain** → automatic Let's Encrypt
- **IP** → internal CA self-signed
3. Writes `/etc/caddy/Caddyfile` and reloads Caddy via `POST http://caddy:2019/load`.
The current `data/Caddyfile` (HTTP mode) includes: admin API on `0.0.0.0:2019`, `:80` block → `digiserver-app:5000`, 2 GB body limit, gzip, security headers, access log.
---
## 7. `verify-deployment.sh` — Pre/Post-Deployment Checks
Sections checked (pass/fail/warn counters):
- git status
- `.env` / `.env.example`
- Docker + Compose versions + `compose config` syntax
- Dockerfile best practices (HEALTHCHECK, non-root, slim base)
- `requirements.txt` critical packages + versions
- migrations directory
- **SSL cert expiry** (openssl)
- Flask config (`ProductionConfig`, `SESSION_COOKIE_SECURE`)
- nginx.conf checks
- runtime container health
- security best practices
> ⚠ Note: the script still references `docker-compose` (v1) and `digiserver-nginx` — the current stack uses Compose v2 + Caddy.
---
## 8. Player Deployment Pipeline
```mermaid
sequenceDiagram
participant U as Admin
participant A as App
participant B as Background
participant H as Player host
U->>A: Add player (name, hostname, password/quickconnect)
A->>B: background_player_deployment()
B->>B: build/refresh staged code (PLAYER_CODE_DIR)
B->>H: sshpass + ssh test
B->>H: rsync code (or git clone/pull fallback)
B->>H: write config/app_config.json (server_ip, quickconnect, https...)
B->>H: temporary passwordless sudo
B->>H: ./install.sh && ./start.sh
B->>H: remove temp sudoers
B-->>A: player.deployment_status = 'deployed' | 'failed'
U->>A: poll /players/deployment-status
```
Statuses: `pending → deploying → deployed | failed`, with `last_deployment_at/status/message` persisted on the player row. Receiving player feedback also auto-marks deployment `deployed`.
---
## 9. Ports & Volumes Summary
| Item | Value |
|---|---|
| App port (container) | 5000 |
| App port (host, dev) | 5000 |
| Caddy HTTP | 8080 → 80 |
| Caddy HTTPS | 8443 → 443 |
| Caddy admin API | 2019 (container) |
| DB volume | `./data/instance` |
| Uploads volume | `./data/uploads` |
| Caddy data/config/logs | `./data/caddy-*` |
---
> Next: [08 · Workflows](08-workflows.md)