Files
LINUX-AETHERFORGE/internal/api/handlers/public.go
drjones 3678b199d0
Some checks failed
Test / test (push) Has been cancelled
Initial commit: AetherForge Linux (forge-mesh) v0.1.0-dev
2026-07-04 09:31:23 +00:00

139 lines
3.5 KiB
Go

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)
}
}