diff options
| author | Peter Stone <thepeterstone@gmail.com> | 2026-06-03 22:56:24 +0000 |
|---|---|---|
| committer | Peter Stone <thepeterstone@gmail.com> | 2026-06-03 22:56:24 +0000 |
| commit | b28cfc6ff288d083f6c8e9c055b69bfcadbceccc (patch) | |
| tree | fb5e16fa588494bb400126a3ea774ae7bd34be1a /internal/api | |
| parent | 476a3136a9f55e151ae689cd098795ec865e7850 (diff) | |
feat: add --base-path flag so UI routes API calls to correct prefix
Allows running multiple instances (e.g. /claudomator and /claudomator-oss)
behind a reverse proxy. The server rewrites the base-path meta tag in
index.html at request time rather than baking it into the embed.
Also adds --branch support to deploy script for isolated branch builds
via git worktrees.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Diffstat (limited to 'internal/api')
| -rw-r--r-- | internal/api/server.go | 36 |
1 files changed, 35 insertions, 1 deletions
diff --git a/internal/api/server.go b/internal/api/server.go index 2dcbf77..afb80ad 100644 --- a/internal/api/server.go +++ b/internal/api/server.go @@ -1,9 +1,11 @@ package api import ( + "bytes" "context" "encoding/json" "fmt" + "io/fs" "log/slog" "net/http" "os" @@ -60,6 +62,7 @@ type Server struct { pushStore pushSubscriptionStore dropsDir string llm *llm.Client + basePath string // URL prefix the UI is mounted at, e.g. "/claudomator" } // SetAPIToken configures a bearer token that must be supplied to access the API. @@ -89,6 +92,12 @@ func (s *Server) SetWorkspaceRoot(path string) { s.workspaceRoot = path } +// SetBasePath sets the URL prefix the UI is served under (e.g. "/claudomator-oss"). +// The server rewrites the base-path meta tag in index.html at request time. +func (s *Server) SetBasePath(p string) { + s.basePath = p +} + // Pool returns the executor pool, for graceful shutdown by the caller. func (s *Server) Pool() *executor.Pool { return s.pool } @@ -180,7 +189,7 @@ func (s *Server) routes() { s.mux.HandleFunc("GET /api/drops", s.handleListDrops) s.mux.HandleFunc("GET /api/drops/{filename}", s.handleGetDrop) s.mux.HandleFunc("POST /api/drops", s.handlePostDrop) - s.mux.Handle("GET /", http.FileServerFS(webui.Files)) + s.mux.HandleFunc("GET /", s.handleStaticFiles) } // forwardResults listens on the executor pool's result and started channels and broadcasts via WebSocket. @@ -777,6 +786,31 @@ func (s *Server) handleGetExecution(w http.ResponseWriter, r *http.Request) { writeJSON(w, http.StatusOK, exec) } +// handleStaticFiles serves embedded web UI files. For index.html it rewrites the +// base-path meta tag so the UI knows which API prefix to use. +func (s *Server) handleStaticFiles(w http.ResponseWriter, r *http.Request) { + if r.URL.Path == "/" || r.URL.Path == "/index.html" { + raw, err := fs.ReadFile(webui.Files, "index.html") + if err != nil { + http.Error(w, "not found", http.StatusNotFound) + return + } + basePath := s.basePath + if basePath == "" { + basePath = "/claudomator" + } + rewritten := bytes.ReplaceAll(raw, + []byte(`content="/claudomator"`), + []byte(`content="`+basePath+`"`), + ) + w.Header().Set("Content-Type", "text/html; charset=utf-8") + w.WriteHeader(http.StatusOK) + _, _ = w.Write(rewritten) + return + } + http.FileServerFS(webui.Files).ServeHTTP(w, r) +} + func writeJSON(w http.ResponseWriter, status int, v interface{}) { w.Header().Set("Content-Type", "application/json") w.WriteHeader(status) |
