Serve Release
Serves an already-published backend.ai-webui release bundle locally so it can
be tested under a stable, version-named Portless URL. Wraps
scripts/serve-release.sh — do not reinvent the download/extract logic.
1. Resolve the version
Pick a version with this priority:
- User-provided version in the prompt — accept
26.4.8-rc.3,v26.4.8-rc.3, or a release URL (.../releases/tag/v26.4.8-rc.3). Strip the leadingvif present; everything downstream uses the bare semver. - Most recent release — if the user said "릴리즈 띄워줘" without a version,
fetch the latest tag with:
Show the top 5–10 entries withgh release list --repo lablup/backend.ai-webui --limit 10AskUserQuestionso the user can pick (include stable and prerelease releases; mark prereleases). Do not silently default to the absolute latest — the user usually wants a specific one. - Tag exists check — before running the script, confirm the tag exists:
If the tag does not exist, surface the error and offer thegh release view v<VERSION> --repo lablup/backend.ai-webui --json tagName -q .tagNamegh release listoutput so the user can correct the version. Do not try fuzzy matching.
The bundle asset must exist on the release. The script downloads
backend.ai-webui-bundle-<VERSION>.zip from
https://github.com/lablup/backend.ai-webui/releases/download/v<VERSION>/....
If the user picked a release where this asset is missing (rare, mostly for
hotfix-only tags), the curl step will fail with 404 — surface the failure and
suggest a different version rather than retrying.
2. Pre-flight checks
Before invoking the script, verify:
config.tomlexists in the project root. The script copies it into the extracted folder so the served bundle points at your local backend. If missing, copyconfig.toml.sampletoconfig.tomlfirst and remind the user to edit endpoints if needed.serveis on PATH (command -v serve). If absent, tell the user to runnpm install -g serve— the script also errors with the same hint.portlessis on PATH (command -v portless). If absent, the script silently falls back to plainserveonhttp://localhost:<SERVE_PORT>; tell the user the URL will be HTTP-only without Portless and suggestnpm install -g portless(orpnpm add -g portless) if they want the named-subdomain experience.
3. Decide ports
The script reads two optional env vars:
SERVE_PORT— the HTTP portservelistens on (default9091). Pin only if the user asked for a specific port, or9091is occupied.PORTLESS_PORT— the Portless daemon port (default1355, same asscripts/dev.mjs). Do not change unless the user explicitly asks.
Quick check for port conflict on the default:
lsof -nP -iTCP:9091 -sTCP:LISTEN 2>/dev/null | head -1
If something's already on 9091, pick the next free port in the 9091–9099 range
and pass SERVE_PORT=<port>.
4. Compute the Portless URL
The script derives the Portless app slug from the version. Replicate the same sanitization so you can announce the URL before the server is fully up:
v<VERSION> -> lowercased
-> [^a-z0-9-] replaced with -
-> repeated - collapsed
-> leading/trailing - trimmed
-> capped at 40 chars
Examples:
26.4.8-rc.3-> appv26-4-8-rc-3->https://v26-4-8-rc-3.localhost:135526.4.7-> appv26-4-7->https://v26-4-7.localhost:135525.7.1-> appv25-7-1->https://v25-7-1.localhost:1355
Use the standard daemon port (1355) unless the user pinned PORTLESS_PORT.
5. Run the script
Always run in the background — serve-release.sh blocks on serve/portless
and the user will need their shell back to interact with the served instance.
Run from the project root so the config.toml and dist/plugins lookup paths
resolve correctly.
Default invocation:
./scripts/serve-release.sh <VERSION>
With a custom serve port:
SERVE_PORT=9092 ./scripts/serve-release.sh <VERSION>
Use run_in_background: true and announce in one short sentence what's
happening (e.g. "Downloading v26.4.8-rc.3 bundle and serving via Portless at
https://v26-4-8-rc-3.localhost:1355.").
6. Wait for the ready signal
The script prints a few stages — download, extract, copy config.toml, then
finally Portless + serve startup. The script's own Press Ctrl+C to stop the server line is printed before exec portless ... -- serve, so it tells you
the script is about to hand off, not that the port is bound.
Poll the background task's output with a short until-loop (cap ~30s for first
download, ~5s if the bundle is cached) for the line serve itself emits
once it's actually listening — typically Accepting connections at (the
"INFO" banner from the serve npm package). That's the earliest moment HTTP
requests will succeed. If you also see Portless's → https://<app>.localhost…
banner, that's confirmation Portless registered the subdomain — but serve's
own readiness line is the reliable one.
If curl fails (404 / network error), the script exits non-zero — surface the
last ~20 lines of output to the user and don't pretend it's running.
7. Announce both URLs
After the server is up, present two URLs on separate lines so the user can pick whichever they prefer — Portless (HTTPS, named) and direct (HTTP, port). Format exactly like this, no preamble:
Portless: https://v26-4-8-rc-3.localhost:1355
Direct: http://localhost:9091
If Portless wasn't available and the script fell back to plain serve, show
only the Direct line and call out the fallback once.
8. Common follow-ups
- "Stop the server" — kill the background task. Don't
portless proxy stopunless asked; that kills all portless apps including any running dev server. - "Serve a different version" — stop the current background task first (port collision otherwise), then invoke the script with the new version.
- "Switch the backend endpoint" — the served bundle uses the project-root
config.tomlthat was copied at script-start. To change endpoints, editconfig.toml, then restart the script (the in-place copy is one-shot). - Cached extraction — the script reuses
scripts/temp-releases/webui-<VERSION>/if it exists, skipping download. If the user reports stale content, delete that directory and rerun.
9. Out of scope
- Do not modify
scripts/serve-release.shfor one-off behaviour changes — prefer driving via env vars (SERVE_PORT,PORTLESS_PORT). - Do not try to install
serveorportlessfor the user; surface the missing binary and let them choose. - Do not run alongside
pnpm devon the same Portless daemon if you suspect a port conflict betweenserve(9091) and the React dev server (Portless picks free port, but pinnedPORT=9081/etc. from the user could collide). When in doubt, checklsoffirst.