# chocoadmin Deployment ## 1. Environment Files Create environment files on the Synology NAS. Do not commit real `.env.*` files. Stage file path: ```bash /volume1/docker/service/jinaju/chocoadmin/.env.stage ``` Stage example: ```bash DATABASE_URL="mysql://USER:PASSWORD@mariadb.jisangs.com:30001/chocomae" AUTH_SECRET="replace-with-stage-secret" NEXTAUTH_SECRET="replace-with-stage-secret" AUTH_URL="https://chocoadmin-stage.jisangs.com" NEXTAUTH_URL="https://chocoadmin-stage.jisangs.com" ``` Production file path: ```bash /volume1/docker/service/jinaju/chocoadmin/.env.production ``` Production example: ```bash DATABASE_URL="mysql://CHCOCO_ADMIN_USER:PASSWORD@chocomae.jinaju.com:3306/chocomae" AUTH_SECRET="replace-with-production-secret" NEXTAUTH_SECRET="replace-with-production-secret" AUTH_URL="https://chocoadmin.jinaju.com" NEXTAUTH_URL="https://chocoadmin.jinaju.com" ``` `AUTH_SECRET` and `NEXTAUTH_SECRET` must be the same strong random value within each environment for Auth.js compatibility. Use different values between production and stage. `APP_ENV` (`production` / `stage`) is set by the `docker-compose*.yml` `environment` block — do **not** put it in the env file. It drives the session cookie name (`${APP_ENV}-chocoadmin.session-token`), which keeps production and stage cookies separate even if they share a browser. When the public URL uses HTTPS, both `AUTH_URL` and `NEXTAUTH_URL` must use the exact external HTTPS URL. Generate a secret on the NAS: ```bash openssl rand -base64 32 ``` ## 2. Synology Stage Deployment Both production and stage share the same Git checkout at `/volume1/docker/service/jinaju/chocoadmin`. Pull from there: ```bash cd /volume1/docker/service/jinaju/chocoadmin-stage git pull --ff-only ``` Stage uses `docker-compose.stage.yml`, the `chocoadmin-stage` container, and the external Docker network `proxy-network`. Verify or create the network: ```bash docker network inspect proxy-network >/dev/null 2>&1 || docker network create proxy-network ``` Start or update stage: ```bash docker compose -f docker-compose.stage.yml up -d --build docker compose -f docker-compose.stage.yml ps docker compose -f docker-compose.stage.yml logs -f ``` If a service was renamed in the compose file, add `--remove-orphans` once to remove the stale container. After redeployment, reload NPM to flush its upstream DNS cache: ```bash docker exec npm nginx -s reload ``` `docker-compose.stage.yml` exposes container port `3000` to `proxy-network` instead of binding host port `3000`, because host port `3000` may already be used by another service such as Gitea. Nginx Proxy Manager settings: - Scheme: `http` - Forward Hostname / IP: `chocoadmin-stage` - Forward Port: `3000` - Websockets Support: enabled - SSL: enabled - Force SSL: enabled Stage URL: ```text https://chocoadmin-stage.jisangs.com ``` After changing `AUTH_URL`, `NEXTAUTH_URL`, or cookie-related settings, clear browser cookies for the stage domain or test in a private window. ## 3. Production Deployment Production uses `docker-compose.yml` and reads `.env.production` by default: ```bash cd /volume1/docker/service/jinaju/chocoadmin git pull --ff-only docker compose up -d --build docker compose ps docker compose logs -f chocoadmin ``` After redeployment, reload NPM: ```bash docker exec npm nginx -s reload ``` Nginx Proxy Manager settings: - Scheme: `http` - Forward Hostname / IP: `chocoadmin` - Forward Port: `3000` - Websockets Support: enabled - SSL: enabled - Force SSL: enabled Production URL: ```text https://chocoadmin.jinaju.com ``` The Docker build uses placeholder build-time environment variables only so Next.js can compile without committing secrets. Runtime values are read from the compose `env_file`. ## 4. AWS EC2 Security Group Allow MariaDB only from the Synology NAS public IP. - Type: `MYSQL/Aurora` - Protocol: `TCP` - Port: `3306` - Source: `NAS_PUBLIC_IP/32` - Description: `chocoadmin Synology NAS` Do not open `3306` to `0.0.0.0/0`. ## 5. Read-Only Rehearsal Before enabling approval/rejection operations against production DB: 1. Create or use a DB account with read-only permissions. 2. Set `DATABASE_URL` in `.env.production` to that read-only account. 3. Start the container. 4. Verify login, maestro list, extension request list, and upgrade request list. 5. Switch to the production write-capable chocoadmin DB account only after read screens work. ## 6. Smoke Checks After each deployment: ```bash docker compose -f docker-compose.stage.yml ps # stage docker compose ps # production ``` Verify these routes in the browser: - `/login` - `/maestros` - `/extension-requests` - `/upgrade-requests` ## 7. Troubleshooting ### ERR_TOO_MANY_REDIRECTS after login Middleware redirects to `/login`, login page redirects back to `/` — infinite loop. Work through this checklist in order: 1. **Check `AUTH_URL` / `NEXTAUTH_URL`** — must exactly match the public HTTPS URL including scheme. 2. **Check NPM scheme** — NPM must forward with scheme `http` (not `https`) to the container. The app detects HTTPS from `AUTH_URL`, not from the incoming request. 3. **Check cookie name consistency** — `auth.ts` (`cookies.sessionToken.name`) and `proxy.ts` (`getToken({ cookieName })`) must both use the same `${APP_ENV}-chocoadmin.session-token` value. If `proxy.ts` uses the default name while `auth.ts` uses a custom one, middleware never finds the session. 4. **Check for `__Secure-` prefix** — do not add it. Middleware runs in Node.js runtime and receives HTTP from Nginx. `__Secure-` cookies are silently rejected over HTTP. 5. **Clear browser cookies** for the domain, then test in a private window. 6. **Recreate the container** after changing `.env.*`. ### Production and stage traffic mixing Symptom: logging into `chocoadmin.jinaju.com` shows the stage UI, or requests hit both containers interchangeably. Cause: Docker registers each `services:` key as a DNS alias in `proxy-network`. If stage and production share the same service name, NPM's upstream resolves to both containers via round-robin. Check the network: ```bash docker network inspect proxy-network --format '{{range .Containers}}{{.Name}} {{.IPv4Address}}{{"\n"}}{{end}}' ``` Expected: `chocoadmin` and `chocoadmin-stage` appear with different IPs. If `chocoadmin` appears twice, a stale container is using that alias. Fix: ensure `docker-compose.stage.yml` has `services: chocoadmin-stage:` (not `chocoadmin`), redeploy stage once with `--remove-orphans` to clean up the stale container, then reload NPM. ### Failed to find Server Action after redeployment Symptom: clicking a button (e.g. logout) returns a 404 or `Failed to find Server Action` error after deploying a new build. Cause: the action was defined as an inline `"use server"` closure. Closures get a new action ID on every build. The browser cached the old ID. Fix: force-reload the page (`Cmd+Shift+R` / `Ctrl+Shift+R`) to discard the cached page with stale action IDs. If the problem recurs after every deployment, the action must be extracted to a module-level named export in a separate `actions.ts` file. ### Container starts but immediately exits ```bash docker compose logs chocoadmin ``` Common causes: - Missing `DATABASE_URL` or malformed connection string. - `AUTH_SECRET` not set — NextAuth throws on startup. - Port already allocated — check if another service uses host port 3000. Both compose files use `expose` (not `ports`) for port 3000, so this should not happen unless the compose file was modified.