mirror of
https://github.com/multica-ai/multica.git
synced 2026-06-16 19:29:26 +02:00
fix(selfhost): derive local port URLs from env (MUL-2506) (#2939)
* fix(selfhost): derive local port URLs from env * fix(selfhost): derive local script URLs
This commit is contained in:
27
.env.example
27
.env.example
@@ -21,14 +21,23 @@ APP_ENV=
|
||||
# 888888 and keep APP_ENV non-production. This is ignored when APP_ENV=production.
|
||||
MULTICA_DEV_VERIFICATION_CODE=
|
||||
PORT=8080
|
||||
# Optional aliases for the local/self-host backend port. If one is set, it
|
||||
# takes precedence over PORT in compose, Makefile, and installer helpers.
|
||||
# BACKEND_PORT=8080
|
||||
# API_PORT=8080
|
||||
# SERVER_PORT=8080
|
||||
# Prometheus metrics are disabled by default. When enabled, bind to loopback
|
||||
# unless you protect the listener with private networking, allowlists, or
|
||||
# proxy auth. Do not expose this endpoint through the public app/API ingress.
|
||||
# HTTP request metrics start accumulating only when this listener is enabled.
|
||||
# METRICS_ADDR=127.0.0.1:9090
|
||||
JWT_SECRET=change-me-in-production
|
||||
MULTICA_SERVER_URL=ws://localhost:8080/ws
|
||||
MULTICA_APP_URL=http://localhost:3000
|
||||
# Derived by Makefile / local scripts from the backend port.
|
||||
# Set explicitly only when the daemon reaches the API through a different URL.
|
||||
# MULTICA_SERVER_URL=ws://localhost:8080/ws
|
||||
# Derived by docker-compose.selfhost.yml / local scripts from FRONTEND_PORT.
|
||||
# Set explicitly only when the app's public URL differs from local frontend.
|
||||
# MULTICA_APP_URL=http://localhost:3000
|
||||
# Public URL the API is reachable at from the open internet (no trailing
|
||||
# slash). Used to mint absolute webhook URLs for autopilot webhook
|
||||
# triggers. Leave unset behind a same-origin reverse proxy or for plain
|
||||
@@ -91,7 +100,9 @@ SMTP_TLS_INSECURE=false
|
||||
# rebuild is needed.
|
||||
GOOGLE_CLIENT_ID=
|
||||
GOOGLE_CLIENT_SECRET=
|
||||
GOOGLE_REDIRECT_URI=http://localhost:3000/auth/callback
|
||||
# Derived by docker-compose.selfhost.yml / local scripts from FRONTEND_PORT.
|
||||
# Set explicitly only when your OAuth callback URL differs from local frontend.
|
||||
# GOOGLE_REDIRECT_URI=http://localhost:3000/auth/callback
|
||||
|
||||
# S3 / CloudFront
|
||||
# S3_BUCKET — bucket NAME only (e.g. "my-bucket"). Do NOT include the
|
||||
@@ -121,7 +132,9 @@ COOKIE_DOMAIN=
|
||||
|
||||
# Local file storage (fallback when S3_BUCKET is not set)
|
||||
LOCAL_UPLOAD_DIR=./data/uploads
|
||||
LOCAL_UPLOAD_BASE_URL=http://localhost:8080
|
||||
# Derived by Makefile / local scripts from the backend port.
|
||||
# Set explicitly only when uploads are served through a different public URL.
|
||||
# LOCAL_UPLOAD_BASE_URL=http://localhost:8080
|
||||
|
||||
# Security
|
||||
# Comma-separated list of allowed origins for CORS and WebSocket connections.
|
||||
@@ -170,9 +183,11 @@ GITHUB_WEBHOOK_SECRET=
|
||||
|
||||
# Frontend
|
||||
FRONTEND_PORT=3000
|
||||
FRONTEND_ORIGIN=http://localhost:3000
|
||||
# Derived by docker-compose.selfhost.yml / local scripts from FRONTEND_PORT.
|
||||
# Set explicitly only when serving frontend on a different origin/domain.
|
||||
# FRONTEND_ORIGIN=http://localhost:3000
|
||||
# Leave empty — auto-derived from page origin in browser, set by Makefile for local dev.
|
||||
# Only set explicitly if frontend and backend are on different domains.
|
||||
# NEXT_PUBLIC_API_URL also feeds the Next.js SSR proxy when explicitly set.
|
||||
NEXT_PUBLIC_API_URL=
|
||||
NEXT_PUBLIC_WS_URL=
|
||||
|
||||
|
||||
3
.github/workflows/ci.yml
vendored
3
.github/workflows/ci.yml
vendored
@@ -29,6 +29,9 @@ jobs:
|
||||
- name: Install dependencies
|
||||
run: pnpm install
|
||||
|
||||
- name: Test self-host env derivation
|
||||
run: bash scripts/selfhost-config.test.sh
|
||||
|
||||
- name: Verify reserved-slugs.ts is up to date
|
||||
# Re-runs the generator and fails on any drift from the
|
||||
# checked-in TypeScript output. The Go side embeds the JSON
|
||||
|
||||
3
Makefile
3
Makefile
@@ -12,7 +12,7 @@ POSTGRES_DB ?= multica
|
||||
POSTGRES_USER ?= multica
|
||||
POSTGRES_PASSWORD ?= multica
|
||||
POSTGRES_PORT ?= 5432
|
||||
PORT ?= 8080
|
||||
PORT := $(or $(BACKEND_PORT),$(API_PORT),$(SERVER_PORT),$(PORT),8080)
|
||||
FRONTEND_PORT ?= 3000
|
||||
FRONTEND_ORIGIN ?= http://localhost:$(FRONTEND_PORT)
|
||||
MULTICA_APP_URL ?= $(FRONTEND_ORIGIN)
|
||||
@@ -21,6 +21,7 @@ NEXT_PUBLIC_API_URL ?= http://localhost:$(PORT)
|
||||
NEXT_PUBLIC_WS_URL ?= ws://localhost:$(PORT)/ws
|
||||
GOOGLE_REDIRECT_URI ?= $(FRONTEND_ORIGIN)/auth/callback
|
||||
MULTICA_SERVER_URL ?= ws://localhost:$(PORT)/ws
|
||||
LOCAL_UPLOAD_BASE_URL ?= http://localhost:$(PORT)
|
||||
|
||||
export
|
||||
|
||||
|
||||
82
apps/web/config/runtime-urls.test.ts
Normal file
82
apps/web/config/runtime-urls.test.ts
Normal file
@@ -0,0 +1,82 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
|
||||
import { resolveRemoteApiUrl } from "./runtime-urls";
|
||||
|
||||
describe("resolveRemoteApiUrl", () => {
|
||||
it("prefers REMOTE_API_URL when explicitly configured", () => {
|
||||
expect(
|
||||
resolveRemoteApiUrl({
|
||||
REMOTE_API_URL: "http://backend:8080",
|
||||
NEXT_PUBLIC_API_URL: "http://localhost:19000",
|
||||
PORT: "18080",
|
||||
}),
|
||||
).toBe("http://backend:8080");
|
||||
});
|
||||
|
||||
it("uses NEXT_PUBLIC_API_URL when REMOTE_API_URL is unset", () => {
|
||||
expect(
|
||||
resolveRemoteApiUrl({
|
||||
NEXT_PUBLIC_API_URL: "http://localhost:19000",
|
||||
PORT: "18080",
|
||||
}),
|
||||
).toBe("http://localhost:19000");
|
||||
});
|
||||
|
||||
it("derives localhost backend URL from PORT when no API URL is set", () => {
|
||||
expect(resolveRemoteApiUrl({ PORT: "19080" })).toBe("http://localhost:19080");
|
||||
});
|
||||
|
||||
it("supports explicit backend port aliases before PORT", () => {
|
||||
expect(resolveRemoteApiUrl({ BACKEND_PORT: "28080", PORT: "19080" })).toBe(
|
||||
"http://localhost:28080",
|
||||
);
|
||||
expect(resolveRemoteApiUrl({ API_PORT: "38080", PORT: "19080" })).toBe(
|
||||
"http://localhost:38080",
|
||||
);
|
||||
expect(resolveRemoteApiUrl({ SERVER_PORT: "48080", PORT: "19080" })).toBe(
|
||||
"http://localhost:48080",
|
||||
);
|
||||
});
|
||||
|
||||
it("prefers backend port aliases by documented precedence", () => {
|
||||
expect(
|
||||
resolveRemoteApiUrl({
|
||||
BACKEND_PORT: "28080",
|
||||
API_PORT: "38080",
|
||||
SERVER_PORT: "48080",
|
||||
PORT: "19080",
|
||||
}),
|
||||
).toBe("http://localhost:28080");
|
||||
|
||||
expect(
|
||||
resolveRemoteApiUrl({
|
||||
API_PORT: "38080",
|
||||
SERVER_PORT: "48080",
|
||||
PORT: "19080",
|
||||
}),
|
||||
).toBe("http://localhost:38080");
|
||||
|
||||
expect(resolveRemoteApiUrl({ SERVER_PORT: "48080", PORT: "19080" })).toBe(
|
||||
"http://localhost:48080",
|
||||
);
|
||||
});
|
||||
|
||||
it("ignores whitespace-only backend URL values", () => {
|
||||
expect(
|
||||
resolveRemoteApiUrl({
|
||||
REMOTE_API_URL: " ",
|
||||
NEXT_PUBLIC_API_URL: " ",
|
||||
BACKEND_PORT: " ",
|
||||
API_PORT: " ",
|
||||
SERVER_PORT: " ",
|
||||
PORT: "19080",
|
||||
}),
|
||||
).toBe("http://localhost:19080");
|
||||
|
||||
expect(resolveRemoteApiUrl({ PORT: " " })).toBe("http://localhost:8080");
|
||||
});
|
||||
|
||||
it("falls back to the historical backend port when no env is configured", () => {
|
||||
expect(resolveRemoteApiUrl({})).toBe("http://localhost:8080");
|
||||
});
|
||||
});
|
||||
18
apps/web/config/runtime-urls.ts
Normal file
18
apps/web/config/runtime-urls.ts
Normal file
@@ -0,0 +1,18 @@
|
||||
type RuntimeEnv = Record<string, string | undefined>;
|
||||
|
||||
export function resolveRemoteApiUrl(env: RuntimeEnv): string {
|
||||
const explicitRemote = env.REMOTE_API_URL?.trim();
|
||||
if (explicitRemote) return explicitRemote;
|
||||
|
||||
const publicApi = env.NEXT_PUBLIC_API_URL?.trim();
|
||||
if (publicApi) return publicApi;
|
||||
|
||||
const port =
|
||||
env.BACKEND_PORT?.trim() ||
|
||||
env.API_PORT?.trim() ||
|
||||
env.SERVER_PORT?.trim() ||
|
||||
env.PORT?.trim();
|
||||
if (port) return `http://localhost:${port}`;
|
||||
|
||||
return "http://localhost:8080";
|
||||
}
|
||||
@@ -1,11 +1,12 @@
|
||||
import type { NextConfig } from "next";
|
||||
import { config } from "dotenv";
|
||||
import { resolve } from "path";
|
||||
import { resolveRemoteApiUrl } from "./config/runtime-urls";
|
||||
|
||||
// Load root .env so REMOTE_API_URL is available to next.config.ts
|
||||
config({ path: resolve(__dirname, "../../.env") });
|
||||
|
||||
const remoteApiUrl = process.env.REMOTE_API_URL || "http://localhost:8080";
|
||||
const remoteApiUrl = resolveRemoteApiUrl(process.env);
|
||||
const docsUrl = process.env.DOCS_URL || "http://localhost:4000";
|
||||
|
||||
// Parse hostnames from CORS_ALLOWED_ORIGINS so that Next.js dev server
|
||||
|
||||
@@ -13,8 +13,8 @@
|
||||
# # Edit .env — change JWT_SECRET at minimum
|
||||
# docker compose -f docker-compose.selfhost.yml up -d
|
||||
#
|
||||
# Frontend: http://localhost:3000
|
||||
# Backend: http://localhost:8080 (also used by CLI/daemon)
|
||||
# Frontend: http://localhost:${FRONTEND_PORT:-3000}
|
||||
# Backend: http://localhost:${BACKEND_PORT:-${API_PORT:-${SERVER_PORT:-${PORT:-8080}}}}
|
||||
|
||||
name: multica
|
||||
|
||||
@@ -40,7 +40,7 @@ services:
|
||||
postgres:
|
||||
condition: service_healthy
|
||||
ports:
|
||||
- "127.0.0.1:${PORT:-8080}:8080"
|
||||
- "127.0.0.1:${BACKEND_PORT:-${API_PORT:-${SERVER_PORT:-${PORT:-8080}}}}:8080"
|
||||
volumes:
|
||||
- backend_uploads:/app/data/uploads
|
||||
environment:
|
||||
@@ -48,7 +48,7 @@ services:
|
||||
PORT: "8080"
|
||||
METRICS_ADDR: ${METRICS_ADDR:-}
|
||||
JWT_SECRET: ${JWT_SECRET:-change-me-in-production}
|
||||
FRONTEND_ORIGIN: ${FRONTEND_ORIGIN:-http://localhost:3000}
|
||||
FRONTEND_ORIGIN: ${FRONTEND_ORIGIN:-http://localhost:${FRONTEND_PORT:-3000}}
|
||||
CORS_ALLOWED_ORIGINS: ${CORS_ALLOWED_ORIGINS:-}
|
||||
RESEND_API_KEY: ${RESEND_API_KEY:-}
|
||||
RESEND_FROM_EMAIL: ${RESEND_FROM_EMAIL:-noreply@multica.ai}
|
||||
@@ -59,7 +59,7 @@ services:
|
||||
SMTP_TLS_INSECURE: ${SMTP_TLS_INSECURE:-false}
|
||||
GOOGLE_CLIENT_ID: ${GOOGLE_CLIENT_ID:-}
|
||||
GOOGLE_CLIENT_SECRET: ${GOOGLE_CLIENT_SECRET:-}
|
||||
GOOGLE_REDIRECT_URI: ${GOOGLE_REDIRECT_URI:-http://localhost:3000/auth/callback}
|
||||
GOOGLE_REDIRECT_URI: ${GOOGLE_REDIRECT_URI:-http://localhost:${FRONTEND_PORT:-3000}/auth/callback}
|
||||
S3_BUCKET: ${S3_BUCKET:-}
|
||||
S3_REGION: ${S3_REGION:-us-west-2}
|
||||
CLOUDFRONT_DOMAIN: ${CLOUDFRONT_DOMAIN:-}
|
||||
@@ -68,7 +68,7 @@ services:
|
||||
COOKIE_DOMAIN: ${COOKIE_DOMAIN:-}
|
||||
APP_ENV: ${APP_ENV:-production}
|
||||
MULTICA_DEV_VERIFICATION_CODE: ${MULTICA_DEV_VERIFICATION_CODE:-}
|
||||
MULTICA_APP_URL: ${MULTICA_APP_URL:-http://localhost:3000}
|
||||
MULTICA_APP_URL: ${MULTICA_APP_URL:-http://localhost:${FRONTEND_PORT:-3000}}
|
||||
ALLOW_SIGNUP: ${ALLOW_SIGNUP:-true}
|
||||
ALLOWED_EMAILS: ${ALLOWED_EMAILS:-}
|
||||
ALLOWED_EMAIL_DOMAINS: ${ALLOWED_EMAIL_DOMAINS:-}
|
||||
|
||||
@@ -18,13 +18,8 @@ set -a
|
||||
. "$ENV_FILE"
|
||||
set +a
|
||||
|
||||
POSTGRES_DB="${POSTGRES_DB:-multica}"
|
||||
POSTGRES_USER="${POSTGRES_USER:-multica}"
|
||||
POSTGRES_PORT="${POSTGRES_PORT:-5432}"
|
||||
PORT="${PORT:-8080}"
|
||||
FRONTEND_PORT="${FRONTEND_PORT:-3000}"
|
||||
PLAYWRIGHT_BASE_URL="${PLAYWRIGHT_BASE_URL:-http://localhost:${FRONTEND_PORT}}"
|
||||
export PLAYWRIGHT_BASE_URL
|
||||
# shellcheck disable=SC1091
|
||||
. scripts/local-env.sh
|
||||
|
||||
BACKEND_PID=""
|
||||
FRONTEND_PID=""
|
||||
|
||||
@@ -40,6 +40,9 @@ set -a
|
||||
. "$ENV_FILE"
|
||||
set +a
|
||||
|
||||
# shellcheck disable=SC1091
|
||||
. scripts/local-env.sh
|
||||
|
||||
# ---------- Install dependencies ----------
|
||||
if [ ! -d node_modules ]; then
|
||||
echo "==> Installing dependencies..."
|
||||
|
||||
@@ -30,6 +30,46 @@ function Test-CommandExists {
|
||||
$null -ne (Get-Command $Name -ErrorAction SilentlyContinue)
|
||||
}
|
||||
|
||||
function Get-EnvFileValue {
|
||||
param(
|
||||
[string]$Path,
|
||||
[string]$Name,
|
||||
[string]$Default
|
||||
)
|
||||
|
||||
if (-not (Test-Path $Path)) {
|
||||
return $Default
|
||||
}
|
||||
|
||||
$prefix = "$Name="
|
||||
$line = Get-Content $Path |
|
||||
Where-Object { $_.StartsWith($prefix) } |
|
||||
Select-Object -Last 1
|
||||
if (-not $line) {
|
||||
return $Default
|
||||
}
|
||||
|
||||
$value = $line.Substring($prefix.Length).Trim().Trim('"').Trim("'")
|
||||
if ([string]::IsNullOrWhiteSpace($value)) {
|
||||
return $Default
|
||||
}
|
||||
return $value
|
||||
}
|
||||
|
||||
function Get-SelfHostBackendPort {
|
||||
foreach ($name in @("BACKEND_PORT", "API_PORT", "SERVER_PORT", "PORT")) {
|
||||
$value = Get-EnvFileValue -Path (Join-Path $InstallDir ".env") -Name $name -Default ""
|
||||
if (-not [string]::IsNullOrWhiteSpace($value)) {
|
||||
return $value
|
||||
}
|
||||
}
|
||||
return "8080"
|
||||
}
|
||||
|
||||
function Get-SelfHostFrontendPort {
|
||||
return Get-EnvFileValue -Path (Join-Path $InstallDir ".env") -Name "FRONTEND_PORT" -Default "3000"
|
||||
}
|
||||
|
||||
function Get-LatestVersion {
|
||||
try {
|
||||
$release = Invoke-RestMethod -Uri "https://api.github.com/repos/multica-ai/multica/releases/latest" -ErrorAction Stop
|
||||
@@ -386,10 +426,11 @@ function Install-Server {
|
||||
docker compose -f docker-compose.selfhost.yml up -d
|
||||
|
||||
Write-Info "Waiting for backend to be ready..."
|
||||
$backendPort = Get-SelfHostBackendPort
|
||||
$ready = $false
|
||||
for ($i = 1; $i -le 45; $i++) {
|
||||
try {
|
||||
$null = Invoke-WebRequest -Uri "http://localhost:8080/health" -UseBasicParsing -TimeoutSec 2
|
||||
$null = Invoke-WebRequest -Uri "http://localhost:$backendPort/health" -UseBasicParsing -TimeoutSec 2
|
||||
$ready = $true
|
||||
break
|
||||
} catch {
|
||||
@@ -451,8 +492,10 @@ function Start-LocalInstall {
|
||||
Write-Host " [OK] Multica server is running and CLI is ready!" -ForegroundColor Green
|
||||
Write-Host " ============================================" -ForegroundColor Green
|
||||
Write-Host ""
|
||||
Write-Host " Frontend: http://localhost:3000"
|
||||
Write-Host " Backend: http://localhost:8080"
|
||||
$frontendPort = Get-SelfHostFrontendPort
|
||||
$backendPort = Get-SelfHostBackendPort
|
||||
Write-Host " Frontend: http://localhost:$frontendPort"
|
||||
Write-Host " Backend: http://localhost:$backendPort"
|
||||
Write-Host " Server at: $InstallDir"
|
||||
Write-Host ""
|
||||
Write-Host " Next: configure your CLI to connect"
|
||||
|
||||
@@ -41,6 +41,46 @@ fail() { printf "${BOLD}${RED}✗ %s${RESET}\n" "$*" >&2; exit 1; }
|
||||
|
||||
command_exists() { command -v "$1" >/dev/null 2>&1; }
|
||||
|
||||
env_file_value() {
|
||||
local file="$1"
|
||||
local key="$2"
|
||||
local default="$3"
|
||||
local line value
|
||||
line="$(grep -E "^${key}=" "$file" 2>/dev/null | tail -n 1 || true)"
|
||||
if [ -z "$line" ]; then
|
||||
printf "%s" "$default"
|
||||
return
|
||||
fi
|
||||
value="${line#*=}"
|
||||
value="${value%$'\r'}"
|
||||
value="${value%\"}"
|
||||
value="${value#\"}"
|
||||
value="${value%\'}"
|
||||
value="${value#\'}"
|
||||
if [ -z "$value" ]; then
|
||||
printf "%s" "$default"
|
||||
else
|
||||
printf "%s" "$value"
|
||||
fi
|
||||
}
|
||||
|
||||
selfhost_backend_port() {
|
||||
local file="${1:-.env}"
|
||||
local value
|
||||
for key in BACKEND_PORT API_PORT SERVER_PORT PORT; do
|
||||
value="$(env_file_value "$file" "$key" "")"
|
||||
if [ -n "$value" ]; then
|
||||
printf "%s" "$value"
|
||||
return
|
||||
fi
|
||||
done
|
||||
printf "8080"
|
||||
}
|
||||
|
||||
selfhost_frontend_port() {
|
||||
env_file_value "${1:-.env}" "FRONTEND_PORT" "3000"
|
||||
}
|
||||
|
||||
detect_os() {
|
||||
case "$(uname -s)" in
|
||||
Darwin) OS="darwin" ;;
|
||||
@@ -339,9 +379,11 @@ setup_server() {
|
||||
|
||||
# Wait for health check
|
||||
info "Waiting for backend to be ready..."
|
||||
local backend_port
|
||||
backend_port="$(selfhost_backend_port .env)"
|
||||
local ready=false
|
||||
for i in $(seq 1 45); do
|
||||
if curl -sf http://localhost:8080/health >/dev/null 2>&1; then
|
||||
if curl -sf "http://localhost:${backend_port}/health" >/dev/null 2>&1; then
|
||||
ready=true
|
||||
break
|
||||
fi
|
||||
@@ -403,8 +445,11 @@ run_with_server() {
|
||||
printf "${BOLD}${GREEN} ✓ Multica server is running and CLI is ready!${RESET}\n"
|
||||
printf "${BOLD}${GREEN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${RESET}\n"
|
||||
printf "\n"
|
||||
printf " ${BOLD}Frontend:${RESET} http://localhost:3000\n"
|
||||
printf " ${BOLD}Backend:${RESET} http://localhost:8080\n"
|
||||
local frontend_port backend_port
|
||||
frontend_port="$(selfhost_frontend_port "$INSTALL_DIR/.env")"
|
||||
backend_port="$(selfhost_backend_port "$INSTALL_DIR/.env")"
|
||||
printf " ${BOLD}Frontend:${RESET} http://localhost:%s\n" "$frontend_port"
|
||||
printf " ${BOLD}Backend:${RESET} http://localhost:%s\n" "$backend_port"
|
||||
printf " ${BOLD}Server at:${RESET} %s\n" "$INSTALL_DIR"
|
||||
printf "\n"
|
||||
printf " ${BOLD}Next: configure your CLI to connect${RESET}\n"
|
||||
|
||||
20
scripts/local-env.sh
Normal file
20
scripts/local-env.sh
Normal file
@@ -0,0 +1,20 @@
|
||||
# Shared local development env derivation. Source this after loading .env.
|
||||
|
||||
POSTGRES_DB="${POSTGRES_DB:-multica}"
|
||||
POSTGRES_USER="${POSTGRES_USER:-multica}"
|
||||
POSTGRES_PORT="${POSTGRES_PORT:-5432}"
|
||||
|
||||
PORT="${BACKEND_PORT:-${API_PORT:-${SERVER_PORT:-${PORT:-8080}}}}"
|
||||
FRONTEND_PORT="${FRONTEND_PORT:-3000}"
|
||||
FRONTEND_ORIGIN="${FRONTEND_ORIGIN:-http://localhost:${FRONTEND_PORT}}"
|
||||
|
||||
MULTICA_APP_URL="${MULTICA_APP_URL:-${FRONTEND_ORIGIN}}"
|
||||
GOOGLE_REDIRECT_URI="${GOOGLE_REDIRECT_URI:-${FRONTEND_ORIGIN}/auth/callback}"
|
||||
MULTICA_SERVER_URL="${MULTICA_SERVER_URL:-ws://localhost:${PORT}/ws}"
|
||||
LOCAL_UPLOAD_BASE_URL="${LOCAL_UPLOAD_BASE_URL:-http://localhost:${PORT}}"
|
||||
PLAYWRIGHT_BASE_URL="${PLAYWRIGHT_BASE_URL:-${FRONTEND_ORIGIN}}"
|
||||
|
||||
export POSTGRES_DB POSTGRES_USER POSTGRES_PORT
|
||||
export PORT FRONTEND_PORT FRONTEND_ORIGIN
|
||||
export MULTICA_APP_URL GOOGLE_REDIRECT_URI MULTICA_SERVER_URL LOCAL_UPLOAD_BASE_URL
|
||||
export PLAYWRIGHT_BASE_URL
|
||||
87
scripts/selfhost-config.test.sh
Executable file
87
scripts/selfhost-config.test.sh
Executable file
@@ -0,0 +1,87 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||
cd "$ROOT_DIR"
|
||||
|
||||
require_config() {
|
||||
local config=$1
|
||||
local expected=$2
|
||||
|
||||
if ! grep -Fq "$expected" <<<"$config"; then
|
||||
echo "Missing expected docker compose config value:"
|
||||
echo " $expected"
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
require_env() {
|
||||
local output=$1
|
||||
local expected=$2
|
||||
|
||||
if ! grep -Fxq "$expected" <<<"$output"; then
|
||||
echo "Missing expected derived env value:"
|
||||
echo " $expected"
|
||||
echo "Observed:"
|
||||
echo "$output"
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
config="$(
|
||||
FRONTEND_PORT=3100 BACKEND_PORT=9100 docker compose \
|
||||
--env-file .env.example \
|
||||
-f docker-compose.selfhost.yml \
|
||||
config
|
||||
)"
|
||||
|
||||
require_config "$config" 'published: "3100"'
|
||||
require_config "$config" 'published: "9100"'
|
||||
require_config "$config" 'FRONTEND_ORIGIN: http://localhost:3100'
|
||||
require_config "$config" 'GOOGLE_REDIRECT_URI: http://localhost:3100/auth/callback'
|
||||
require_config "$config" 'MULTICA_APP_URL: http://localhost:3100'
|
||||
|
||||
for script in scripts/dev.sh scripts/check.sh; do
|
||||
if ! grep -Fq '. scripts/local-env.sh' "$script"; then
|
||||
echo "$script must source scripts/local-env.sh for shared local env derivation."
|
||||
exit 1
|
||||
fi
|
||||
done
|
||||
|
||||
tmp_env="$(mktemp)"
|
||||
trap 'rm -f "$tmp_env"' EXIT
|
||||
sed 's/^FRONTEND_PORT=.*/FRONTEND_PORT=3100/' .env.example >"$tmp_env"
|
||||
printf '\nBACKEND_PORT=9100\n' >>"$tmp_env"
|
||||
|
||||
local_env="$(
|
||||
env -i PATH="$PATH" bash -c '
|
||||
set -euo pipefail
|
||||
env_file=$1
|
||||
set -a
|
||||
# shellcheck disable=SC1090
|
||||
. "$env_file"
|
||||
set +a
|
||||
# shellcheck disable=SC1091
|
||||
. scripts/local-env.sh
|
||||
printf "%s\n" \
|
||||
"PORT=${PORT}" \
|
||||
"FRONTEND_PORT=${FRONTEND_PORT}" \
|
||||
"FRONTEND_ORIGIN=${FRONTEND_ORIGIN}" \
|
||||
"MULTICA_APP_URL=${MULTICA_APP_URL}" \
|
||||
"GOOGLE_REDIRECT_URI=${GOOGLE_REDIRECT_URI}" \
|
||||
"MULTICA_SERVER_URL=${MULTICA_SERVER_URL}" \
|
||||
"LOCAL_UPLOAD_BASE_URL=${LOCAL_UPLOAD_BASE_URL}" \
|
||||
"PLAYWRIGHT_BASE_URL=${PLAYWRIGHT_BASE_URL}"
|
||||
' _ "$tmp_env"
|
||||
)"
|
||||
|
||||
require_env "$local_env" 'PORT=9100'
|
||||
require_env "$local_env" 'FRONTEND_PORT=3100'
|
||||
require_env "$local_env" 'FRONTEND_ORIGIN=http://localhost:3100'
|
||||
require_env "$local_env" 'MULTICA_APP_URL=http://localhost:3100'
|
||||
require_env "$local_env" 'GOOGLE_REDIRECT_URI=http://localhost:3100/auth/callback'
|
||||
require_env "$local_env" 'MULTICA_SERVER_URL=ws://localhost:9100/ws'
|
||||
require_env "$local_env" 'LOCAL_UPLOAD_BASE_URL=http://localhost:9100'
|
||||
require_env "$local_env" 'PLAYWRIGHT_BASE_URL=http://localhost:3100'
|
||||
|
||||
echo "self-host env derivation ok"
|
||||
Reference in New Issue
Block a user