mirror of
https://github.com/different-ai/openwork
synced 2026-04-25 17:15:34 +02:00
fix(docker): expose dev stacks on LAN and Tailscale (#1009)
This commit is contained in:
@@ -38,6 +38,81 @@ random_hex() {
|
||||
node -e "console.log(require('crypto').randomBytes(${bytes}).toString('hex'))"
|
||||
}
|
||||
|
||||
detect_lan_ipv4() {
|
||||
node -e '
|
||||
const os = require("os");
|
||||
const nets = os.networkInterfaces();
|
||||
for (const entries of Object.values(nets)) {
|
||||
for (const entry of entries || []) {
|
||||
if (!entry || entry.internal || entry.family !== "IPv4") continue;
|
||||
if (entry.address.startsWith("127.")) continue;
|
||||
process.stdout.write(entry.address);
|
||||
process.exit(0);
|
||||
}
|
||||
}
|
||||
'
|
||||
}
|
||||
|
||||
detect_tailscale_dns_name() {
|
||||
if ! command -v tailscale >/dev/null 2>&1; then
|
||||
return 1
|
||||
fi
|
||||
|
||||
tailscale status --json 2>/dev/null | node -e '
|
||||
let data = "";
|
||||
process.stdin.setEncoding("utf8");
|
||||
process.stdin.on("data", (chunk) => { data += chunk; });
|
||||
process.stdin.on("end", () => {
|
||||
try {
|
||||
const parsed = JSON.parse(data);
|
||||
const value = (parsed?.Self?.DNSName || "").replace(/\.$/, "").trim();
|
||||
if (!value) process.exit(1);
|
||||
process.stdout.write(value);
|
||||
} catch {
|
||||
process.exit(1);
|
||||
}
|
||||
});
|
||||
'
|
||||
}
|
||||
|
||||
detect_public_host() {
|
||||
if [ -n "${DEN_PUBLIC_HOST:-}" ]; then
|
||||
printf '%s\n' "$DEN_PUBLIC_HOST"
|
||||
return
|
||||
fi
|
||||
|
||||
local lan_ipv4
|
||||
lan_ipv4="$(detect_lan_ipv4 || true)"
|
||||
if [ -n "$lan_ipv4" ]; then
|
||||
printf '%s\n' "$lan_ipv4"
|
||||
return
|
||||
fi
|
||||
|
||||
local host
|
||||
host="$(hostname -s 2>/dev/null || hostname 2>/dev/null || true)"
|
||||
host="${host//$'\n'/}"
|
||||
host="${host// /}"
|
||||
if [ -n "$host" ]; then
|
||||
printf '%s\n' "$host"
|
||||
return
|
||||
fi
|
||||
|
||||
printf '%s\n' "localhost"
|
||||
}
|
||||
|
||||
append_origin() {
|
||||
local value="$1"
|
||||
[ -n "$value" ] || return 0
|
||||
if [ -z "${DEN_CORS_ORIGINS:-}" ]; then
|
||||
DEN_CORS_ORIGINS="$value"
|
||||
return 0
|
||||
fi
|
||||
case ",${DEN_CORS_ORIGINS}," in
|
||||
*",${value},"*) ;;
|
||||
*) DEN_CORS_ORIGINS="${DEN_CORS_ORIGINS},${value}" ;;
|
||||
esac
|
||||
}
|
||||
|
||||
DEV_ID="$(node -e "console.log(require('crypto').randomUUID().slice(0, 8))")"
|
||||
PROJECT="openwork-den-dev-$DEV_ID"
|
||||
|
||||
@@ -55,12 +130,25 @@ if [ "$DEN_MYSQL_PORT" = "$DEN_API_PORT" ] || [ "$DEN_MYSQL_PORT" = "$DEN_WEB_PO
|
||||
DEN_MYSQL_PORT="$(pick_port)"
|
||||
fi
|
||||
|
||||
PUBLIC_HOST="$(detect_public_host)"
|
||||
LAN_IPV4="$(detect_lan_ipv4 || true)"
|
||||
TAILSCALE_DNS_NAME="$(detect_tailscale_dns_name || true)"
|
||||
|
||||
DEN_BETTER_AUTH_SECRET="${DEN_BETTER_AUTH_SECRET:-$(random_hex 32)}"
|
||||
DEN_BETTER_AUTH_URL="${DEN_BETTER_AUTH_URL:-http://localhost:$DEN_WEB_PORT}"
|
||||
DEN_BETTER_AUTH_URL="${DEN_BETTER_AUTH_URL:-http://$PUBLIC_HOST:$DEN_WEB_PORT}"
|
||||
DEN_PROVISIONER_MODE="${DEN_PROVISIONER_MODE:-stub}"
|
||||
DEN_WORKER_URL_TEMPLATE="${DEN_WORKER_URL_TEMPLATE:-https://workers.local/{workerId}}"
|
||||
DEN_DAYTONA_WORKER_PROXY_BASE_URL="${DEN_DAYTONA_WORKER_PROXY_BASE_URL:-http://localhost:$DEN_WORKER_PROXY_PORT}"
|
||||
DEN_DAYTONA_WORKER_PROXY_BASE_URL="${DEN_DAYTONA_WORKER_PROXY_BASE_URL:-http://$PUBLIC_HOST:$DEN_WORKER_PROXY_PORT}"
|
||||
DEN_CORS_ORIGINS="${DEN_CORS_ORIGINS:-http://localhost:$DEN_WEB_PORT,http://127.0.0.1:$DEN_WEB_PORT,http://localhost:$DEN_API_PORT,http://127.0.0.1:$DEN_API_PORT}"
|
||||
append_origin "http://$PUBLIC_HOST:$DEN_WEB_PORT"
|
||||
append_origin "http://$PUBLIC_HOST:$DEN_API_PORT"
|
||||
append_origin "http://$PUBLIC_HOST:$DEN_WORKER_PROXY_PORT"
|
||||
if [ -n "$LAN_IPV4" ]; then
|
||||
append_origin "http://$LAN_IPV4:$DEN_WEB_PORT"
|
||||
append_origin "http://$LAN_IPV4:$DEN_API_PORT"
|
||||
append_origin "http://$LAN_IPV4:$DEN_WORKER_PROXY_PORT"
|
||||
fi
|
||||
DEN_BETTER_AUTH_TRUSTED_ORIGINS="${DEN_BETTER_AUTH_TRUSTED_ORIGINS:-$DEN_CORS_ORIGINS}"
|
||||
|
||||
if [ "$DEN_PROVISIONER_MODE" = "daytona" ] && [ -f "$DAYTONA_ENV_FILE" ]; then
|
||||
set -a
|
||||
@@ -87,6 +175,9 @@ DEN_MYSQL_PORT=$DEN_MYSQL_PORT
|
||||
DEN_API_URL=http://localhost:$DEN_API_PORT
|
||||
DEN_WEB_URL=http://localhost:$DEN_WEB_PORT
|
||||
DEN_WORKER_PROXY_URL=http://localhost:$DEN_WORKER_PROXY_PORT
|
||||
DEN_API_PUBLIC_URL=http://$PUBLIC_HOST:$DEN_API_PORT
|
||||
DEN_WEB_PUBLIC_URL=http://$PUBLIC_HOST:$DEN_WEB_PORT
|
||||
DEN_WORKER_PROXY_PUBLIC_URL=http://$PUBLIC_HOST:$DEN_WORKER_PROXY_PORT
|
||||
DEN_MYSQL_URL=mysql://root:password@127.0.0.1:$DEN_MYSQL_PORT/openwork_den
|
||||
DEN_BETTER_AUTH_URL=$DEN_BETTER_AUTH_URL
|
||||
COMPOSE_FILE=$COMPOSE_FILE
|
||||
@@ -113,6 +204,7 @@ if ! DEN_API_PORT="$DEN_API_PORT" \
|
||||
DEN_BETTER_AUTH_SECRET="$DEN_BETTER_AUTH_SECRET" \
|
||||
DEN_BETTER_AUTH_URL="$DEN_BETTER_AUTH_URL" \
|
||||
DEN_CORS_ORIGINS="$DEN_CORS_ORIGINS" \
|
||||
DEN_BETTER_AUTH_TRUSTED_ORIGINS="$DEN_BETTER_AUTH_TRUSTED_ORIGINS" \
|
||||
DEN_PROVISIONER_MODE="$DEN_PROVISIONER_MODE" \
|
||||
DEN_WORKER_URL_TEMPLATE="$DEN_WORKER_URL_TEMPLATE" \
|
||||
DEN_DAYTONA_WORKER_PROXY_BASE_URL="$DEN_DAYTONA_WORKER_PROXY_BASE_URL" \
|
||||
@@ -129,8 +221,29 @@ fi
|
||||
|
||||
echo "" >&2
|
||||
echo "OpenWork Cloud web UI: http://localhost:$DEN_WEB_PORT" >&2
|
||||
echo "OpenWork Cloud web UI (LAN/public): http://$PUBLIC_HOST:$DEN_WEB_PORT" >&2
|
||||
if [ -n "$LAN_IPV4" ]; then
|
||||
echo "OpenWork Cloud web UI (LAN IP): http://$LAN_IPV4:$DEN_WEB_PORT" >&2
|
||||
fi
|
||||
if [ -n "$TAILSCALE_DNS_NAME" ]; then
|
||||
echo "OpenWork Cloud web UI (Tailscale): http://$TAILSCALE_DNS_NAME:$DEN_WEB_PORT" >&2
|
||||
fi
|
||||
echo "Den demo/API: http://localhost:$DEN_API_PORT" >&2
|
||||
echo "Den demo/API (LAN/public): http://$PUBLIC_HOST:$DEN_API_PORT" >&2
|
||||
if [ -n "$LAN_IPV4" ]; then
|
||||
echo "Den demo/API (LAN IP): http://$LAN_IPV4:$DEN_API_PORT" >&2
|
||||
fi
|
||||
if [ -n "$TAILSCALE_DNS_NAME" ]; then
|
||||
echo "Den demo/API (Tailscale): http://$TAILSCALE_DNS_NAME:$DEN_API_PORT" >&2
|
||||
fi
|
||||
echo "Worker proxy: http://localhost:$DEN_WORKER_PROXY_PORT" >&2
|
||||
echo "Worker proxy (LAN/public): http://$PUBLIC_HOST:$DEN_WORKER_PROXY_PORT" >&2
|
||||
if [ -n "$LAN_IPV4" ]; then
|
||||
echo "Worker proxy (LAN IP): http://$LAN_IPV4:$DEN_WORKER_PROXY_PORT" >&2
|
||||
fi
|
||||
if [ -n "$TAILSCALE_DNS_NAME" ]; then
|
||||
echo "Worker proxy (Tailscale): http://$TAILSCALE_DNS_NAME:$DEN_WORKER_PROXY_PORT" >&2
|
||||
fi
|
||||
echo "MySQL: mysql://root:password@127.0.0.1:$DEN_MYSQL_PORT/openwork_den" >&2
|
||||
echo "Health check: http://localhost:$DEN_API_PORT/health" >&2
|
||||
echo "Runtime env file: $RUNTIME_FILE" >&2
|
||||
|
||||
@@ -117,6 +117,13 @@ detect_public_host() {
|
||||
return
|
||||
fi
|
||||
|
||||
local lan_ipv4
|
||||
lan_ipv4="$(detect_lan_ipv4 || true)"
|
||||
if [ -n "$lan_ipv4" ]; then
|
||||
printf '%s\n' "$lan_ipv4"
|
||||
return
|
||||
fi
|
||||
|
||||
local host
|
||||
host="$(hostname -s 2>/dev/null || hostname 2>/dev/null || true)"
|
||||
host="${host//$'\n'/}"
|
||||
@@ -144,6 +151,28 @@ detect_lan_ipv4() {
|
||||
'
|
||||
}
|
||||
|
||||
detect_tailscale_dns_name() {
|
||||
if ! command -v tailscale >/dev/null 2>&1; then
|
||||
return 1
|
||||
fi
|
||||
|
||||
tailscale status --json 2>/dev/null | node -e '
|
||||
let data = "";
|
||||
process.stdin.setEncoding("utf8");
|
||||
process.stdin.on("data", (chunk) => { data += chunk; });
|
||||
process.stdin.on("end", () => {
|
||||
try {
|
||||
const parsed = JSON.parse(data);
|
||||
const value = (parsed?.Self?.DNSName || "").replace(/\.$/, "").trim();
|
||||
if (!value) process.exit(1);
|
||||
process.stdout.write(value);
|
||||
} catch {
|
||||
process.exit(1);
|
||||
}
|
||||
});
|
||||
'
|
||||
}
|
||||
|
||||
DEV_ID="$(node -e "console.log(require('crypto').randomUUID().slice(0, 8))")"
|
||||
PROJECT="openwork-dev-$DEV_ID"
|
||||
|
||||
@@ -181,6 +210,7 @@ fi
|
||||
|
||||
PUBLIC_HOST="$(detect_public_host)"
|
||||
LAN_IPV4="$(detect_lan_ipv4 || true)"
|
||||
TAILSCALE_DNS_NAME="$(detect_tailscale_dns_name || true)"
|
||||
|
||||
echo "Starting Docker Compose project: $PROJECT" >&2
|
||||
echo "- OPENWORK_PORT=$OPENWORK_PORT" >&2
|
||||
@@ -198,6 +228,7 @@ start_stack() {
|
||||
local data_dir="$2"
|
||||
OPENWORK_DEV_ID="$DEV_ID" OPENWORK_PORT="$OPENWORK_PORT" WEB_PORT="$WEB_PORT" SHARE_PORT="$SHARE_PORT" \
|
||||
OPENWORK_DEV_MODE="1" \
|
||||
OPENWORK_PUBLIC_HOST="$PUBLIC_HOST" \
|
||||
OPENWORK_HOST_OPENCODE_CONFIG_DIR="$config_dir" \
|
||||
OPENWORK_HOST_OPENCODE_DATA_DIR="$data_dir" \
|
||||
docker compose -p "$PROJECT" -f "$COMPOSE_FILE" up -d
|
||||
@@ -229,16 +260,25 @@ echo "OpenWork web UI (LAN/public): http://$PUBLIC_HOST:$WEB_PORT" >&2
|
||||
if [ -n "$LAN_IPV4" ]; then
|
||||
echo "OpenWork web UI (LAN IP): http://$LAN_IPV4:$WEB_PORT" >&2
|
||||
fi
|
||||
if [ -n "$TAILSCALE_DNS_NAME" ]; then
|
||||
echo "OpenWork web UI (Tailscale): http://$TAILSCALE_DNS_NAME:$WEB_PORT" >&2
|
||||
fi
|
||||
echo "OpenWork server: http://localhost:$OPENWORK_PORT" >&2
|
||||
echo "OpenWork server (LAN/public): http://$PUBLIC_HOST:$OPENWORK_PORT" >&2
|
||||
if [ -n "$LAN_IPV4" ]; then
|
||||
echo "OpenWork server (LAN IP): http://$LAN_IPV4:$OPENWORK_PORT" >&2
|
||||
fi
|
||||
if [ -n "$TAILSCALE_DNS_NAME" ]; then
|
||||
echo "OpenWork server (Tailscale): http://$TAILSCALE_DNS_NAME:$OPENWORK_PORT" >&2
|
||||
fi
|
||||
echo "Share service: http://localhost:$SHARE_PORT" >&2
|
||||
echo "Share service (LAN/public): http://$PUBLIC_HOST:$SHARE_PORT" >&2
|
||||
if [ -n "$LAN_IPV4" ]; then
|
||||
echo "Share service (LAN IP): http://$LAN_IPV4:$SHARE_PORT" >&2
|
||||
fi
|
||||
if [ -n "$TAILSCALE_DNS_NAME" ]; then
|
||||
echo "Share service (Tailscale): http://$TAILSCALE_DNS_NAME:$SHARE_PORT" >&2
|
||||
fi
|
||||
echo "Token file: $ROOT_DIR/tmp/.dev-env-$DEV_ID" >&2
|
||||
echo "" >&2
|
||||
echo "To stop this stack:" >&2
|
||||
|
||||
@@ -11,7 +11,9 @@
|
||||
# DEN_WORKER_PROXY_PORT — host port to map to the worker proxy :8789
|
||||
# DEN_MYSQL_PORT — host port to map to MySQL :3306
|
||||
# DEN_BETTER_AUTH_SECRET — Better Auth secret (auto-generated by den-dev-up.sh)
|
||||
# DEN_BETTER_AUTH_URL — browser-facing auth origin (default: http://localhost:<DEN_WEB_PORT>)
|
||||
# DEN_PUBLIC_HOST — browser-facing host/IP for LAN access (set by den-dev-up.sh)
|
||||
# DEN_BETTER_AUTH_URL — browser-facing auth origin (default: http://<DEN_PUBLIC_HOST>:<DEN_WEB_PORT>)
|
||||
# DEN_BETTER_AUTH_TRUSTED_ORIGINS — Better Auth trusted origins (defaults to DEN_CORS_ORIGINS)
|
||||
# DEN_CORS_ORIGINS — comma-separated trusted origins for Better Auth + CORS
|
||||
# DEN_PROVISIONER_MODE — stub, render, or daytona (default: stub)
|
||||
# DEN_WORKER_URL_TEMPLATE — worker URL template used by stub provisioning
|
||||
@@ -66,6 +68,7 @@ services:
|
||||
DATABASE_URL: mysql://root:password@mysql:3306/openwork_den
|
||||
BETTER_AUTH_SECRET: ${DEN_BETTER_AUTH_SECRET:-dev-den-local-auth-secret-please-override-1234567890}
|
||||
BETTER_AUTH_URL: ${DEN_BETTER_AUTH_URL:-http://localhost:3005}
|
||||
DEN_BETTER_AUTH_TRUSTED_ORIGINS: ${DEN_BETTER_AUTH_TRUSTED_ORIGINS:-}
|
||||
PORT: "8788"
|
||||
CORS_ORIGINS: ${DEN_CORS_ORIGINS:-http://localhost:3005,http://127.0.0.1:3005,http://localhost:8788,http://127.0.0.1:8788}
|
||||
PROVISIONER_MODE: ${DEN_PROVISIONER_MODE:-stub}
|
||||
@@ -110,6 +113,7 @@ services:
|
||||
build:
|
||||
context: ../../
|
||||
dockerfile: packaging/docker/Dockerfile.den-web
|
||||
command: ["sh", "-lc", "npm run build && npm run start"]
|
||||
depends_on:
|
||||
den:
|
||||
condition: service_healthy
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
# Usage (from repo root):
|
||||
# docker compose -f packaging/docker/docker-compose.dev.yml up
|
||||
#
|
||||
# Then open http://localhost:5173 — already wired to headless, no config needed.
|
||||
# Then open the printed Web UI URL — already wired to headless, no config needed.
|
||||
#
|
||||
# Env overrides (optional, via .env or export):
|
||||
# OPENWORK_TOKEN — shared client token (auto-generated if unset)
|
||||
@@ -14,6 +14,7 @@
|
||||
# SHARE_PORT — host port to map to the share service :3000 (default: 3006)
|
||||
# OPENWORK_DEV_ID — unique ID for this stack (default: default)
|
||||
# OPENWORK_DEV_MODE — enables isolated OpenCode dev state (set by dev-up.sh)
|
||||
# OPENWORK_PUBLIC_HOST — browser-facing host/IP for LAN-accessible URLs (set by dev-up.sh)
|
||||
# OPENWORK_DOCKER_DEV_MOUNT_HOST_OPENCODE=1 — import host OpenCode config/auth into the isolated dev state
|
||||
# OPENWORK_OPENCODE_CONFIG_DIR — host OpenCode config dir detection override (read by dev-up.sh)
|
||||
# OPENWORK_OPENCODE_DATA_DIR — host OpenCode data dir detection override (read by dev-up.sh)
|
||||
@@ -161,9 +162,9 @@ services:
|
||||
echo "============================================"
|
||||
echo ""
|
||||
|
||||
export VITE_OPENWORK_URL="http://localhost:${OPENWORK_PORT:-8787}"
|
||||
export VITE_OPENWORK_URL="http://${OPENWORK_PUBLIC_HOST:-localhost}:${OPENWORK_PORT:-8787}"
|
||||
export VITE_OPENWORK_PORT="${OPENWORK_PORT:-8787}"
|
||||
export VITE_OPENWORK_PUBLISHER_BASE_URL="http://localhost:${SHARE_PORT:-3006}"
|
||||
export VITE_OPENWORK_PUBLISHER_BASE_URL="http://${OPENWORK_PUBLIC_HOST:-localhost}:${SHARE_PORT:-3006}"
|
||||
export VITE_ALLOWED_HOSTS="all"
|
||||
export HOST="0.0.0.0"
|
||||
export PORT="5173"
|
||||
@@ -217,8 +218,8 @@ services:
|
||||
CI: "true"
|
||||
OPENWORK_DEV_MODE: ${OPENWORK_DEV_MODE:-1}
|
||||
LOCAL_BLOB_DIR: /app/tmp/share-service-blobs
|
||||
PUBLIC_BASE_URL: http://localhost:${SHARE_PORT:-3006}
|
||||
PUBLIC_OPENWORK_APP_URL: http://localhost:${WEB_PORT:-5173}
|
||||
PUBLIC_BASE_URL: http://${OPENWORK_PUBLIC_HOST:-localhost}:${SHARE_PORT:-3006}
|
||||
PUBLIC_OPENWORK_APP_URL: http://${OPENWORK_PUBLIC_HOST:-localhost}:${WEB_PORT:-5173}
|
||||
|
||||
volumes:
|
||||
pnpm-store:
|
||||
|
||||
Reference in New Issue
Block a user