190 lines
5.6 KiB
Bash
190 lines
5.6 KiB
Bash
#!/bin/sh
|
|
# ============================================
|
|
# Chat Switchboard - Frontend Entrypoint
|
|
# ============================================
|
|
# nginx serves static assets and proxies page
|
|
# routes to the Go backend for template rendering.
|
|
# API/WS routes are handled by Ingress directly.
|
|
#
|
|
# Environment (required):
|
|
# BACKEND_URL - Backend service URL
|
|
# (e.g. "http://switchboard-be:8080")
|
|
#
|
|
# Environment (optional):
|
|
# BASE_PATH - URL prefix (e.g. "/dev", "" for root)
|
|
# ENVIRONMENT - Environment name for logging
|
|
# ============================================
|
|
set -e
|
|
|
|
BASE_PATH="${BASE_PATH:-}"
|
|
BACKEND_URL="${BACKEND_URL:?BACKEND_URL is required (e.g. http://switchboard-be:8080)}"
|
|
ENVIRONMENT="${ENVIRONMENT:-production}"
|
|
|
|
# Read version from VERSION file (baked in at build time)
|
|
APP_VERSION="dev"
|
|
if [ -f /VERSION ]; then
|
|
APP_VERSION=$(cat /VERSION | tr -d '[:space:]')
|
|
fi
|
|
|
|
# Compute build hash from JS file contents (unique per deploy)
|
|
BUILD_HASH=$(find /usr/share/nginx/html/js -name '*.js' -exec md5sum {} + 2>/dev/null | sort | md5sum | cut -c1-8)
|
|
if [ -z "${BUILD_HASH}" ]; then
|
|
BUILD_HASH=$(date +%s | md5sum | cut -c1-8)
|
|
fi
|
|
|
|
# ── Inject into index.html (redirect page) ──
|
|
sed -i \
|
|
-e "s|%%BASE_PATH%%|${BASE_PATH}|g" \
|
|
-e "s|%%APP_VERSION%%|${APP_VERSION}|g" \
|
|
/usr/share/nginx/html/index.html
|
|
|
|
# Inject version and build hash into service worker
|
|
sed -i \
|
|
-e "s|%%APP_VERSION%%|${APP_VERSION}|g" \
|
|
-e "s|%%BUILD_HASH%%|${BUILD_HASH}|g" \
|
|
/usr/share/nginx/html/sw.js
|
|
|
|
echo "✅ Frontend: BASE_PATH=${BASE_PATH:-/} VERSION=${APP_VERSION} BUILD=${BUILD_HASH} BACKEND=${BACKEND_URL}"
|
|
|
|
# ── Page route proxy blocks ─────────────────
|
|
page_proxy_block() {
|
|
local path="$1"
|
|
cat <<PROXY
|
|
location ${path} {
|
|
proxy_pass ${BACKEND_URL};
|
|
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
|
|
}
|
|
|
|
PAGE_ROUTES=$(cat <<ROUTES
|
|
# ── Page routes → backend ────────────────
|
|
$(page_proxy_block "= ${BASE_PATH}/")
|
|
$(page_proxy_block "= ${BASE_PATH}/login")
|
|
location ~ ^${BASE_PATH}/chat/[^/]+\$ {
|
|
proxy_pass ${BACKEND_URL};
|
|
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;
|
|
}
|
|
$(page_proxy_block "${BASE_PATH}/admin")
|
|
$(page_proxy_block "${BASE_PATH}/editor")
|
|
$(page_proxy_block "${BASE_PATH}/notes")
|
|
$(page_proxy_block "${BASE_PATH}/settings")
|
|
$(page_proxy_block "${BASE_PATH}/w")
|
|
# v0.27.0: Extension surface page routes
|
|
$(page_proxy_block "${BASE_PATH}/s")
|
|
# v0.27.0: Extension surface static assets (proxied to backend; on-disk at /data/packages/)
|
|
$(page_proxy_block "${BASE_PATH}/surfaces")
|
|
ROUTES
|
|
)
|
|
|
|
# ── Generate nginx config ───────────────────
|
|
if [ -z "${BASE_PATH}" ]; then
|
|
cat > /etc/nginx/conf.d/default.conf <<NGINX
|
|
server {
|
|
listen 80;
|
|
server_name _;
|
|
root /usr/share/nginx/html;
|
|
index index.html;
|
|
|
|
gzip on;
|
|
gzip_vary on;
|
|
gzip_min_length 1024;
|
|
gzip_proxied expired no-cache no-store private auth;
|
|
gzip_types text/plain text/css text/xml text/javascript application/x-javascript application/xml application/javascript;
|
|
|
|
location ~* \.(jpg|jpeg|png|gif|ico|css|js|woff|woff2)$ {
|
|
expires 1y;
|
|
add_header Cache-Control "public, immutable";
|
|
}
|
|
|
|
location = /sw.js {
|
|
expires off;
|
|
add_header Cache-Control "no-cache, no-store, must-revalidate";
|
|
}
|
|
|
|
location /branding/ {
|
|
alias /branding/;
|
|
expires 1h;
|
|
add_header Cache-Control "public";
|
|
try_files \$uri =404;
|
|
}
|
|
|
|
${PAGE_ROUTES}
|
|
|
|
location / {
|
|
try_files \$uri \$uri/ /index.html;
|
|
}
|
|
|
|
location = /healthz {
|
|
return 200 'ok';
|
|
add_header Content-Type text/plain;
|
|
}
|
|
|
|
add_header X-Frame-Options "SAMEORIGIN" always;
|
|
add_header X-Content-Type-Options "nosniff" always;
|
|
add_header X-XSS-Protection "1; mode=block" always;
|
|
}
|
|
NGINX
|
|
else
|
|
cat > /etc/nginx/conf.d/default.conf <<NGINX
|
|
server {
|
|
listen 80;
|
|
server_name _;
|
|
|
|
gzip on;
|
|
gzip_vary on;
|
|
gzip_min_length 1024;
|
|
gzip_proxied expired no-cache no-store private auth;
|
|
gzip_types text/plain text/css text/xml text/javascript application/x-javascript application/xml application/javascript;
|
|
|
|
${PAGE_ROUTES}
|
|
|
|
location ${BASE_PATH}/ {
|
|
alias /usr/share/nginx/html/;
|
|
try_files \$uri \$uri/ ${BASE_PATH}/index.html;
|
|
|
|
location ~* \.(jpg|jpeg|png|gif|ico|css|js|woff|woff2)\$ {
|
|
expires 1y;
|
|
add_header Cache-Control "public, immutable";
|
|
}
|
|
|
|
location = ${BASE_PATH}/sw.js {
|
|
alias /usr/share/nginx/html/sw.js;
|
|
expires off;
|
|
add_header Cache-Control "no-cache, no-store, must-revalidate";
|
|
}
|
|
}
|
|
|
|
location ${BASE_PATH}/branding/ {
|
|
alias /branding/;
|
|
expires 1h;
|
|
add_header Cache-Control "public";
|
|
try_files \$uri =404;
|
|
}
|
|
|
|
location = ${BASE_PATH} {
|
|
return 301 ${BASE_PATH}/;
|
|
}
|
|
|
|
location = /healthz {
|
|
return 200 'ok';
|
|
add_header Content-Type text/plain;
|
|
}
|
|
|
|
add_header X-Frame-Options "SAMEORIGIN" always;
|
|
add_header X-Content-Type-Options "nosniff" always;
|
|
add_header X-XSS-Protection "1; mode=block" always;
|
|
}
|
|
NGINX
|
|
fi
|
|
|
|
exec nginx -g 'daemon off;'
|