@askexenow/exe-os 0.9.82 → 0.9.84
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/deploy/compose/.env +73 -0
- package/deploy/compose/.env.askexe-control-plane.example +18 -0
- package/deploy/compose/.env.customer.example +69 -0
- package/deploy/compose/.env.example +69 -0
- package/deploy/compose/README.md +164 -0
- package/deploy/compose/docker-compose.yml +392 -0
- package/deploy/compose/gateway.json +1 -0
- package/deploy/compose/generate-env.ts +252 -0
- package/deploy/stack-manifests/v0.9.json +137 -1
- package/dist/bin/cli.js +175 -16
- package/dist/bin/stack-update.js +169 -10
- package/package.json +3 -2
- package/stack.release.json +4 -4
|
@@ -0,0 +1,392 @@
|
|
|
1
|
+
# exe-os VPS stack — full production compose
|
|
2
|
+
#
|
|
3
|
+
# Services: exe-crm + crm-postgres + clickhouse + redis + exe-wiki + exed + exe-gateway
|
|
4
|
+
# Standard for managed customer VPSs: exe-monitor-agent reports fleet health to monitor.askexe.com.
|
|
5
|
+
# All image tags pinned per-client via .env (no :latest). Healthchecks on every service.
|
|
6
|
+
# Named volumes for state; explicit subnets; depends_on with service_healthy gates.
|
|
7
|
+
#
|
|
8
|
+
# Validate without secrets:
|
|
9
|
+
# cp .env.example .env && docker compose -f docker-compose.yml config
|
|
10
|
+
#
|
|
11
|
+
# Boot stack on a Lane-A-1-provisioned host:
|
|
12
|
+
# docker compose -f docker-compose.yml up -d
|
|
13
|
+
#
|
|
14
|
+
# nginx snippets at ../nginx/snippets/*.conf are included by the Ansible
|
|
15
|
+
# nginx-tls role from /etc/nginx/snippets/.
|
|
16
|
+
|
|
17
|
+
name: exe-os
|
|
18
|
+
|
|
19
|
+
services:
|
|
20
|
+
# ------------------------------------------------------------------
|
|
21
|
+
# Data layer
|
|
22
|
+
# ------------------------------------------------------------------
|
|
23
|
+
|
|
24
|
+
crm-postgres:
|
|
25
|
+
image: postgres:16.6-alpine
|
|
26
|
+
container_name: crm-postgres
|
|
27
|
+
restart: unless-stopped
|
|
28
|
+
env_file:
|
|
29
|
+
- path: .env
|
|
30
|
+
required: false
|
|
31
|
+
environment:
|
|
32
|
+
POSTGRES_USER: ${POSTGRES_USER:-exe}
|
|
33
|
+
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:?POSTGRES_PASSWORD is required}
|
|
34
|
+
POSTGRES_DB: ${POSTGRES_DB:-default}
|
|
35
|
+
PGDATA: /var/lib/postgresql/data/pgdata
|
|
36
|
+
volumes:
|
|
37
|
+
- postgres_data:/var/lib/postgresql/data
|
|
38
|
+
networks:
|
|
39
|
+
backend:
|
|
40
|
+
ipv4_address: 10.42.0.10
|
|
41
|
+
healthcheck:
|
|
42
|
+
test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER:-exe} -d ${POSTGRES_DB:-default}"]
|
|
43
|
+
interval: 10s
|
|
44
|
+
timeout: 5s
|
|
45
|
+
start_period: 30s
|
|
46
|
+
retries: 5
|
|
47
|
+
logging:
|
|
48
|
+
driver: json-file
|
|
49
|
+
options: { max-size: "10m", max-file: "3" }
|
|
50
|
+
|
|
51
|
+
clickhouse:
|
|
52
|
+
image: clickhouse/clickhouse-server:24.8.4.13-alpine
|
|
53
|
+
container_name: clickhouse
|
|
54
|
+
restart: unless-stopped
|
|
55
|
+
env_file:
|
|
56
|
+
- path: .env
|
|
57
|
+
required: false
|
|
58
|
+
environment:
|
|
59
|
+
CLICKHOUSE_DB: ${CLICKHOUSE_DB:-default}
|
|
60
|
+
CLICKHOUSE_USER: ${CLICKHOUSE_USER:-exe}
|
|
61
|
+
CLICKHOUSE_PASSWORD: ${CLICKHOUSE_PASSWORD:?CLICKHOUSE_PASSWORD is required}
|
|
62
|
+
CLICKHOUSE_DEFAULT_ACCESS_MANAGEMENT: "1"
|
|
63
|
+
volumes:
|
|
64
|
+
- clickhouse_data:/var/lib/clickhouse
|
|
65
|
+
- clickhouse_logs:/var/log/clickhouse-server
|
|
66
|
+
ulimits:
|
|
67
|
+
nofile:
|
|
68
|
+
soft: 262144
|
|
69
|
+
hard: 262144
|
|
70
|
+
networks:
|
|
71
|
+
backend:
|
|
72
|
+
ipv4_address: 10.42.0.11
|
|
73
|
+
healthcheck:
|
|
74
|
+
test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://127.0.0.1:8123/ping"]
|
|
75
|
+
interval: 10s
|
|
76
|
+
timeout: 5s
|
|
77
|
+
start_period: 30s
|
|
78
|
+
retries: 5
|
|
79
|
+
logging:
|
|
80
|
+
driver: json-file
|
|
81
|
+
options: { max-size: "10m", max-file: "3" }
|
|
82
|
+
|
|
83
|
+
redis:
|
|
84
|
+
image: redis:7.4-alpine
|
|
85
|
+
container_name: redis
|
|
86
|
+
restart: unless-stopped
|
|
87
|
+
env_file:
|
|
88
|
+
- path: .env
|
|
89
|
+
required: false
|
|
90
|
+
command: ["redis-server", "--requirepass", "${REDIS_PASSWORD:?REDIS_PASSWORD is required}", "--save", "60", "1", "--appendonly", "yes"]
|
|
91
|
+
volumes:
|
|
92
|
+
- redis_data:/data
|
|
93
|
+
networks:
|
|
94
|
+
backend:
|
|
95
|
+
ipv4_address: 10.42.0.12
|
|
96
|
+
healthcheck:
|
|
97
|
+
test: ["CMD-SHELL", "redis-cli -a $${REDIS_PASSWORD:?REDIS_PASSWORD is required} ping | grep -q PONG"]
|
|
98
|
+
interval: 10s
|
|
99
|
+
timeout: 5s
|
|
100
|
+
start_period: 10s
|
|
101
|
+
retries: 5
|
|
102
|
+
logging:
|
|
103
|
+
driver: json-file
|
|
104
|
+
options: { max-size: "10m", max-file: "3" }
|
|
105
|
+
|
|
106
|
+
# ------------------------------------------------------------------
|
|
107
|
+
# Apps
|
|
108
|
+
# ------------------------------------------------------------------
|
|
109
|
+
|
|
110
|
+
exe-crm:
|
|
111
|
+
image: ${CRM_IMAGE_TAG:-registry.askexe.com/askexe/exe-crm:v0.9.3}
|
|
112
|
+
container_name: exe-crm
|
|
113
|
+
restart: unless-stopped
|
|
114
|
+
depends_on:
|
|
115
|
+
crm-postgres:
|
|
116
|
+
condition: service_healthy
|
|
117
|
+
clickhouse:
|
|
118
|
+
condition: service_healthy
|
|
119
|
+
redis:
|
|
120
|
+
condition: service_healthy
|
|
121
|
+
env_file:
|
|
122
|
+
- path: .env
|
|
123
|
+
required: false
|
|
124
|
+
environment:
|
|
125
|
+
NODE_ENV: production
|
|
126
|
+
NODE_PORT: "3000"
|
|
127
|
+
EXE_LICENSE_KEY: ${EXE_LICENSE_KEY:?EXE_LICENSE_KEY is required — purchase at https://askexe.com}
|
|
128
|
+
SERVER_URL: ${CRM_SERVER_URL:-https://crm.askexe.com}
|
|
129
|
+
APP_SECRET: ${CRM_APP_SECRET:?CRM_APP_SECRET is required}
|
|
130
|
+
PG_DATABASE_URL: postgres://${POSTGRES_USER:-exe}:${POSTGRES_PASSWORD:?POSTGRES_PASSWORD is required}@crm-postgres:5432/${POSTGRES_DB:-default}
|
|
131
|
+
REDIS_URL: redis://:${REDIS_PASSWORD:?REDIS_PASSWORD is required}@redis:6379
|
|
132
|
+
CLICKHOUSE_URL: http://${CLICKHOUSE_USER:-exe}:${CLICKHOUSE_PASSWORD:?CLICKHOUSE_PASSWORD is required}@clickhouse:8123/${CLICKHOUSE_DB:-default}
|
|
133
|
+
STORAGE_TYPE: local
|
|
134
|
+
STORAGE_LOCAL_PATH: /app/.local-storage
|
|
135
|
+
BRANDING_CONFIG_PATH: /app/branding.json
|
|
136
|
+
ports:
|
|
137
|
+
- "127.0.0.1:${CRM_HOST_PORT:-3000}:3000"
|
|
138
|
+
volumes:
|
|
139
|
+
- crm_data:/app/.local-storage
|
|
140
|
+
- ${BRANDING_CONFIG:-./branding.json}:/app/branding.json:ro
|
|
141
|
+
networks:
|
|
142
|
+
backend:
|
|
143
|
+
ipv4_address: 10.42.0.20
|
|
144
|
+
frontend:
|
|
145
|
+
ipv4_address: 10.43.0.20
|
|
146
|
+
healthcheck:
|
|
147
|
+
test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://localhost:3000/healthz"]
|
|
148
|
+
interval: 30s
|
|
149
|
+
timeout: 5s
|
|
150
|
+
start_period: 60s
|
|
151
|
+
retries: 5
|
|
152
|
+
logging:
|
|
153
|
+
driver: json-file
|
|
154
|
+
options: { max-size: "10m", max-file: "3" }
|
|
155
|
+
|
|
156
|
+
exe-crm-worker:
|
|
157
|
+
image: ${CRM_IMAGE_TAG:-registry.askexe.com/askexe/exe-crm:v0.9.3}
|
|
158
|
+
container_name: exe-crm-worker
|
|
159
|
+
restart: unless-stopped
|
|
160
|
+
command: ["yarn", "worker:prod"]
|
|
161
|
+
depends_on:
|
|
162
|
+
crm-postgres:
|
|
163
|
+
condition: service_healthy
|
|
164
|
+
clickhouse:
|
|
165
|
+
condition: service_healthy
|
|
166
|
+
redis:
|
|
167
|
+
condition: service_healthy
|
|
168
|
+
exe-crm:
|
|
169
|
+
condition: service_healthy
|
|
170
|
+
env_file:
|
|
171
|
+
- path: .env
|
|
172
|
+
required: false
|
|
173
|
+
environment:
|
|
174
|
+
NODE_ENV: production
|
|
175
|
+
EXE_LICENSE_KEY: ${EXE_LICENSE_KEY:?EXE_LICENSE_KEY is required — purchase at https://askexe.com}
|
|
176
|
+
SERVER_URL: ${CRM_SERVER_URL:-https://crm.askexe.com}
|
|
177
|
+
APP_SECRET: ${CRM_APP_SECRET:?CRM_APP_SECRET is required}
|
|
178
|
+
PG_DATABASE_URL: postgres://${POSTGRES_USER:-exe}:${POSTGRES_PASSWORD:?POSTGRES_PASSWORD is required}@crm-postgres:5432/${POSTGRES_DB:-default}
|
|
179
|
+
REDIS_URL: redis://:${REDIS_PASSWORD:?REDIS_PASSWORD is required}@redis:6379
|
|
180
|
+
CLICKHOUSE_URL: http://${CLICKHOUSE_USER:-exe}:${CLICKHOUSE_PASSWORD:?CLICKHOUSE_PASSWORD is required}@clickhouse:8123/${CLICKHOUSE_DB:-default}
|
|
181
|
+
STORAGE_TYPE: local
|
|
182
|
+
STORAGE_LOCAL_PATH: /app/.local-storage
|
|
183
|
+
BRANDING_CONFIG_PATH: /app/branding.json
|
|
184
|
+
DISABLE_DB_MIGRATIONS: "true"
|
|
185
|
+
DISABLE_CRON_JOBS_REGISTRATION: "true"
|
|
186
|
+
volumes:
|
|
187
|
+
- crm_data:/app/.local-storage
|
|
188
|
+
- ${BRANDING_CONFIG:-./branding.json}:/app/branding.json:ro
|
|
189
|
+
networks:
|
|
190
|
+
backend:
|
|
191
|
+
ipv4_address: 10.42.0.24
|
|
192
|
+
logging:
|
|
193
|
+
driver: json-file
|
|
194
|
+
options: { max-size: "10m", max-file: "3" }
|
|
195
|
+
|
|
196
|
+
exe-wiki:
|
|
197
|
+
image: ${WIKI_IMAGE_TAG:-registry.askexe.com/askexe/exe-wiki:v0.9.3}
|
|
198
|
+
container_name: exe-wiki
|
|
199
|
+
restart: unless-stopped
|
|
200
|
+
depends_on:
|
|
201
|
+
crm-postgres:
|
|
202
|
+
condition: service_healthy
|
|
203
|
+
env_file:
|
|
204
|
+
- path: .env
|
|
205
|
+
required: false
|
|
206
|
+
environment:
|
|
207
|
+
NODE_ENV: production
|
|
208
|
+
SERVER_PORT: "3001"
|
|
209
|
+
EXE_LICENSE_KEY: ${EXE_LICENSE_KEY:?EXE_LICENSE_KEY is required — purchase at https://askexe.com}
|
|
210
|
+
STORAGE_DIR: /app/server/storage
|
|
211
|
+
DATABASE_URL: postgres://${POSTGRES_USER:-exe}:${POSTGRES_PASSWORD:?POSTGRES_PASSWORD is required}@crm-postgres:5432/${POSTGRES_DB:-default}?schema=${WIKI_DB_SCHEMA:-wiki}
|
|
212
|
+
AUTH_TOKEN: ${WIKI_AUTH_TOKEN:?WIKI_AUTH_TOKEN is required}
|
|
213
|
+
JWT_SECRET: ${WIKI_JWT_SECRET:?WIKI_JWT_SECRET is required}
|
|
214
|
+
SIG_KEY: ${WIKI_SIG_KEY:?WIKI_SIG_KEY is required}
|
|
215
|
+
SIG_SALT: ${WIKI_SIG_SALT:?WIKI_SIG_SALT is required}
|
|
216
|
+
VECTOR_DB: ${WIKI_VECTOR_DB:-postgres}
|
|
217
|
+
BRANDING_CONFIG_PATH: /app/branding.json
|
|
218
|
+
ports:
|
|
219
|
+
- "127.0.0.1:${WIKI_HOST_PORT:-3001}:3001"
|
|
220
|
+
volumes:
|
|
221
|
+
- wiki_data:/app/server/storage
|
|
222
|
+
- ${BRANDING_CONFIG:-./branding.json}:/app/branding.json:ro
|
|
223
|
+
networks:
|
|
224
|
+
backend:
|
|
225
|
+
ipv4_address: 10.42.0.21
|
|
226
|
+
frontend:
|
|
227
|
+
ipv4_address: 10.43.0.21
|
|
228
|
+
healthcheck:
|
|
229
|
+
test: ["CMD", "node", "-e", "const http=require('http');const r=http.get('http://127.0.0.1:3001/api/ping',s=>{process.exit(s.statusCode===200?0:1)});r.on('error',()=>process.exit(1));r.setTimeout(4000,()=>process.exit(1))"]
|
|
230
|
+
interval: 30s
|
|
231
|
+
timeout: 5s
|
|
232
|
+
start_period: 60s
|
|
233
|
+
retries: 5
|
|
234
|
+
logging:
|
|
235
|
+
driver: json-file
|
|
236
|
+
options: { max-size: "10m", max-file: "3" }
|
|
237
|
+
|
|
238
|
+
exed:
|
|
239
|
+
image: ${EXED_IMAGE_TAG:-registry.askexe.com/askexe/exed:v0.9.7}
|
|
240
|
+
container_name: exed
|
|
241
|
+
restart: unless-stopped
|
|
242
|
+
env_file:
|
|
243
|
+
- path: .env
|
|
244
|
+
required: false
|
|
245
|
+
environment:
|
|
246
|
+
NODE_ENV: production
|
|
247
|
+
EXED_PORT: "8765"
|
|
248
|
+
EXED_HOST: 0.0.0.0
|
|
249
|
+
EXED_MCP_TOKEN: ${EXED_MCP_TOKEN:?EXED_MCP_TOKEN is required}
|
|
250
|
+
EXED_DEVICE_ID: ${EXED_DEVICE_ID:-vps-default}
|
|
251
|
+
EXE_LICENSE_KEY: ${EXE_LICENSE_KEY:?EXE_LICENSE_KEY is required — purchase at https://askexe.com}
|
|
252
|
+
DATABASE_URL: ${EXED_DATABASE_URL:-postgres://${POSTGRES_USER:-exe}:${POSTGRES_PASSWORD:?POSTGRES_PASSWORD is required}@crm-postgres:5432/${POSTGRES_DB:-default}}
|
|
253
|
+
EXE_CLOUD_SYNC_TO_POSTGRES: ${EXE_CLOUD_SYNC_TO_POSTGRES:-true}
|
|
254
|
+
EXE_RSS_WARN_MB: ${EXE_RSS_WARN_MB:-6144}
|
|
255
|
+
EXE_RSS_RESTART_MB: ${EXE_RSS_RESTART_MB:-8192}
|
|
256
|
+
EXE_OS_DIR: /home/exed/.exe-os
|
|
257
|
+
volumes:
|
|
258
|
+
- exed_data:/home/exed/.exe-os
|
|
259
|
+
ports:
|
|
260
|
+
- "127.0.0.1:${EXED_HOST_PORT:-8765}:8765"
|
|
261
|
+
networks:
|
|
262
|
+
backend:
|
|
263
|
+
ipv4_address: 10.42.0.22
|
|
264
|
+
healthcheck:
|
|
265
|
+
test: ["CMD", "node", "-e", "const http=require('http');const r=http.get('http://127.0.0.1:8765/health',s=>{process.exit(s.statusCode===200?0:1)});r.on('error',()=>process.exit(1));r.setTimeout(4000,()=>process.exit(1))"]
|
|
266
|
+
interval: 30s
|
|
267
|
+
timeout: 5s
|
|
268
|
+
start_period: 30s
|
|
269
|
+
retries: 5
|
|
270
|
+
logging:
|
|
271
|
+
driver: json-file
|
|
272
|
+
options: { max-size: "10m", max-file: "3" }
|
|
273
|
+
|
|
274
|
+
exe-gateway:
|
|
275
|
+
image: ${GATEWAY_IMAGE_TAG:-registry.askexe.com/askexe/exe-gateway:v0.9.3}
|
|
276
|
+
container_name: exe-gateway
|
|
277
|
+
restart: unless-stopped
|
|
278
|
+
depends_on:
|
|
279
|
+
exed:
|
|
280
|
+
condition: service_healthy
|
|
281
|
+
env_file:
|
|
282
|
+
- path: .env
|
|
283
|
+
required: false
|
|
284
|
+
environment:
|
|
285
|
+
NODE_ENV: production
|
|
286
|
+
EXE_GATEWAY_HOME: /data
|
|
287
|
+
EXE_GATEWAY_CONFIG: /data/gateway.json
|
|
288
|
+
EXE_GATEWAY_PORT: "3100"
|
|
289
|
+
EXE_GATEWAY_HOST: 0.0.0.0
|
|
290
|
+
EXE_GATEWAY_AUTH_TOKEN: ${EXE_GATEWAY_AUTH_TOKEN:?EXE_GATEWAY_AUTH_TOKEN is required}
|
|
291
|
+
EXE_GATEWAY_WHATSAPP_VERIFY_TOKEN: ${EXE_GATEWAY_WHATSAPP_VERIFY_TOKEN:?EXE_GATEWAY_WHATSAPP_VERIFY_TOKEN is required}
|
|
292
|
+
EXE_GATEWAY_WS_RELAY_ENABLED: "true"
|
|
293
|
+
EXE_GATEWAY_WS_RELAY_HOST: 0.0.0.0
|
|
294
|
+
EXE_GATEWAY_WS_RELAY_PORT: "3101"
|
|
295
|
+
EXE_GATEWAY_WS_RELAY_AUTH_TOKEN: ${EXE_GATEWAY_WS_RELAY_AUTH_TOKEN:?EXE_GATEWAY_WS_RELAY_AUTH_TOKEN is required}
|
|
296
|
+
WHATSAPP_ACCESS_TOKEN: ${WHATSAPP_ACCESS_TOKEN:-}
|
|
297
|
+
API_ROUTER_URL: ${API_ROUTER_URL:-https://gateway.askexe.com}
|
|
298
|
+
API_ROUTER_KEY: ${API_ROUTER_KEY:-}
|
|
299
|
+
BYOK_ENABLED: ${BYOK_ENABLED:-false}
|
|
300
|
+
ANTHROPIC_API_KEY: ${ANTHROPIC_API_KEY:-}
|
|
301
|
+
EXE_LICENSE_KEY: ${EXE_LICENSE_KEY:?EXE_LICENSE_KEY is required — purchase at https://askexe.com}
|
|
302
|
+
ports:
|
|
303
|
+
- "127.0.0.1:${GATEWAY_HTTP_HOST_PORT:-3100}:3100"
|
|
304
|
+
- "127.0.0.1:${GATEWAY_WS_HOST_PORT:-3101}:3101"
|
|
305
|
+
volumes:
|
|
306
|
+
- gateway_data:/data
|
|
307
|
+
- ./gateway.json:/data/gateway.json:ro
|
|
308
|
+
networks:
|
|
309
|
+
backend:
|
|
310
|
+
ipv4_address: 10.42.0.23
|
|
311
|
+
frontend:
|
|
312
|
+
ipv4_address: 10.43.0.23
|
|
313
|
+
healthcheck:
|
|
314
|
+
test: ["CMD", "node", "-e", "const http=require('http');const r=http.get('http://localhost:3100/health',s=>{process.exit(s.statusCode===200?0:1)});r.on('error',()=>process.exit(1));r.setTimeout(4000,()=>process.exit(1))"]
|
|
315
|
+
interval: 30s
|
|
316
|
+
timeout: 5s
|
|
317
|
+
start_period: 30s
|
|
318
|
+
retries: 5
|
|
319
|
+
logging:
|
|
320
|
+
driver: json-file
|
|
321
|
+
options: { max-size: "10m", max-file: "3" }
|
|
322
|
+
|
|
323
|
+
exe-monitor-agent:
|
|
324
|
+
image: ${MONITOR_AGENT_IMAGE_TAG:-registry.askexe.com/askexe/exe-monitor-agent:v0.9.3}
|
|
325
|
+
container_name: exe-monitor-agent
|
|
326
|
+
restart: unless-stopped
|
|
327
|
+
environment:
|
|
328
|
+
HUB_URL: ${MONITOR_HUB_URL:-https://monitor.askexe.com}
|
|
329
|
+
TOKEN: ${MONITOR_AGENT_TOKEN:?MONITOR_AGENT_TOKEN is required — create Hygo system in monitor.askexe.com}
|
|
330
|
+
KEY: ${MONITOR_AGENT_KEY:?MONITOR_AGENT_KEY is required — copy public key from monitor.askexe.com}
|
|
331
|
+
LISTEN: ${MONITOR_AGENT_LISTEN:-:45876}
|
|
332
|
+
volumes:
|
|
333
|
+
- /var/run/docker.sock:/var/run/docker.sock:ro
|
|
334
|
+
- /proc:/host/proc:ro
|
|
335
|
+
- /sys:/host/sys:ro
|
|
336
|
+
- /etc/os-release:/host/etc/os-release:ro
|
|
337
|
+
- monitor_agent_data:/var/lib/beszel-agent
|
|
338
|
+
networks:
|
|
339
|
+
backend:
|
|
340
|
+
ipv4_address: 10.42.0.30
|
|
341
|
+
healthcheck:
|
|
342
|
+
test: ["CMD", "/agent", "health"]
|
|
343
|
+
interval: 30s
|
|
344
|
+
timeout: 5s
|
|
345
|
+
start_period: 30s
|
|
346
|
+
retries: 5
|
|
347
|
+
logging:
|
|
348
|
+
driver: json-file
|
|
349
|
+
options: { max-size: "10m", max-file: "3" }
|
|
350
|
+
|
|
351
|
+
# ------------------------------------------------------------------
|
|
352
|
+
# Volumes
|
|
353
|
+
# ------------------------------------------------------------------
|
|
354
|
+
volumes:
|
|
355
|
+
postgres_data:
|
|
356
|
+
driver: local
|
|
357
|
+
clickhouse_data:
|
|
358
|
+
driver: local
|
|
359
|
+
clickhouse_logs:
|
|
360
|
+
driver: local
|
|
361
|
+
redis_data:
|
|
362
|
+
driver: local
|
|
363
|
+
crm_data:
|
|
364
|
+
driver: local
|
|
365
|
+
wiki_data:
|
|
366
|
+
driver: local
|
|
367
|
+
exed_data:
|
|
368
|
+
driver: local
|
|
369
|
+
gateway_data:
|
|
370
|
+
driver: local
|
|
371
|
+
monitor_agent_data:
|
|
372
|
+
driver: local
|
|
373
|
+
|
|
374
|
+
# ------------------------------------------------------------------
|
|
375
|
+
# Networks
|
|
376
|
+
# ------------------------------------------------------------------
|
|
377
|
+
# backend — internal service-to-service traffic (DBs + apps)
|
|
378
|
+
# frontend — exposed to host nginx via published ports on apps that
|
|
379
|
+
# terminate user traffic (CRM, wiki, gateway). DBs never touch this net.
|
|
380
|
+
networks:
|
|
381
|
+
backend:
|
|
382
|
+
driver: bridge
|
|
383
|
+
ipam:
|
|
384
|
+
driver: default
|
|
385
|
+
config:
|
|
386
|
+
- subnet: 10.42.0.0/24
|
|
387
|
+
frontend:
|
|
388
|
+
driver: bridge
|
|
389
|
+
ipam:
|
|
390
|
+
driver: default
|
|
391
|
+
config:
|
|
392
|
+
- subnet: 10.43.0.0/24
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{}
|
|
@@ -0,0 +1,252 @@
|
|
|
1
|
+
import { randomBytes } from "node:crypto";
|
|
2
|
+
const REGISTRY = "ghcr.io/askexe";
|
|
3
|
+
|
|
4
|
+
// v0.9.x is the private/customer pilot stack line. Component repos keep their
|
|
5
|
+
// own package semver internally, but customer deployments use the tested stack
|
|
6
|
+
// image alias so one stack manifest can pin the whole release. exe-os/exed is
|
|
7
|
+
// the exception because the runtime package version is the orchestrator release.
|
|
8
|
+
const STACK_IMAGE_TAG = "v0.9.2";
|
|
9
|
+
const EXED_RELEASE_TAG = "v0.9.2";
|
|
10
|
+
|
|
11
|
+
const POSTGRES_USER = "exe";
|
|
12
|
+
const POSTGRES_DB = "default";
|
|
13
|
+
const CLICKHOUSE_DB = "default";
|
|
14
|
+
const CLICKHOUSE_USER = "exe";
|
|
15
|
+
export const CRM_IMAGE_TAG = `${REGISTRY}/exe-crm:${STACK_IMAGE_TAG}`;
|
|
16
|
+
const CRM_HOST_PORT = "3000";
|
|
17
|
+
export const WIKI_IMAGE_TAG = `${REGISTRY}/exe-wiki:${STACK_IMAGE_TAG}`;
|
|
18
|
+
const WIKI_DB_SCHEMA = "wiki";
|
|
19
|
+
const WIKI_VECTOR_DB = "postgres";
|
|
20
|
+
const WIKI_HOST_PORT = "3001";
|
|
21
|
+
export const EXED_IMAGE_TAG = `${REGISTRY}/exed:${EXED_RELEASE_TAG}`;
|
|
22
|
+
export const GATEWAY_IMAGE_TAG = `${REGISTRY}/exe-gateway:${STACK_IMAGE_TAG}`;
|
|
23
|
+
export const MONITOR_AGENT_IMAGE_TAG = `${REGISTRY}/exe-monitor-agent:${STACK_IMAGE_TAG}`;
|
|
24
|
+
const GATEWAY_HTTP_HOST_PORT = "3100";
|
|
25
|
+
const GATEWAY_WS_HOST_PORT = "3101";
|
|
26
|
+
const RANDOM_SECRET_16 = 16;
|
|
27
|
+
const RANDOM_SECRET_32 = 32;
|
|
28
|
+
const RANDOM_SECRET_48 = 48;
|
|
29
|
+
const LICENSE_KEY_COMMENT = "# injected by deploy_client";
|
|
30
|
+
const MANUAL_VALUE_COMMENT = "# SET_MANUALLY";
|
|
31
|
+
const EXAMPLE_LICENSE_KEY = "CHANGEME_EXE_LICENSE_KEY";
|
|
32
|
+
const DEFAULT_API_ROUTER_URL = "https://gateway.askexe.com";
|
|
33
|
+
const DEFAULT_MONITOR_HUB_URL = "https://monitor.askexe.com";
|
|
34
|
+
const DEFAULT_MONITOR_AGENT_LISTEN = ":45876";
|
|
35
|
+
|
|
36
|
+
export interface GenerateEnvOptions {
|
|
37
|
+
clientName: string;
|
|
38
|
+
domain: string;
|
|
39
|
+
licenseKey?: string;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export function generateEnv(options: GenerateEnvOptions): string {
|
|
43
|
+
const normalizedDomain = normalizeDomain(options.domain);
|
|
44
|
+
const normalizedClientName = normalizeClientName(options.clientName);
|
|
45
|
+
const gatewayWsAuthToken = randomSecret(RANDOM_SECRET_48);
|
|
46
|
+
|
|
47
|
+
return joinEnvLines([
|
|
48
|
+
"# --- Data Layer ---",
|
|
49
|
+
`POSTGRES_USER=${POSTGRES_USER}`,
|
|
50
|
+
`POSTGRES_PASSWORD=${randomSecret(RANDOM_SECRET_32)}`,
|
|
51
|
+
`POSTGRES_DB=${POSTGRES_DB}`,
|
|
52
|
+
"",
|
|
53
|
+
`CLICKHOUSE_DB=${CLICKHOUSE_DB}`,
|
|
54
|
+
`CLICKHOUSE_USER=${CLICKHOUSE_USER}`,
|
|
55
|
+
`CLICKHOUSE_PASSWORD=${randomSecret(RANDOM_SECRET_32)}`,
|
|
56
|
+
"",
|
|
57
|
+
`REDIS_PASSWORD=${randomSecret(RANDOM_SECRET_32)}`,
|
|
58
|
+
"",
|
|
59
|
+
"# --- CRM ---",
|
|
60
|
+
`CRM_IMAGE_TAG=${CRM_IMAGE_TAG}`,
|
|
61
|
+
`CRM_SERVER_URL=https://${normalizedDomain}`,
|
|
62
|
+
`CRM_APP_SECRET=${randomSecret(RANDOM_SECRET_48)}`,
|
|
63
|
+
`CRM_HOST_PORT=${CRM_HOST_PORT}`,
|
|
64
|
+
"",
|
|
65
|
+
"# --- Wiki ---",
|
|
66
|
+
`WIKI_IMAGE_TAG=${WIKI_IMAGE_TAG}`,
|
|
67
|
+
`WIKI_DB_SCHEMA=${WIKI_DB_SCHEMA}`,
|
|
68
|
+
`WIKI_VECTOR_DB=${WIKI_VECTOR_DB}`,
|
|
69
|
+
`WIKI_AUTH_TOKEN=${randomSecret(RANDOM_SECRET_48)}`,
|
|
70
|
+
`WIKI_JWT_SECRET=${randomSecret(RANDOM_SECRET_48)}`,
|
|
71
|
+
`WIKI_SIG_KEY=${randomSecret(RANDOM_SECRET_48)}`,
|
|
72
|
+
`WIKI_SIG_SALT=${randomSecret(RANDOM_SECRET_16)}`,
|
|
73
|
+
`WIKI_HOST_PORT=${WIKI_HOST_PORT}`,
|
|
74
|
+
"",
|
|
75
|
+
"# --- exed ---",
|
|
76
|
+
`EXED_IMAGE_TAG=${EXED_IMAGE_TAG}`,
|
|
77
|
+
`EXED_MCP_TOKEN=${randomSecret(RANDOM_SECRET_48)}`,
|
|
78
|
+
`EXED_DEVICE_ID=vps-${normalizedClientName}`,
|
|
79
|
+
"# VPS-only: enables cloud/local SQLite -> exe-db Postgres projection.",
|
|
80
|
+
"EXE_CLOUD_SYNC_TO_POSTGRES=true",
|
|
81
|
+
"",
|
|
82
|
+
"# --- Gateway ---",
|
|
83
|
+
`GATEWAY_IMAGE_TAG=${GATEWAY_IMAGE_TAG}`,
|
|
84
|
+
`EXE_GATEWAY_AUTH_TOKEN=${randomSecret(RANDOM_SECRET_48)}`,
|
|
85
|
+
`EXE_GATEWAY_WS_RELAY_AUTH_TOKEN=${gatewayWsAuthToken}`,
|
|
86
|
+
`EXE_GATEWAY_WHATSAPP_VERIFY_TOKEN=${randomSecret(RANDOM_SECRET_32)}`,
|
|
87
|
+
MANUAL_VALUE_COMMENT,
|
|
88
|
+
"WHATSAPP_ACCESS_TOKEN=",
|
|
89
|
+
`API_ROUTER_URL=${DEFAULT_API_ROUTER_URL}`,
|
|
90
|
+
`API_ROUTER_KEY=exe_rk_${randomSecret(48)}`,
|
|
91
|
+
"# BYOK: to use your own API keys instead of the Exe API Router,",
|
|
92
|
+
"# set BYOK_ENABLED=true and provide ANTHROPIC_API_KEY below.",
|
|
93
|
+
"# BYOK_ENABLED=false",
|
|
94
|
+
"# ANTHROPIC_API_KEY=",
|
|
95
|
+
`GATEWAY_HTTP_HOST_PORT=${GATEWAY_HTTP_HOST_PORT}`,
|
|
96
|
+
`GATEWAY_WS_HOST_PORT=${GATEWAY_WS_HOST_PORT}`,
|
|
97
|
+
"",
|
|
98
|
+
"# --- Monitoring agent (standard for managed customer VPSs) ---",
|
|
99
|
+
`MONITOR_AGENT_IMAGE_TAG=${MONITOR_AGENT_IMAGE_TAG}`,
|
|
100
|
+
`MONITOR_HUB_URL=${DEFAULT_MONITOR_HUB_URL}`,
|
|
101
|
+
"MONITOR_AGENT_TOKEN=CHANGEME_MONITOR_AGENT_TOKEN_FROM_MONITOR_HUB",
|
|
102
|
+
"MONITOR_AGENT_KEY=CHANGEME_MONITOR_AGENT_PUBLIC_KEY_FROM_MONITOR_HUB",
|
|
103
|
+
`MONITOR_AGENT_LISTEN=${DEFAULT_MONITOR_AGENT_LISTEN}`,
|
|
104
|
+
"",
|
|
105
|
+
"# --- License ---",
|
|
106
|
+
LICENSE_KEY_COMMENT,
|
|
107
|
+
`EXE_LICENSE_KEY=${options.licenseKey ?? EXAMPLE_LICENSE_KEY}`,
|
|
108
|
+
]);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
export function generateExampleEnv(): string {
|
|
112
|
+
return joinEnvLines([
|
|
113
|
+
"# exe-os VPS stack — example environment variables",
|
|
114
|
+
"# Copy to .env before deployment and replace every CHANGEME_* value.",
|
|
115
|
+
"# Values under # SET_MANUALLY must be provided by the operator.",
|
|
116
|
+
"",
|
|
117
|
+
"# --- Data Layer ---",
|
|
118
|
+
`POSTGRES_USER=${POSTGRES_USER}`,
|
|
119
|
+
"POSTGRES_PASSWORD=CHANGEME_POSTGRES_PASSWORD",
|
|
120
|
+
`POSTGRES_DB=${POSTGRES_DB}`,
|
|
121
|
+
"",
|
|
122
|
+
`CLICKHOUSE_DB=${CLICKHOUSE_DB}`,
|
|
123
|
+
`CLICKHOUSE_USER=${CLICKHOUSE_USER}`,
|
|
124
|
+
"CLICKHOUSE_PASSWORD=CHANGEME_CLICKHOUSE_PASSWORD",
|
|
125
|
+
"",
|
|
126
|
+
"REDIS_PASSWORD=CHANGEME_REDIS_PASSWORD",
|
|
127
|
+
"",
|
|
128
|
+
"# --- CRM ---",
|
|
129
|
+
`CRM_IMAGE_TAG=${CRM_IMAGE_TAG}`,
|
|
130
|
+
"CRM_SERVER_URL=https://CHANGEME_DOMAIN",
|
|
131
|
+
"CRM_APP_SECRET=CHANGEME_CRM_APP_SECRET",
|
|
132
|
+
`CRM_HOST_PORT=${CRM_HOST_PORT}`,
|
|
133
|
+
"",
|
|
134
|
+
"# --- Wiki ---",
|
|
135
|
+
`WIKI_IMAGE_TAG=${WIKI_IMAGE_TAG}`,
|
|
136
|
+
`WIKI_DB_SCHEMA=${WIKI_DB_SCHEMA}`,
|
|
137
|
+
`WIKI_VECTOR_DB=${WIKI_VECTOR_DB}`,
|
|
138
|
+
"WIKI_AUTH_TOKEN=CHANGEME_WIKI_AUTH_TOKEN",
|
|
139
|
+
"WIKI_JWT_SECRET=CHANGEME_WIKI_JWT_SECRET",
|
|
140
|
+
"WIKI_SIG_KEY=CHANGEME_WIKI_SIG_KEY",
|
|
141
|
+
"WIKI_SIG_SALT=CHANGEME_WIKI_SIG_SALT",
|
|
142
|
+
`WIKI_HOST_PORT=${WIKI_HOST_PORT}`,
|
|
143
|
+
"",
|
|
144
|
+
"# --- exed ---",
|
|
145
|
+
`EXED_IMAGE_TAG=${EXED_IMAGE_TAG}`,
|
|
146
|
+
"EXED_MCP_TOKEN=CHANGEME_EXED_MCP_TOKEN",
|
|
147
|
+
"EXED_DEVICE_ID=vps-default",
|
|
148
|
+
"# VPS-only: enables cloud/local SQLite -> exe-db Postgres projection.",
|
|
149
|
+
"# Keep false on laptops/dev boxes.",
|
|
150
|
+
"EXE_CLOUD_SYNC_TO_POSTGRES=true",
|
|
151
|
+
"",
|
|
152
|
+
"# --- Gateway ---",
|
|
153
|
+
`GATEWAY_IMAGE_TAG=${GATEWAY_IMAGE_TAG}`,
|
|
154
|
+
"EXE_GATEWAY_AUTH_TOKEN=CHANGEME_EXE_GATEWAY_AUTH_TOKEN",
|
|
155
|
+
"EXE_GATEWAY_WS_RELAY_AUTH_TOKEN=CHANGEME_EXE_GATEWAY_WS_RELAY_AUTH_TOKEN",
|
|
156
|
+
"EXE_GATEWAY_WHATSAPP_VERIFY_TOKEN=CHANGEME_EXE_GATEWAY_WHATSAPP_VERIFY_TOKEN",
|
|
157
|
+
MANUAL_VALUE_COMMENT,
|
|
158
|
+
"WHATSAPP_ACCESS_TOKEN=",
|
|
159
|
+
`API_ROUTER_URL=${DEFAULT_API_ROUTER_URL}`,
|
|
160
|
+
"API_ROUTER_KEY=exe_rk_CHANGEME_API_ROUTER_KEY",
|
|
161
|
+
"# BYOK: to use your own API keys instead of the Exe API Router,",
|
|
162
|
+
"# set BYOK_ENABLED=true and provide ANTHROPIC_API_KEY below.",
|
|
163
|
+
"# BYOK_ENABLED=false",
|
|
164
|
+
"# ANTHROPIC_API_KEY=CHANGEME_ANTHROPIC_API_KEY",
|
|
165
|
+
`GATEWAY_HTTP_HOST_PORT=${GATEWAY_HTTP_HOST_PORT}`,
|
|
166
|
+
`GATEWAY_WS_HOST_PORT=${GATEWAY_WS_HOST_PORT}`,
|
|
167
|
+
"",
|
|
168
|
+
"# --- Monitoring agent (standard for managed customer VPSs) ---",
|
|
169
|
+
`MONITOR_AGENT_IMAGE_TAG=${MONITOR_AGENT_IMAGE_TAG}`,
|
|
170
|
+
`MONITOR_HUB_URL=${DEFAULT_MONITOR_HUB_URL}`,
|
|
171
|
+
"# Values copied from monitor.askexe.com when adding a new system.",
|
|
172
|
+
"MONITOR_AGENT_TOKEN=CHANGEME_MONITOR_AGENT_TOKEN_FROM_MONITOR_HUB",
|
|
173
|
+
"MONITOR_AGENT_KEY=CHANGEME_MONITOR_AGENT_PUBLIC_KEY_FROM_MONITOR_HUB",
|
|
174
|
+
`MONITOR_AGENT_LISTEN=${DEFAULT_MONITOR_AGENT_LISTEN}`,
|
|
175
|
+
"",
|
|
176
|
+
"# --- AskExe central monitoring hub auth (AskExe-owned infra only) ---",
|
|
177
|
+
"# Customer VPSs normally run only MONITOR_AGENT_* above. These hub values are",
|
|
178
|
+
"# for monitor.askexe.com on exe-db-jkt.",
|
|
179
|
+
"MONITOR_HUB_PUBLIC_URL=https://monitor.askexe.com",
|
|
180
|
+
"MONITOR_HUB_SOURCE_DIR=/opt/exe-monitor",
|
|
181
|
+
"MONITOR_HUB_HOST_PORT=8090",
|
|
182
|
+
"MONITOR_HUB_DATA_DIR=/opt/exe-monitor-data",
|
|
183
|
+
"MONITOR_TRUSTED_AUTH_HEADER=X-AskExe-User-Email",
|
|
184
|
+
"# Keep false during bootstrap; flip true after the GoTrue auth proxy is live.",
|
|
185
|
+
"MONITOR_DISABLE_PASSWORD_AUTH=false",
|
|
186
|
+
"MONITOR_USER_CREATION=true",
|
|
187
|
+
"MONITOR_SHARE_ALL_SYSTEMS=false",
|
|
188
|
+
"",
|
|
189
|
+
"# --- License ---",
|
|
190
|
+
LICENSE_KEY_COMMENT,
|
|
191
|
+
`EXE_LICENSE_KEY=${EXAMPLE_LICENSE_KEY}`,
|
|
192
|
+
]);
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
export function randomSecret(length: number): string {
|
|
196
|
+
return randomBytes(Math.ceil(length / 2)).toString("hex").slice(0, length);
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
function normalizeDomain(domain: string): string {
|
|
200
|
+
return domain.trim().replace(/^https?:\/\//, "").replace(/\/+$/, "");
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
function normalizeClientName(clientName: string): string {
|
|
204
|
+
return clientName
|
|
205
|
+
.trim()
|
|
206
|
+
.toLowerCase()
|
|
207
|
+
.replace(/[^a-z0-9-]/g, "-")
|
|
208
|
+
.replace(/-+/g, "-")
|
|
209
|
+
.replace(/^-|-$/g, "") || "client";
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
function joinEnvLines(lines: string[]): string {
|
|
213
|
+
return `${lines.join("\n")}\n`;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
function printUsage(): void {
|
|
217
|
+
process.stderr.write(
|
|
218
|
+
"Usage:\n" +
|
|
219
|
+
" npx tsx deploy/compose/generate-env.ts <client-name> <domain> [license-key]\n" +
|
|
220
|
+
" npx tsx deploy/compose/generate-env.ts --example\n",
|
|
221
|
+
);
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
function isCliInvocation(): boolean {
|
|
225
|
+
return process.argv[1]?.endsWith("generate-env.ts") === true || process.argv[1]?.endsWith("generate-env.js") === true;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
function main(): void {
|
|
229
|
+
const args = process.argv.slice(2);
|
|
230
|
+
|
|
231
|
+
if (args.includes("--help") || args.includes("-h")) {
|
|
232
|
+
printUsage();
|
|
233
|
+
process.exit(0);
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
if (args.includes("--example")) {
|
|
237
|
+
process.stdout.write(generateExampleEnv());
|
|
238
|
+
return;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
const [clientName, domain, licenseKey] = args;
|
|
242
|
+
if (!clientName || !domain) {
|
|
243
|
+
printUsage();
|
|
244
|
+
process.exit(1);
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
process.stdout.write(generateEnv({ clientName, domain, licenseKey }));
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
if (isCliInvocation()) {
|
|
251
|
+
main();
|
|
252
|
+
}
|