chore: sync submodule state (parent ref update)
Made-with: Cursor
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
# Production Environment Configuration Template
|
||||
# Copy this to /home/explorer/explorer-monorepo/.env and fill in values
|
||||
# See docs/PRODUCTION_CHECKLIST.md for JWT_SECRET and migration steps before going live.
|
||||
|
||||
# ============================================
|
||||
# Database Configuration
|
||||
@@ -105,6 +106,8 @@ SOUL_MACHINES_API_SECRET=
|
||||
# ============================================
|
||||
# Security
|
||||
# ============================================
|
||||
# Optional: restrict CORS (default *). Example: https://explorer.d-bis.org
|
||||
CORS_ALLOWED_ORIGIN=
|
||||
JWT_SECRET=CHANGE_THIS_JWT_SECRET
|
||||
ENCRYPTION_KEY=CHANGE_THIS_ENCRYPTION_KEY_32_BYTES
|
||||
|
||||
|
||||
16
deployment/add-csp-http-location.sh
Normal file
16
deployment/add-csp-http-location.sh
Normal file
@@ -0,0 +1,16 @@
|
||||
#!/bin/bash
|
||||
# Add CSP with unsafe-eval to HTTP location = / in blockscout nginx (for NPM proxy on :80)
|
||||
set -e
|
||||
CONFIG=/etc/nginx/sites-available/blockscout
|
||||
if grep -q "Content-Security-Policy" "$CONFIG" 2>/dev/null; then
|
||||
echo "CSP already present"
|
||||
else
|
||||
# Insert CSP line after add_header Cache-Control in first location = /
|
||||
sed -i '/location = \/ {/,/try_files \/index.html =404;/{
|
||||
/add_header Cache-Control "no-store, no-cache, must-revalidate"/a\
|
||||
add_header Content-Security-Policy "default-src '\''self'\''; script-src '\''self'\'' '\''unsafe-inline'\'' '\''unsafe-eval'\'' https://cdn.jsdelivr.net https://unpkg.com https://cdnjs.cloudflare.com; style-src '\''self'\'' '\''unsafe-inline'\'' https://cdnjs.cloudflare.com; img-src '\''self'\'' data: https:; font-src '\''self'\'' https://cdnjs.cloudflare.com; connect-src '\''self'\'' https://explorer.d-bis.org wss://explorer.d-bis.org https://rpc-http-pub.d-bis.org wss://rpc-ws-pub.d-bis.org http://192.168.11.221:8545 ws://192.168.11.221:8546;" always;
|
||||
}' "$CONFIG"
|
||||
echo "Added CSP to HTTP location = /"
|
||||
fi
|
||||
nginx -t && systemctl reload nginx
|
||||
echo "Done"
|
||||
12
deployment/common/README.md
Normal file
12
deployment/common/README.md
Normal file
@@ -0,0 +1,12 @@
|
||||
# deployment-common
|
||||
|
||||
Reusable deployment snippets (nginx, systemd, Cloudflare, fail2ban).
|
||||
Use as reference or copy into your project.
|
||||
|
||||
## Contents
|
||||
|
||||
- **nginx-api-location.conf** – Generic `location /api/` proxy snippet (upstream host/port to be adjusted).
|
||||
- **systemd-api-service.example** – Example systemd unit for a REST API (env and paths to be adjusted).
|
||||
- **cloudflare / fail2ban** – See parent `../cloudflare/` and `../fail2ban/` for full configs.
|
||||
|
||||
When this is a separate repo, add as submodule at `deployment/common`.
|
||||
16
deployment/common/nginx-api-location.conf
Normal file
16
deployment/common/nginx-api-location.conf
Normal file
@@ -0,0 +1,16 @@
|
||||
# Generic snippet: proxy /api/ to a backend (Blockscout, Go API, etc.)
|
||||
# Include in your server block. Replace upstream host/port as needed.
|
||||
|
||||
location /api/ {
|
||||
proxy_pass http://127.0.0.1:4000;
|
||||
proxy_http_version 1.1;
|
||||
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 300s;
|
||||
proxy_connect_timeout 75s;
|
||||
add_header Access-Control-Allow-Origin *;
|
||||
add_header Access-Control-Allow-Methods "GET, POST, OPTIONS";
|
||||
add_header Access-Control-Allow-Headers "Content-Type";
|
||||
}
|
||||
21
deployment/common/systemd-api-service.example
Normal file
21
deployment/common/systemd-api-service.example
Normal file
@@ -0,0 +1,21 @@
|
||||
# Example systemd unit for a REST API (e.g. explorer API on port 8080)
|
||||
# Copy to /etc/systemd/system/explorer-api.service and adjust paths/env.
|
||||
|
||||
[Unit]
|
||||
Description=Explorer REST API
|
||||
After=network.target postgresql.service
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
User=explorer
|
||||
WorkingDirectory=/opt/explorer
|
||||
Environment=PORT=8080
|
||||
Environment=DB_HOST=localhost
|
||||
Environment=DB_NAME=explorer
|
||||
Environment=CHAIN_ID=138
|
||||
ExecStart=/opt/explorer/bin/api-server
|
||||
Restart=on-failure
|
||||
RestartSec=5
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
@@ -1,207 +0,0 @@
|
||||
# Rate limiting zones
|
||||
limit_req_zone $binary_remote_addr zone=api_limit:10m rate=10r/s;
|
||||
limit_req_zone $binary_remote_addr zone=general_limit:10m rate=50r/s;
|
||||
limit_conn_zone $binary_remote_addr zone=conn_limit:10m;
|
||||
|
||||
# Upstream servers
|
||||
upstream explorer_api {
|
||||
server 127.0.0.1:8080;
|
||||
keepalive 32;
|
||||
}
|
||||
|
||||
upstream explorer_frontend {
|
||||
server 127.0.0.1:3000;
|
||||
keepalive 32;
|
||||
}
|
||||
|
||||
# Redirect HTTP to HTTPS
|
||||
server {
|
||||
listen 80;
|
||||
listen [::]:80;
|
||||
server_name explorer.d-bis.org www.explorer.d-bis.org;
|
||||
|
||||
# Allow Let's Encrypt validation
|
||||
location /.well-known/acme-challenge/ {
|
||||
root /var/www/html;
|
||||
}
|
||||
|
||||
# Redirect all other traffic to HTTPS
|
||||
location / {
|
||||
return 301 https://$server_name$request_uri;
|
||||
}
|
||||
}
|
||||
|
||||
# Main HTTPS server
|
||||
server {
|
||||
listen 443 ssl http2;
|
||||
listen [::]:443 ssl http2;
|
||||
server_name explorer.d-bis.org www.explorer.d-bis.org;
|
||||
|
||||
# SSL Configuration (Cloudflare handles SSL, but we can add local certs too)
|
||||
# ssl_certificate /etc/letsencrypt/live/explorer.d-bis.org/fullchain.pem;
|
||||
# ssl_certificate_key /etc/letsencrypt/live/explorer.d-bis.org/privkey.pem;
|
||||
# ssl_protocols TLSv1.2 TLSv1.3;
|
||||
# ssl_ciphers HIGH:!aNULL:!MD5;
|
||||
# ssl_prefer_server_ciphers on;
|
||||
|
||||
# Security headers
|
||||
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" always;
|
||||
add_header X-Frame-Options "SAMEORIGIN" always;
|
||||
add_header X-Content-Type-Options "nosniff" always;
|
||||
add_header X-XSS-Protection "1; mode=block" always;
|
||||
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
|
||||
add_header Permissions-Policy "geolocation=(), microphone=(), camera=()" always;
|
||||
|
||||
# Content Security Policy (adjust as needed)
|
||||
# CSP: unsafe-eval required by ethers.js v5 UMD from CDN
|
||||
add_header Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval' https://cdn.jsdelivr.net https://unpkg.com https://cdnjs.cloudflare.com; style-src 'self' 'unsafe-inline' https://cdnjs.cloudflare.com; img-src 'self' data: https:; font-src 'self' data: https://cdnjs.cloudflare.com; connect-src 'self' https://api.cloudflare.com https://explorer.d-bis.org wss://explorer.d-bis.org https://rpc-http-pub.d-bis.org wss://rpc-ws-pub.d-bis.org http://192.168.11.221:8545 ws://192.168.11.221:8546;" always;
|
||||
|
||||
# Logging
|
||||
access_log /var/log/nginx/explorer-access.log combined buffer=32k flush=5m;
|
||||
error_log /var/log/nginx/explorer-error.log warn;
|
||||
|
||||
# Client settings
|
||||
client_max_body_size 10M;
|
||||
client_body_timeout 60s;
|
||||
client_header_timeout 60s;
|
||||
|
||||
# Gzip compression
|
||||
gzip on;
|
||||
gzip_vary on;
|
||||
gzip_proxied any;
|
||||
gzip_comp_level 6;
|
||||
gzip_min_length 1000;
|
||||
gzip_types
|
||||
text/plain
|
||||
text/css
|
||||
text/xml
|
||||
text/javascript
|
||||
application/json
|
||||
application/javascript
|
||||
application/xml+rss
|
||||
application/rss+xml
|
||||
font/truetype
|
||||
font/opentype
|
||||
application/vnd.ms-fontobject
|
||||
image/svg+xml;
|
||||
|
||||
# Brotli compression (if available)
|
||||
# brotli on;
|
||||
# brotli_comp_level 6;
|
||||
# brotli_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript;
|
||||
|
||||
# Frontend
|
||||
location / {
|
||||
limit_req zone=general_limit burst=20 nodelay;
|
||||
limit_conn conn_limit 10;
|
||||
|
||||
proxy_pass http://explorer_frontend;
|
||||
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_set_header X-Forwarded-Host $host;
|
||||
proxy_set_header X-Forwarded-Port $server_port;
|
||||
|
||||
proxy_cache_bypass $http_upgrade;
|
||||
proxy_read_timeout 300s;
|
||||
proxy_connect_timeout 75s;
|
||||
proxy_send_timeout 300s;
|
||||
|
||||
# Buffering
|
||||
proxy_buffering on;
|
||||
proxy_buffer_size 4k;
|
||||
proxy_buffers 8 4k;
|
||||
proxy_busy_buffers_size 8k;
|
||||
}
|
||||
|
||||
# API endpoints
|
||||
location /api/ {
|
||||
limit_req zone=api_limit burst=20 nodelay;
|
||||
limit_conn conn_limit 5;
|
||||
|
||||
proxy_pass http://explorer_api;
|
||||
proxy_http_version 1.1;
|
||||
|
||||
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_set_header X-Forwarded-Host $host;
|
||||
proxy_set_header Connection "";
|
||||
|
||||
proxy_read_timeout 300s;
|
||||
proxy_connect_timeout 75s;
|
||||
proxy_send_timeout 300s;
|
||||
|
||||
# Disable buffering for API responses
|
||||
proxy_buffering off;
|
||||
|
||||
# CORS headers (Cloudflare will also add these)
|
||||
add_header Access-Control-Allow-Origin "*" always;
|
||||
add_header Access-Control-Allow-Methods "GET, POST, OPTIONS" always;
|
||||
add_header Access-Control-Allow-Headers "Content-Type, X-API-Key, Authorization" always;
|
||||
|
||||
# Handle preflight
|
||||
if ($request_method = OPTIONS) {
|
||||
add_header Access-Control-Allow-Origin "*";
|
||||
add_header Access-Control-Allow-Methods "GET, POST, OPTIONS";
|
||||
add_header Access-Control-Allow-Headers "Content-Type, X-API-Key, Authorization";
|
||||
add_header Access-Control-Max-Age 1728000;
|
||||
add_header Content-Type "text/plain; charset=utf-8";
|
||||
add_header Content-Length 0;
|
||||
return 204;
|
||||
}
|
||||
}
|
||||
|
||||
# WebSocket support
|
||||
location /ws {
|
||||
proxy_pass http://explorer_api;
|
||||
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_read_timeout 86400s;
|
||||
proxy_send_timeout 86400s;
|
||||
proxy_connect_timeout 75s;
|
||||
}
|
||||
|
||||
# Static files caching
|
||||
location ~* \.(jpg|jpeg|png|gif|ico|css|js|svg|woff|woff2|ttf|eot|webp|avif)$ {
|
||||
expires 1y;
|
||||
add_header Cache-Control "public, immutable";
|
||||
add_header X-Content-Type-Options "nosniff";
|
||||
access_log off;
|
||||
log_not_found off;
|
||||
}
|
||||
|
||||
# Health check endpoint (internal only)
|
||||
location /health {
|
||||
access_log off;
|
||||
proxy_pass http://explorer_api/health;
|
||||
proxy_set_header Host $host;
|
||||
}
|
||||
|
||||
# Block access to sensitive files
|
||||
location ~ /\. {
|
||||
deny all;
|
||||
access_log off;
|
||||
log_not_found off;
|
||||
}
|
||||
|
||||
location ~ \.(env|git|gitignore|md|sh)$ {
|
||||
deny all;
|
||||
access_log off;
|
||||
log_not_found off;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user