mirror of
https://github.com/warmbly/warmbly.git
synced 2026-08-19 00:01:14 +00:00
103 lines
2.9 KiB
Go
103 lines
2.9 KiB
Go
package handler
|
|
|
|
import (
|
|
"crypto/sha256"
|
|
"encoding/hex"
|
|
"net/http"
|
|
"strings"
|
|
|
|
"github.com/gin-gonic/gin"
|
|
"github.com/warmbly/warmbly/internal/errx"
|
|
"github.com/warmbly/warmbly/internal/models"
|
|
)
|
|
|
|
func (h *Handler) ServeWorkerInstaller(c *gin.Context) {
|
|
if h.WorkerOrchestrator == nil {
|
|
errx.JSON(c, errx.New(errx.ServiceUnavailable, "worker installer is not configured"))
|
|
return
|
|
}
|
|
script, err := h.WorkerOrchestrator.InstallerScript()
|
|
if err != nil {
|
|
errx.JSON(c, errx.New(errx.Internal, "failed to load worker installer"))
|
|
return
|
|
}
|
|
c.Header("Content-Type", "text/x-shellscript; charset=utf-8")
|
|
c.Header("Cache-Control", "no-cache")
|
|
c.Data(http.StatusOK, "text/x-shellscript; charset=utf-8", script)
|
|
}
|
|
|
|
type workerEnrollmentRequest struct {
|
|
Token string `json:"token"`
|
|
PublicIP string `json:"public_ip,omitempty"`
|
|
}
|
|
|
|
// EnrollWorker exchanges a one-time enrollment token for a complete worker
|
|
// dotenv file. It is intentionally public: the high-entropy one-time token is
|
|
// the credential, and it is consumed atomically before secrets are returned.
|
|
func (h *Handler) EnrollWorker(c *gin.Context) {
|
|
if h.WorkerRepo == nil || h.WorkerOrchestrator == nil {
|
|
errx.JSON(c, errx.New(errx.ServiceUnavailable, "worker enrollment is not configured"))
|
|
return
|
|
}
|
|
|
|
var req workerEnrollmentRequest
|
|
if err := c.ShouldBindJSON(&req); err != nil {
|
|
errx.JSON(c, errx.New(errx.BadRequest, "invalid request body"))
|
|
return
|
|
}
|
|
req.Token = strings.TrimSpace(req.Token)
|
|
if req.Token == "" {
|
|
errx.JSON(c, errx.New(errx.BadRequest, "enrollment token is required"))
|
|
return
|
|
}
|
|
|
|
sum := sha256.Sum256([]byte(req.Token))
|
|
worker, err := h.WorkerRepo.ConsumeEnrollmentToken(c.Request.Context(), hex.EncodeToString(sum[:]))
|
|
if err != nil {
|
|
errx.JSON(c, errx.New(errx.Internal, "failed to consume enrollment token"))
|
|
return
|
|
}
|
|
if worker == nil {
|
|
errx.JSON(c, errx.New(errx.Unauthorized, "enrollment token is invalid or expired"))
|
|
return
|
|
}
|
|
|
|
ip := strings.TrimSpace(req.PublicIP)
|
|
if ip == "" {
|
|
ip = c.ClientIP()
|
|
}
|
|
if ip != "" {
|
|
_ = h.WorkerRepo.RecordEnrolledIP(c.Request.Context(), worker.ID, ip)
|
|
}
|
|
|
|
envFile, _, err := h.WorkerOrchestrator.RenderEnrollmentEnv(c.Request.Context(), worker.ID)
|
|
if err != nil {
|
|
errx.JSON(c, errx.New(errx.Internal, "failed to render worker config"))
|
|
return
|
|
}
|
|
envFile += "WORKER_TIER=" + workerTierLabel(worker) + "\n"
|
|
if ip != "" {
|
|
envFile += "WORKER_PUBLIC_IP=" + ip + "\n"
|
|
}
|
|
if worker.EgressKind != "" {
|
|
envFile += "WORKER_EGRESS_KIND=" + string(worker.EgressKind) + "\n"
|
|
}
|
|
|
|
c.Header("Content-Type", "text/plain; charset=utf-8")
|
|
c.Header("Cache-Control", "no-store")
|
|
c.String(http.StatusOK, envFile)
|
|
}
|
|
|
|
func workerTierLabel(w *models.Worker) string {
|
|
if w == nil {
|
|
return "shared_premium"
|
|
}
|
|
if w.WorkerType == models.WorkerTypeDedicated {
|
|
return "dedicated"
|
|
}
|
|
if w.FreeTier {
|
|
return "shared_free"
|
|
}
|
|
return "shared_premium"
|
|
}
|