fix(deploy+docker+frontend): enforce same-origin via Nginx, disable Next Image optimization in Docker, allow Socket.IO polling fallback, and improve health checks and access info
- generate-config.sh: add --with-nginx flag handling; when enabled, set NEXT_PUBLIC_API_URL empty to use same-origin /api and /socket.io; add BACKEND_INTERNAL_URL for SSR/internal fetch; adjust lan-tls HTTPS (8443) and TLS generation policy - deploy.sh: show only valid access URLs when Nginx is enabled (gateway URLs), avoid misleading :3002/:3001 entries - frontend (env/webrtc): return mutable transports [websocket,polling]; use empty signaling server for same-origin; comments in English - frontend (next.config): support NEXT_IMAGE_UNOPTIMIZED to turn off image optimization in Docker - frontend (health): prefer BACKEND_INTERNAL_URL for internal health checks, fallback to public URL/localhost - docker-compose + Dockerfile(frontend): pass NEXT_IMAGE_UNOPTIMIZED and BACKEND_INTERNAL_URL envs
This commit is contained in:
@@ -582,8 +582,13 @@ show_deployment_info() {
|
||||
echo " API: http://$domain_name:${backend_port:-3001}"
|
||||
fi
|
||||
elif [[ -n "$public_ip" ]]; then
|
||||
if [[ "$WITH_NGINX" == "true" ]]; then
|
||||
echo " Public access: http://$public_ip"
|
||||
echo " API: http://$public_ip"
|
||||
else
|
||||
echo " Public access: http://$public_ip:${frontend_port:-3002}"
|
||||
echo " API: http://$public_ip:${backend_port:-3001}"
|
||||
fi
|
||||
else
|
||||
# Fallback: show LAN and localhost if public IP is unavailable
|
||||
echo " Frontend: http://localhost:${frontend_port:-3002}"
|
||||
@@ -591,15 +596,26 @@ show_deployment_info() {
|
||||
fi
|
||||
else
|
||||
# Private/basic: localhost + LAN
|
||||
if [[ "$WITH_NGINX" == "true" ]]; then
|
||||
# When Nginx is enabled and frontend uses same-origin API, prefer the gateway as the primary entry
|
||||
echo " Frontend: http://localhost"
|
||||
echo " API: http://localhost"
|
||||
else
|
||||
echo " Frontend: http://localhost:${frontend_port:-3002}"
|
||||
echo " Backend API: http://localhost:${backend_port:-3001}"
|
||||
fi
|
||||
if [[ -n "$local_ip" ]] && [[ "$local_ip" != "127.0.0.1" ]]; then
|
||||
echo ""
|
||||
echo -e "${BLUE}🌐 LAN Access:${NC}"
|
||||
if [[ "$WITH_NGINX" == "true" ]]; then
|
||||
echo " Frontend: http://$local_ip"
|
||||
echo " API: http://$local_ip"
|
||||
else
|
||||
echo " Frontend: http://$local_ip:${frontend_port:-3002}"
|
||||
echo " Backend API: http://$local_ip:${backend_port:-3001}"
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
|
||||
if [[ "$WITH_NGINX" == "true" ]]; then
|
||||
echo ""
|
||||
|
||||
@@ -62,11 +62,14 @@ services:
|
||||
- NEXT_PUBLIC_TURN_HOST=${NEXT_PUBLIC_TURN_HOST}
|
||||
- NEXT_PUBLIC_TURN_USERNAME=${NEXT_PUBLIC_TURN_USERNAME}
|
||||
- NEXT_PUBLIC_TURN_PASSWORD=${NEXT_PUBLIC_TURN_PASSWORD}
|
||||
- NEXT_IMAGE_UNOPTIMIZED=${NEXT_IMAGE_UNOPTIMIZED:-true}
|
||||
container_name: privydrop-frontend
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
- NODE_ENV=production
|
||||
- NEXT_PUBLIC_API_URL=${NEXT_PUBLIC_API_URL:-http://localhost:3001}
|
||||
- BACKEND_INTERNAL_URL=${BACKEND_INTERNAL_URL:-http://backend:3001}
|
||||
- NEXT_IMAGE_UNOPTIMIZED=${NEXT_IMAGE_UNOPTIMIZED:-true}
|
||||
- PORT=3002
|
||||
- HOSTNAME=0.0.0.0
|
||||
- NODE_EXTRA_CA_CERTS=/opt/privydrop/ssl/ca-cert.pem
|
||||
|
||||
@@ -30,6 +30,7 @@ log_error() {
|
||||
|
||||
# Defaults and global parameters
|
||||
WITH_TURN="${WITH_TURN:-false}"
|
||||
WITH_NGINX="${WITH_NGINX:-false}"
|
||||
TURN_EXTERNAL_IP_OVERRIDE=""
|
||||
TURN_MIN_PORT_DEFAULT=49152
|
||||
TURN_MAX_PORT_DEFAULT=49252
|
||||
@@ -95,6 +96,7 @@ Options:
|
||||
public: Public HTTP + TURN (no domain)
|
||||
full: Domain + HTTPS (Let’s Encrypt) + TURN
|
||||
--with-turn Enable TURN in any mode. Default external-ip=LOCAL_IP
|
||||
--with-nginx Indicate Nginx reverse proxy is enabled (frontdoor same-origin)
|
||||
--turn-external-ip IP Explicit TURN external-ip; if not set, use PUBLIC_IP, otherwise fallback to LOCAL_IP
|
||||
--turn-port-range R TURN UDP port range, format MIN-MAX; default 49152-49252
|
||||
--domain DOMAIN Domain (for Nginx/certs/TURN realm, e.g., turn.DOMAIN)
|
||||
@@ -174,6 +176,7 @@ generate_env_file() {
|
||||
# Compute access endpoints for different deployment modes
|
||||
# Support both localhost and host IP for browser access; helpful for Docker direct access or local debugging
|
||||
local cors_origin="http://${LOCAL_IP}:3002,http://localhost:3002"
|
||||
# API URL exposed to browser. When WITH_NGINX=true, prefer same-origin (empty => use relative /api)
|
||||
local api_url="http://${LOCAL_IP}:3001"
|
||||
local ssl_mode="none"
|
||||
local turn_enabled="false"
|
||||
@@ -188,22 +191,36 @@ generate_env_file() {
|
||||
lan-http)
|
||||
# Allow both dev ports and nginx origins to avoid CORS when --with-nginx is used
|
||||
cors_origin="http://${LOCAL_IP}:3002,http://localhost:3002,http://${LOCAL_IP},http://localhost"
|
||||
if [[ "$WITH_NGINX" == "true" ]]; then
|
||||
# Same-origin via Nginx (frontend uses relative /api)
|
||||
api_url=""
|
||||
else
|
||||
api_url="http://${LOCAL_IP}:3001"
|
||||
fi
|
||||
;;
|
||||
lan-tls)
|
||||
if [[ "$WEB_HTTPS_ENABLED" == "true" ]]; then
|
||||
HTTPS_LISTEN_PORT="8443"
|
||||
# Allow common dev origins to avoid CORS when accessing via http://localhost and :3002
|
||||
# Keep API URL on HTTPS to go through nginx TLS
|
||||
# Allow HTTP for local debug; HTTPS is exposed on 8443 by default
|
||||
cors_origin="https://${LOCAL_IP}:${HTTPS_LISTEN_PORT},https://localhost:${HTTPS_LISTEN_PORT},http://${LOCAL_IP},http://${LOCAL_IP}:3002,http://localhost,http://localhost:3002"
|
||||
if [[ "$WITH_NGINX" == "true" ]]; then
|
||||
# Same-origin via Nginx (relative /api), TLS is terminated by Nginx
|
||||
api_url=""
|
||||
else
|
||||
api_url="https://${LOCAL_IP}:${HTTPS_LISTEN_PORT}"
|
||||
fi
|
||||
ssl_mode="self-signed"
|
||||
fi
|
||||
;;
|
||||
public)
|
||||
local effective_public_host="${PUBLIC_IP:-$LOCAL_IP}"
|
||||
cors_origin="http://${effective_public_host}:3002,http://localhost:3002,http://${effective_public_host},http://localhost"
|
||||
if [[ "$WITH_NGINX" == "true" ]]; then
|
||||
# Same-origin via Nginx gateway
|
||||
api_url=""
|
||||
else
|
||||
api_url="http://${effective_public_host}:3001"
|
||||
fi
|
||||
turn_enabled="true"
|
||||
;;
|
||||
full)
|
||||
@@ -287,6 +304,11 @@ HTTP_PORT=80
|
||||
HTTPS_PORT=${HTTPS_LISTEN_PORT:-443}
|
||||
DOCKER_HTTPS_CONTAINER_PORT=${docker_https_container_port}
|
||||
|
||||
# =============================================================================
|
||||
# Internal backend URL for server-side (frontend container only)
|
||||
# =============================================================================
|
||||
BACKEND_INTERNAL_URL=http://backend:3001
|
||||
|
||||
# =============================================================================
|
||||
# Redis config
|
||||
# =============================================================================
|
||||
@@ -924,6 +946,10 @@ main() {
|
||||
ENABLE_SNI443=false
|
||||
shift
|
||||
;;
|
||||
--with-nginx)
|
||||
WITH_NGINX=true
|
||||
shift
|
||||
;;
|
||||
--enable-web-https)
|
||||
WEB_HTTPS_ENABLED=true
|
||||
HTTPS_LISTEN_PORT="8443"
|
||||
|
||||
@@ -28,12 +28,14 @@ ARG NEXT_PUBLIC_API_URL
|
||||
ARG NEXT_PUBLIC_TURN_HOST
|
||||
ARG NEXT_PUBLIC_TURN_USERNAME
|
||||
ARG NEXT_PUBLIC_TURN_PASSWORD
|
||||
ARG NEXT_IMAGE_UNOPTIMIZED
|
||||
|
||||
# Inject public env vars during frontend build (for client direct access to backend and TURN)
|
||||
ENV NEXT_PUBLIC_API_URL=${NEXT_PUBLIC_API_URL}
|
||||
ENV NEXT_PUBLIC_TURN_HOST=${NEXT_PUBLIC_TURN_HOST}
|
||||
ENV NEXT_PUBLIC_TURN_USERNAME=${NEXT_PUBLIC_TURN_USERNAME}
|
||||
ENV NEXT_PUBLIC_TURN_PASSWORD=${NEXT_PUBLIC_TURN_PASSWORD}
|
||||
ENV NEXT_IMAGE_UNOPTIMIZED=${NEXT_IMAGE_UNOPTIMIZED}
|
||||
|
||||
# Set environment variables
|
||||
ENV NEXT_TELEMETRY_DISABLED 1
|
||||
@@ -81,7 +83,9 @@ ARG NEXT_PUBLIC_API_URL
|
||||
ARG NEXT_PUBLIC_TURN_HOST
|
||||
ARG NEXT_PUBLIC_TURN_USERNAME
|
||||
ARG NEXT_PUBLIC_TURN_PASSWORD
|
||||
ARG NEXT_IMAGE_UNOPTIMIZED
|
||||
ENV NEXT_PUBLIC_API_URL=${NEXT_PUBLIC_API_URL}
|
||||
ENV NEXT_PUBLIC_TURN_HOST=${NEXT_PUBLIC_TURN_HOST}
|
||||
ENV NEXT_PUBLIC_TURN_USERNAME=${NEXT_PUBLIC_TURN_USERNAME}
|
||||
ENV NEXT_PUBLIC_TURN_PASSWORD=${NEXT_PUBLIC_TURN_PASSWORD}
|
||||
ENV NEXT_IMAGE_UNOPTIMIZED=${NEXT_IMAGE_UNOPTIMIZED}
|
||||
|
||||
@@ -17,14 +17,14 @@ export async function GET(request: NextRequest) {
|
||||
environment: process.env.NODE_ENV || 'development'
|
||||
};
|
||||
|
||||
// 检查后端API连接
|
||||
// Check backend API connectivity
|
||||
const backendHealth = await checkBackendHealth();
|
||||
if (backendHealth.status !== 'connected') {
|
||||
errors.push('Backend API connection failed');
|
||||
status = 'degraded';
|
||||
}
|
||||
|
||||
// 系统信息
|
||||
// System info snapshot
|
||||
const systemInfo = {
|
||||
runtime: process.env.NEXT_RUNTIME || 'nodejs',
|
||||
nextjs: {
|
||||
@@ -64,10 +64,11 @@ export async function GET(request: NextRequest) {
|
||||
}
|
||||
}
|
||||
|
||||
// 检查后端API健康状态
|
||||
// Check backend API health
|
||||
async function checkBackendHealth() {
|
||||
try {
|
||||
const backendUrl = process.env.NEXT_PUBLIC_API_URL || 'http://localhost:3001';
|
||||
// Prefer container-internal URL, then public URL, then localhost fallback
|
||||
const backendUrl = process.env.BACKEND_INTERNAL_URL || process.env.NEXT_PUBLIC_API_URL || 'http://localhost:3001';
|
||||
const start = Date.now();
|
||||
|
||||
const response = await fetch(`${backendUrl}/health`, {
|
||||
@@ -75,7 +76,7 @@ async function checkBackendHealth() {
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
// 设置超时时间
|
||||
// Timeout to avoid long-hanging connections in degraded networks
|
||||
signal: AbortSignal.timeout(5000)
|
||||
});
|
||||
|
||||
@@ -101,7 +102,7 @@ async function checkBackendHealth() {
|
||||
} catch (error) {
|
||||
return {
|
||||
status: 'disconnected',
|
||||
backendUrl: process.env.NEXT_PUBLIC_API_URL || 'http://localhost:3001',
|
||||
backendUrl: process.env.BACKEND_INTERNAL_URL || process.env.NEXT_PUBLIC_API_URL || 'http://localhost:3001',
|
||||
error: error instanceof Error ? error.message : 'Unknown error'
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import { ManagerOptions, SocketOptions } from "socket.io-client";
|
||||
|
||||
export const config = {
|
||||
API_URL: process.env.NEXT_PUBLIC_API_URL!,
|
||||
USE_HTTPS: process.env.NODE_ENV !== "development",
|
||||
@@ -54,14 +56,12 @@ export const getIceServers = () => {
|
||||
return iceServers;
|
||||
};
|
||||
|
||||
export const getSocketOptions = () => {
|
||||
return config.USE_HTTPS
|
||||
? {
|
||||
secure: true,
|
||||
export const getSocketOptions = (): Partial<ManagerOptions & SocketOptions> => {
|
||||
// Allow polling fallback; do not force "secure" here — protocol will be inferred
|
||||
return {
|
||||
path: "/socket.io/",
|
||||
transports: ["websocket"],
|
||||
}
|
||||
: undefined;
|
||||
transports: ["websocket", "polling"],
|
||||
};
|
||||
};
|
||||
|
||||
export const getFetchOptions = (options: RequestInit = {}): RequestInit => {
|
||||
|
||||
@@ -18,10 +18,13 @@ class WebRTCService {
|
||||
private static instance: WebRTCService;
|
||||
|
||||
private constructor() {
|
||||
const apiUrl = (config.API_URL || "").trim();
|
||||
// Use same-origin when API_URL is empty string — socket.io accepts empty string for same-origin
|
||||
const signalingServer: string = apiUrl.length > 0 ? apiUrl : "";
|
||||
const webRTCConfig = {
|
||||
iceServers: getIceServers(),
|
||||
socketOptions: getSocketOptions() || {},
|
||||
signalingServer: config.API_URL,
|
||||
signalingServer,
|
||||
};
|
||||
|
||||
this.sender = new WebRTC_Initiator(webRTCConfig);
|
||||
|
||||
@@ -12,6 +12,8 @@ const withMDX = createMDX({
|
||||
const nextConfig = {
|
||||
pageExtensions: ['ts', 'tsx', 'js', 'jsx', 'md', 'mdx'],
|
||||
images: {
|
||||
// Disable optimization inside Docker to avoid container loopback fetch failures (502)
|
||||
unoptimized: process.env.NEXT_IMAGE_UNOPTIMIZED === 'true',
|
||||
remotePatterns: [
|
||||
{
|
||||
protocol: 'https',
|
||||
|
||||
Reference in New Issue
Block a user