Spaces:
Runtime error
Runtime error
nyk commited on
fix: resolve open docker and gateway-optional regressions (#291)
Browse files- .dockerignore +2 -1
- .env.example +3 -0
- README.md +26 -0
- docker-compose.yml +1 -1
- docs/deployment.md +33 -0
- src/components/hud/connection-status.tsx +5 -0
.dockerignore
CHANGED
|
@@ -6,4 +6,5 @@ node_modules
|
|
| 6 |
*.md
|
| 7 |
.github
|
| 8 |
ops
|
| 9 |
-
scripts
|
|
|
|
|
|
| 6 |
*.md
|
| 7 |
.github
|
| 8 |
ops
|
| 9 |
+
scripts/*
|
| 10 |
+
!scripts/check-node-version.mjs
|
.env.example
CHANGED
|
@@ -71,6 +71,9 @@ NEXT_PUBLIC_GATEWAY_URL=
|
|
| 71 |
# Keep gateway auth secrets server-side only (OPENCLAW_GATEWAY_TOKEN / GATEWAY_TOKEN).
|
| 72 |
# Gateway client id used in websocket handshake (role=operator UI client).
|
| 73 |
NEXT_PUBLIC_GATEWAY_CLIENT_ID=openclaw-control-ui
|
|
|
|
|
|
|
|
|
|
| 74 |
|
| 75 |
# === Data Paths (all optional, defaults to .data/ in project root) ===
|
| 76 |
# MISSION_CONTROL_DATA_DIR=.data
|
|
|
|
| 71 |
# Keep gateway auth secrets server-side only (OPENCLAW_GATEWAY_TOKEN / GATEWAY_TOKEN).
|
| 72 |
# Gateway client id used in websocket handshake (role=operator UI client).
|
| 73 |
NEXT_PUBLIC_GATEWAY_CLIENT_ID=openclaw-control-ui
|
| 74 |
+
# Gateway optional mode: set to 'true' if deploying on VPS with firewall blocking WebSocket ports.
|
| 75 |
+
# In optional mode, Mission Control runs standalone; core CRUD features work but live gateway events do not.
|
| 76 |
+
# NEXT_PUBLIC_GATEWAY_OPTIONAL=false
|
| 77 |
|
| 78 |
# === Data Paths (all optional, defaults to .data/ in project root) ===
|
| 79 |
# MISSION_CONTROL_DATA_DIR=.data
|
README.md
CHANGED
|
@@ -73,6 +73,32 @@ pnpm dev # http://localhost:3000
|
|
| 73 |
Initial login is seeded from `AUTH_USER` / `AUTH_PASS` on first run.
|
| 74 |
If `AUTH_PASS` contains `#`, quote it (e.g. `AUTH_PASS="my#password"`) or use `AUTH_PASS_B64`.
|
| 75 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 76 |
### Docker Hardening (Production)
|
| 77 |
|
| 78 |
For production deployments, use the hardened compose overlay:
|
|
|
|
| 73 |
Initial login is seeded from `AUTH_USER` / `AUTH_PASS` on first run.
|
| 74 |
If `AUTH_PASS` contains `#`, quote it (e.g. `AUTH_PASS="my#password"`) or use `AUTH_PASS_B64`.
|
| 75 |
|
| 76 |
+
## Gateway Optional Mode (Standalone Deployment)
|
| 77 |
+
|
| 78 |
+
Mission Control can run in standalone mode without a gateway connection. This is useful when:
|
| 79 |
+
|
| 80 |
+
- Deploying on a VPS with firewall rules blocking non-standard WebSocket ports (18789/18790)
|
| 81 |
+
- Testing UI/core workflows without a running gateway
|
| 82 |
+
- Running Mission Control primarily for project/task operations
|
| 83 |
+
|
| 84 |
+
Enable with:
|
| 85 |
+
|
| 86 |
+
```bash
|
| 87 |
+
NEXT_PUBLIC_GATEWAY_OPTIONAL=true
|
| 88 |
+
```
|
| 89 |
+
|
| 90 |
+
When enabled, the HUD status shows `Gateway Optional (Standalone)` instead of `Disconnected`.
|
| 91 |
+
|
| 92 |
+
Works without gateway:
|
| 93 |
+
- Task board, projects, agents, sessions, scheduler, webhooks, alerts, activity/audit, cost tracking
|
| 94 |
+
|
| 95 |
+
Requires active gateway:
|
| 96 |
+
- Real-time session updates
|
| 97 |
+
- Agent-to-agent messaging
|
| 98 |
+
- Gateway log streaming
|
| 99 |
+
|
| 100 |
+
For production VPS setups, you can also proxy gateway WebSockets over 443. See `docs/deployment.md`.
|
| 101 |
+
|
| 102 |
### Docker Hardening (Production)
|
| 103 |
|
| 104 |
For production deployments, use the hardened compose overlay:
|
docker-compose.yml
CHANGED
|
@@ -21,12 +21,12 @@ services:
|
|
| 21 |
- NET_BIND_SERVICE
|
| 22 |
security_opt:
|
| 23 |
- no-new-privileges:true
|
| 24 |
-
pids_limit: 256
|
| 25 |
deploy:
|
| 26 |
resources:
|
| 27 |
limits:
|
| 28 |
memory: 512M
|
| 29 |
cpus: '1.0'
|
|
|
|
| 30 |
networks:
|
| 31 |
- mc-net
|
| 32 |
restart: unless-stopped
|
|
|
|
| 21 |
- NET_BIND_SERVICE
|
| 22 |
security_opt:
|
| 23 |
- no-new-privileges:true
|
|
|
|
| 24 |
deploy:
|
| 25 |
resources:
|
| 26 |
limits:
|
| 27 |
memory: 512M
|
| 28 |
cpus: '1.0'
|
| 29 |
+
pids: 256
|
| 30 |
networks:
|
| 31 |
- mc-net
|
| 32 |
restart: unless-stopped
|
docs/deployment.md
CHANGED
|
@@ -175,3 +175,36 @@ Then restart the gateway and reconnect from Mission Control.
|
|
| 175 |
|
| 176 |
Device identity signing uses WebCrypto and requires a secure browser context.
|
| 177 |
Open Mission Control over HTTPS (or localhost), then reconnect.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 175 |
|
| 176 |
Device identity signing uses WebCrypto and requires a secure browser context.
|
| 177 |
Open Mission Control over HTTPS (or localhost), then reconnect.
|
| 178 |
+
|
| 179 |
+
### "Gateway shows offline on VPS deployment"
|
| 180 |
+
|
| 181 |
+
Browser WebSocket connections to non-standard ports (like 18789/18790) are often blocked by VPS firewall/provider rules.
|
| 182 |
+
|
| 183 |
+
Quick option:
|
| 184 |
+
|
| 185 |
+
```bash
|
| 186 |
+
NEXT_PUBLIC_GATEWAY_OPTIONAL=true
|
| 187 |
+
```
|
| 188 |
+
|
| 189 |
+
This runs Mission Control in standalone mode (core features available, live gateway streams unavailable).
|
| 190 |
+
|
| 191 |
+
Production option: reverse-proxy gateway WebSocket over 443.
|
| 192 |
+
|
| 193 |
+
nginx example:
|
| 194 |
+
|
| 195 |
+
```nginx
|
| 196 |
+
location /gateway-ws {
|
| 197 |
+
proxy_pass http://127.0.0.1:18789;
|
| 198 |
+
proxy_http_version 1.1;
|
| 199 |
+
proxy_set_header Upgrade $http_upgrade;
|
| 200 |
+
proxy_set_header Connection "upgrade";
|
| 201 |
+
proxy_set_header Host $host;
|
| 202 |
+
proxy_read_timeout 86400;
|
| 203 |
+
}
|
| 204 |
+
```
|
| 205 |
+
|
| 206 |
+
Then point UI to:
|
| 207 |
+
|
| 208 |
+
```bash
|
| 209 |
+
NEXT_PUBLIC_GATEWAY_URL=wss://your-domain.com/gateway-ws
|
| 210 |
+
```
|
src/components/hud/connection-status.tsx
CHANGED
|
@@ -18,10 +18,12 @@ export function ConnectionStatus({
|
|
| 18 |
}: ConnectionStatusProps) {
|
| 19 |
const { connection } = useMissionControl()
|
| 20 |
const displayUrl = connection.url || 'ws://<gateway-host>:<gateway-port>'
|
|
|
|
| 21 |
|
| 22 |
const getStatusColor = () => {
|
| 23 |
if (isConnected) return 'bg-green-500 animate-pulse'
|
| 24 |
if (connection.reconnectAttempts > 0) return 'bg-yellow-500'
|
|
|
|
| 25 |
return 'bg-red-500'
|
| 26 |
}
|
| 27 |
|
|
@@ -32,6 +34,9 @@ export function ConnectionStatus({
|
|
| 32 |
if (connection.reconnectAttempts > 0) {
|
| 33 |
return `Reconnecting... (${connection.reconnectAttempts}/10)`
|
| 34 |
}
|
|
|
|
|
|
|
|
|
|
| 35 |
return 'Disconnected'
|
| 36 |
}
|
| 37 |
|
|
|
|
| 18 |
}: ConnectionStatusProps) {
|
| 19 |
const { connection } = useMissionControl()
|
| 20 |
const displayUrl = connection.url || 'ws://<gateway-host>:<gateway-port>'
|
| 21 |
+
const isGatewayOptional = process.env.NEXT_PUBLIC_GATEWAY_OPTIONAL === 'true'
|
| 22 |
|
| 23 |
const getStatusColor = () => {
|
| 24 |
if (isConnected) return 'bg-green-500 animate-pulse'
|
| 25 |
if (connection.reconnectAttempts > 0) return 'bg-yellow-500'
|
| 26 |
+
if (isGatewayOptional && !isConnected) return 'bg-blue-500'
|
| 27 |
return 'bg-red-500'
|
| 28 |
}
|
| 29 |
|
|
|
|
| 34 |
if (connection.reconnectAttempts > 0) {
|
| 35 |
return `Reconnecting... (${connection.reconnectAttempts}/10)`
|
| 36 |
}
|
| 37 |
+
if (isGatewayOptional && !isConnected) {
|
| 38 |
+
return 'Gateway Optional (Standalone)'
|
| 39 |
+
}
|
| 40 |
return 'Disconnected'
|
| 41 |
}
|
| 42 |
|