feat(deploy): serve the App Lab onboarding zip at /download/ (prod + LAN)
Turnkey hosting for the distributable app: both web containers now mount deploy/download/ read-only and nginx serves it at /download/<file> (autoindex off, no-store). Drop dist/apess-onboard.zip into deploy/download/ and students fetch https://apess.redclaw.dev/download/apess-onboard.zip → App Lab "Import an app" → Run. No image rebuild to update the artifact. The zip carries a baked cloud token, so deploy/download/*.zip is gitignored and the URL should be treated as a secret (workshop network / access-gated, not a public link). Co-Authored-By: Claude Opus 4.8 <[email protected]>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
d59bbc0ef3
commit
c02898a02d
@@ -32,3 +32,7 @@ deploy/workshop-llm/.env
|
|||||||
*.njsproj
|
*.njsproj
|
||||||
*.sln
|
*.sln
|
||||||
*.sw?
|
*.sw?
|
||||||
|
|
||||||
|
# Distributable artifacts (baked token) — never commit
|
||||||
|
deploy/download/*.zip
|
||||||
|
deploy/download/*.tar*
|
||||||
|
|||||||
@@ -8,6 +8,10 @@ services:
|
|||||||
image: apess-web:latest
|
image: apess-web:latest
|
||||||
container_name: apess-web
|
container_name: apess-web
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
|
volumes:
|
||||||
|
# Serve /download/apess-onboard.zip (the App Lab onboarding app). Drop the
|
||||||
|
# zip into deploy/download/ — no image rebuild. The zip holds a baked token.
|
||||||
|
- ./download:/usr/share/nginx/download:ro
|
||||||
networks:
|
networks:
|
||||||
- clawbooks-net
|
- clawbooks-net
|
||||||
# Routing is defined in deploy/traefik/apess.yml (file provider) to avoid
|
# Routing is defined in deploy/traefik/apess.yml (file provider) to avoid
|
||||||
|
|||||||
@@ -0,0 +1,26 @@
|
|||||||
|
# Download directory
|
||||||
|
|
||||||
|
Drop distributable artifacts here — they're served at `/download/<file>` by both
|
||||||
|
the prod (`deploy/docker-compose.yml`) and LAN (`deploy/lan/`) web containers via a
|
||||||
|
read-only volume mount. No image rebuild needed; the container picks up new files
|
||||||
|
immediately.
|
||||||
|
|
||||||
|
## The App Lab onboarding app
|
||||||
|
|
||||||
|
```sh
|
||||||
|
# 1. build the zip (bakes in the cloud token)
|
||||||
|
export ANTHROPIC_OAUTH_TOKEN=sk-ant-oat01-…
|
||||||
|
./deploy/uno-q/package-onboard-app.sh
|
||||||
|
|
||||||
|
# 2. place it here for download
|
||||||
|
cp deploy/uno-q/dist/apess-onboard.zip deploy/download/
|
||||||
|
|
||||||
|
# 3. students fetch it (then App Lab → "Import an app" → Run)
|
||||||
|
# https://apess.redclaw.dev/download/apess-onboard.zip
|
||||||
|
```
|
||||||
|
|
||||||
|
## ⚠️ Secret
|
||||||
|
|
||||||
|
`apess-onboard.zip` contains a **baked cloud token**. Never commit it (this dir's
|
||||||
|
`*.zip` is gitignored). Treat the download URL as a secret — serve it on the
|
||||||
|
workshop network, or behind the workshop's access, not as a public link.
|
||||||
@@ -27,6 +27,9 @@ services:
|
|||||||
- '${WEB_PORT:-80}:80' # WEB_PORT=8080 if the box can't bind :80
|
- '${WEB_PORT:-80}:80' # WEB_PORT=8080 if the box can't bind :80
|
||||||
volumes:
|
volumes:
|
||||||
- ./nginx.lan.conf:/etc/nginx/conf.d/default.conf:ro
|
- ./nginx.lan.conf:/etc/nginx/conf.d/default.conf:ro
|
||||||
|
# Serve /download/apess-onboard.zip — drop the packaged app zip into
|
||||||
|
# deploy/download/ (shared with the prod deploy). Holds a baked token.
|
||||||
|
- ../download:/usr/share/nginx/download:ro
|
||||||
depends_on:
|
depends_on:
|
||||||
- apess-api
|
- apess-api
|
||||||
networks: [apess-lan]
|
networks: [apess-lan]
|
||||||
|
|||||||
@@ -52,6 +52,17 @@ server {
|
|||||||
return 200 "ok\n";
|
return 200 "ok\n";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# Downloadable artifacts (the App Lab onboarding zip) — mounted dir, so you drop
|
||||||
|
# the file in without rebuilding (compose: ../download:/usr/share/nginx/download).
|
||||||
|
# apess-onboard.zip carries a baked cloud token; it's on the isolated workshop
|
||||||
|
# LAN, but treat the path as a secret. autoindex off = not browsable.
|
||||||
|
location /download/ {
|
||||||
|
alias /usr/share/nginx/download/;
|
||||||
|
autoindex off;
|
||||||
|
add_header Cache-Control "no-store";
|
||||||
|
try_files $uri =404;
|
||||||
|
}
|
||||||
|
|
||||||
# SPA fallback (client-side routes: /workshop, /admin, /judge, …)
|
# SPA fallback (client-side routes: /workshop, /admin, /judge, …)
|
||||||
location / {
|
location / {
|
||||||
try_files $uri $uri/ /index.html;
|
try_files $uri $uri/ /index.html;
|
||||||
|
|||||||
@@ -23,6 +23,18 @@ server {
|
|||||||
add_header Cache-Control "no-cache, no-store, must-revalidate";
|
add_header Cache-Control "no-cache, no-store, must-revalidate";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# Downloadable artifacts (the App Lab onboarding zip). Served from a MOUNTED
|
||||||
|
# dir so you drop files in without rebuilding the image (see docker-compose:
|
||||||
|
# ./download:/usr/share/nginx/download:ro). NOTE: apess-onboard.zip carries a
|
||||||
|
# baked cloud token — treat the URL as a secret (share on the workshop network,
|
||||||
|
# not publicly). autoindex off so the directory can't be browsed.
|
||||||
|
location /download/ {
|
||||||
|
alias /usr/share/nginx/download/;
|
||||||
|
autoindex off;
|
||||||
|
add_header Cache-Control "no-store";
|
||||||
|
try_files $uri =404;
|
||||||
|
}
|
||||||
|
|
||||||
location = /healthz {
|
location = /healthz {
|
||||||
access_log off;
|
access_log off;
|
||||||
return 200 "ok\n";
|
return 200 "ok\n";
|
||||||
|
|||||||
Reference in New Issue
Block a user