package handlers import ( "bytes" "database/sql" "fmt" "text/template" "net/http" "os" "strings" "forge-mesh/internal/forge" ) // PublicHandlers serves unauthenticated summon routes. type PublicHandlers struct { DB *sql.DB ArtifactsDir string PublicKeyHex string FleetSecret string Version string InstallTmpl *template.Template DeckURL func(r *http.Request) string } type installData struct { DeckURL string PublicKey string FleetSecret string Pin string Campaign string } func NewPublicHandlers(db *sql.DB, artifactsDir, publicKeyHex string, installTmplPath string) (*PublicHandlers, error) { tmplBytes, err := os.ReadFile(installTmplPath) if err != nil { return nil, fmt.Errorf("read install template: %w", err) } tmpl, err := template.New("install.sh").Parse(string(tmplBytes)) if err != nil { return nil, fmt.Errorf("parse install template: %w", err) } return &PublicHandlers{ DB: db, ArtifactsDir: artifactsDir, PublicKeyHex: publicKeyHex, InstallTmpl: tmpl, DeckURL: func(r *http.Request) string { scheme := "http" if r.TLS != nil || r.Header.Get("X-Forwarded-Proto") == "https" { scheme = "https" } return fmt.Sprintf("%s://%s", scheme, r.Host) }, }, nil } func (h *PublicHandlers) InstallSh(w http.ResponseWriter, r *http.Request) { data := installData{ DeckURL: h.DeckURL(r), PublicKey: h.PublicKeyHex, FleetSecret: h.FleetSecret, Pin: r.URL.Query().Get("pin"), Campaign: r.URL.Query().Get("c"), } var buf bytes.Buffer if err := h.InstallTmpl.Execute(&buf, data); err != nil { http.Error(w, "template error", http.StatusInternalServerError) return } w.Header().Set("Content-Type", "text/x-shellscript; charset=utf-8") w.Write(buf.Bytes()) } func (h *PublicHandlers) GetRedirect(w http.ResponseWriter, r *http.Request) { target := "/install.sh" if q := r.URL.RawQuery; q != "" { target += "?" + q } http.Redirect(w, r, target, http.StatusFound) } func (h *PublicHandlers) LatestBuild(w http.ResponseWriter, r *http.Request) { osName := r.URL.Query().Get("os") arch := r.URL.Query().Get("arch") if osName == "" { osName = "linux" } if arch == "" { arch = "amd64" } build, err := forge.LatestPublic(h.DB, osName, arch) if err != nil { if err == sql.ErrNoRows { http.Error(w, "no public build", http.StatusNotFound) return } http.Error(w, "internal error", http.StatusInternalServerError) return } w.Header().Set("Content-Type", "application/json") fmt.Fprintf(w, `{"id":%q,"os":%q,"arch":%q,"version":%q,"checksum":%q,"signature":%q,"download_url":"/api/v1/public/download/%s"}`, build.ID, build.OS, build.Arch, build.Version, build.Checksum, build.Signature, build.ID) } func (h *PublicHandlers) Download(w http.ResponseWriter, r *http.Request) { id := r.PathValue("id") if strings.Contains(id, "..") { http.Error(w, "bad request", http.StatusBadRequest) return } build, err := forge.GetBuild(h.DB, id) if err != nil { http.Error(w, "not found", http.StatusNotFound) return } if !build.Public { http.Error(w, "forbidden", http.StatusForbidden) return } if build.Path == "" { http.Error(w, "artifact missing", http.StatusNotFound) return } w.Header().Set("Content-Type", "application/octet-stream") w.Header().Set("Content-Disposition", fmt.Sprintf(`attachment; filename="forge-mesh-agent-%s-%s"`, build.OS, build.Arch)) if err := forge.CopyArtifact(build.Path, w); err != nil { http.Error(w, "read error", http.StatusInternalServerError) } }