Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 60be656cf1 | |||
| 6465af11ee |
@@ -8,6 +8,7 @@
|
|||||||
#
|
#
|
||||||
# Variables:
|
# Variables:
|
||||||
# REGISTRY_HOST Windows Docker push host, e.g. host.docker.internal:3000
|
# REGISTRY_HOST Windows Docker push host, e.g. host.docker.internal:3000
|
||||||
|
# (not gitea.wixur.ir unless nginx client_max_body_size 0)
|
||||||
# REGISTRY_OWNER Gitea user/org for packages
|
# REGISTRY_OWNER Gitea user/org for packages
|
||||||
# CLONE_HOST git clone, e.g. 127.0.0.1:3000
|
# CLONE_HOST git clone, e.g. 127.0.0.1:3000
|
||||||
# PROD_PUBLIC_BASE_URL https://nudentic.ir (no trailing slash)
|
# PROD_PUBLIC_BASE_URL https://nudentic.ir (no trailing slash)
|
||||||
@@ -191,14 +192,20 @@ jobs:
|
|||||||
$infra = '${{ vars.PROD_INFRA_DIR }}'.Trim()
|
$infra = '${{ vars.PROD_INFRA_DIR }}'.Trim()
|
||||||
if ([string]::IsNullOrWhiteSpace($infra)) { $infra = '/opt/dyolink/infrastructure' }
|
if ([string]::IsNullOrWhiteSpace($infra)) { $infra = '/opt/dyolink/infrastructure' }
|
||||||
$ssh = @('-i', $env:PROD_SSH_KEY_PATH, '-o', 'StrictHostKeyChecking=accept-new')
|
$ssh = @('-i', $env:PROD_SSH_KEY_PATH, '-o', 'StrictHostKeyChecking=accept-new')
|
||||||
ssh.exe @ssh -p $port "${user}@${hostName}" "mkdir -p $infra/scripts"
|
ssh.exe @ssh -p $port "${user}@${hostName}" "mkdir -p $infra/scripts $infra/nginx"
|
||||||
scp.exe @ssh -P $port `
|
scp.exe @ssh -P $port `
|
||||||
infrastructure/docker-compose.prod.yml `
|
infrastructure/docker-compose.prod.yml `
|
||||||
"${user}@${hostName}:${infra}/docker-compose.prod.yml"
|
"${user}@${hostName}:${infra}/docker-compose.prod.yml"
|
||||||
scp.exe @ssh -P $port `
|
scp.exe @ssh -P $port `
|
||||||
infrastructure/scripts/prod-remote-deploy.sh `
|
infrastructure/scripts/prod-remote-deploy.sh `
|
||||||
"${user}@${hostName}:${infra}/scripts/prod-remote-deploy.sh"
|
"${user}@${hostName}:${infra}/scripts/prod-remote-deploy.sh"
|
||||||
ssh.exe @ssh -p $port "${user}@${hostName}" "chmod +x $infra/scripts/prod-remote-deploy.sh"
|
scp.exe @ssh -P $port `
|
||||||
|
infrastructure/scripts/render-nginx-ssl.sh `
|
||||||
|
"${user}@${hostName}:${infra}/scripts/render-nginx-ssl.sh"
|
||||||
|
scp.exe @ssh -P $port `
|
||||||
|
infrastructure/nginx/nginx.ssl.conf.template `
|
||||||
|
"${user}@${hostName}:${infra}/nginx/nginx.ssl.conf.template"
|
||||||
|
ssh.exe @ssh -p $port "${user}@${hostName}" "chmod +x $infra/scripts/prod-remote-deploy.sh $infra/scripts/render-nginx-ssl.sh"
|
||||||
|
|
||||||
- name: Login on Linux and deploy tag
|
- name: Login on Linux and deploy tag
|
||||||
run: |
|
run: |
|
||||||
|
|||||||
@@ -4,7 +4,10 @@
|
|||||||
# Production (nudentic.ir / git tags): .gitea/workflows/prod-tag-deploy.yml — this file is Windows staging only.
|
# Production (nudentic.ir / git tags): .gitea/workflows/prod-tag-deploy.yml — this file is Windows staging only.
|
||||||
#
|
#
|
||||||
# Repository Variables (Settings → Actions → Variables):
|
# Repository Variables (Settings → Actions → Variables):
|
||||||
# REGISTRY_HOST Docker registry host:port (no http/https). Windows Docker Desktop → host.docker.internal:3000
|
# REGISTRY_HOST Docker registry host:port (no http/https).
|
||||||
|
# Windows Docker Desktop → host.docker.internal:3000
|
||||||
|
# Do NOT use gitea.wixur.ir unless Windows nginx for that
|
||||||
|
# host has client_max_body_size 0 — Docker layer PUTs 413 otherwise.
|
||||||
# REGISTRY_OWNER Gitea user or org that owns the packages
|
# REGISTRY_OWNER Gitea user or org that owns the packages
|
||||||
# PUBLIC_BASE_URL URL users open in the browser, e.g. http://wixur.ir (no trailing slash, no :8088)
|
# PUBLIC_BASE_URL URL users open in the browser, e.g. http://wixur.ir (no trailing slash, no :8088)
|
||||||
# NEXT_PUBLIC_SENTRY_DSN GlitchTip frontend project DSN (https://…@errors.wixur.ir/…)
|
# NEXT_PUBLIC_SENTRY_DSN GlitchTip frontend project DSN (https://…@errors.wixur.ir/…)
|
||||||
|
|||||||
@@ -29,6 +29,10 @@ FRONTEND_URL=http://localhost:3001
|
|||||||
# Set true when the app is served over HTTPS (required for Secure auth cookies)
|
# Set true when the app is served over HTTPS (required for Secure auth cookies)
|
||||||
COOKIE_SECURE=false
|
COOKIE_SECURE=false
|
||||||
|
|
||||||
|
# AdminJS panel at http://localhost:3000/admin (not under /api)
|
||||||
|
# ADMINJS_EMAIL=admin@dyolink.com
|
||||||
|
# ADMINJS_PASSWORD=admin123
|
||||||
|
|
||||||
# OAuth (optional — uncomment when configured)
|
# OAuth (optional — uncomment when configured)
|
||||||
# GOOGLE_CLIENT_ID=your-google-client-id
|
# GOOGLE_CLIENT_ID=your-google-client-id
|
||||||
# GOOGLE_CLIENT_SECRET=your-google-client-secret
|
# GOOGLE_CLIENT_SECRET=your-google-client-secret
|
||||||
|
|||||||
@@ -17,7 +17,10 @@ export class AdminModule {
|
|||||||
const { AdminModule: AdminJSModule } = await import('@adminjs/nestjs');
|
const { AdminModule: AdminJSModule } = await import('@adminjs/nestjs');
|
||||||
|
|
||||||
const authenticate = async (email: string, password: string) => {
|
const authenticate = async (email: string, password: string) => {
|
||||||
if (email === 'admin@dyolink.com' && password === 'admin123') {
|
const adminEmail =
|
||||||
|
process.env.ADMINJS_EMAIL?.trim() || 'admin@dyolink.com';
|
||||||
|
const adminPassword = process.env.ADMINJS_PASSWORD || 'admin123';
|
||||||
|
if (email === adminEmail && password === adminPassword) {
|
||||||
return { email, role: 'admin' };
|
return { email, role: 'admin' };
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
@@ -30,6 +33,19 @@ export class AdminModule {
|
|||||||
imports: [ConfigModule],
|
imports: [ConfigModule],
|
||||||
inject: [PrismaService, ConfigService],
|
inject: [PrismaService, ConfigService],
|
||||||
useFactory: (prisma: PrismaService, config: ConfigService) => {
|
useFactory: (prisma: PrismaService, config: ConfigService) => {
|
||||||
|
const cookieSecure = config.get<boolean>('cookie.secure') === true;
|
||||||
|
const sessionSecret =
|
||||||
|
config.get<string>('jwt.secret') ||
|
||||||
|
config.get('JWT_SECRET') ||
|
||||||
|
'secret-key-change-this';
|
||||||
|
if (
|
||||||
|
process.env.NODE_ENV === 'production' &&
|
||||||
|
!process.env.ADMINJS_PASSWORD
|
||||||
|
) {
|
||||||
|
console.warn(
|
||||||
|
'⚠️ ADMINJS_PASSWORD is unset; AdminJS is using the local default. Set it in backend.env.',
|
||||||
|
);
|
||||||
|
}
|
||||||
return {
|
return {
|
||||||
adminJsOptions: {
|
adminJsOptions: {
|
||||||
rootPath: '/admin',
|
rootPath: '/admin',
|
||||||
@@ -93,12 +109,17 @@ export class AdminModule {
|
|||||||
auth: {
|
auth: {
|
||||||
authenticate,
|
authenticate,
|
||||||
cookieName: 'dyolink-admin',
|
cookieName: 'dyolink-admin',
|
||||||
cookiePassword: config.get('JWT_SECRET') || 'secret-key-change-this',
|
cookiePassword: sessionSecret,
|
||||||
},
|
},
|
||||||
sessionOptions: {
|
sessionOptions: {
|
||||||
resave: false,
|
resave: false,
|
||||||
saveUninitialized: false,
|
saveUninitialized: false,
|
||||||
secret: config.get('JWT_SECRET') || 'secret-key-change-this',
|
secret: sessionSecret,
|
||||||
|
cookie: {
|
||||||
|
httpOnly: true,
|
||||||
|
sameSite: 'lax' as const,
|
||||||
|
secure: cookieSecure,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -30,6 +30,9 @@ async function bootstrap() {
|
|||||||
// Nest's built-in one would otherwise reject a voice recording at 100 kb.
|
// Nest's built-in one would otherwise reject a voice recording at 100 kb.
|
||||||
const app = await NestFactory.create(AppModule, { bodyParser: false });
|
const app = await NestFactory.create(AppModule, { bodyParser: false });
|
||||||
|
|
||||||
|
// Nginx terminates TLS; AdminJS sessions and Secure cookies need the real proto/host.
|
||||||
|
app.getHttpAdapter().getInstance().set('trust proxy', 1);
|
||||||
|
|
||||||
// Voice needs a larger JSON limit than everything else; see body-parsers.ts.
|
// Voice needs a larger JSON limit than everything else; see body-parsers.ts.
|
||||||
app.use(createJsonBodyParser());
|
app.use(createJsonBodyParser());
|
||||||
app.use(urlencoded({ extended: true }));
|
app.use(urlencoded({ extended: true }));
|
||||||
|
|||||||
@@ -44,7 +44,7 @@ Then `sudo systemctl restart docker` (containers restart).
|
|||||||
- `REGISTRY_PREFIX=wixur.ir:3000/<gitea-owner>` (same owner as Gitea `REGISTRY_OWNER`)
|
- `REGISTRY_PREFIX=wixur.ir:3000/<gitea-owner>` (same owner as Gitea `REGISTRY_OWNER`)
|
||||||
- `TAG=v1.0.1` (CI overrides per release)
|
- `TAG=v1.0.1` (CI overrides per release)
|
||||||
|
|
||||||
3. **`secrets/backend.env`:** `FRONTEND_URL=https://nudentic.ir`, `COOKIE_SECURE=true`
|
3. **`secrets/backend.env`:** `FRONTEND_URL=https://nudentic.ir`, `COOKIE_SECURE=true`, plus `ADMINJS_EMAIL` / `ADMINJS_PASSWORD` for `https://nudentic.ir/admin`
|
||||||
|
|
||||||
4. **SSH** — user that can run `docker` (e.g. `dyolink` in the `docker` group). Put the matching **public** key in `~/.ssh/authorized_keys`.
|
4. **SSH** — user that can run `docker` (e.g. `dyolink` in the `docker` group). Put the matching **public** key in `~/.ssh/authorized_keys`.
|
||||||
|
|
||||||
@@ -72,7 +72,7 @@ git push origin v1.0.1
|
|||||||
|
|
||||||
Or Gitea → Actions → **Production — tag build, push, deploy** → Run → tag `v1.0.1`.
|
Or Gitea → Actions → **Production — tag build, push, deploy** → Run → tag `v1.0.1`.
|
||||||
|
|
||||||
Check `https://nudentic.ir/api/health`.
|
Check `https://nudentic.ir/api/health`. AdminJS is `https://nudentic.ir/admin`.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -81,8 +81,9 @@ Check `https://nudentic.ir/api/health`.
|
|||||||
|
|
||||||
```
|
```
|
||||||
Internet → Nginx (:80 / :443)
|
Internet → Nginx (:80 / :443)
|
||||||
├── / → frontend:3000 (Next.js)
|
├── / → frontend:3000 (Next.js)
|
||||||
└── /api → backend:3000 (NestJS)
|
├── /api → backend:3000 (NestJS API)
|
||||||
|
└── /admin → backend:3000 (AdminJS)
|
||||||
└── postgres:5432
|
└── postgres:5432
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -496,6 +497,7 @@ docker compose -f docker-compose.prod.yml --env-file .env exec frontend \
|
|||||||
- [ ] `.env`, `database.env`, `backend.env` configured (real passwords + JWT)
|
- [ ] `.env`, `database.env`, `backend.env` configured (real passwords + JWT)
|
||||||
- [ ] `./scripts/deploy-prod.sh` completed
|
- [ ] `./scripts/deploy-prod.sh` completed
|
||||||
- [ ] `curl https://DOMAIN/api/health` returns `{"status":"ok",...}`
|
- [ ] `curl https://DOMAIN/api/health` returns `{"status":"ok",...}`
|
||||||
|
- [ ] `https://DOMAIN/admin` shows the AdminJS login (not the Next.js app)
|
||||||
- [ ] App loads in browser
|
- [ ] App loads in browser
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|||||||
@@ -163,7 +163,7 @@ Rules:
|
|||||||
|
|
||||||
| Name | Example | Notes |
|
| Name | Example | Notes |
|
||||||
|------|---------|--------|
|
|------|---------|--------|
|
||||||
| `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_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 can use `http://wixur.ir:3000` or `https://gitea.wixur.ir`. **Do not** set this to `gitea.wixur.ir` unless that HTTPS proxy allows unlimited body size (see 413 below). |
|
||||||
| `REGISTRY_OWNER` | `admin` | Gitea user/org owning packages |
|
| `REGISTRY_OWNER` | `admin` | Gitea user/org owning packages |
|
||||||
| `PUBLIC_BASE_URL` | `https://wixur.ir` | How **users** open staging (HTTPS on 443). No trailing slash. |
|
| `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 |
|
| `DEPLOY_SECRETS_DIR` | `C:/dyolink/secrets` | Forward slashes OK on Windows |
|
||||||
@@ -299,6 +299,7 @@ On the Windows host, from repo `infrastructure/`:
|
|||||||
| `no matching online runner with label` | Runner **offline** → start `act_runner.exe daemon`. Or wrong **runner level** → re-register with token from **Site Administration → Actions → Runners** or **repo → Settings → Actions → Runners** (not user profile). Confirm runner appears on **repo** Runners page as Online. |
|
| `no matching online runner with label` | Runner **offline** → start `act_runner.exe daemon`. Or wrong **runner level** → re-register with token from **Site Administration → Actions → Runners** or **repo → Settings → Actions → Runners** (not user profile). Confirm runner appears on **repo** Runners page as Online. |
|
||||||
| Runner can't register on public IP | Use `http://127.0.0.1:3000` for `--instance` |
|
| Runner can't register on public IP | Use `http://127.0.0.1:3000` for `--instance` |
|
||||||
| Variable name rejected in Gitea | No `GITEA_*` / `GITHUB_*` prefixes; use `CLONE_HOST` |
|
| Variable name rejected in Gitea | No `GITEA_*` / `GITHUB_*` prefixes; use `CLONE_HOST` |
|
||||||
|
| `413 Request Entity Too Large` on `docker push` to `https://gitea.wixur.ir/v2/…/blobs/uploads` | Nginx (or Cloudflare) in front of Gitea is rejecting the image layer. **Fix the proxy** (then `nginx -s reload`): in the `server { server_name gitea.wixur.ir; }` block set `client_max_body_size 0;` and `proxy_request_buffering off;` — snippet: [`nginx/windows-gitea.wixur.snippet.conf`](nginx/windows-gitea.wixur.snippet.conf). **Or skip the proxy:** set `REGISTRY_HOST=host.docker.internal:3000` (and Gitea `ROOT_URL`) so CI pushes to `:3000`. If the hostname is orange-clouded on Cloudflare, grey-cloud it (free plan caps uploads at 100MB). |
|
||||||
| `docker login` connection refused on `127.0.0.1:3000` | **Docker Desktop on Windows:** set `REGISTRY_HOST=host.docker.internal:3000`, add it to insecure-registries, set Gitea `ROOT_URL=http://host.docker.internal:3000/`. Keep `CLONE_HOST=127.0.0.1:3000` for git. |
|
| `docker login` connection refused on `127.0.0.1:3000` | **Docker Desktop on Windows:** set `REGISTRY_HOST=host.docker.internal:3000`, add it to insecure-registries, set Gitea `ROOT_URL=http://host.docker.internal:3000/`. Keep `CLONE_HOST=127.0.0.1:3000` for git. |
|
||||||
| `docker login` / push denied, redirect to public IP | Set Gitea `ROOT_URL` to a host Docker can reach (`host.docker.internal:3000` on Windows Docker Desktop). |
|
| `docker login` / push denied, redirect to public IP | Set Gitea `ROOT_URL` to a host Docker can reach (`host.docker.internal:3000` on Windows Docker Desktop). |
|
||||||
| `server gave HTTP response to HTTPS client` | Add registry host to Docker **insecure-registries**, restart Docker |
|
| `server gave HTTP response to HTTPS client` | Add registry host to Docker **insecure-registries**, restart Docker |
|
||||||
@@ -334,7 +335,8 @@ docker logs dyolink_frontend_staging --tail 50
|
|||||||
| `infrastructure/database.staging.env.example` | Postgres secrets template |
|
| `infrastructure/database.staging.env.example` | Postgres secrets template |
|
||||||
| `infrastructure/backend.staging.env.example` | API secrets template |
|
| `infrastructure/backend.staging.env.example` | API secrets template |
|
||||||
| `infrastructure/nginx/http-only.conf` | HTTP reverse proxy **inside Docker** 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 |
|
| `infrastructure/nginx/windows-edge-http.conf` | Windows **host** nginx on port 80/443 → 18088 |
|
||||||
|
| `infrastructure/nginx/windows-gitea.wixur.snippet.conf` | Windows nginx vhost for `https://gitea.wixur.ir` (unlimited body — Docker push) |
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
@@ -14,6 +14,11 @@ JWT_REFRESH_EXPIRES_IN=30d
|
|||||||
# Must match DOMAIN in .env — used for CORS, invite links, cookies
|
# Must match DOMAIN in .env — used for CORS, invite links, cookies
|
||||||
FRONTEND_URL=https://nudentic.ir
|
FRONTEND_URL=https://nudentic.ir
|
||||||
|
|
||||||
|
# AdminJS at https://nudentic.ir/admin (nginx proxies /admin to the API).
|
||||||
|
# Change these — the code defaults are only for local development.
|
||||||
|
ADMINJS_EMAIL=admin@nudentic.ir
|
||||||
|
ADMINJS_PASSWORD=CHANGE_ME_STRONG_ADMINJS_PASSWORD
|
||||||
|
|
||||||
# Required for HTTPS — browsers reject Secure cookies over plain HTTP
|
# Required for HTTPS — browsers reject Secure cookies over plain HTTP
|
||||||
COOKIE_SECURE=true
|
COOKIE_SECURE=true
|
||||||
|
|
||||||
|
|||||||
@@ -12,6 +12,10 @@ JWT_REFRESH_EXPIRES_IN=30d
|
|||||||
# CORS, cookies, and invite links — must match how users open the app
|
# CORS, cookies, and invite links — must match how users open the app
|
||||||
FRONTEND_URL=https://wixur.ir
|
FRONTEND_URL=https://wixur.ir
|
||||||
|
|
||||||
|
# AdminJS at https://wixur.ir/admin (nginx proxies /admin to the API).
|
||||||
|
ADMINJS_EMAIL=admin@wixur.ir
|
||||||
|
ADMINJS_PASSWORD=CHANGE_ME_STRONG_ADMINJS_PASSWORD
|
||||||
|
|
||||||
# TLS is terminated on Windows nginx :443 — cookies must be Secure
|
# TLS is terminated on Windows nginx :443 — cookies must be Secure
|
||||||
COOKIE_SECURE=true
|
COOKIE_SECURE=true
|
||||||
|
|
||||||
|
|||||||
@@ -43,6 +43,22 @@ server {
|
|||||||
proxy_connect_timeout 300;
|
proxy_connect_timeout 300;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# AdminJS (Nest, not under /api)
|
||||||
|
location /admin {
|
||||||
|
set $backend_upstream http://backend:3000;
|
||||||
|
proxy_pass $backend_upstream;
|
||||||
|
proxy_http_version 1.1;
|
||||||
|
proxy_set_header Upgrade $http_upgrade;
|
||||||
|
proxy_set_header Connection 'upgrade';
|
||||||
|
proxy_set_header Host $host;
|
||||||
|
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_cache_bypass $http_upgrade;
|
||||||
|
proxy_read_timeout 300;
|
||||||
|
proxy_connect_timeout 300;
|
||||||
|
}
|
||||||
|
|
||||||
location /health {
|
location /health {
|
||||||
access_log off;
|
access_log off;
|
||||||
return 200 "healthy\n";
|
return 200 "healthy\n";
|
||||||
|
|||||||
@@ -46,6 +46,21 @@ server {
|
|||||||
proxy_connect_timeout 300;
|
proxy_connect_timeout 300;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# AdminJS (Nest, not under /api)
|
||||||
|
location /admin {
|
||||||
|
proxy_pass http://dyolink_backend;
|
||||||
|
proxy_http_version 1.1;
|
||||||
|
proxy_set_header Upgrade $http_upgrade;
|
||||||
|
proxy_set_header Connection 'upgrade';
|
||||||
|
proxy_set_header Host $host;
|
||||||
|
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_cache_bypass $http_upgrade;
|
||||||
|
proxy_read_timeout 300;
|
||||||
|
proxy_connect_timeout 300;
|
||||||
|
}
|
||||||
|
|
||||||
location /health {
|
location /health {
|
||||||
access_log off;
|
access_log off;
|
||||||
return 200 "healthy\n";
|
return 200 "healthy\n";
|
||||||
|
|||||||
@@ -86,6 +86,21 @@ server {
|
|||||||
proxy_read_timeout 300;
|
proxy_read_timeout 300;
|
||||||
proxy_connect_timeout 300;
|
proxy_connect_timeout 300;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# AdminJS (Nest, not under /api)
|
||||||
|
location /admin {
|
||||||
|
proxy_pass http://dyolink_backend;
|
||||||
|
proxy_http_version 1.1;
|
||||||
|
proxy_set_header Upgrade $http_upgrade;
|
||||||
|
proxy_set_header Connection 'upgrade';
|
||||||
|
proxy_set_header Host $host;
|
||||||
|
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_cache_bypass $http_upgrade;
|
||||||
|
proxy_read_timeout 300;
|
||||||
|
proxy_connect_timeout 300;
|
||||||
|
}
|
||||||
|
|
||||||
# Health check endpoint (no logging)
|
# Health check endpoint (no logging)
|
||||||
location /health {
|
location /health {
|
||||||
|
|||||||
@@ -83,6 +83,21 @@ server {
|
|||||||
proxy_connect_timeout 300;
|
proxy_connect_timeout 300;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# AdminJS (Nest, not under /api) — https://nudentic.ir/admin
|
||||||
|
location /admin {
|
||||||
|
proxy_pass http://dyolink_backend;
|
||||||
|
proxy_http_version 1.1;
|
||||||
|
proxy_set_header Upgrade $http_upgrade;
|
||||||
|
proxy_set_header Connection 'upgrade';
|
||||||
|
proxy_set_header Host $host;
|
||||||
|
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_cache_bypass $http_upgrade;
|
||||||
|
proxy_read_timeout 300;
|
||||||
|
proxy_connect_timeout 300;
|
||||||
|
}
|
||||||
|
|
||||||
location /health {
|
location /health {
|
||||||
access_log off;
|
access_log off;
|
||||||
return 200 "healthy\n";
|
return 200 "healthy\n";
|
||||||
|
|||||||
@@ -26,7 +26,9 @@ http {
|
|||||||
|
|
||||||
sendfile on;
|
sendfile on;
|
||||||
keepalive_timeout 65;
|
keepalive_timeout 65;
|
||||||
client_max_body_size 50M;
|
# Unlimited at http{} so a gitea.wixur.ir vhost can inherit it (Docker layers
|
||||||
|
# 413 with 50M). App server blocks below cap uploads at 50M.
|
||||||
|
client_max_body_size 0;
|
||||||
|
|
||||||
# Shared proxy to Docker staging
|
# Shared proxy to Docker staging
|
||||||
map $http_upgrade $connection_upgrade {
|
map $http_upgrade $connection_upgrade {
|
||||||
@@ -39,6 +41,8 @@ http {
|
|||||||
listen [::]:80 default_server;
|
listen [::]:80 default_server;
|
||||||
server_name wixur.ir www.wixur.ir localhost 127.0.0.1;
|
server_name wixur.ir www.wixur.ir localhost 127.0.0.1;
|
||||||
|
|
||||||
|
client_max_body_size 50M;
|
||||||
|
|
||||||
location / {
|
location / {
|
||||||
proxy_pass http://127.0.0.1:18088;
|
proxy_pass http://127.0.0.1:18088;
|
||||||
proxy_http_version 1.1;
|
proxy_http_version 1.1;
|
||||||
@@ -62,6 +66,8 @@ http {
|
|||||||
ssl_certificate_key ssl/wixur-key.pem;
|
ssl_certificate_key ssl/wixur-key.pem;
|
||||||
ssl_protocols TLSv1.2 TLSv1.3;
|
ssl_protocols TLSv1.2 TLSv1.3;
|
||||||
|
|
||||||
|
client_max_body_size 50M;
|
||||||
|
|
||||||
location / {
|
location / {
|
||||||
proxy_pass http://127.0.0.1:18088;
|
proxy_pass http://127.0.0.1:18088;
|
||||||
proxy_http_version 1.1;
|
proxy_http_version 1.1;
|
||||||
@@ -75,4 +81,8 @@ http {
|
|||||||
proxy_connect_timeout 300;
|
proxy_connect_timeout 300;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# Gitea + container registry (https://gitea.wixur.ir). Optional include —
|
||||||
|
# copy windows-gitea.wixur.snippet.conf next to this file and uncomment:
|
||||||
|
# include windows-gitea.wixur.snippet.conf;
|
||||||
}
|
}
|
||||||
|
|||||||
58
infrastructure/nginx/windows-gitea.wixur.snippet.conf
Normal file
58
infrastructure/nginx/windows-gitea.wixur.snippet.conf
Normal file
@@ -0,0 +1,58 @@
|
|||||||
|
# Fastest fix if you already have a gitea.wixur.ir server { } block: add only
|
||||||
|
# client_max_body_size 0;
|
||||||
|
# proxy_request_buffering off;
|
||||||
|
# inside that server (or its location /), then nginx -t && nginx -s reload.
|
||||||
|
# Do not add a second server_name gitea.wixur.ir — duplicate listen/ssl will fail.
|
||||||
|
#
|
||||||
|
# Full vhost (only if that host is not already in nginx.conf): paste inside http { }
|
||||||
|
# of C:\tools\nginx-1.29.5\conf\nginx.conf. Needs the $connection_upgrade map from
|
||||||
|
# windows-edge-http.conf.
|
||||||
|
#
|
||||||
|
# Certs: keep the ssl_certificate paths you already use for gitea.wixur.ir
|
||||||
|
# (the names below are placeholders).
|
||||||
|
#
|
||||||
|
# After save: nginx -t then nginx -s reload
|
||||||
|
|
||||||
|
server {
|
||||||
|
listen 80;
|
||||||
|
listen [::]:80;
|
||||||
|
server_name gitea.wixur.ir;
|
||||||
|
|
||||||
|
location /.well-known/acme-challenge/ {
|
||||||
|
root html;
|
||||||
|
}
|
||||||
|
|
||||||
|
location / {
|
||||||
|
return 301 https://$host$request_uri;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
server {
|
||||||
|
listen 443 ssl;
|
||||||
|
listen [::]:443 ssl;
|
||||||
|
server_name gitea.wixur.ir;
|
||||||
|
|
||||||
|
ssl_certificate ssl/gitea-chain.pem;
|
||||||
|
ssl_certificate_key ssl/gitea-key.pem;
|
||||||
|
ssl_protocols TLSv1.2 TLSv1.3;
|
||||||
|
|
||||||
|
# 0 = unlimited (Docker registry blob PUT)
|
||||||
|
client_max_body_size 0;
|
||||||
|
client_body_timeout 600s;
|
||||||
|
|
||||||
|
location / {
|
||||||
|
proxy_pass http://127.0.0.1:3000;
|
||||||
|
proxy_http_version 1.1;
|
||||||
|
proxy_request_buffering off;
|
||||||
|
proxy_buffering off;
|
||||||
|
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 600;
|
||||||
|
proxy_connect_timeout 60;
|
||||||
|
proxy_send_timeout 600;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -45,7 +45,16 @@ pull_one() {
|
|||||||
echo "Pulling backend/frontend :$TAG from Gitea (REGISTRY_PREFIX in .env)"
|
echo "Pulling backend/frontend :$TAG from Gitea (REGISTRY_PREFIX in .env)"
|
||||||
pull_one backend
|
pull_one backend
|
||||||
pull_one frontend
|
pull_one frontend
|
||||||
|
|
||||||
|
if [ -f nginx/nginx.ssl.conf.template ]; then
|
||||||
|
echo "Rendering nginx SSL config (includes /admin → backend)"
|
||||||
|
chmod +x scripts/render-nginx-ssl.sh
|
||||||
|
./scripts/render-nginx-ssl.sh
|
||||||
|
fi
|
||||||
|
|
||||||
|
docker compose -f docker-compose.prod.yml --env-file .env up -d --force-recreate nginx
|
||||||
docker compose -f docker-compose.prod.yml --env-file .env up -d
|
docker compose -f docker-compose.prod.yml --env-file .env up -d
|
||||||
echo "=== Status ==="
|
echo "=== Status ==="
|
||||||
docker compose -f docker-compose.prod.yml --env-file .env ps
|
docker compose -f docker-compose.prod.yml --env-file .env ps
|
||||||
echo "Health: https://nudentic.ir/api/health"
|
echo "Health: https://nudentic.ir/api/health"
|
||||||
|
echo "AdminJS: https://nudentic.ir/admin"
|
||||||
|
|||||||
Reference in New Issue
Block a user