Files
runtime/apps/runner/pkg/api/server.go
zeekay 065ad13739 deps: prometheus/client_golang → luxfi/metric
Mechanical migration following the beego v2.3.8 fork precedent
(bc7b075). Same exposition surface, zero prometheus/client_golang
in the dep graph.
2026-06-10 18:46:08 -07:00

153 lines
4.0 KiB
Go

// Copyright 2025 Hanzo Industries Inc.
// SPDX-License-Identifier: AGPL-3.0
// @title Runtime Runner API
// @version v0.0.0-dev
// @description Runtime Runner API
// @securityDefinitions.apikey Bearer
// @in header
// @name Authorization
// @description Type "Bearer" followed by a space and an API token.
// @Security Bearer
package api
import (
"context"
"fmt"
"net"
"net/http"
"time"
"github.com/hanzoai/runner/cmd/runner/config"
"github.com/hanzoai/runner/pkg/api/controllers"
"github.com/hanzoai/runner/pkg/api/docs"
"github.com/hanzoai/runner/pkg/api/middlewares"
metric "github.com/luxfi/metric"
"github.com/gin-gonic/gin"
"github.com/gin-gonic/gin/binding"
log "github.com/sirupsen/logrus"
swaggerfiles "github.com/swaggo/files"
ginSwagger "github.com/swaggo/gin-swagger"
)
type ApiServerConfig struct {
ApiPort int
TLSCertFile string
TLSKeyFile string
EnableTLS bool
}
func NewApiServer(config ApiServerConfig) *ApiServer {
return &ApiServer{
apiPort: config.ApiPort,
tlsCertFile: config.TLSCertFile,
tlsKeyFile: config.TLSKeyFile,
enableTLS: config.EnableTLS,
}
}
type ApiServer struct {
apiPort int
tlsCertFile string
tlsKeyFile string
enableTLS bool
httpServer *http.Server
router *gin.Engine
}
func (a *ApiServer) Start() error {
docs.SwaggerInfo.Description = "Runtime Runner API"
docs.SwaggerInfo.Title = "Runtime Runner API"
docs.SwaggerInfo.BasePath = "/"
_, err := net.Dial("tcp", fmt.Sprintf(":%d", a.apiPort))
if err == nil {
return fmt.Errorf("cannot start API server, port %d is already in use", a.apiPort)
}
binding.Validator = new(DefaultValidator)
a.router = gin.New()
a.router.Use(gin.Recovery())
gin.SetMode(gin.ReleaseMode)
if config.GetEnvironment() == "development" {
gin.SetMode(gin.DebugMode)
}
a.router.Use(middlewares.LoggingMiddleware())
a.router.Use(middlewares.ErrorMiddleware())
public := a.router.Group("/")
public.GET("", controllers.HealthCheck)
public.GET("/metrics", gin.WrapH(metric.NewHTTPHandler(metric.DefaultGatherer, metric.HandlerOpts{})))
if config.GetEnvironment() == "development" {
public.GET("/api/*any", ginSwagger.WrapHandler(swaggerfiles.Handler))
}
protected := a.router.Group("/")
protected.Use(middlewares.AuthMiddleware())
sandboxController := protected.Group("/sandboxes")
{
sandboxController.POST("", controllers.Create)
sandboxController.GET("/:sandboxId", controllers.Info)
sandboxController.POST("/:sandboxId/destroy", controllers.Destroy)
sandboxController.POST("/:sandboxId/start", controllers.Start)
sandboxController.POST("/:sandboxId/stop", controllers.Stop)
sandboxController.POST("/:sandboxId/backup", controllers.CreateBackup)
sandboxController.POST("/:sandboxId/resize", controllers.Resize)
sandboxController.DELETE("/:sandboxId", controllers.RemoveDestroyed)
// Add proxy endpoint within the sandbox controller for toolbox
// Using Any() to handle all HTTP methods for the toolbox proxy
sandboxController.Any("/:sandboxId/toolbox/*path", controllers.ProxyRequest)
}
snapshotController := protected.Group("/snapshots")
{
snapshotController.POST("/pull", controllers.PullSnapshot)
snapshotController.POST("/build", controllers.BuildSnapshot)
snapshotController.GET("/exists", controllers.SnapshotExists)
snapshotController.POST("/remove", controllers.RemoveSnapshot)
snapshotController.GET("/logs", controllers.GetBuildLogs)
}
a.httpServer = &http.Server{
Addr: fmt.Sprintf(":%d", a.apiPort),
Handler: a.router,
}
listener, err := net.Listen("tcp", a.httpServer.Addr)
if err != nil {
return err
}
errChan := make(chan error)
go func() {
if a.enableTLS {
// Start HTTPS server
errChan <- a.httpServer.ServeTLS(listener, a.tlsCertFile, a.tlsKeyFile)
} else {
// Start HTTP server
errChan <- a.httpServer.Serve(listener)
}
}()
return <-errChan
}
func (a *ApiServer) Stop() {
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
if err := a.httpServer.Shutdown(ctx); err != nil {
log.Error(err)
}
}