studio-console 1.3.4__py3-none-any.whl
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.
- VERSION +1 -0
- docker-compose.yml +282 -0
- nginx/studio.conf.template +89 -0
- studio_console/__init__.py +17 -0
- studio_console/__main__.py +3 -0
- studio_console/cli.py +176 -0
- studio_console/cloudflare/__init__.py +1 -0
- studio_console/cloudflare/cf_api.py +285 -0
- studio_console/cloudflare/cf_wizard.py +1549 -0
- studio_console/commands.py +3001 -0
- studio_console/commands_container.py +526 -0
- studio_console/commands_launch.py +684 -0
- studio_console/constants.py +184 -0
- studio_console/data/README.md +37 -0
- studio_console/data/known_baselines.json +3 -0
- studio_console/env.py +600 -0
- studio_console/major_version.py +257 -0
- studio_console/tui.py +412 -0
- studio_console/wizard.py +1953 -0
- studio_console-1.3.4.dist-info/METADATA +235 -0
- studio_console-1.3.4.dist-info/RECORD +26 -0
- studio_console-1.3.4.dist-info/WHEEL +4 -0
- studio_console-1.3.4.dist-info/entry_points.txt +2 -0
- studio_console-1.3.4.dist-info/licenses/LEGAL.md +60 -0
- studio_console-1.3.4.dist-info/licenses/LICENSE +112 -0
- templates/.env.example +76 -0
docker-compose.yml
ADDED
|
@@ -0,0 +1,282 @@
|
|
|
1
|
+
# Studio - Production
|
|
2
|
+
# Pre-built images from GHCR. No build: directives.
|
|
3
|
+
# GPU workers: docker compose -f docker-compose.yml -f workers/docker-compose.gpu.yml up -d
|
|
4
|
+
#
|
|
5
|
+
# Usage:
|
|
6
|
+
# docker compose up -d # Core (postgres + api + ui + nginx)
|
|
7
|
+
# docker compose --profile worker-general up -d # Core + a specific worker
|
|
8
|
+
# docker compose --profile workers up -d # Core + all workers (if using COMPOSE_PROFILES)
|
|
9
|
+
#
|
|
10
|
+
# nginx is always the front door on SHS_NGINX_PORT (default 80).
|
|
11
|
+
# api and ui are internal — not bound to host ports.
|
|
12
|
+
# Scaling is handled by console via docker-compose.override.yml.
|
|
13
|
+
|
|
14
|
+
services:
|
|
15
|
+
postgres:
|
|
16
|
+
image: pgvector/pgvector:0.8.3-pg18-trixie@sha256:6232c5ea178707f278d060b51747c30f310164595674e95d5f9d100f4a48b56c
|
|
17
|
+
|
|
18
|
+
environment:
|
|
19
|
+
POSTGRES_USER: ${POSTGRES_USER:-postgres}
|
|
20
|
+
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-postgres}
|
|
21
|
+
POSTGRES_DB: selfhost_studio
|
|
22
|
+
PGDATA: /var/lib/postgresql/data
|
|
23
|
+
ports:
|
|
24
|
+
- "${POSTGRES_PORT:-5432}:5432"
|
|
25
|
+
volumes:
|
|
26
|
+
- ${SHS_DB_DATA:?SHS_DB_DATA is required - set in .env}:/var/lib/postgresql/data
|
|
27
|
+
healthcheck:
|
|
28
|
+
test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER:-postgres}"]
|
|
29
|
+
interval: 5s
|
|
30
|
+
timeout: 5s
|
|
31
|
+
retries: 5
|
|
32
|
+
networks:
|
|
33
|
+
- prod-network
|
|
34
|
+
restart: unless-stopped
|
|
35
|
+
logging:
|
|
36
|
+
driver: json-file
|
|
37
|
+
options:
|
|
38
|
+
max-size: "50m"
|
|
39
|
+
max-file: "3"
|
|
40
|
+
|
|
41
|
+
api:
|
|
42
|
+
image: ghcr.io/selfhosthub/studio-api:${SHS_STUDIO_VERSION:?SHS_STUDIO_VERSION is required - set in .env}
|
|
43
|
+
|
|
44
|
+
env_file:
|
|
45
|
+
- ${SHS_WORKSPACE_DIR}/.env
|
|
46
|
+
environment:
|
|
47
|
+
- SHS_WORKSPACE_ROOT=/workspace
|
|
48
|
+
volumes:
|
|
49
|
+
- ${SHS_STORAGE_ROOT:?SHS_STORAGE_ROOT is required - set in .env}:/workspace
|
|
50
|
+
depends_on:
|
|
51
|
+
postgres:
|
|
52
|
+
condition: service_healthy
|
|
53
|
+
extra_hosts:
|
|
54
|
+
- "host.docker.internal:host-gateway"
|
|
55
|
+
healthcheck:
|
|
56
|
+
test: ["CMD", "curl", "-f", "http://127.0.0.1:8000/health"]
|
|
57
|
+
interval: 30s
|
|
58
|
+
timeout: 10s
|
|
59
|
+
retries: 3
|
|
60
|
+
start_period: 10s
|
|
61
|
+
networks:
|
|
62
|
+
- prod-network
|
|
63
|
+
restart: unless-stopped
|
|
64
|
+
logging:
|
|
65
|
+
driver: json-file
|
|
66
|
+
options:
|
|
67
|
+
max-size: "50m"
|
|
68
|
+
max-file: "3"
|
|
69
|
+
|
|
70
|
+
ui:
|
|
71
|
+
image: ghcr.io/selfhosthub/studio-ui:${SHS_STUDIO_VERSION:?SHS_STUDIO_VERSION is required - set in .env}
|
|
72
|
+
|
|
73
|
+
environment:
|
|
74
|
+
- NEXT_PUBLIC_API_URL=${SHS_PUBLIC_API_URL:-${SHS_API_BASE_URL}}
|
|
75
|
+
- NEXT_PUBLIC_WS_URL=${SHS_WS_URL:?SHS_WS_URL is required - set in .env}
|
|
76
|
+
- NEXT_PUBLIC_API_ENV=production
|
|
77
|
+
# SSR target; internal nginx.
|
|
78
|
+
- SHS_API_BASE_URL=http://nginx:${SHS_NGINX_PORT:-80}
|
|
79
|
+
- SHS_WS_URL=${SHS_WS_URL}
|
|
80
|
+
depends_on:
|
|
81
|
+
- api
|
|
82
|
+
networks:
|
|
83
|
+
- prod-network
|
|
84
|
+
restart: unless-stopped
|
|
85
|
+
logging:
|
|
86
|
+
driver: json-file
|
|
87
|
+
options:
|
|
88
|
+
max-size: "50m"
|
|
89
|
+
max-file: "3"
|
|
90
|
+
|
|
91
|
+
nginx:
|
|
92
|
+
image: nginx:alpine
|
|
93
|
+
ports:
|
|
94
|
+
- "${SHS_NGINX_PORT:-80}:80"
|
|
95
|
+
volumes:
|
|
96
|
+
- ${SHS_WORKSPACE_DIR}/nginx/studio.conf:/etc/nginx/conf.d/default.conf:ro
|
|
97
|
+
depends_on:
|
|
98
|
+
api:
|
|
99
|
+
condition: service_started
|
|
100
|
+
required: false
|
|
101
|
+
ui:
|
|
102
|
+
condition: service_started
|
|
103
|
+
required: false
|
|
104
|
+
healthcheck:
|
|
105
|
+
# 127.0.0.1, not localhost: /etc/hosts maps localhost to ::1 too, and
|
|
106
|
+
# nginx listens only on IPv4 0.0.0.0:80 — wget tries ::1 first and gets
|
|
107
|
+
# 'connection refused', leaving the container perpetually unhealthy.
|
|
108
|
+
test: ["CMD", "wget", "-qO-", "http://127.0.0.1/nginx-health"]
|
|
109
|
+
interval: 10s
|
|
110
|
+
timeout: 5s
|
|
111
|
+
retries: 3
|
|
112
|
+
networks:
|
|
113
|
+
- prod-network
|
|
114
|
+
restart: unless-stopped
|
|
115
|
+
logging:
|
|
116
|
+
driver: json-file
|
|
117
|
+
options:
|
|
118
|
+
max-size: "50m"
|
|
119
|
+
max-file: "3"
|
|
120
|
+
|
|
121
|
+
# ===========================================================================
|
|
122
|
+
# Workers - each has its own profile, activated via COMPOSE_PROFILES in .env
|
|
123
|
+
# ===========================================================================
|
|
124
|
+
|
|
125
|
+
worker-general:
|
|
126
|
+
image: ghcr.io/selfhosthub/studio-worker-general:${SHS_STUDIO_VERSION:?SHS_STUDIO_VERSION is required - set in .env}
|
|
127
|
+
|
|
128
|
+
profiles: [worker-general]
|
|
129
|
+
|
|
130
|
+
env_file:
|
|
131
|
+
- ${SHS_WORKSPACE_DIR}/.env
|
|
132
|
+
environment:
|
|
133
|
+
- SHS_API_BASE_URL=http://api:8000
|
|
134
|
+
- SHS_PUBLIC_BASE_URL=${SHS_PUBLIC_BASE_URL:?SHS_PUBLIC_BASE_URL is required - set in .env}
|
|
135
|
+
- SHS_WORKER_SHARED_SECRET=${SHS_WORKER_SHARED_SECRET:?SHS_WORKER_SHARED_SECRET is required - set in .env}
|
|
136
|
+
- SHS_WORKSPACE_ROOT=/workspace
|
|
137
|
+
- SHS_WORKER_TYPE=general
|
|
138
|
+
volumes:
|
|
139
|
+
- ${SHS_STORAGE_ROOT}:/workspace
|
|
140
|
+
extra_hosts:
|
|
141
|
+
- "host.docker.internal:host-gateway"
|
|
142
|
+
networks:
|
|
143
|
+
- prod-network
|
|
144
|
+
restart: unless-stopped
|
|
145
|
+
logging:
|
|
146
|
+
driver: json-file
|
|
147
|
+
options:
|
|
148
|
+
max-size: "50m"
|
|
149
|
+
max-file: "3"
|
|
150
|
+
|
|
151
|
+
worker-transfer:
|
|
152
|
+
image: ghcr.io/selfhosthub/studio-worker-transfer:${SHS_STUDIO_VERSION:?SHS_STUDIO_VERSION is required - set in .env}
|
|
153
|
+
|
|
154
|
+
profiles: [worker-transfer]
|
|
155
|
+
|
|
156
|
+
env_file:
|
|
157
|
+
- ${SHS_WORKSPACE_DIR}/.env
|
|
158
|
+
environment:
|
|
159
|
+
- SHS_API_BASE_URL=http://api:8000
|
|
160
|
+
- SHS_PUBLIC_BASE_URL=${SHS_PUBLIC_BASE_URL:?SHS_PUBLIC_BASE_URL is required - set in .env}
|
|
161
|
+
- SHS_WORKER_SHARED_SECRET=${SHS_WORKER_SHARED_SECRET:?SHS_WORKER_SHARED_SECRET is required - set in .env}
|
|
162
|
+
- SHS_WORKSPACE_ROOT=/workspace
|
|
163
|
+
- SHS_WORKER_TYPE=transfer
|
|
164
|
+
volumes:
|
|
165
|
+
- ${SHS_STORAGE_ROOT}:/workspace
|
|
166
|
+
extra_hosts:
|
|
167
|
+
- "host.docker.internal:host-gateway"
|
|
168
|
+
networks:
|
|
169
|
+
- prod-network
|
|
170
|
+
restart: unless-stopped
|
|
171
|
+
logging:
|
|
172
|
+
driver: json-file
|
|
173
|
+
options:
|
|
174
|
+
max-size: "50m"
|
|
175
|
+
max-file: "3"
|
|
176
|
+
|
|
177
|
+
worker-video:
|
|
178
|
+
image: ghcr.io/selfhosthub/studio-worker-video:${SHS_STUDIO_VERSION:?SHS_STUDIO_VERSION is required - set in .env}
|
|
179
|
+
|
|
180
|
+
profiles: [worker-video]
|
|
181
|
+
|
|
182
|
+
env_file:
|
|
183
|
+
- ${SHS_WORKSPACE_DIR}/.env
|
|
184
|
+
environment:
|
|
185
|
+
- SHS_API_BASE_URL=http://api:8000
|
|
186
|
+
- SHS_PUBLIC_BASE_URL=${SHS_PUBLIC_BASE_URL:?SHS_PUBLIC_BASE_URL is required - set in .env}
|
|
187
|
+
- SHS_WORKER_SHARED_SECRET=${SHS_WORKER_SHARED_SECRET:?SHS_WORKER_SHARED_SECRET is required - set in .env}
|
|
188
|
+
- SHS_WORKSPACE_ROOT=/workspace
|
|
189
|
+
- SHS_WORKER_TYPE=video
|
|
190
|
+
- SHS_WHISPER_MODEL=${SHS_WHISPER_MODEL:-base}
|
|
191
|
+
volumes:
|
|
192
|
+
- ${SHS_STORAGE_ROOT}:/workspace
|
|
193
|
+
extra_hosts:
|
|
194
|
+
- "host.docker.internal:host-gateway"
|
|
195
|
+
networks:
|
|
196
|
+
- prod-network
|
|
197
|
+
restart: unless-stopped
|
|
198
|
+
logging:
|
|
199
|
+
driver: json-file
|
|
200
|
+
options:
|
|
201
|
+
max-size: "50m"
|
|
202
|
+
max-file: "3"
|
|
203
|
+
|
|
204
|
+
worker-audio:
|
|
205
|
+
image: ghcr.io/selfhosthub/studio-worker-audio:${SHS_STUDIO_VERSION:?SHS_STUDIO_VERSION is required - set in .env}
|
|
206
|
+
|
|
207
|
+
profiles: [worker-audio]
|
|
208
|
+
|
|
209
|
+
env_file:
|
|
210
|
+
- ${SHS_WORKSPACE_DIR}/.env
|
|
211
|
+
environment:
|
|
212
|
+
- SHS_API_BASE_URL=http://api:8000
|
|
213
|
+
- SHS_PUBLIC_BASE_URL=${SHS_PUBLIC_BASE_URL:?SHS_PUBLIC_BASE_URL is required - set in .env}
|
|
214
|
+
- SHS_WORKER_SHARED_SECRET=${SHS_WORKER_SHARED_SECRET:?SHS_WORKER_SHARED_SECRET is required - set in .env}
|
|
215
|
+
- SHS_WORKSPACE_ROOT=/workspace
|
|
216
|
+
- SHS_WORKER_TYPE=audio
|
|
217
|
+
- HF_HOME=/models/huggingface
|
|
218
|
+
volumes:
|
|
219
|
+
- ${SHS_STORAGE_ROOT}:/workspace
|
|
220
|
+
- ${SHS_MODELS_ROOT:?SHS_MODELS_ROOT is required - set in .env}:/models
|
|
221
|
+
extra_hosts:
|
|
222
|
+
- "host.docker.internal:host-gateway"
|
|
223
|
+
networks:
|
|
224
|
+
- prod-network
|
|
225
|
+
restart: unless-stopped
|
|
226
|
+
logging:
|
|
227
|
+
driver: json-file
|
|
228
|
+
options:
|
|
229
|
+
max-size: "50m"
|
|
230
|
+
max-file: "3"
|
|
231
|
+
|
|
232
|
+
# ComfyUI worker (proxy to operator-managed ComfyUI server)
|
|
233
|
+
# Deploy with SHS_WORKER_TYPE=comfyui-image (default) or SHS_WORKER_TYPE=comfyui-video
|
|
234
|
+
worker-comfyui-image:
|
|
235
|
+
image: ghcr.io/selfhosthub/studio-worker-comfyui:${SHS_STUDIO_VERSION:?SHS_STUDIO_VERSION is required - set in .env}
|
|
236
|
+
|
|
237
|
+
profiles: [worker-comfyui-image]
|
|
238
|
+
|
|
239
|
+
env_file:
|
|
240
|
+
- ${SHS_WORKSPACE_DIR}/.env
|
|
241
|
+
environment:
|
|
242
|
+
- SHS_API_BASE_URL=http://api:8000
|
|
243
|
+
- SHS_PUBLIC_BASE_URL=${SHS_PUBLIC_BASE_URL:?SHS_PUBLIC_BASE_URL is required - set in .env}
|
|
244
|
+
- SHS_WORKER_SHARED_SECRET=${SHS_WORKER_SHARED_SECRET:?SHS_WORKER_SHARED_SECRET is required - set in .env}
|
|
245
|
+
- SHS_WORKSPACE_ROOT=/workspace
|
|
246
|
+
- SHS_WORKER_TYPE=comfyui-image
|
|
247
|
+
- SHS_COMFYUI_URL=${SHS_COMFYUI_URL:-}
|
|
248
|
+
volumes:
|
|
249
|
+
- ${SHS_STORAGE_ROOT}:/workspace
|
|
250
|
+
extra_hosts:
|
|
251
|
+
- "host.docker.internal:host-gateway"
|
|
252
|
+
networks:
|
|
253
|
+
- prod-network
|
|
254
|
+
restart: unless-stopped
|
|
255
|
+
logging:
|
|
256
|
+
driver: json-file
|
|
257
|
+
options:
|
|
258
|
+
max-size: "50m"
|
|
259
|
+
max-file: "3"
|
|
260
|
+
|
|
261
|
+
# ===========================================================================
|
|
262
|
+
# Cloudflare Tunnel - activated via COMPOSE_PROFILES=cloudflared
|
|
263
|
+
# ===========================================================================
|
|
264
|
+
|
|
265
|
+
cloudflared:
|
|
266
|
+
image: cloudflare/cloudflared:latest
|
|
267
|
+
profiles: [cloudflared]
|
|
268
|
+
command: tunnel --no-autoupdate run --token ${CLOUDFLARE_TUNNEL_TOKEN:-}
|
|
269
|
+
depends_on:
|
|
270
|
+
- nginx
|
|
271
|
+
networks:
|
|
272
|
+
- prod-network
|
|
273
|
+
restart: unless-stopped
|
|
274
|
+
logging:
|
|
275
|
+
driver: json-file
|
|
276
|
+
options:
|
|
277
|
+
max-size: "50m"
|
|
278
|
+
max-file: "3"
|
|
279
|
+
|
|
280
|
+
networks:
|
|
281
|
+
prod-network:
|
|
282
|
+
driver: bridge
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
upstream studio_api {
|
|
2
|
+
ip_hash;
|
|
3
|
+
{{API_UPSTREAM}}
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
upstream studio_ui {
|
|
7
|
+
{{UI_UPSTREAM}}
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
server {
|
|
11
|
+
listen 80;
|
|
12
|
+
|
|
13
|
+
# Compression — all responses are proxied from upstreams, so gzip_proxied
|
|
14
|
+
# must be set or nginx skips them by default.
|
|
15
|
+
gzip on;
|
|
16
|
+
gzip_vary on;
|
|
17
|
+
gzip_proxied any;
|
|
18
|
+
gzip_comp_level 5;
|
|
19
|
+
gzip_min_length 256;
|
|
20
|
+
gzip_types
|
|
21
|
+
application/json
|
|
22
|
+
application/javascript
|
|
23
|
+
text/javascript
|
|
24
|
+
text/css
|
|
25
|
+
text/plain
|
|
26
|
+
application/xml
|
|
27
|
+
image/svg+xml
|
|
28
|
+
application/rss+xml
|
|
29
|
+
font/ttf
|
|
30
|
+
font/otf
|
|
31
|
+
application/font-woff;
|
|
32
|
+
|
|
33
|
+
# Health endpoint for the nginx container itself
|
|
34
|
+
location /nginx-health {
|
|
35
|
+
access_log off;
|
|
36
|
+
return 200 "ok\n";
|
|
37
|
+
add_header Content-Type text/plain;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
# API health check (top-level route, not under /api)
|
|
41
|
+
location = /health {
|
|
42
|
+
proxy_pass http://studio_api;
|
|
43
|
+
proxy_http_version 1.1;
|
|
44
|
+
proxy_set_header Host $host;
|
|
45
|
+
proxy_set_header X-Real-IP $remote_addr;
|
|
46
|
+
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
|
47
|
+
proxy_set_header X-Forwarded-Proto $scheme;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
# API + WebSocket + static uploads (org media served by API)
|
|
51
|
+
location ~ ^/(api|ws|uploads)(/|$) {
|
|
52
|
+
proxy_pass http://studio_api;
|
|
53
|
+
proxy_http_version 1.1;
|
|
54
|
+
proxy_set_header Host $host;
|
|
55
|
+
proxy_set_header X-Real-IP $remote_addr;
|
|
56
|
+
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
|
57
|
+
proxy_set_header X-Forwarded-Proto $scheme;
|
|
58
|
+
|
|
59
|
+
# WebSocket upgrade
|
|
60
|
+
proxy_set_header Upgrade $http_upgrade;
|
|
61
|
+
proxy_set_header Connection $connection_upgrade;
|
|
62
|
+
proxy_read_timeout 86400s;
|
|
63
|
+
proxy_send_timeout 86400s;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
# Hashed Next.js assets — content-hashed filenames, safe to cache forever.
|
|
67
|
+
location /_next/static/ {
|
|
68
|
+
proxy_pass http://studio_ui;
|
|
69
|
+
proxy_http_version 1.1;
|
|
70
|
+
proxy_set_header Host $host;
|
|
71
|
+
add_header Cache-Control "public, max-age=31536000, immutable";
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
# UI (everything else)
|
|
75
|
+
location / {
|
|
76
|
+
proxy_pass http://studio_ui;
|
|
77
|
+
proxy_http_version 1.1;
|
|
78
|
+
proxy_set_header Host $host;
|
|
79
|
+
proxy_set_header X-Real-IP $remote_addr;
|
|
80
|
+
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
|
81
|
+
proxy_set_header X-Forwarded-Proto $scheme;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
# WebSocket connection upgrade map (referenced above)
|
|
86
|
+
map $http_upgrade $connection_upgrade {
|
|
87
|
+
default upgrade;
|
|
88
|
+
"" close;
|
|
89
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# studio_console/__init__.py
|
|
2
|
+
"""Studio Console — operator management CLI."""
|
|
3
|
+
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
|
|
6
|
+
_VERSION_FILE = Path(__file__).resolve().parent.parent / "VERSION"
|
|
7
|
+
__version__: str = _VERSION_FILE.read_text().strip() if _VERSION_FILE.exists() else "dev"
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def main() -> None:
|
|
11
|
+
from .cli import main as _main
|
|
12
|
+
|
|
13
|
+
_main()
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
__all__ = ["main", "__version__"]
|
|
17
|
+
|
studio_console/cli.py
ADDED
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
# studio_console/cli.py
|
|
2
|
+
"""Argument parser and main entry point."""
|
|
3
|
+
|
|
4
|
+
import argparse
|
|
5
|
+
import os
|
|
6
|
+
import sys
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
|
|
9
|
+
from .commands import (
|
|
10
|
+
cmd_backup,
|
|
11
|
+
cmd_build,
|
|
12
|
+
cmd_config_set,
|
|
13
|
+
cmd_config_unset,
|
|
14
|
+
cmd_health,
|
|
15
|
+
cmd_links,
|
|
16
|
+
cmd_logs,
|
|
17
|
+
cmd_reset_password,
|
|
18
|
+
cmd_restart,
|
|
19
|
+
cmd_restore,
|
|
20
|
+
cmd_self_update,
|
|
21
|
+
cmd_show_config,
|
|
22
|
+
cmd_start,
|
|
23
|
+
cmd_stop,
|
|
24
|
+
cmd_upgrade,
|
|
25
|
+
cmd_workers,
|
|
26
|
+
config_menu,
|
|
27
|
+
)
|
|
28
|
+
from .commands_container import container_menu
|
|
29
|
+
from .commands_launch import cmd_launch_core, cmd_launch_full, cmd_set_core_db_url
|
|
30
|
+
from .env import _workspace_dir, detect_context, env_path
|
|
31
|
+
from .tui import NavBack, NavExit
|
|
32
|
+
from .wizard import wizard, wizard_non_interactive
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def build_parser() -> argparse.ArgumentParser:
|
|
36
|
+
parser = argparse.ArgumentParser(
|
|
37
|
+
prog="studio-console",
|
|
38
|
+
description="Studio Console — operator management CLI",
|
|
39
|
+
)
|
|
40
|
+
sub = parser.add_subparsers(dest="command")
|
|
41
|
+
|
|
42
|
+
p_build = sub.add_parser("build", help="Build Docker images from local source")
|
|
43
|
+
p_build.add_argument(
|
|
44
|
+
"images",
|
|
45
|
+
nargs="*",
|
|
46
|
+
default=None,
|
|
47
|
+
help="Images to build (e.g. api ui worker-general). Omit for all.",
|
|
48
|
+
)
|
|
49
|
+
|
|
50
|
+
sub.add_parser("start", help="Start services")
|
|
51
|
+
sub.add_parser("stop", help="Stop services")
|
|
52
|
+
|
|
53
|
+
p_restart = sub.add_parser("restart", help="Restart service or all")
|
|
54
|
+
p_restart.add_argument("service", nargs="?", default=None)
|
|
55
|
+
|
|
56
|
+
sub.add_parser("health", help="Health check")
|
|
57
|
+
|
|
58
|
+
p_config = sub.add_parser("config", help="Show or set config")
|
|
59
|
+
config_sub = p_config.add_subparsers(dest="config_action")
|
|
60
|
+
p_set = config_sub.add_parser("set", help="Set a config value")
|
|
61
|
+
p_set.add_argument("key")
|
|
62
|
+
p_set.add_argument("value")
|
|
63
|
+
p_unset = config_sub.add_parser("unset", help="Remove a config value")
|
|
64
|
+
p_unset.add_argument("key")
|
|
65
|
+
|
|
66
|
+
p_logs = sub.add_parser("logs", help="View logs")
|
|
67
|
+
p_logs.add_argument("service", nargs="?", default=None)
|
|
68
|
+
|
|
69
|
+
sub.add_parser("workers", help="List/scale workers")
|
|
70
|
+
sub.add_parser("reset-password", help="Reset admin password")
|
|
71
|
+
|
|
72
|
+
p_backup = sub.add_parser("backup", help="Backup database + files")
|
|
73
|
+
p_backup.add_argument(
|
|
74
|
+
"what",
|
|
75
|
+
nargs="?",
|
|
76
|
+
choices=["all", "db", "orgs"],
|
|
77
|
+
default="all",
|
|
78
|
+
help="What to back up (default: all)",
|
|
79
|
+
)
|
|
80
|
+
|
|
81
|
+
p_restore = sub.add_parser("restore", help="Restore from backup")
|
|
82
|
+
p_restore.add_argument("path", nargs="?", default=None)
|
|
83
|
+
|
|
84
|
+
sub.add_parser("upgrade", help="Pull latest Studio version + restart")
|
|
85
|
+
sub.add_parser("links", help="Print service URLs")
|
|
86
|
+
p_launch = sub.add_parser("launch-full", help="Launch the self-contained full image and open its console")
|
|
87
|
+
p_launch.add_argument("--tag", help="Image tag to launch (default: latest)")
|
|
88
|
+
p_launch.add_argument("--workspace", help="Host data dir (default: ~/.studio)")
|
|
89
|
+
p_launch_core = sub.add_parser("launch-core", help="Launch the core image (external Postgres) and open its console")
|
|
90
|
+
p_launch_core.add_argument("--tag", help="Image tag to launch (default: latest)")
|
|
91
|
+
p_launch_core.add_argument("--workspace", help="Host data dir (default: ~/.studio-core)")
|
|
92
|
+
p_core_db = sub.add_parser("core-db-url", help="Set core's external database URL for the next launch-core")
|
|
93
|
+
p_core_db.add_argument("url", nargs="?", default=None, help="postgresql+asyncpg://user:pass@host:5432/db (prompts if omitted)")
|
|
94
|
+
sub.add_parser("wizard", help="Run setup wizard")
|
|
95
|
+
sub.add_parser("init", help="Non-interactive setup from env vars (undocumented)")
|
|
96
|
+
sub.add_parser("self-update", help="Update studio-console to the latest version")
|
|
97
|
+
sub.add_parser("version", help="Print version")
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
return parser
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
def main() -> None:
|
|
104
|
+
parser = build_parser()
|
|
105
|
+
args = parser.parse_args()
|
|
106
|
+
|
|
107
|
+
context = detect_context()
|
|
108
|
+
ef = env_path(context)
|
|
109
|
+
|
|
110
|
+
if args.command is None:
|
|
111
|
+
try:
|
|
112
|
+
if context in ("container", "runpod"):
|
|
113
|
+
# In-container mode: entrypoint owns provisioning, console is
|
|
114
|
+
# purely an operator tool. No wizard, no compose-shaped commands.
|
|
115
|
+
container_menu(context, ef)
|
|
116
|
+
else:
|
|
117
|
+
if not ef.exists():
|
|
118
|
+
if not wizard(context, ef):
|
|
119
|
+
return # wizard aborted
|
|
120
|
+
# Always show the main menu (wizard saves .env, then we land here)
|
|
121
|
+
config_menu(context, ef)
|
|
122
|
+
except (NavBack, NavExit, KeyboardInterrupt):
|
|
123
|
+
print()
|
|
124
|
+
return
|
|
125
|
+
|
|
126
|
+
dispatch = {
|
|
127
|
+
"build": lambda: cmd_build(ef, args.images if args.images else None),
|
|
128
|
+
"start": lambda: sys.exit(0 if cmd_start(context, ef) is not False else 1),
|
|
129
|
+
"stop": lambda: cmd_stop(context, ef),
|
|
130
|
+
"restart": lambda: cmd_restart(context, ef, args.service),
|
|
131
|
+
"health": lambda: cmd_health(context, ef),
|
|
132
|
+
"config": lambda: (
|
|
133
|
+
cmd_config_set(ef, args.key, args.value)
|
|
134
|
+
if getattr(args, "config_action", None) == "set"
|
|
135
|
+
else cmd_config_unset(ef, args.key)
|
|
136
|
+
if getattr(args, "config_action", None) == "unset"
|
|
137
|
+
else cmd_show_config(context, ef)
|
|
138
|
+
),
|
|
139
|
+
"logs": lambda: cmd_logs(context, ef, args.service),
|
|
140
|
+
"workers": lambda: cmd_workers(context, ef),
|
|
141
|
+
"reset-password": lambda: cmd_reset_password(context, ef),
|
|
142
|
+
"backup": lambda: cmd_backup(context, ef, args.what),
|
|
143
|
+
"restore": lambda: cmd_restore(context, ef, args.path),
|
|
144
|
+
"upgrade": lambda: cmd_upgrade(context, ef),
|
|
145
|
+
"links": lambda: cmd_links(context, ef),
|
|
146
|
+
"launch-full": lambda: sys.exit(
|
|
147
|
+
0 if cmd_launch_full(
|
|
148
|
+
context,
|
|
149
|
+
args.tag,
|
|
150
|
+
Path(os.path.expanduser(args.workspace)) if args.workspace else None,
|
|
151
|
+
) else 1
|
|
152
|
+
),
|
|
153
|
+
"launch-core": lambda: sys.exit(
|
|
154
|
+
0 if cmd_launch_core(
|
|
155
|
+
context,
|
|
156
|
+
args.tag,
|
|
157
|
+
Path(os.path.expanduser(args.workspace)) if args.workspace else None,
|
|
158
|
+
) else 1
|
|
159
|
+
),
|
|
160
|
+
"core-db-url": lambda: sys.exit(0 if cmd_set_core_db_url(context, args.url) else 1),
|
|
161
|
+
"wizard": lambda: wizard(context, ef),
|
|
162
|
+
"init": lambda: sys.exit(0 if wizard_non_interactive(context, ef) else 1),
|
|
163
|
+
"self-update": lambda: cmd_self_update(context),
|
|
164
|
+
"version": lambda: cmd_version(),
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
handler = dispatch.get(args.command)
|
|
168
|
+
if handler:
|
|
169
|
+
handler()
|
|
170
|
+
else:
|
|
171
|
+
parser.print_help()
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
def cmd_version() -> None:
|
|
175
|
+
from . import __version__
|
|
176
|
+
print(f"studio-console {__version__}")
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
# studio_console/cloudflare/__init__.py
|