chore: dockerize api proxy and add gitignore
Add a Docker-based FastAPI proxy skeleton (charter §4) for parity across dev / homelab VPS / fly.io, plus root .gitignore for Godot 4.7 and Python. - api/Dockerfile: python:3.12-slim, non-root, PORT-aware CMD - api/app/main.py: /health + five role endpoint stubs (§4) - api/requirements.txt: fastapi/uvicorn/httpx/pydantic, pinned - docker-compose.yml: local dev with live reload - api/fly.toml: prod deploy stub with /health check - api/.env.example: key lives in the proxy, never the client (§4) - .gitignore: ignores .env, whitelists .env.example Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
58
.gitignore
vendored
Normal file
58
.gitignore
vendored
Normal file
@@ -0,0 +1,58 @@
|
||||
# ─────────────────────────────────────────────
|
||||
# Godot 4.7 (client/)
|
||||
# ─────────────────────────────────────────────
|
||||
# Editor + import cache (4.x replaced .import/ with .godot/)
|
||||
.godot/
|
||||
# Exported builds
|
||||
/client/build/
|
||||
export.cfg
|
||||
# export_presets.cfg holds keystore paths / signing config — keep secrets out of history
|
||||
export_presets.cfg
|
||||
# Mono/C# (only if we drop to C# per charter §16)
|
||||
.mono/
|
||||
data_*/
|
||||
*.mono/
|
||||
|
||||
# ─────────────────────────────────────────────
|
||||
# Python / FastAPI (api/)
|
||||
# ─────────────────────────────────────────────
|
||||
__pycache__/
|
||||
*.py[cod]
|
||||
*$py.class
|
||||
*.egg-info/
|
||||
.eggs/
|
||||
build/
|
||||
dist/
|
||||
# Virtual envs
|
||||
.venv/
|
||||
venv/
|
||||
env/
|
||||
ENV/
|
||||
# Tooling caches
|
||||
.pytest_cache/
|
||||
.mypy_cache/
|
||||
.ruff_cache/
|
||||
.coverage
|
||||
htmlcov/
|
||||
.tox/
|
||||
|
||||
# ─────────────────────────────────────────────
|
||||
# Secrets — the API key lives here in dev, NEVER in the client (charter §4)
|
||||
# ─────────────────────────────────────────────
|
||||
.env
|
||||
.env.*
|
||||
!.env.example
|
||||
|
||||
# ─────────────────────────────────────────────
|
||||
# Editors / OS
|
||||
# ─────────────────────────────────────────────
|
||||
.vscode/
|
||||
.idea/
|
||||
*.swp
|
||||
.DS_Store
|
||||
Thumbs.db
|
||||
|
||||
# ─────────────────────────────────────────────
|
||||
# Logs (charter §4/§10 logs go to a store, not the repo)
|
||||
# ─────────────────────────────────────────────
|
||||
*.log
|
||||
16
api/.dockerignore
Normal file
16
api/.dockerignore
Normal file
@@ -0,0 +1,16 @@
|
||||
# Keep the build context small and secrets out of the image.
|
||||
.env
|
||||
.env.*
|
||||
__pycache__/
|
||||
*.py[cod]
|
||||
.venv/
|
||||
venv/
|
||||
.pytest_cache/
|
||||
.mypy_cache/
|
||||
.ruff_cache/
|
||||
.coverage
|
||||
htmlcov/
|
||||
docs/
|
||||
README.md
|
||||
Dockerfile
|
||||
.dockerignore
|
||||
11
api/.env.example
Normal file
11
api/.env.example
Normal file
@@ -0,0 +1,11 @@
|
||||
# Copy to .env for local dev. NEVER commit the real .env. The client never sees
|
||||
# any of these — keys live only in the proxy (charter §4).
|
||||
|
||||
# Where the proxy sends model calls in dev (Ollama on the homelab).
|
||||
OLLAMA_BASE_URL=http://localhost:11434
|
||||
|
||||
# Prod model provider (charter §4). Leave blank in dev.
|
||||
REPLICATE_API_TOKEN=
|
||||
|
||||
# Port the proxy binds. compose / fly.io override this.
|
||||
PORT=8000
|
||||
26
api/Dockerfile
Normal file
26
api/Dockerfile
Normal file
@@ -0,0 +1,26 @@
|
||||
# /api — FastAPI proxy (charter §4). Docker-based for parity across
|
||||
# dev / homelab VPS / fly.io.
|
||||
FROM python:3.12-slim
|
||||
|
||||
# Faster, quieter Python in containers.
|
||||
ENV PYTHONUNBUFFERED=1 \
|
||||
PYTHONDONTWRITEBYTECODE=1 \
|
||||
PIP_NO_CACHE_DIR=1
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
# Install deps first so the layer caches when only app code changes.
|
||||
COPY requirements.txt .
|
||||
RUN pip install --no-cache-dir -r requirements.txt
|
||||
|
||||
# App code.
|
||||
COPY app ./app
|
||||
|
||||
# Run as non-root.
|
||||
RUN useradd --create-home --uid 1000 appuser
|
||||
USER appuser
|
||||
|
||||
EXPOSE 8000
|
||||
|
||||
# fly.io / compose set PORT; default to 8000 (charter localhost:8000).
|
||||
CMD ["sh", "-c", "uvicorn app.main:app --host 0.0.0.0 --port ${PORT:-8000}"]
|
||||
0
api/app/__init__.py
Normal file
0
api/app/__init__.py
Normal file
47
api/app/main.py
Normal file
47
api/app/main.py
Normal file
@@ -0,0 +1,47 @@
|
||||
"""FastAPI proxy entrypoint — the guarding proxy of charter §4.
|
||||
|
||||
Skeleton only: a health check plus the five role endpoints as stubs so the
|
||||
container runs and the client has something to point at. Real prompt routing,
|
||||
model selection, logging, and auth land later — all server-side (§4, §5).
|
||||
"""
|
||||
|
||||
from fastapi import FastAPI
|
||||
|
||||
app = FastAPI(title="coc-rpg proxy", version="0.0.1")
|
||||
|
||||
|
||||
@app.get("/health")
|
||||
def health() -> dict:
|
||||
"""Liveness probe for compose / fly.io."""
|
||||
return {"status": "ok"}
|
||||
|
||||
|
||||
# ── Role endpoints (charter §4) ──────────────────────────────────────────────
|
||||
# The client knows these paths and nothing about which model or prompt serves
|
||||
# them. Stubs for now; each will load its prompt from api/prompts/ and route to
|
||||
# the configured model.
|
||||
|
||||
|
||||
@app.post("/dm/narrate")
|
||||
def narrate() -> dict:
|
||||
return {"detail": "not implemented"}
|
||||
|
||||
|
||||
@app.post("/dm/adjudicate")
|
||||
def adjudicate() -> dict:
|
||||
return {"detail": "not implemented"}
|
||||
|
||||
|
||||
@app.post("/dm/improvise")
|
||||
def improvise() -> dict:
|
||||
return {"detail": "not implemented"}
|
||||
|
||||
|
||||
@app.post("/npc/speak")
|
||||
def npc_speak() -> dict:
|
||||
return {"detail": "not implemented"}
|
||||
|
||||
|
||||
@app.post("/party/banter")
|
||||
def banter() -> dict:
|
||||
return {"detail": "not implemented"}
|
||||
21
api/fly.toml
Normal file
21
api/fly.toml
Normal file
@@ -0,0 +1,21 @@
|
||||
# fly.io deploy config for the proxy (charter §4, prod). Stub — set app name and
|
||||
# region, then `fly deploy` from api/. Secrets (REPLICATE_API_TOKEN) go via
|
||||
# `fly secrets set`, never in this file.
|
||||
app = "coc-rpg-proxy" # TODO: claim a real app name
|
||||
primary_region = "ord" # TODO: pick a region
|
||||
|
||||
[build]
|
||||
dockerfile = "Dockerfile"
|
||||
|
||||
[http_service]
|
||||
internal_port = 8000
|
||||
force_https = true
|
||||
auto_stop_machines = true
|
||||
auto_start_machines = true
|
||||
min_machines_running = 0
|
||||
|
||||
[[http_service.checks]]
|
||||
method = "GET"
|
||||
path = "/health"
|
||||
interval = "15s"
|
||||
timeout = "2s"
|
||||
5
api/requirements.txt
Normal file
5
api/requirements.txt
Normal file
@@ -0,0 +1,5 @@
|
||||
# FastAPI proxy runtime deps. Pinned from first resolve; bump deliberately.
|
||||
fastapi==0.139.0
|
||||
uvicorn[standard]==0.51.0
|
||||
httpx==0.28.1 # calling Ollama / Replicate
|
||||
pydantic==2.13.4 # request/response contracts
|
||||
17
docker-compose.yml
Normal file
17
docker-compose.yml
Normal file
@@ -0,0 +1,17 @@
|
||||
# Local dev — run the proxy in Docker with live reload.
|
||||
# docker compose up --build
|
||||
# Ollama runs on the homelab (charter §4), not here — point OLLAMA_BASE_URL at it.
|
||||
services:
|
||||
api:
|
||||
build: ./api
|
||||
ports:
|
||||
- "8000:8000"
|
||||
env_file:
|
||||
- ./api/.env
|
||||
environment:
|
||||
PORT: 8000
|
||||
# Mount source + reload so edits are live without a rebuild.
|
||||
volumes:
|
||||
- ./api/app:/app/app:ro
|
||||
command: >
|
||||
uvicorn app.main:app --host 0.0.0.0 --port 8000 --reload
|
||||
Reference in New Issue
Block a user