diff --git a/.env.example b/.env.example index 1a9d508..2734c8b 100644 --- a/.env.example +++ b/.env.example @@ -1,7 +1,20 @@ # Cyclone — environment configuration # Copy this file to `.env.local` and fill in values for your environment. -# Base URL for the Python (FastAPI) backend that powers the Upload page and -# the real /api/parse-837 + /api/parse-835 endpoints. Leave empty to keep -# the in-memory sample data store and disable real EDI parsing. -VITE_API_BASE_URL=http://localhost:8000 +# Required on first boot. Cyclone refuses to start without these unless +# at least one user already exists (e.g. seeded via `python -m cyclone users create`). +# Min 12 chars for password. +CYCLONE_ADMIN_USERNAME=admin +CYCLONE_ADMIN_PASSWORD=change-me-to-a-strong-password-min-12-chars + +# Base URL for the Python (FastAPI) backend. Leave empty for the +# Docker deployment (nginx proxies /api/* to backend on compose network). +VITE_API_BASE_URL= + +# Optional. Set to 1 if you're behind an HTTPS reverse proxy and want +# the session cookie to include the Secure flag. +# CYCLONE_BEHIND_HTTPS=1 + +# Optional. Set to 1 to disable auth entirely (DEV ONLY). When set, +# the backend auto-grants admin access without checking credentials. +# CYCLONE_AUTH_DISABLED=0 \ No newline at end of file diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..2601fba --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,101 @@ +# Cyclone — local docker-compose stack. +# +# Two services on a user-defined network: +# +# frontend (nginx) — published on http://127.0.0.1:8080 +# serves the built SPA + reverse-proxies /api/* to backend +# backend (uvicorn) — NOT published externally by default; reachable from +# the frontend over the compose network on backend:8000 +# (override `ports:` below to expose it for curl/debug) +# +# Persistent state: +# - `cyclone-data` named volume mounted at /data in the backend holds +# the SQLite database file. Survives `docker compose down`; only +# `docker compose down -v` wipes it. +# - `config/payers.yaml` is baked into the backend image at build time. +# To edit payer config, change the YAML, rebuild the backend image, +# and `POST /api/admin/reload-config` (or just restart the container). +# +# Usage: +# docker compose up -d --build # start (or rebuild + restart) +# docker compose logs -f # tail both services +# docker compose restart backend # bounce the backend (e.g. after crash) +# docker compose down # stop containers, KEEP the data volume +# docker compose down -v # stop AND wipe the data volume + +name: cyclone + +services: + backend: + build: + context: . + dockerfile: backend/Dockerfile + image: cyclone-backend:local + container_name: cyclone-backend + restart: unless-stopped + environment: + # Bind on all interfaces inside the container (required — 127.0.0.1 + # would only be reachable from inside the container itself). + CYCLONE_HOST: "0.0.0.0" + CYCLONE_PORT: "8000" + CYCLONE_RELOAD: "0" + # Absolute path inside the container; the named volume mounts at /data. + CYCLONE_DB_URL: "sqlite:////data/cyclone.db" + # Bootstrap admin (required on first boot unless at least one user + # already exists). The ${VAR:?msg} syntax makes docker-compose refuse + # to start with a clear error if the env var isn't set in the host + # environment. + CYCLONE_ADMIN_USERNAME: ${CYCLONE_ADMIN_USERNAME:?CYCLONE_ADMIN_USERNAME is required on first boot} + CYCLONE_ADMIN_PASSWORD: ${CYCLONE_ADMIN_PASSWORD:?CYCLONE_ADMIN_PASSWORD is required on first boot (min 12 chars)} + volumes: + - cyclone-data:/data + # The healthcheck in the Dockerfile hits /api/health. The frontend + # depends_on `service_healthy` so it won't accept traffic until the + # backend is responsive. + healthcheck: + test: ["CMD", "curl", "--fail", "--silent", "http://127.0.0.1:8000/api/health"] + interval: 15s + timeout: 5s + retries: 3 + start_period: 10s + # No `ports:` — the frontend reaches the backend over the compose + # network. Uncomment the next line to expose the API directly for + # `curl http://localhost:8000/api/...` from the host. + # ports: + # - "127.0.0.1:8000:8000" + networks: + - cyclone-net + + frontend: + build: + context: . + dockerfile: frontend/Dockerfile + image: cyclone-frontend:local + container_name: cyclone-frontend + restart: unless-stopped + depends_on: + backend: + condition: service_healthy + ports: + # Bind address defaults to 0.0.0.0 (reachable from the LAN). Set + # CYCLONE_BIND_ADDRESS=127.0.0.1 to tighten to loopback-only and + # match the standalone install's local-only posture. + # Port defaults to 8081 to dodge the common clash on 8080; override + # with CYCLONE_WEB_PORT=... (see README). + - "${CYCLONE_BIND_ADDRESS:-0.0.0.0}:${CYCLONE_WEB_PORT:-8081}:80" + healthcheck: + test: ["CMD", "wget", "--quiet", "--tries=1", "--spider", "http://127.0.0.1/healthz"] + interval: 15s + timeout: 3s + retries: 3 + start_period: 5s + networks: + - cyclone-net + +networks: + cyclone-net: + driver: bridge + +volumes: + cyclone-data: + name: cyclone-data \ No newline at end of file