diff --git a/README.md b/README.md index a012352..3ad602d 100644 --- a/README.md +++ b/README.md @@ -16,6 +16,7 @@ This project is intentionally conservative by design: - [Screenshots](#screenshots) - [Quick start](#quick-start) - [Self-hosted deployment](#self-hosted-deployment-docker-compose) +- [Self-hosted deployment (no Docker)](#self-hosted-deployment-no-docker) - [Development (API)](#development-api-python) ## What it does @@ -42,7 +43,7 @@ What it does not do: - `ui/`: web UI - `api/`: API service (Python) - `docker-compose.yml`: local stack and self-hosted deployment -- `nginx/`: reverse proxy / TLS termination for local HTTPS +- `nginx/`: optional reverse proxy / TLS termination (used by Docker Compose) ## Quick start @@ -63,6 +64,7 @@ docker compose up --build Open `https://localhost` for the UI (HTTP redirects to HTTPS). In the default Compose configuration, only `nginx` is published to the host. The API, Postgres, and wallet-rpc services are reachable only inside the Docker network. +If you prefer not to run `nginx`, you can publish `ui` and `api` ports directly instead (you will also need to serve `qr/` at `/qr/` on your site URL). ## Self-hosted deployment (Docker Compose) @@ -127,6 +129,130 @@ To enable donations: The UI uses the same flag (via Compose), so `/donate` stays unavailable unless donations are explicitly enabled. +## Self-hosted deployment (no Docker) + +This section describes running the services directly on a host (or VMs) without Docker. + +You will run: +- Postgres (external service) +- API (`gunicorn` / `uvicorn`) +- Reconciler worker (a separate process) +- UI (Next.js) +- A reverse proxy is optional, but strongly recommended for TLS, serving `/qr/`, and providing a single origin for UI + API. + +### 1. Install prerequisites + +- Postgres 16+ +- Python 3.12+ +- Node.js 20+ (for the UI) +- A Monero daemon endpoint (`MONERO_DAEMON_URL`) and at least one `monero-wallet-rpc` instance reachable by the API + +### 2. Configure environment variables + +Start from `.env.example` and adjust for your host. For non-Docker deployments you will typically set: +- `DATABASE_URL=postgresql://...@127.0.0.1:5432/...` +- `QR_STORAGE_DIR` to a persistent directory on disk (for example `/var/lib/xmrcheckout/qr`) +- `SITE_URL` to your public URL (for example `https://example.com`) +- `API_BASE_URL` for the UI: + - If using a reverse proxy that routes `/api/` to the API on the same origin, set `API_BASE_URL=https://example.com` + - If not using a reverse proxy, set `API_BASE_URL=http://127.0.0.1:8000` (see the notes in step 6) + +If you need a new `API_KEY_ENCRYPTION_KEY` value: + +``` +python -c 'from cryptography.fernet import Fernet; print(Fernet.generate_key().decode())' +``` + +### 3. Create the Postgres database + +Create a database and user matching your `DATABASE_URL`. On first startup, the API will create tables automatically. + +### 4. Run the API + +``` +python -m venv api/.venv +api/.venv/bin/pip install -r api/requirements.txt + +# Export env vars (or use your process manager to load them) +set -a +source .env +set +a + +api/.venv/bin/gunicorn app.main:app -k uvicorn.workers.UvicornWorker -b 127.0.0.1:8000 -w "${GUNICORN_WORKERS:-2}" --chdir api +``` + +### 5. Run the reconciler worker + +Run this as a separate long-running process (same environment variables as the API): + +``` +set -a +source .env +set +a + +api/.venv/bin/python -m app.reconciler +``` + +### 6. Build and run the UI + +``` +cd ui +npm ci + +# Optional but recommended to keep these explicit in non-Docker deployments +export API_BASE_URL="${API_BASE_URL:-http://127.0.0.1:8000}" +export NEXT_PUBLIC_SITE_URL="${SITE_URL:-http://127.0.0.1:3000}" +export NEXT_PUBLIC_DONATIONS_ENABLED="${DONATIONS_ENABLED:-false}" + +npm run build +npm run start -- -p 3000 -H 127.0.0.1 +``` + +Notes: +- The UI makes some in-browser requests to `/api/...` on the same origin. In production, prefer a reverse proxy that serves the UI and forwards `/api/` to the API. +- The QR PNGs are written to `QR_STORAGE_DIR` and must be reachable at `https:///qr/.png`. + +### Optional: reverse proxy (Nginx) + +Nginx is optional. Any reverse proxy that can: +- route `/api/` to the API, +- route `/` to the UI, +- and serve the QR directory at `/qr/` + +is fine. + +Example (adjust `server_name`, TLS, and paths to match your host): + +``` +server { + listen 443 ssl; + server_name example.com; + + # ssl_certificate ...; + # ssl_certificate_key ...; + + location /api/ { + proxy_pass http://127.0.0.1:8000/api/; + proxy_set_header Host $host; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + } + + location /qr/ { + alias /var/lib/xmrcheckout/qr/; + add_header Cache-Control "public, max-age=31536000, immutable"; + try_files $uri =404; + } + + location / { + proxy_pass http://127.0.0.1:3000/; + proxy_set_header Host $host; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + } +} +``` + ## Development (API, Python) 1. Set environment variables (see `api/.env.example`): @@ -147,7 +273,8 @@ pip install -r api/requirements.txt 3. Start the API: ``` -uvicorn api.app.main:app --reload +cd api +uvicorn app.main:app --reload ``` The API listens on `http://127.0.0.1:8000`.