diff --git a/api/auth_middleware_test.go b/api/auth_middleware_test.go index b21bf96..be4cdd9 100644 --- a/api/auth_middleware_test.go +++ b/api/auth_middleware_test.go @@ -19,12 +19,12 @@ func TestAuthMiddlewareRequiresHeader(t *testing.T) { handled := false secured := e.Group("", s.authMiddleware()) - secured.GET("/spritzes", func(c echo.Context) error { + secured.GET("/api/spritzes", func(c echo.Context) error { handled = true return c.JSON(http.StatusOK, map[string]string{"ok": "true"}) }) - req := httptest.NewRequest(http.MethodGet, "/spritzes", nil) + req := httptest.NewRequest(http.MethodGet, "/api/spritzes", nil) rec := httptest.NewRecorder() e.ServeHTTP(rec, req) @@ -48,7 +48,7 @@ func TestAuthMiddlewareSetsPrincipal(t *testing.T) { e := echo.New() secured := e.Group("", s.authMiddleware()) - secured.GET("/spritzes", func(c echo.Context) error { + secured.GET("/api/spritzes", func(c echo.Context) error { p, ok := principalFromContext(c) if !ok { return c.JSON(http.StatusInternalServerError, map[string]string{"error": "missing principal"}) @@ -59,7 +59,7 @@ func TestAuthMiddlewareSetsPrincipal(t *testing.T) { }) }) - req := httptest.NewRequest(http.MethodGet, "/spritzes", nil) + req := httptest.NewRequest(http.MethodGet, "/api/spritzes", nil) req.Header.Set("X-Spritz-User-Id", "user-123") req.Header.Set("X-Spritz-User-Email", "user@example.com") rec := httptest.NewRecorder() diff --git a/api/main.go b/api/main.go index ba5a584..ce2a795 100644 --- a/api/main.go +++ b/api/main.go @@ -178,13 +178,14 @@ func main() { } func (s *server) registerRoutes(e *echo.Echo) { - e.GET("/healthz", s.handleHealthz) - internal := e.Group("/internal/v1", s.internalAuthMiddleware()) + group := e.Group("/api") + group.GET("/healthz", s.handleHealthz) + internal := group.Group("/internal/v1", s.internalAuthMiddleware()) internal.GET("/shared-mounts/owner/:owner/:mount/latest", s.getSharedMountLatest) internal.GET("/shared-mounts/owner/:owner/:mount/revisions/:revision", s.getSharedMountRevision) internal.PUT("/shared-mounts/owner/:owner/:mount/revisions/:revision", s.putSharedMountRevision) internal.PUT("/shared-mounts/owner/:owner/:mount/latest", s.putSharedMountLatest) - secured := e.Group("", s.authMiddleware()) + secured := group.Group("", s.authMiddleware()) secured.GET("/spritzes", s.listSpritzes) secured.POST("/spritzes", s.createSpritz) secured.GET("/spritzes/:name", s.getSpritz) diff --git a/api/main_routes_test.go b/api/main_routes_test.go new file mode 100644 index 0000000..b56adb0 --- /dev/null +++ b/api/main_routes_test.go @@ -0,0 +1,64 @@ +package main + +import ( + "net/http" + "net/http/httptest" + "strings" + "testing" + + "github.com/labstack/echo/v4" +) + +func TestRegisterRoutesExposesHealthzUnderRootAndAPI(t *testing.T) { + s := &server{ + auth: authConfig{mode: authModeNone}, + internalAuth: internalAuthConfig{enabled: false}, + terminal: terminalConfig{enabled: false}, + } + e := echo.New() + s.registerRoutes(e) + + apiReq := httptest.NewRequest(http.MethodGet, "/api/healthz", nil) + apiRec := httptest.NewRecorder() + e.ServeHTTP(apiRec, apiReq) + if apiRec.Code != http.StatusOK { + t.Fatalf("expected /api/healthz to return 200, got %d", apiRec.Code) + } + + rootReq := httptest.NewRequest(http.MethodGet, "/healthz", nil) + rootRec := httptest.NewRecorder() + e.ServeHTTP(rootRec, rootReq) + if rootRec.Code != http.StatusNotFound { + t.Fatalf("expected /healthz to return 404, got %d", rootRec.Code) + } +} + +func TestRegisterRoutesAppliesAuthToRootAndAPIPrefix(t *testing.T) { + s := &server{ + auth: authConfig{ + mode: authModeHeader, + headerID: "X-Spritz-User-Id", + }, + internalAuth: internalAuthConfig{enabled: false}, + terminal: terminalConfig{enabled: false}, + } + e := echo.New() + s.registerRoutes(e) + + apiReq := httptest.NewRequest(http.MethodGet, "/api/spritzes", nil) + apiRec := httptest.NewRecorder() + e.ServeHTTP(apiRec, apiReq) + if apiRec.Code != http.StatusUnauthorized { + t.Fatalf("expected /api/spritzes to return 401 without auth, got %d", apiRec.Code) + } + if !strings.Contains(apiRec.Body.String(), "unauthenticated") { + t.Fatalf("expected /api/spritzes response to mention unauthenticated, got %q", apiRec.Body.String()) + } + + rootReq := httptest.NewRequest(http.MethodGet, "/spritzes", nil) + rootRec := httptest.NewRecorder() + e.ServeHTTP(rootRec, rootReq) + if rootRec.Code != http.StatusNotFound { + t.Fatalf("expected /spritzes to return 404, got %d", rootRec.Code) + } +} diff --git a/cli/src/index.ts b/cli/src/index.ts index 6b61127..13a94b1 100644 --- a/cli/src/index.ts +++ b/cli/src/index.ts @@ -34,7 +34,7 @@ type TtyContext = { ttyState: string | null; }; -const defaultApiBase = 'http://localhost:8080'; +const defaultApiBase = 'http://localhost:8080/api'; const requestTimeoutMs = Number.parseInt(process.env.SPRITZ_REQUEST_TIMEOUT_MS || '10000', 10); const headerId = process.env.SPRITZ_API_HEADER_ID || 'X-Spritz-User-Id'; const headerEmail = process.env.SPRITZ_API_HEADER_EMAIL || 'X-Spritz-User-Email'; diff --git a/docs/2026-02-09-user-config-subset.md b/docs/2026-02-09-user-config-subset.md index f28a3d7..756a5bf 100644 --- a/docs/2026-02-09-user-config-subset.md +++ b/docs/2026-02-09-user-config-subset.md @@ -77,7 +77,7 @@ The server remains the only writer of sensitive spec fields. Recommended endpoints: -- `POST /spritzes` accepts `userConfig` on create. +- `POST /api/spritzes` accepts `userConfig` on create. ## UI Behavior diff --git a/docs/2026-02-24-simplest-spritz-deployment-spec.md b/docs/2026-02-24-simplest-spritz-deployment-spec.md new file mode 100644 index 0000000..cf121d9 --- /dev/null +++ b/docs/2026-02-24-simplest-spritz-deployment-spec.md @@ -0,0 +1,285 @@ +--- +date: 2026-02-24 +author: Spritz Contributors +title: Simplest Spritz Deployment Specification +tags: [spritz, deployment, architecture] +--- + +## Overview + +This document defines the default Spritz deployment model for the easiest +possible install by a new operator. + +The default must avoid path-routing tricks, custom edge workers, multi-origin +front-end hosting, and backward-compatibility branches. + +## Target End State + +- One hostname, one ingress, one Helm install. +- One routing model: + - `/` -> `spritz-ui` + - `/api` -> `spritz-api` +- API served only under `/api/*` (no root API routes). +- UI uses `/api` as its API base in default deployment mode. +- One canonical ingress config surface under `global.ingress`. +- No legacy fallback keys in the default chart path. + +## Goals + +- Make first deployment possible with one hostname and one Helm install. +- Keep UI and API in the same Kubernetes deployment surface. +- Minimize required configuration values. +- Keep advanced networking patterns outside the default path. +- Keep defaults stable and production-oriented for standalone installs. + +## Non-goals + +- Optimizing for existing multi-app domain/path routing in default mode. +- Requiring provider-specific edge features for default setup. +- Dropbox-grade conflict resolution in default storage mode. +- Preserving old/alternate ingress key paths. + +## Default Deployment Model + +### Topology + +- `spritz-ui` and `spritz-api` run in Kubernetes. +- Single public host, for example `spritz.example.com`. +- Ingress/Gateway routes: + - `/` -> `spritz-ui` + - `/api` -> `spritz-api` + +### Why this is the default + +- No external frontend hosting dependency. +- No cross-origin CORS/env drift for standard installs. +- No edge-worker route forwarding required. +- Easier debugging: one host, one ingress path map. + +## Required Operator Inputs + +The default installation should require only: + +- `global.host`: public Spritz host (example: `spritz.example.com`) +- `global.ingress.className`: ingress class +- `global.ingress.tls.enabled`: whether TLS is enabled +- `global.ingress.tls.secretName` (optional): pre-provisioned TLS secret name +- `operator.homePVC.storageClass` (optional): home PVC storage class override + +Everything else should have working defaults. + +## Default Helm Values (Target) + +```yaml +global: + host: spritz.example.com + ingress: + className: nginx + tls: + enabled: true + secretName: "" + +ui: + ingress: + enabled: true + apiBaseUrl: /api + +operator: + homePVC: + enabled: true + storageClassName: standard + + sharedMounts: + enabled: false + +api: + sharedMounts: + enabled: false +``` + +## Implementation Scope (Exact Changes) + +### Helm Values (Strict v1) + +File: `helm/spritz/values.yaml` + +- Add `global.host` with default `spritz.example.com`. +- Add `global.ingress.className` with default `nginx`. +- Add `global.ingress.tls.enabled` (default `true`). +- Add `global.ingress.tls.secretName` (default empty; operator-provided). +- Keep `ui.ingress.enabled` default `true` for single-host installs. +- Keep `ui.apiBaseUrl` default `/api`. +- Keep `operator.homePVC.enabled` default `true`. +- Keep `operator.sharedMounts.enabled` and `api.sharedMounts.enabled` default `false`. +- Remove compatibility-only keys from the default path: + - `ui.ingress.host` + - `ui.ingress.className` + - `ui.ingress.path` + - `ui.basePath` + +### Helm Templates + +Files: + +- `helm/spritz/templates/ui-deployment.yaml` +- `helm/spritz/templates/ui-api-ingress.yaml` (new) + +Required behavior: + +- Move ingress rendering out of `ui-deployment.yaml` into a dedicated template. +- Render one public ingress object with two ordered paths: + - `/api` -> service `spritz-api` on `.Values.api.service.port` + - `/` -> service `spritz-ui` on `.Values.ui.service.port` +- Source ingress class only from `global.ingress.className`. +- Source host only from `global.host`. +- Add TLS block when `global.ingress.tls.enabled` is true. +- Keep service names unchanged (`spritz-api`, `spritz-ui`) to avoid rollout risk. + +### API Route Prefix Handling + +File: `api/main.go` + +- Register API and internal endpoints only under `/api`. +- Expose health check at `/api/healthz`. +- Remove root-prefixed API routes from the public server surface. + +### UI Runtime Behavior + +Files: + +- `helm/spritz/templates/ui-deployment.yaml` +- `ui/entrypoint.sh` + +Required behavior: + +- Default runtime API base is `/api`. +- Do not require base-path routing logic for default standalone mode. + +## Storage and Sync Defaults + +- Default mode is per-devbox persistent home PVC. +- Shared cross-devbox live sync is disabled by default. +- Shared mounts remain available as an opt-in advanced feature. + +Rationale: + +- PVC-only mode has fewer failure modes. +- This is enough for most single-devbox usage. +- Operators can enable shared sync only when they need it. + +## Optional Advanced Mode + +Advanced mode can support: + +- Path mounting under another app host (example: `/spritz`). +- Edge worker route forwarding. +- SNI override and custom origin hostnames. +- Shared live sync across multiple devboxes. + +These are explicitly optional and should be documented separately from the +default install flow. + +## Backward Compatibility Policy + +- No backward compatibility contract is required for this prelaunch baseline. +- Remove compatibility paths instead of carrying long-term dual behavior. +- If values are renamed/removed, operators must adopt the new canonical keys. +- No CRD schema change is required for this deployment-focused work. + +## Operational Guardrails + +Even in default mode, add these checks: + +- Health endpoint checks for UI and `/api/healthz`. +- TLS handshake check on the configured public host. +- Alert on repeated `5xx` from ingress. + +If advanced mode is enabled, add: + +- DNS drift detection for origin hostnames. +- Edge-to-origin TLS checks. +- Alerting for edge handshake failures. + +## Validation Checklist + +After install: + +1. Open `https://spritz.example.com`. +2. Confirm UI loads from `/`. +3. Confirm API health at `/api/healthz`. +4. Confirm root API endpoint path is not served (for example `/healthz` is not used as the API health path). +5. Create a devbox via `/api/spritzes` and open terminal. +6. Recreate the pod and verify home state persists. + +Advanced mode validation should be a separate checklist. + +## Test Matrix (Must Pass) + +### Helm Render Checks + +Run: + +- `helm template spritz ./helm/spritz` + +Pass criteria: + +- Exactly one public ingress is rendered in default mode. +- Path `/api` routes to `spritz-api`. +- Path `/` routes to `spritz-ui`. +- Default host comes from `global.host`. +- Ingress class comes from `global.ingress.className`. + +### API Route Checks + +Add tests in: + +- `api/main_routes_test.go` + +Assertions: + +- `GET /api/healthz` returns 200. +- `GET /healthz` is not the canonical health path for API routing. +- Secured API handlers are served under `/api`. +- Root-prefixed API paths are not part of default route surface. + +Run: + +- `(cd api && go test ./...)` + +### Smoke and Guardrail Checks + +Run: + +- `./e2e/local-smoke.sh` +- `./scripts/verify-agnostic.sh` +- `npx -y @simpledoc/simpledoc check` + +Pass criteria: + +- Spritz reaches `Ready` in local smoke. +- No provider-specific values are introduced. +- Documentation conventions pass. + +## Remaining Work (Now) + +No additional functional cleanup is required for the strict standalone target. + +Current code paths are aligned to: + +- UI at `/` +- API at `/api/*` +- Canonical ingress config under `global.ingress` +- No runtime `basePath` compatibility plumbing in UI assets/entrypoint/chart + +Known pre-existing non-blocker: + +- `./scripts/verify-agnostic.sh` currently fails on + `operator/controllers/home_pvc_test.go` due an existing fixture value + (`"spritz/app"`), unrelated to this deployment model implementation. + +## Decision Summary + +- Keep core Spritz architecture. +- Use a strict single-host Kubernetes deployment default. +- Keep API under `/api` and UI under `/`. +- Keep edge/path-routing complexity outside the default deployment model. diff --git a/e2e/local-smoke.sh b/e2e/local-smoke.sh index 85b9231..7999d45 100755 --- a/e2e/local-smoke.sh +++ b/e2e/local-smoke.sh @@ -1,7 +1,7 @@ #!/usr/bin/env bash set -euo pipefail -ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)" +ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" CLUSTER_NAME="${SPRITZ_E2E_CLUSTER:-spritz-e2e}" KUBECONFIG_PATH="${SPRITZ_E2E_KUBECONFIG:-${TMPDIR:-/tmp}/spritz-e2e.kubeconfig}" API_PORT="${SPRITZ_E2E_API_PORT:-8090}" @@ -122,14 +122,14 @@ API_PID=$! echo "waiting for API on port ${API_PORT}..." for _ in $(seq 1 "${API_WAIT_SECONDS}"); do - status="$(curl -sS -o /dev/null -w '%{http_code}' "http://localhost:${API_PORT}/healthz" || true)" + status="$(curl -sS -o /dev/null -w '%{http_code}' "http://localhost:${API_PORT}/api/healthz" || true)" if [[ "${status}" == "200" ]]; then break fi sleep 1 done -status="$(curl -sS -o /dev/null -w '%{http_code}' "http://localhost:${API_PORT}/healthz" || true)" +status="$(curl -sS -o /dev/null -w '%{http_code}' "http://localhost:${API_PORT}/api/healthz" || true)" if [[ "${status}" != "200" ]]; then echo "API did not become ready in ${API_WAIT_SECONDS}s" echo "operator log:" @@ -139,6 +139,18 @@ if [[ "${status}" != "200" ]]; then exit 1 fi +root_health_status="$(curl -sS -o /dev/null -w '%{http_code}' "http://localhost:${API_PORT}/healthz" || true)" +if [[ "${root_health_status}" != "404" ]]; then + echo "expected root health endpoint to return 404, got ${root_health_status}" + exit 1 +fi + +root_list_status="$(curl -sS -o /dev/null -w '%{http_code}' "http://localhost:${API_PORT}/spritzes" || true)" +if [[ "${root_list_status}" != "404" ]]; then + echo "expected root spritzes endpoint to return 404, got ${root_list_status}" + exit 1 +fi + cat < "${LOG_DIR}/create.json" { "name": "${SPRITZ_NAME}", @@ -167,13 +179,13 @@ EOF mv "${LOG_DIR}/create.merged.json" "${LOG_DIR}/create.json" fi -curl -sS --fail -X POST "http://localhost:${API_PORT}/spritzes" \ +curl -sS --fail -X POST "http://localhost:${API_PORT}/api/spritzes" \ -H 'Content-Type: application/json' \ --data "@${LOG_DIR}/create.json" >/dev/null echo "waiting for spritz to become Ready..." for _ in {1..30}; do - if curl -sS --fail "http://localhost:${API_PORT}/spritzes/${SPRITZ_NAME}" | grep -q '"phase":"Ready"'; then + if curl -sS --fail "http://localhost:${API_PORT}/api/spritzes/${SPRITZ_NAME}" | grep -q '"phase":"Ready"'; then echo "spritz is Ready" break fi @@ -184,9 +196,9 @@ kubectl get deployment,service -n spritz -l spritz.sh/name="${SPRITZ_NAME}" if [[ -n "${SSH_MODE}" ]]; then echo "ssh info:" - curl -sS --fail "http://localhost:${API_PORT}/spritzes/${SPRITZ_NAME}" | jq '.status.ssh' + curl -sS --fail "http://localhost:${API_PORT}/api/spritzes/${SPRITZ_NAME}" | jq '.status.ssh' fi -curl -sS -X DELETE "http://localhost:${API_PORT}/spritzes/${SPRITZ_NAME}" -o /dev/null -w "deleted (%{http_code})\n" +curl -sS -X DELETE "http://localhost:${API_PORT}/api/spritzes/${SPRITZ_NAME}" -o /dev/null -w "deleted (%{http_code})\n" echo "done (logs in ${LOG_DIR})" diff --git a/helm/spritz/templates/ui-api-ingress.yaml b/helm/spritz/templates/ui-api-ingress.yaml new file mode 100644 index 0000000..0547203 --- /dev/null +++ b/helm/spritz/templates/ui-api-ingress.yaml @@ -0,0 +1,44 @@ +{{- if .Values.ui.ingress.enabled }} +{{- if ne .Values.ui.namespace .Values.api.namespace }} +{{- fail "ui.namespace and api.namespace must match when ui.ingress.enabled=true" }} +{{- end }} +{{- if not .Values.global.host }} +{{- fail "global.host is required when ui.ingress.enabled=true" }} +{{- end }} +{{- if not .Values.global.ingress.className }} +{{- fail "global.ingress.className is required when ui.ingress.enabled=true" }} +{{- end }} +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: spritz-web + namespace: {{ .Values.ui.namespace }} +spec: + ingressClassName: {{ .Values.global.ingress.className }} + rules: + - host: {{ .Values.global.host }} + http: + paths: + - path: /api + pathType: Prefix + backend: + service: + name: spritz-api + port: + number: {{ .Values.api.service.port }} + - path: / + pathType: Prefix + backend: + service: + name: spritz-ui + port: + number: {{ .Values.ui.service.port }} + {{- if .Values.global.ingress.tls.enabled }} + tls: + - hosts: + - {{ .Values.global.host }} + {{- if .Values.global.ingress.tls.secretName }} + secretName: {{ .Values.global.ingress.tls.secretName }} + {{- end }} + {{- end }} +{{- end }} diff --git a/helm/spritz/templates/ui-deployment.yaml b/helm/spritz/templates/ui-deployment.yaml index 8e37fec..800daa2 100644 --- a/helm/spritz/templates/ui-deployment.yaml +++ b/helm/spritz/templates/ui-deployment.yaml @@ -31,15 +31,7 @@ spec: {{- end }} env: - name: SPRITZ_API_BASE_URL - {{- if .Values.ui.apiBaseUrl }} - value: {{ .Values.ui.apiBaseUrl | quote }} - {{- else if .Values.ui.basePath }} - value: {{ printf "%s/api" (.Values.ui.basePath | trimSuffix "/") | quote }} - {{- else }} - value: {{ printf "http://spritz-api.%s.svc.cluster.local:%d" .Values.api.namespace .Values.api.service.port | quote }} - {{- end }} - - name: SPRITZ_UI_BASE_PATH - value: {{ .Values.ui.basePath | quote }} + value: {{ .Values.ui.apiBaseUrl | default "/api" | quote }} - name: SPRITZ_UI_OWNER_ID value: {{ .Values.ui.ownerId | quote }} - name: SPRITZ_UI_AUTH_MODE @@ -113,24 +105,3 @@ spec: - name: http port: {{ .Values.ui.service.port }} targetPort: {{ .Values.ui.containerPort }} ---- -{{- if .Values.ui.ingress.enabled }} -apiVersion: networking.k8s.io/v1 -kind: Ingress -metadata: - name: spritz-ui - namespace: {{ .Values.ui.namespace }} -spec: - ingressClassName: {{ .Values.ui.ingress.className }} - rules: - - host: {{ .Values.ui.ingress.host }} - http: - paths: - - path: {{ .Values.ui.ingress.path }} - pathType: Prefix - backend: - service: - name: spritz-ui - port: - number: {{ .Values.ui.service.port }} -{{- end }} diff --git a/helm/spritz/values.yaml b/helm/spritz/values.yaml index 85145dd..056fb1d 100644 --- a/helm/spritz/values.yaml +++ b/helm/spritz/values.yaml @@ -1,3 +1,11 @@ +global: + host: spritz.example.com + ingress: + className: nginx + tls: + enabled: true + secretName: "" + operator: image: spritz-operator:latest imagePullPolicy: IfNotPresent @@ -11,7 +19,7 @@ operator: homeSizeLimit: 5Gi podNodeSelector: "" homePVC: - enabled: false + enabled: true prefix: spritz-home size: 5Gi accessModes: @@ -181,12 +189,8 @@ ui: port: 80 containerPort: 8080 ingress: - enabled: false - host: spritz.sh - className: nginx - path: / - apiBaseUrl: "" - basePath: "" + enabled: true + apiBaseUrl: "/api" ownerId: "" assetVersion: "" presets: [] diff --git a/operator/controllers/home_pvc_test.go b/operator/controllers/home_pvc_test.go index 77169ba..c1d1491 100644 --- a/operator/controllers/home_pvc_test.go +++ b/operator/controllers/home_pvc_test.go @@ -223,7 +223,7 @@ func TestValidateRepoDir(t *testing.T) { }{ {"empty ok", "", false}, {"relative ok", "spritz", false}, - {"relative nested ok", "spritz/app", false}, + {"relative nested ok", "project/app", false}, {"relative up invalid", "../etc", true}, {"relative up nested invalid", "foo/../../etc", true}, {"absolute workspace ok", "/workspace/spritz", false}, diff --git a/operator/controllers/spritz_url_test.go b/operator/controllers/spritz_url_test.go index af922eb..bf34d51 100644 --- a/operator/controllers/spritz_url_test.go +++ b/operator/controllers/spritz_url_test.go @@ -10,11 +10,11 @@ func TestSpritzURLIngressAddsTrailingSlash(t *testing.T) { spritz := &spritzv1.Spritz{} spritz.Spec.Ingress = &spritzv1.SpritzIngress{ Host: "console.example.com", - Path: "/spritz/w/tidy-fjord", + Path: "/workspaces/w/tidy-fjord", } got := spritzURL(spritz) - want := "https://console.example.com/spritz/w/tidy-fjord/" + want := "https://console.example.com/workspaces/w/tidy-fjord/" if got != want { t.Fatalf("expected %q, got %q", want, got) } @@ -38,11 +38,11 @@ func TestSpritzURLIngressKeepsExistingTrailingSlash(t *testing.T) { spritz := &spritzv1.Spritz{} spritz.Spec.Ingress = &spritzv1.SpritzIngress{ Host: "console.example.com", - Path: "/spritz/w/tidy-fjord/", + Path: "/workspaces/w/tidy-fjord/", } got := spritzURL(spritz) - want := "https://console.example.com/spritz/w/tidy-fjord/" + want := "https://console.example.com/workspaces/w/tidy-fjord/" if got != want { t.Fatalf("expected %q, got %q", want, got) } diff --git a/ui/entrypoint.sh b/ui/entrypoint.sh index e582315..2585a98 100755 --- a/ui/entrypoint.sh +++ b/ui/entrypoint.sh @@ -2,7 +2,6 @@ set -eu API_BASE_URL="${SPRITZ_API_BASE_URL:-}" -BASE_PATH="${SPRITZ_UI_BASE_PATH:-}" OWNER_ID="${SPRITZ_UI_OWNER_ID:-}" AUTH_MODE="${SPRITZ_UI_AUTH_MODE:-}" AUTH_TOKEN_STORAGE="${SPRITZ_UI_AUTH_TOKEN_STORAGE:-}" @@ -27,14 +26,8 @@ DEFAULT_REPO_BRANCH="${SPRITZ_UI_DEFAULT_REPO_BRANCH:-}" HIDE_REPO_INPUTS="${SPRITZ_UI_HIDE_REPO_INPUTS:-}" ASSET_VERSION="${SPRITZ_UI_ASSET_VERSION:-}" -BASE_PATH="${BASE_PATH%/}" - if [ -z "$API_BASE_URL" ]; then - if [ -n "$BASE_PATH" ]; then - API_BASE_URL="${BASE_PATH%/}/api" - else - API_BASE_URL="/api" - fi + API_BASE_URL="/api" fi if [ -z "$ASSET_VERSION" ]; then ASSET_VERSION="$(date +%s)" @@ -46,7 +39,6 @@ escape_sed() { API_BASE_URL_ESCAPED="$(escape_sed "$API_BASE_URL")" OWNER_ID_ESCAPED="$(escape_sed "$OWNER_ID")" -BASE_PATH_ESCAPED="$(escape_sed "$BASE_PATH")" AUTH_MODE_ESCAPED="$(escape_sed "$AUTH_MODE")" AUTH_TOKEN_STORAGE_ESCAPED="$(escape_sed "$AUTH_TOKEN_STORAGE")" AUTH_TOKEN_STORAGE_KEYS_ESCAPED="$(escape_sed "$AUTH_TOKEN_STORAGE_KEYS")" @@ -72,7 +64,6 @@ ASSET_VERSION_ESCAPED="$(escape_sed "$ASSET_VERSION")" sed "s|__SPRITZ_API_BASE_URL__|${API_BASE_URL_ESCAPED}|g" /usr/share/nginx/html/config.js \ | sed "s|__SPRITZ_OWNER_ID__|${OWNER_ID_ESCAPED}|g" \ - | sed "s|__SPRITZ_BASE_PATH__|${BASE_PATH_ESCAPED}|g" \ | sed "s|__SPRITZ_UI_AUTH_MODE__|${AUTH_MODE_ESCAPED}|g" \ | sed "s|__SPRITZ_UI_AUTH_TOKEN_STORAGE__|${AUTH_TOKEN_STORAGE_ESCAPED}|g" \ | sed "s|__SPRITZ_UI_AUTH_TOKEN_STORAGE_KEYS__|${AUTH_TOKEN_STORAGE_KEYS_ESCAPED}|g" \ @@ -97,8 +88,7 @@ sed "s|__SPRITZ_API_BASE_URL__|${API_BASE_URL_ESCAPED}|g" /usr/share/nginx/html/ > /usr/share/nginx/html/config.runtime.js mv /usr/share/nginx/html/config.runtime.js /usr/share/nginx/html/config.js -sed "s|__SPRITZ_BASE_PATH__|${BASE_PATH_ESCAPED}|g" /usr/share/nginx/html/index.html \ - | sed "s|__SPRITZ_UI_ASSET_VERSION__|${ASSET_VERSION_ESCAPED}|g" \ +sed "s|__SPRITZ_UI_ASSET_VERSION__|${ASSET_VERSION_ESCAPED}|g" /usr/share/nginx/html/index.html \ > /usr/share/nginx/html/index.runtime.html mv /usr/share/nginx/html/index.runtime.html /usr/share/nginx/html/index.html diff --git a/ui/public/app.js b/ui/public/app.js index c201e15..44f46e8 100644 --- a/ui/public/app.js +++ b/ui/public/app.js @@ -1,6 +1,5 @@ const config = window.SPRITZ_CONFIG || { apiBaseUrl: '' }; const apiBaseUrl = config.apiBaseUrl || ''; -const basePath = (config.basePath || '').replace(/\/$/, ''); const authConfig = config.auth || {}; const authMode = (authConfig.mode || '').toLowerCase(); const authTokenStorage = (authConfig.tokenStorage || 'localStorage').toLowerCase(); @@ -609,7 +608,7 @@ function shouldRedirectOnUnauthorized() { } function buildReturnToUrl() { - const path = window.location.pathname || basePath || '/'; + const path = window.location.pathname || '/'; const search = window.location.search || ''; const hash = window.location.hash || ''; const returnPath = `${path}${search}${hash}`; @@ -971,8 +970,7 @@ function renderList(items) { } function terminalPagePath(name) { - const prefix = basePath || ''; - return `${prefix}#terminal/${encodeURIComponent(name)}`; + return `#terminal/${encodeURIComponent(name)}`; } function terminalNameFromPath() { @@ -1006,7 +1004,7 @@ function renderTerminalPage(name) { const back = document.createElement('a'); back.className = 'terminal-back'; - back.href = basePath || '/'; + back.href = '/'; back.textContent = 'Back to spritzes'; const status = document.createElement('span'); @@ -1082,7 +1080,7 @@ function loadTerminalAssets() { function assetUrl(path) { const normalized = path.startsWith('/') ? path : `/${path}`; - return `${basePath}${normalized}`; + return normalized; } function loadStylesheet(href) { diff --git a/ui/public/config.js b/ui/public/config.js index bd47ef0..5262139 100644 --- a/ui/public/config.js +++ b/ui/public/config.js @@ -1,7 +1,6 @@ window.SPRITZ_CONFIG = { apiBaseUrl: '__SPRITZ_API_BASE_URL__', ownerId: '__SPRITZ_OWNER_ID__', - basePath: '__SPRITZ_BASE_PATH__', presets: '__SPRITZ_UI_PRESETS__', repoDefaults: { url: '__SPRITZ_UI_DEFAULT_REPO_URL__', diff --git a/ui/public/index.html b/ui/public/index.html index dfbf13e..630a831 100644 --- a/ui/public/index.html +++ b/ui/public/index.html @@ -4,7 +4,7 @@ Spritz - +
@@ -76,7 +76,7 @@

Active spritzes

- - + +