139 lines
3.5 KiB
Go
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)
|
|
}
|
|
}
|