From 2e2c910d22fa6d7d4d075a7ab3fb599967a34ab7 Mon Sep 17 00:00:00 2001 From: rameen Date: Sat, 29 Aug 2026 14:46:05 +0330 Subject: [PATCH] Document staging HTTPS on wixur.ir (Windows nginx + Let's Encrypt). Mobinnet holds public port 80, so staging is served on 443. Examples and the host nginx template now match FRONTEND_URL=https://wixur.ir. Co-authored-by: Cursor --- infrastructure/STAGING-DEPLOY.md | 62 +++++++-------- infrastructure/backend.staging.env.example | 8 +- infrastructure/nginx/windows-edge-http.conf | 78 +++++++++++++++++++ .../scripts/windows-renew-wixur-cert.cmd | 11 +++ 4 files changed, 122 insertions(+), 37 deletions(-) create mode 100644 infrastructure/nginx/windows-edge-http.conf create mode 100644 infrastructure/scripts/windows-renew-wixur-cert.cmd diff --git a/infrastructure/STAGING-DEPLOY.md b/infrastructure/STAGING-DEPLOY.md index eff91d5..33ee6ec 100644 --- a/infrastructure/STAGING-DEPLOY.md +++ b/infrastructure/STAGING-DEPLOY.md @@ -4,7 +4,8 @@ Automatic staging on a **self-hosted Gitea** machine: merge (or push) to **`mast | Public URL | What | |------------|------| -| `http://wixur.ir` | Staging app (port **80** → Docker nginx) | +| `https://wixur.ir` | Staging app (port **443** → Windows nginx → Docker). Mobinnet: public **80** is the modem. | +| `http://wixur.ir:8088` | HTTP fallback | | `http://wixur.ir:3000` | Gitea + container registry | DNS `wixur.ir` must point at the **Windows** host. Production (`https://nudentic.ir` on Linux) is separate — see [`DEPLOY.md`](DEPLOY.md). Do **not** point `wixur.ir` at the Linux VPS. @@ -22,8 +23,9 @@ Build backend + frontend → push to Gitea registry ↓ docker compose pull + up -d (docker-compose.registry.yml) ↓ -http://wixur.ir → portproxy :80 → nginx :18088 → frontend / backend → postgres -http://wixur.ir:3000 → Gitea (native, no Docker) +https://wixur.ir → Windows nginx :443 → 127.0.0.1:18088 → Docker nginx → app +http://wixur.ir:8088 → portproxy :8088 → 127.0.0.1:18088 (fallback) +http://wixur.ir:3000 → Gitea (native, no Docker) ``` | Service | Image source | @@ -41,14 +43,14 @@ Frontend public URLs are **baked in at build time** via `PUBLIC_BASE_URL`. After DNS A record for `wixur.ir` → Windows IP (already done if `http://wixur.ir:3000` and `:8088` work). -On Windows (Admin PowerShell): portproxy **80 → 18088**, firewall + router **port 80** — see §6–7. +On Windows: host nginx on **80** proxies to Docker **18088** ([`nginx/windows-edge-http.conf`](nginx/windows-edge-http.conf)). Remove portproxy on **80** (keep **8088** as fallback). Router must forward **80**. See §7. In Gitea → repo → **Settings → Actions → Variables**: -- `PUBLIC_BASE_URL` = `http://wixur.ir` +- `PUBLIC_BASE_URL` = `https://wixur.ir` - `STAGING_HTTP_PORT` = `80` (optional; workflow default is 80) -On disk: `FRONTEND_URL=http://wixur.ir` in `C:\dyolink\secrets\backend.staging.env`. +On disk: `FRONTEND_URL=https://wixur.ir` and `COOKIE_SECURE=true` in `C:\dyolink\secrets\backend.staging.env`. Then run the **Registry — build, push, deploy** workflow so the frontend image is rebuilt without `:8088`. @@ -163,7 +165,7 @@ Rules: |------|---------|--------| | `REGISTRY_HOST` | `host.docker.internal:3000` | **Windows + Docker Desktop:** Docker runs in a Linux VM — `127.0.0.1` is the VM, not Gitea. Use `host.docker.internal:3000`. Gitea `ROOT_URL` should match this so registry login from CI works. Browsers and the Linux VPS use `http://wixur.ir:3000`. | | `REGISTRY_OWNER` | `admin` | Gitea user/org owning packages | -| `PUBLIC_BASE_URL` | `http://wixur.ir` | How **users** open staging (no `:8088`, no trailing slash). Requires portproxy **80 → 18088** + router forward 80. | +| `PUBLIC_BASE_URL` | `https://wixur.ir` | How **users** open staging (HTTPS on 443). No trailing slash. | | `DEPLOY_SECRETS_DIR` | `C:/dyolink/secrets` | Forward slashes OK on Windows | | `CLONE_HOST` | `127.0.0.1:3000` | Git clone (runs on Windows host, not inside Docker VM) | | `STAGING_HTTP_PORT` | `80` | Public HTTP port (portproxy). Optional; workflow default is 80. | @@ -190,44 +192,37 @@ New-NetFirewallRule -DisplayName "Dyolink Staging HTTP 80" -Direction Inbound -P New-NetFirewallRule -DisplayName "Dyolink Staging 8088" -Direction Inbound -Protocol TCP -LocalPort 8088 -Action Allow ``` -### 7. External access on Windows + Docker Desktop (portproxy) +### 7. Port 80 = host nginx (not portproxy) -Gitea on **:3000** runs natively on Windows (`http://wixur.ir:3000`). Staging nginx binds **127.0.0.1:18088** (not 80). Windows **portproxy** maps public **80 → 18088** so users open **`http://wixur.ir`**. Do **not** bind Docker on 80 — portproxy already owns that port (`bind: access permissions` if you try). +Gitea stays on **:3000**. Docker staging nginx binds **127.0.0.1:18088**. A **Windows nginx** (the 1.29.x you already have) listens on **80** and proxies to 18088. Config: [`nginx/windows-edge-http.conf`](nginx/windows-edge-http.conf). -If IIS or another service already listens on 80, stop it first or portproxy will fail. - -Run **once** in **PowerShell as Administrator**: +**Do not** run portproxy on port 80 at the same time — it will lose to nginx (404 / hang). Keep portproxy **8088 → 18088** as fallback. ```powershell +# 1) Free port 80 from portproxy (nginx will bind 80) netsh interface portproxy delete v4tov4 listenaddress=0.0.0.0 listenport=80 -netsh interface portproxy add v4tov4 listenaddress=0.0.0.0 listenport=80 connectaddress=127.0.0.1 connectport=18088 -# Keep :8088 as a fallback until http://wixur.ir works, then you can delete it: -# netsh interface portproxy add v4tov4 listenaddress=0.0.0.0 listenport=8088 connectaddress=127.0.0.1 connectport=18088 -netsh interface portproxy show all -Start-Service iphlpsvc -Set-Service iphlpsvc -StartupType Automatic + +# 2) Confirm Docker staging is up +curl.exe http://127.0.0.1:18088/health + +# 3) Install windows-edge-http.conf into host nginx, then: +# nginx -t +# nginx -s reload +# (paths depend on where nginx is installed) + +# 4) Must print "healthy" (not 404) +curl.exe http://127.0.0.1/health ``` -Verify on the Windows PC: +Replace any **default_server** / leftover `server { listen 80; }` in the host nginx that returns 404, or this file will never win. -```powershell -curl http://127.0.0.1:18088/health -curl http://127.0.0.1/health -``` - -From another machine (DNS `wixur.ir` → this PC): +From another machine (after **router forward TCP 80** → this PC): ```bash curl http://wixur.ir/health ``` -If LAN works but the public hostname fails, add **router port forward 80** → Windows PC (same as Gitea **3000**). - -To remove the HTTP proxy later: - -```powershell -netsh interface portproxy delete v4tov4 listenaddress=0.0.0.0 listenport=80 -``` +**Mobinnet:** public **80** is the modem. Use **443** instead: router forward **TCP 443 → 192.168.1.100**, then `https://wixur.ir`. Let's Encrypt after mobile-data `/health` works (self-signed is enough for that test). --- @@ -338,7 +333,8 @@ docker logs dyolink_frontend_staging --tail 50 | `infrastructure/deploy.registry.env.example` | Manual deploy env template | | `infrastructure/database.staging.env.example` | Postgres secrets template | | `infrastructure/backend.staging.env.example` | API secrets template | -| `infrastructure/nginx/http-only.conf` | HTTP reverse proxy for staging | +| `infrastructure/nginx/http-only.conf` | HTTP reverse proxy **inside Docker** staging | +| `infrastructure/nginx/windows-edge-http.conf` | Windows **host** nginx on port 80 → 18088 | --- diff --git a/infrastructure/backend.staging.env.example b/infrastructure/backend.staging.env.example index 6c68292..6526996 100644 --- a/infrastructure/backend.staging.env.example +++ b/infrastructure/backend.staging.env.example @@ -9,11 +9,11 @@ JWT_EXPIRES_IN=15m JWT_REFRESH_SECRET=another_long_random_secret_different_from_JWT_SECRET JWT_REFRESH_EXPIRES_IN=30d -# CORS, cookies, and invite links — must match how users open the app (nginx host port) -FRONTEND_URL=http://wixur.ir +# CORS, cookies, and invite links — must match how users open the app +FRONTEND_URL=https://wixur.ir -# HTTP staging — keep false unless you terminate TLS in front of the app -COOKIE_SECURE=false +# TLS is terminated on Windows nginx :443 — cookies must be Secure +COOKIE_SECURE=true # SMS (sms.ir) SMS_IR_API_KEY=CHANGE_ME_SMS_IR_API_KEY diff --git a/infrastructure/nginx/windows-edge-http.conf b/infrastructure/nginx/windows-edge-http.conf new file mode 100644 index 0000000..36044a1 --- /dev/null +++ b/infrastructure/nginx/windows-edge-http.conf @@ -0,0 +1,78 @@ +# Windows host nginx.conf — paste over +# C:\tools\nginx-1.29.5\conf\nginx.conf +# +# :80 — LAN / 127.0.0.1 only (Mobinnet keeps public :80 for the modem) +# :443 — public https://wixur.ir (router must forward 443 → this PC) +# +# Put Let's Encrypt PEMs in conf\ssl\ (win-acme --pemfilesname wixur): +# ssl\wixur-chain.pem +# ssl\wixur-key.pem +# +# After save: nginx -t then restart nginx +# curl.exe https://wixur.ir/health → healthy +# (curl to 127.0.0.1:443 fails name-check; cert is for wixur.ir) + +#user nobody; +worker_processes 1; + +events { + worker_connections 1024; +} + + +http { + include mime.types; + default_type application/octet-stream; + + sendfile on; + keepalive_timeout 65; + client_max_body_size 50M; + + # Shared proxy to Docker staging + map $http_upgrade $connection_upgrade { + default upgrade; + '' close; + } + + server { + listen 80 default_server; + listen [::]:80 default_server; + server_name wixur.ir www.wixur.ir localhost 127.0.0.1; + + location / { + proxy_pass http://127.0.0.1:18088; + proxy_http_version 1.1; + proxy_set_header Host $host; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection $connection_upgrade; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_read_timeout 300; + proxy_connect_timeout 300; + } + } + + server { + listen 443 ssl; + listen [::]:443 ssl; + server_name wixur.ir www.wixur.ir localhost 127.0.0.1; + + ssl_certificate ssl/wixur-chain.pem; + ssl_certificate_key ssl/wixur-key.pem; + ssl_protocols TLSv1.2 TLSv1.3; + + location / { + proxy_pass http://127.0.0.1:18088; + proxy_http_version 1.1; + proxy_set_header Host $host; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection $connection_upgrade; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_read_timeout 300; + proxy_connect_timeout 300; + } + } +} diff --git a/infrastructure/scripts/windows-renew-wixur-cert.cmd b/infrastructure/scripts/windows-renew-wixur-cert.cmd new file mode 100644 index 0000000..08e30ce --- /dev/null +++ b/infrastructure/scripts/windows-renew-wixur-cert.cmd @@ -0,0 +1,11 @@ +@echo off +REM Run as Administrator. Stops nginx so win-acme can bind 443 (TLS-ALPN), +REM renews, then starts nginx again. Point the win-acme scheduled task here +REM instead of calling wacs.exe directly. +cd /d C:\tools\nginx-1.29.5 +taskkill /F /IM nginx.exe >nul 2>&1 +timeout /t 2 /nobreak >nul +cd /d C:\tools\win-acme +wacs.exe --renew --closeonfinish +cd /d C:\tools\nginx-1.29.5 +start "" nginx.exe