# Production deployment and maintenance This file is the operational source of truth for the current Jyotisha demo deployment. ## Current production | Item | Value | | --- | --- | | Public domain | `https://jyotisha.chat` | | DNS | Spaceship nameservers (`launch1.spaceship.net`, `launch2.spaceship.net`) | | Server | Hong Kong VPS, Ubuntu 22.04 x86_64 | | Public host | `103.117.123.53` | | SSH | port `22000`, public-key authentication only | | Capacity | 1 vCPU / 2 GB RAM / 40 GB disk / 5 Mbps | | App directory | `/opt/jyotisha-app` | | Environment file | `/opt/jyotisha-app/.env.production` (`0600`) | | Source repository | `https://github.com/jesse-ux/Jyotisha.git` | | Supabase project | `vtvnfqmonbfuxmqkqdlc` | This machine is suitable for a client demo and low concurrency. Supabase and the model provider stay managed externally; do not self-host them on this VPS. ## Architecture ```text Spaceship DNS -> Caddy :80/:443 -> web:3000 (Next.js + Mastra, Docker-private) -> api:5200 (Python Jyotish API, Docker-private) -> Swiss Ephemeris / local engine -> VedAstro gateway with local fallback -> Supabase Cloud -> external OpenAI-compatible model API ``` Only Caddy publishes host ports. Ports `3000` and `5200` must remain private. ## DNS and Supabase Auth Spaceship resource records: ```text A @ 103.117.123.53 CNAME www jyotisha.chat ``` Supabase Authentication URL Configuration: ```text Site URL: https://jyotisha.chat Redirect URLs: https://jyotisha.chat/** https://www.jyotisha.chat/** ``` Before changing Caddy to the domain, verify the authoritative DNS result: ```bash dig +short @launch1.spaceship.net A jyotisha.chat ``` It must return `103.117.123.53`. Caddy provisions and renews HTTPS automatically after DNS resolves. ## Production environment `.env.production` combines the backend and frontend server variables. Required groups: ```dotenv SITE_ADDRESS=https://jyotisha.chat JYOTISH_API_BASE=http://api:5200 NEXT_PUBLIC_SUPABASE_URL=... NEXT_PUBLIC_SUPABASE_ANON_KEY=... SUPABASE_SERVICE_ROLE_KEY=... ADMIN_EMAILS=... # Either OpenAI: OPENAI_API_KEY=... MASTRA_MODEL=... # Or an OpenAI-compatible provider: LLM_BASE_URL=... LLM_API_KEY=... LLM_MODEL=... # Optional VedAstro official upstream; local fallback remains available: VEDASTRO_API_ENDPOINT=... VEDASTRO_API_KEY=... ``` Never commit `.env.production`, `SUPABASE_SERVICE_ROLE_KEY`, model keys, user JWTs, SSH private keys or passwords. `NEXT_PUBLIC_SUPABASE_ANON_KEY` is intentionally public; authorization is enforced by Supabase RLS and server-side checks. ## Connect and inspect ```bash ssh -p 22000 root@103.117.123.53 cd /opt/jyotisha-app COMPOSE='docker compose --env-file .env.production -f deploy/docker-compose.server.yml' $COMPOSE ps $COMPOSE logs --tail=100 api web caddy free -h docker stats --no-stream ``` The server has a persistent 2 GB `/swapfile`. UFW permits only SSH `22000/tcp`, HTTP `80/tcp`, HTTPS `443/tcp`, and the pre-existing WireGuard `51820/udp` rule. ## Automatic deployment A successful `Jyotish Skill CI` run for a push to `main` triggers `.github/workflows/deploy-production.yml`. The workflow syncs the tested revision with `rsync`, preserves `/opt/jyotisha-app/.env.production`, rebuilds both Docker services, and verifies the public login route, logged-out account response, and private Python health endpoint. Required GitHub Actions secret: ```text PRODUCTION_SSH_PRIVATE_KEY = dedicated production deploy private key ``` The workflow pins the VPS Ed25519 host key and serializes deployments with the `production` concurrency group. It can also be run manually from GitHub Actions. ## Manual deployment fallback If GitHub Actions is unavailable, deploy the tracked tree without copying local secrets: ```bash cd /Users/jesse/Downloads/Copse/astrology/yinduzhanxing git status --short --branch rsync -az --delete \ --exclude='.git/' \ --exclude='.env.production' \ --exclude='frontend/node_modules/' \ --exclude='frontend/.next/' \ --exclude='jyotish-app/node_modules/' \ --exclude='jyotish-app/dist/' \ -e 'ssh -p 22000' \ ./ root@103.117.123.53:/opt/jyotisha-app/ ssh -p 22000 root@103.117.123.53 \ 'cd /opt/jyotisha-app && docker compose --env-file .env.production -f deploy/docker-compose.server.yml up -d --build --remove-orphans' ``` The excluded `.env.production` remains only on the VPS. ## Verification ```bash curl -fsS https://jyotisha.chat/login >/dev/null curl -fsS -o /dev/null -w '%{http_code}\n' https://jyotisha.chat/api/account ``` The second command should return `401` while logged out. Verify the private Python API from inside the web container: ```bash ssh -p 22000 root@103.117.123.53 \ 'cd /opt/jyotisha-app && docker compose --env-file .env.production -f deploy/docker-compose.server.yml exec -T web node -e "fetch(\"http://api:5200/api/health\").then(async r=>{console.log(r.status); console.log(await r.text())})"' ``` Expected: HTTP `200`, `"status": "ok"`, and `"swisseph_available": true`. Public access to `103.117.123.53:5200` must fail. Then manually verify: OTP login, onboarding/profile persistence, chat-session persistence, code redemption, admin code generation, streaming response, one-credit charge, and refund on failure before the first output chunk. ## Common operations ```bash # Restart without rebuilding docker compose --env-file .env.production -f deploy/docker-compose.server.yml up -d # Rebuild only the web container docker compose --env-file .env.production -f deploy/docker-compose.server.yml up -d --build web caddy # Rebuild only the Python API docker compose --env-file .env.production -f deploy/docker-compose.server.yml up -d --build api # Follow logs docker compose --env-file .env.production -f deploy/docker-compose.server.yml logs -f --tail=100 api web caddy ``` ## Optional Railway deployment Railway is not the current production target. If needed, create `web` and `api` services from the same repository using `deploy/railway-web.Dockerfile` and `deploy/railway-api.Dockerfile`; keep `api` private and set the web service's `JYOTISH_API_BASE` to Railway's private API hostname.