fix(ops): enable HTTPS backoffice and Sentry via Finland relay
Ship TLS nginx for backoffice, keep HTTP bootstrap until certs exist, proxy /monitoring to sentry-relay, and tunnel server/edge Sentry through Finland.
This commit is contained in:
parent
d2222fe73e
commit
d3c48a1ed1
29
deploy/nginx/backoffice-http-bootstrap.conf
Normal file
29
deploy/nginx/backoffice-http-bootstrap.conf
Normal file
@ -0,0 +1,29 @@
|
||||
# Admin backoffice — HTTP bootstrap until Let's Encrypt exists.
|
||||
# After: certbot certonly --webroot -w /var/www/html -d backoffice.ghabilee.ir
|
||||
# deploy switches to deploy/nginx/backoffice.conf (TLS).
|
||||
|
||||
upstream ghabilee_admin {
|
||||
server 127.0.0.1:3009;
|
||||
keepalive 16;
|
||||
}
|
||||
|
||||
server {
|
||||
listen 80;
|
||||
listen [::]:80;
|
||||
server_name backoffice.ghabilee.ir;
|
||||
|
||||
location /.well-known/acme-challenge/ {
|
||||
root /var/www/html;
|
||||
}
|
||||
|
||||
location / {
|
||||
proxy_pass http://ghabilee_admin;
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Connection "";
|
||||
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_read_timeout 60s;
|
||||
}
|
||||
}
|
||||
@ -1,17 +1,19 @@
|
||||
# Admin backoffice — TLS terminated by Nginx, app on 127.0.0.1:3009
|
||||
# DNS: point backoffice.ghabilee.ir at this VPS, then:
|
||||
# certbot --nginx -d backoffice.ghabilee.ir
|
||||
|
||||
map $http_upgrade $connection_upgrade {
|
||||
default upgrade;
|
||||
'' close;
|
||||
}
|
||||
#
|
||||
# Requires:
|
||||
# - DNS A for backoffice.ghabilee.ir → this VPS
|
||||
# - certbot cert: /etc/letsencrypt/live/backoffice.ghabilee.ir/
|
||||
# - ghabilee site enabled (provides $connection_upgrade map)
|
||||
#
|
||||
# Issue / renew:
|
||||
# certbot certonly --webroot -w /var/www/html -d backoffice.ghabilee.ir
|
||||
|
||||
upstream ghabilee_admin {
|
||||
server 127.0.0.1:3009;
|
||||
keepalive 16;
|
||||
}
|
||||
|
||||
# HTTP → ACME + redirect to HTTPS
|
||||
server {
|
||||
listen 80;
|
||||
listen [::]:80;
|
||||
@ -21,6 +23,38 @@ server {
|
||||
root /var/www/html;
|
||||
}
|
||||
|
||||
location / {
|
||||
return 301 https://backoffice.ghabilee.ir$request_uri;
|
||||
}
|
||||
}
|
||||
|
||||
server {
|
||||
listen 443 ssl;
|
||||
listen [::]:443 ssl;
|
||||
server_name backoffice.ghabilee.ir;
|
||||
|
||||
ssl_certificate /etc/letsencrypt/live/backoffice.ghabilee.ir/fullchain.pem;
|
||||
ssl_certificate_key /etc/letsencrypt/live/backoffice.ghabilee.ir/privkey.pem;
|
||||
include /etc/letsencrypt/options-ssl-nginx.conf;
|
||||
ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem;
|
||||
|
||||
client_max_body_size 10m;
|
||||
|
||||
# Browser Sentry tunnel → Finland (ingest.de is blocked from Iran egress).
|
||||
location = /monitoring {
|
||||
proxy_pass https://sentry-relay.ghabilee.ir/monitoring;
|
||||
proxy_ssl_server_name on;
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Host sentry-relay.ghabilee.ir;
|
||||
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_connect_timeout 5s;
|
||||
proxy_read_timeout 20s;
|
||||
proxy_send_timeout 20s;
|
||||
client_max_body_size 1m;
|
||||
}
|
||||
|
||||
location / {
|
||||
proxy_pass http://ghabilee_admin;
|
||||
proxy_http_version 1.1;
|
||||
|
||||
@ -2,8 +2,9 @@
|
||||
|
||||
1. Point the DNS record for `backoffice.ghabilee.ir` to the production VPS.
|
||||
2. Create `/opt/ghabilee-admin/.env` from `.env.example` and set production values.
|
||||
3. Configure the reverse proxy to forward `backoffice.ghabilee.ir` to `127.0.0.1:3009` and enable TLS.
|
||||
4. Push to `main` (or run the deploy workflow manually) after the environment is ready.
|
||||
3. Issue TLS: `certbot certonly --webroot -w /var/www/html -d backoffice.ghabilee.ir` (after ACME path is reachable on port 80).
|
||||
4. Deploy installs `deploy/nginx/backoffice.conf` when the cert exists (otherwise HTTP bootstrap); app listens on `127.0.0.1:3009`.
|
||||
5. Push to `main` (or run the deploy workflow manually) after the environment is ready.
|
||||
|
||||
Deploy Telegram alerts use admin-specific copy in `scripts/notify-deploy.sh`
|
||||
(success → backoffice URL; distinct from the consumer frontend message).
|
||||
|
||||
@ -109,8 +109,14 @@ scp -i "$HOME/.ssh/iran_key" -o IdentitiesOnly=yes -o StrictHostKeyChecking=acce
|
||||
deploy/docker-compose.production.yml \
|
||||
"${IRAN_USER}@${IRAN_HOST}:${IRAN_APP}/docker-compose.yml"
|
||||
if [[ -f deploy/nginx/backoffice.conf ]]; then
|
||||
NGINX_BACKOFFICE=deploy/nginx/backoffice.conf
|
||||
if ! "${ISSH[@]}" "${IRAN_USER}@${IRAN_HOST}" 'test -s /etc/letsencrypt/live/backoffice.ghabilee.ir/fullchain.pem'; then
|
||||
if [[ -f deploy/nginx/backoffice-http-bootstrap.conf ]]; then
|
||||
NGINX_BACKOFFICE=deploy/nginx/backoffice-http-bootstrap.conf
|
||||
fi
|
||||
fi
|
||||
scp -i "$HOME/.ssh/iran_key" -o IdentitiesOnly=yes -o StrictHostKeyChecking=accept-new \
|
||||
deploy/nginx/backoffice.conf \
|
||||
"$NGINX_BACKOFFICE" \
|
||||
"${IRAN_USER}@${IRAN_HOST}:/etc/nginx/sites-available/backoffice"
|
||||
"${ISSH[@]}" "${IRAN_USER}@${IRAN_HOST}" '
|
||||
ln -sfn /etc/nginx/sites-available/backoffice /etc/nginx/sites-enabled/backoffice
|
||||
|
||||
@ -21,7 +21,13 @@ fi
|
||||
|
||||
install -m 0644 "$SRC_DIR/deploy/docker-compose.production.yml" "$APP_DIR/docker-compose.yml"
|
||||
if [ -f "$SRC_DIR/deploy/nginx/backoffice.conf" ]; then
|
||||
install -m 0644 "$SRC_DIR/deploy/nginx/backoffice.conf" /etc/nginx/sites-available/backoffice
|
||||
if [ -s /etc/letsencrypt/live/backoffice.ghabilee.ir/fullchain.pem ]; then
|
||||
install -m 0644 "$SRC_DIR/deploy/nginx/backoffice.conf" /etc/nginx/sites-available/backoffice
|
||||
elif [ -f "$SRC_DIR/deploy/nginx/backoffice-http-bootstrap.conf" ]; then
|
||||
install -m 0644 "$SRC_DIR/deploy/nginx/backoffice-http-bootstrap.conf" /etc/nginx/sites-available/backoffice
|
||||
else
|
||||
install -m 0644 "$SRC_DIR/deploy/nginx/backoffice.conf" /etc/nginx/sites-available/backoffice
|
||||
fi
|
||||
ln -sfn /etc/nginx/sites-available/backoffice /etc/nginx/sites-enabled/backoffice
|
||||
nginx -t
|
||||
systemctl reload nginx
|
||||
|
||||
@ -4,6 +4,14 @@ import { shouldDropExpectedProductSentryEvent } from './lib/observability/expect
|
||||
|
||||
const dsn = process.env.SENTRY_DSN?.trim() || process.env.NEXT_PUBLIC_SENTRY_DSN?.trim()
|
||||
|
||||
/** Iran runtime cannot reach ingest.de; send via Finland relay. */
|
||||
function sentryTunnel(): string | undefined {
|
||||
const fromEnv = process.env.SENTRY_TUNNEL?.trim()
|
||||
if (fromEnv) return fromEnv
|
||||
if (process.env.NODE_ENV === 'production') return 'https://sentry-relay.ghabilee.ir/monitoring'
|
||||
return undefined
|
||||
}
|
||||
|
||||
function tracesSampleRate(): number {
|
||||
const raw = process.env.SENTRY_TRACES_SAMPLE_RATE ?? '0.1'
|
||||
const parsed = Number.parseFloat(raw)
|
||||
@ -17,6 +25,7 @@ export function initEdgeSentry() {
|
||||
|
||||
Sentry.init({
|
||||
dsn,
|
||||
tunnel: sentryTunnel(),
|
||||
environment: process.env.SENTRY_ENVIRONMENT?.trim() || process.env.NODE_ENV || 'production',
|
||||
release: process.env.SENTRY_RELEASE?.trim() || process.env.NEXT_PUBLIC_APP_VERSION || undefined,
|
||||
tracesSampleRate: tracesSampleRate(),
|
||||
|
||||
@ -4,6 +4,14 @@ import { shouldDropExpectedProductSentryEvent } from './lib/observability/expect
|
||||
|
||||
const dsn = process.env.SENTRY_DSN?.trim() || process.env.NEXT_PUBLIC_SENTRY_DSN?.trim()
|
||||
|
||||
/** Iran runtime cannot reach ingest.de; send via Finland relay. */
|
||||
function sentryTunnel(): string | undefined {
|
||||
const fromEnv = process.env.SENTRY_TUNNEL?.trim()
|
||||
if (fromEnv) return fromEnv
|
||||
if (process.env.NODE_ENV === 'production') return 'https://sentry-relay.ghabilee.ir/monitoring'
|
||||
return undefined
|
||||
}
|
||||
|
||||
function tracesSampleRate(): number {
|
||||
const raw = process.env.SENTRY_TRACES_SAMPLE_RATE ?? '0.1'
|
||||
const parsed = Number.parseFloat(raw)
|
||||
@ -17,6 +25,7 @@ export function initServerSentry() {
|
||||
|
||||
Sentry.init({
|
||||
dsn,
|
||||
tunnel: sentryTunnel(),
|
||||
environment: process.env.SENTRY_ENVIRONMENT?.trim() || process.env.NODE_ENV || 'production',
|
||||
release: process.env.SENTRY_RELEASE?.trim() || process.env.NEXT_PUBLIC_APP_VERSION || undefined,
|
||||
tracesSampleRate: tracesSampleRate(),
|
||||
|
||||
Loading…
Reference in New Issue
Block a user