Repository navigation
Description
Current behavior (read from source, EyevinnOSC/client-ts @ main)
osaas web publish <name> <dir> (packages/cli/src/web/cmd.ts) calls publish() in packages/web/src/publish.ts, which in turn calls createStorageBucket() in packages/web/src/bucket.ts. Concretely, today it:
- Provisions (or reuses) a
minio-minioservice instance per site name via@osaas/client-services'createMinioMinioInstance, with a random root password. - Creates an S3 bucket named after the site and applies a world-readable bucket policy (
s3:GetObject/s3:ListBucketforPrincipal: *). - Uploads every file under
<dir>(recursively, skipping.git) directly to that bucket via theminioSDK'sputObject. - Prints the published URL as
{minio endpoint}/{bucket name}/index.html— note it points directly atindex.html, not at the bucket root, precisely because the bare root 403s (no directory-index support on a raw MinIO bucket). - No list, delete, or status command exists for these sites — the CLI has no memory of what it published, so cleanup/removal is entirely manual (delete the
minio-minioinstance yourself). --syncclears the whole bucket viaremoveBucketContentsbefore re-uploading (no incremental diff).
This is the same backend approach used by the now-deprecated MCP tool publish-static-site (Eyevinn/osaas-ai#1644, closing #1638) — both provision a dedicated minio-minio instance per site with the same limitations. Per specs/my-page.md (Eyevinn/agent-team-osc-dev) §Overview:
OSC already ships a working static-publish path (
@osaas/cli web publish, and its MCP portpublish-static-site...). Feasibility is settled ... My Page is the delta on top: a presentable URL, a proper lifecycle, and a UI, plus fixing two limitations #1638 explicitly left out of scope:
- The URL is
https://{tenant}-{site}.minio-minio.auto.prod.osaas.io/{site}/index.html(tenant prefix + doubled site name)create-my-domainmaps a custom domain to the MinIO instance, not to a bucket sub-path, so the bare apex returns S3AccessDenied.
Proposed change
Add (not replace, see "Migration" below) a My Pages-backed publish path that talks to osaas-deploy-manager's /mypages API family instead of provisioning MinIO directly. Contract, verified by reading Eyevinn/osaas-deploy-manager/src/api_mypages.ts on main directly (there is no specs/my-page-upload-urls-contract.md file inside osaas-deploy-manager itself — that file lives in Eyevinn/agent-team-osc-dev/specs/, cited below for the upload-policy design rationale):
POST {deploy.svc.{env}.osaas.io}/mypages{ name }→201 { id, name, url, status: 'draft' }(name: 3–52 chars, lowercase letters/digits/hyphens, must start/end alphanumeric; globally unique —409if taken;503+Retry-Afterif the tenant'smypagesMinIO instance is still starting up).POST /mypages/{id}/upload-urls{ files: [{ path, size }] }(max 500 files/request, max ~25 MiB/file — seeMAX_PAGE_FILE_SIZE_BYTES/MAX_UPLOAD_FILES_PER_REQUESTinsrc/apps/mypage.ts) →200 { uploads: [{ path, url, method: 'POST', contentType, fields }], expiresAt, maxFileSizeBytes }. Eachuploads[]entry is a presigned S3 POST policy (MinIOpresignedPostPolicy, not a presigned PUT) —fieldsmust be appended to a multipart form in order, with the file appended last under the field namefile, then POSTed tourl.POST /mypages/{id}/publishoptional{ files?: string[], prune?: boolean }→200 { id, name, url, status: 'live', prunedCount? }. This is what fixes the directory-index/403 problem: it server-side copies eachdir/index.htmlto keydir/so directory URLs resolve. Passing the complete file manifest withprune: truedeletes any bucket object not in the manifest (full-replace semantics) — omit both to publish additively.DELETE /mypages/{id}→200, removes the Service/Ingress pair and the bucket.GET /mypages/GET /mypages/{id}→ list/get, for a futureweb pagesorweb listcommand.
Auth: all five endpoints require x-pat-jwt: Bearer <PAT> (verified via fastifyJwt + extractToken: extractPat in api_mypages.ts). The CLI's existing Context class (packages/core/src/context.ts) already carries a personalAccessToken (constructor option or OSC_ACCESS_TOKEN env var) and exposes getPersonalAccessToken() — this is the exact mechanism osaas-ai's My Page MCP tools use (ctx.osc.getPersonalAccessToken()), so no new auth plumbing is needed in the CLI; web publish already builds a Context the same way.
The presigned-POST consideration (CLI ≠ MCP tool)
The MCP tools in osaas-ai (src/mcp/tools/mypages.ts, #1770–#1774) take contentBase64 in the tool schema and do the multipart POST server-side inside the MCP tool handler, because an MCP client has no browser FormData/fetch runtime to hit a presigned URL directly. The CLI does not have this constraint — it runs in Node.js and can call POST /mypages/{id}/upload-urls, then build a FormData/Blob (or use form-data + node-fetch/native fetch) and POST each file directly against the returned presigned target, exactly like the MCP tool does internally but without the base64 round-trip. Read local files as streams directly; no base64 relay needed.
Suggested CLI shape (additive, not a breaking change)
Do not remove or repoint the current web publish in this change. Recommended sequencing, consistent with how the MCP-side deprecation was staged (osaas-ai#1775 deprecates publish-static-site only after its My Page tool replacements shipped; osaas-ai#1776 removal is explicitly gated/deferred):
- Add a new flag or subcommand, e.g.
osaas web publish <name> <dir> --backend mypages(or a distinctosaas pages publish <name> <dir>command family mirroringweb config-create/config-delete), that drivesPOST /mypages→POST /mypages/{id}/upload-urls(+ direct S3 POST per file) →POST /mypages/{id}/publish. - Leave today's
web publish(direct MinIO) as the default/legacy path until the new path is proven in real use. - Once proven, flip the default and only then consider deprecating/removing the legacy path in a follow-up, separately-tracked change — do not couple that decision to this ticket.
Known gap to flag, not silently work around
Per specs/my-page.md D2 (Eyevinn/agent-team-osc-dev), v1 of My Pages explicitly excludes: SPA history fallback, a branded 404, per-site Cache-Control, redirect rules, custom headers, and per-page custom domains. Custom-domain support is tracked separately and unimplemented (Eyevinn/osaas-deploy-manager#1409, closed as a design-needed follow-up, not shipped). If today's CLI users rely on web cdn-create / a custom domain pointed at their published bucket, that is not yet achievable via /mypages — the new path is a strict improvement for the default *.pages.osaas.io / *.pages.liivo.io case only, and should not be pitched as a full replacement until #1409 lands.
Cross-references
- Epic:
Eyevinn/osaas-deploy-manager#1380(My Page — upload a folder of static files, get a live URL on*.pages.osaas.io/*.pages.liivo.io), closed. - Backend contract:
Eyevinn/osaas-deploy-manager/src/api_mypages.ts(main), andEyevinn/agent-team-osc-dev/specs/my-page.md+specs/my-page-upload-urls-contract.mdfor design rationale (why POST-policy over presigned PUT, etc). - Parallel MCP-side migration:
Eyevinn/osaas-ai#1770–#1774(My Page MCP tools, implemented),#1775(deprecatepublish-static-site),#1776(removal, gated). This CLI ticket is the third leg of the same migration — the direct-API/SDK/CLI-consumer surface — alongside the UI (osaas-app, in progress) and MCP (osaas-ai, done). - Custom domains gap:
Eyevinn/osaas-deploy-manager#1409(open design question, not yet implemented).
Lessons for whoever picks this up
- Don't assume the CLI's static-site publish and the MCP tool's are identical just because they share a "MinIO-per-site" description — read
packages/web/src/{publish,bucket}.tsdirectly; the URL construction and bucket-policy details matter for a correct migration. specs/my-page-upload-urls-contract.mdlives inEyevinn/agent-team-osc-dev, not insideosaas-deploy-manager— don't go looking for aspecs/folder in that repo.
Activity
birme commentedon Aug 31, 2026
Scope update (birme, 2026-08-31): replace the default
web publishimplementation with the My Pages backend, rather than the additive/opt-in--backend mypagesflag originally proposed above. The API contract, auth, and error-handling details in the ticket body still apply — only the invocation model changes (no flag, this becomes the one publish path). Implementation in progress.fix(web,cli): replace MinIO publish backend with My Pages (no flag)
birme commentedon Aug 31, 2026
Design corrected on PR #153: `osc web publish` now unconditionally publishes via the My Pages backend (no `--backend` flag, no branching) — matching the scope update above. The old direct-MinIO implementation (`publish.ts`/`bucket.ts`) has been removed as dead code since nothing else in the repo depended on it. CI is green and the PR is ready for review: #153