feature: Adds backend system and basic API.
This adds the backend API, as well as backend infrastructure, including autostarting and basic communication between the Goroutine + Application.
This commit is contained in:
parent
611d7f24f8
commit
0b73b4aa47
12 changed files with 777 additions and 44 deletions
237
backend/api/backendruntime/runtime.go
Normal file
237
backend/api/backendruntime/runtime.go
Normal file
|
@ -0,0 +1,237 @@
|
|||
package backendruntime
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net"
|
||||
"os"
|
||||
"os/exec"
|
||||
"time"
|
||||
|
||||
"git.terah.dev/imterah/hermes/backendlauncher"
|
||||
"git.terah.dev/imterah/hermes/commonbackend"
|
||||
"github.com/charmbracelet/log"
|
||||
)
|
||||
|
||||
var (
|
||||
AvailableBackends []*Backend
|
||||
RunningBackends map[uint]*Runtime
|
||||
TempDir string
|
||||
)
|
||||
|
||||
func init() {
|
||||
RunningBackends = make(map[uint]*Runtime)
|
||||
}
|
||||
|
||||
func handleCommand(commandType string, command interface{}, sock net.Conn, rtcChan chan interface{}) {
|
||||
bytes, err := commonbackend.Marshal(commandType, command)
|
||||
|
||||
if err != nil {
|
||||
log.Warnf("Failed to marshal message: %s", err.Error())
|
||||
rtcChan <- fmt.Errorf("failed to marshal message: %s", err.Error())
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
if _, err := sock.Write(bytes); err != nil {
|
||||
log.Warnf("Failed to write message: %s", err.Error())
|
||||
rtcChan <- fmt.Errorf("failed to write message: %s", err.Error())
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
_, data, err := commonbackend.Unmarshal(sock)
|
||||
|
||||
if err != nil {
|
||||
log.Warnf("Failed to unmarshal message: %s", err.Error())
|
||||
rtcChan <- fmt.Errorf("failed to unmarshal message: %s", err.Error())
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
rtcChan <- data
|
||||
}
|
||||
|
||||
func (runtime *Runtime) goRoutineHandler() error {
|
||||
log.Debug("Starting up backend runtime")
|
||||
log.Debug("Running socket acquisition")
|
||||
|
||||
logLevel := os.Getenv("HERMES_LOG_LEVEL")
|
||||
|
||||
sockPath, sockListener, err := backendlauncher.GetUnixSocket(TempDir)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
runtime.currentListener = sockListener
|
||||
|
||||
log.Debugf("Acquired unix socket at: %s", sockPath)
|
||||
|
||||
go func() {
|
||||
log.Debug("Creating new goroutine for socket connection handling")
|
||||
|
||||
for {
|
||||
log.Debug("Waiting for Unix socket connections...")
|
||||
sock, err := runtime.currentListener.Accept()
|
||||
|
||||
if err != nil {
|
||||
log.Warnf("Failed to accept Unix socket connection in a backend runtime instance: %s", err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
log.Debug("Recieved connection. Initializing...")
|
||||
|
||||
defer sock.Close()
|
||||
|
||||
for {
|
||||
commandRaw := <-runtime.RuntimeCommands
|
||||
|
||||
log.Debug("Got message from server")
|
||||
|
||||
switch command := commandRaw.(type) {
|
||||
case *commonbackend.AddProxy:
|
||||
handleCommand("addProxy", command, sock, runtime.RuntimeCommands)
|
||||
case *commonbackend.BackendStatusRequest:
|
||||
handleCommand("backendStatusRequest", command, sock, runtime.RuntimeCommands)
|
||||
case *commonbackend.BackendStatusResponse:
|
||||
handleCommand("backendStatusResponse", command, sock, runtime.RuntimeCommands)
|
||||
case *commonbackend.CheckClientParameters:
|
||||
handleCommand("checkClientParameters", command, sock, runtime.RuntimeCommands)
|
||||
case *commonbackend.CheckParametersResponse:
|
||||
handleCommand("checkParametersResponse", command, sock, runtime.RuntimeCommands)
|
||||
case *commonbackend.CheckServerParameters:
|
||||
handleCommand("checkServerParameters", command, sock, runtime.RuntimeCommands)
|
||||
case *commonbackend.ProxyClientConnection:
|
||||
handleCommand("proxyClientConnection", command, sock, runtime.RuntimeCommands)
|
||||
case *commonbackend.ProxyConnectionsRequest:
|
||||
handleCommand("proxyConnectionsRequest", command, sock, runtime.RuntimeCommands)
|
||||
case *commonbackend.ProxyConnectionsResponse:
|
||||
handleCommand("proxyConnectionsResponse", command, sock, runtime.RuntimeCommands)
|
||||
case *commonbackend.ProxyInstanceResponse:
|
||||
handleCommand("proxyInstanceResponse", command, sock, runtime.RuntimeCommands)
|
||||
case *commonbackend.ProxyInstanceRequest:
|
||||
handleCommand("proxyInstanceRequest", command, sock, runtime.RuntimeCommands)
|
||||
case *commonbackend.ProxyStatusRequest:
|
||||
handleCommand("proxyStatusRequest", command, sock, runtime.RuntimeCommands)
|
||||
case *commonbackend.ProxyStatusResponse:
|
||||
handleCommand("proxyStatusResponse", command, sock, runtime.RuntimeCommands)
|
||||
case *commonbackend.RemoveProxy:
|
||||
handleCommand("removeProxy", command, sock, runtime.RuntimeCommands)
|
||||
case *commonbackend.Start:
|
||||
handleCommand("start", command, sock, runtime.RuntimeCommands)
|
||||
case *commonbackend.Stop:
|
||||
handleCommand("stop", command, sock, runtime.RuntimeCommands)
|
||||
default:
|
||||
log.Warnf("Recieved unknown command type from channel: %q", command)
|
||||
runtime.RuntimeCommands <- fmt.Errorf("unknown command recieved")
|
||||
}
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
for {
|
||||
log.Debug("Starting process...")
|
||||
|
||||
ctx := context.Background()
|
||||
|
||||
runtime.currentProcess = exec.CommandContext(ctx, runtime.ProcessPath)
|
||||
runtime.currentProcess.Env = append(runtime.currentProcess.Env, fmt.Sprintf("HERMES_API_SOCK=%s", sockPath), fmt.Sprintf("HERMES_LOG_LEVEL=%s", logLevel))
|
||||
|
||||
runtime.currentProcess.Stdout = runtime.logger
|
||||
runtime.currentProcess.Stderr = runtime.logger
|
||||
|
||||
err := runtime.currentProcess.Run()
|
||||
|
||||
if err != nil {
|
||||
if err, ok := err.(*exec.ExitError); ok {
|
||||
if err.ExitCode() != -1 && err.ExitCode() != 0 {
|
||||
log.Warnf("A backend process died with exit code '%d' and with error '%s'", err.ExitCode(), err.Error())
|
||||
}
|
||||
} else {
|
||||
log.Warnf("A backend process died with error: %s", err.Error())
|
||||
}
|
||||
} else {
|
||||
log.Debug("Process exited gracefully.")
|
||||
}
|
||||
|
||||
if !runtime.isRuntimeRunning {
|
||||
return nil
|
||||
}
|
||||
|
||||
log.Debug("Sleeping 5 seconds, and then restarting process")
|
||||
time.Sleep(5 * time.Second)
|
||||
}
|
||||
}
|
||||
|
||||
func (runtime *Runtime) Start() error {
|
||||
if runtime.isRuntimeRunning {
|
||||
return fmt.Errorf("runtime already running")
|
||||
}
|
||||
|
||||
runtime.RuntimeCommands = make(chan interface{})
|
||||
|
||||
runtime.logger = &writeLogger{
|
||||
Runtime: runtime,
|
||||
}
|
||||
|
||||
go func() {
|
||||
err := runtime.goRoutineHandler()
|
||||
|
||||
if err != nil {
|
||||
log.Errorf("Failed during execution of runtime: %s", err.Error())
|
||||
}
|
||||
}()
|
||||
|
||||
runtime.isRuntimeRunning = true
|
||||
return nil
|
||||
}
|
||||
|
||||
func (runtime *Runtime) Stop() error {
|
||||
if !runtime.isRuntimeRunning {
|
||||
return fmt.Errorf("runtime not running")
|
||||
}
|
||||
|
||||
runtime.isRuntimeRunning = false
|
||||
|
||||
if runtime.currentProcess != nil && runtime.currentProcess.Cancel != nil {
|
||||
err := runtime.currentProcess.Cancel()
|
||||
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to stop process: %s", err.Error())
|
||||
}
|
||||
} else {
|
||||
log.Warn("Failed to kill process (Stop recieved), currentProcess or currentProcess.Cancel is nil")
|
||||
}
|
||||
|
||||
if runtime.currentListener != nil {
|
||||
err := runtime.currentListener.Close()
|
||||
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to stop listener: %s", err.Error())
|
||||
}
|
||||
} else {
|
||||
log.Warn("Failed to kill listener, as the listener is nil")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func NewBackend(path string) *Runtime {
|
||||
return &Runtime{
|
||||
ProcessPath: path,
|
||||
}
|
||||
}
|
||||
|
||||
func Init(backends []*Backend) error {
|
||||
var err error
|
||||
TempDir, err = os.MkdirTemp("", "hermes-sockets-")
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
AvailableBackends = backends
|
||||
|
||||
return nil
|
||||
}
|
34
backend/api/backendruntime/struct.go
Normal file
34
backend/api/backendruntime/struct.go
Normal file
|
@ -0,0 +1,34 @@
|
|||
package backendruntime
|
||||
|
||||
import (
|
||||
"net"
|
||||
"os/exec"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type Backend struct {
|
||||
Name string `validate:"required"`
|
||||
Path string `validate:"required"`
|
||||
}
|
||||
|
||||
type Runtime struct {
|
||||
isRuntimeRunning bool
|
||||
logger *writeLogger
|
||||
currentProcess *exec.Cmd
|
||||
currentListener net.Listener
|
||||
|
||||
ProcessPath string
|
||||
Logs []string
|
||||
RuntimeCommands chan interface{}
|
||||
}
|
||||
|
||||
type writeLogger struct {
|
||||
Runtime *Runtime
|
||||
}
|
||||
|
||||
func (writer writeLogger) Write(p []byte) (n int, err error) {
|
||||
logSplit := strings.Split(string(p), "\n")
|
||||
writer.Runtime.Logs = append(writer.Runtime.Logs, logSplit...)
|
||||
|
||||
return len(p), err
|
||||
}
|
271
backend/api/controllers/v1/backends/create.go
Normal file
271
backend/api/controllers/v1/backends/create.go
Normal file
|
@ -0,0 +1,271 @@
|
|||
package backends
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
"git.terah.dev/imterah/hermes/api/backendruntime"
|
||||
"git.terah.dev/imterah/hermes/api/dbcore"
|
||||
"git.terah.dev/imterah/hermes/api/jwtcore"
|
||||
"git.terah.dev/imterah/hermes/api/permissions"
|
||||
"git.terah.dev/imterah/hermes/commonbackend"
|
||||
"github.com/charmbracelet/log"
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/go-playground/validator/v10"
|
||||
)
|
||||
|
||||
type BackendCreationRequest struct {
|
||||
Token string `validate:"required"`
|
||||
Name string `validate:"required"`
|
||||
Description *string
|
||||
Backend string `validate:"required"`
|
||||
BackendParameters interface{} `json:"connectionDetails" validate:"required"`
|
||||
}
|
||||
|
||||
func CreateBackend(c *gin.Context) {
|
||||
var req BackendCreationRequest
|
||||
|
||||
if err := c.BindJSON(&req); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{
|
||||
"error": fmt.Sprintf("Failed to parse body: %s", err.Error()),
|
||||
})
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
if err := validator.New().Struct(&req); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{
|
||||
"error": fmt.Sprintf("Failed to validate body: %s", err.Error()),
|
||||
})
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
user, err := jwtcore.GetUserFromJWT(req.Token)
|
||||
|
||||
if err != nil {
|
||||
if err.Error() == "token is expired" || err.Error() == "user does not exist" {
|
||||
c.JSON(http.StatusForbidden, gin.H{
|
||||
"error": err.Error(),
|
||||
})
|
||||
|
||||
return
|
||||
} else {
|
||||
log.Warnf("Failed to get user from the provided JWT token: %s", err.Error())
|
||||
|
||||
c.JSON(http.StatusInternalServerError, gin.H{
|
||||
"error": "Failed to parse token",
|
||||
})
|
||||
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if !permissions.UserHasPermission(user, "backends.add") {
|
||||
c.JSON(http.StatusForbidden, gin.H{
|
||||
"error": "Missing permissions",
|
||||
})
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
var backendParameters []byte
|
||||
|
||||
switch parameters := req.BackendParameters.(type) {
|
||||
case string:
|
||||
backendParameters = []byte(parameters)
|
||||
case map[string]interface{}:
|
||||
backendParameters, err = json.Marshal(parameters)
|
||||
|
||||
if err != nil {
|
||||
log.Warnf("Failed to marshal JSON recieved as BackendParameters: %s", err.Error())
|
||||
|
||||
c.JSON(http.StatusInternalServerError, gin.H{
|
||||
"error": "Failed to prepare parameters",
|
||||
})
|
||||
|
||||
return
|
||||
}
|
||||
default:
|
||||
c.JSON(http.StatusBadRequest, gin.H{
|
||||
"error": fmt.Sprintf("Invalid type for connectionDetails (recieved %T)", parameters),
|
||||
})
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
var backendRuntimeFilePath string
|
||||
|
||||
for _, runtime := range backendruntime.AvailableBackends {
|
||||
if runtime.Name == req.Backend {
|
||||
backendRuntimeFilePath = runtime.Path
|
||||
}
|
||||
}
|
||||
|
||||
if backendRuntimeFilePath == "" {
|
||||
c.JSON(http.StatusBadRequest, gin.H{
|
||||
"error": "Unsupported backend recieved",
|
||||
})
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
backend := backendruntime.NewBackend(backendRuntimeFilePath)
|
||||
err = backend.Start()
|
||||
|
||||
if err != nil {
|
||||
log.Warnf("Failed to start backend: %s", err.Error())
|
||||
|
||||
c.JSON(http.StatusInternalServerError, gin.H{
|
||||
"error": "Failed to start backend",
|
||||
})
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
backend.RuntimeCommands <- &commonbackend.CheckServerParameters{
|
||||
Type: "checkServerParameters",
|
||||
Arguments: backendParameters,
|
||||
}
|
||||
|
||||
backendParamCheckResponse := <-backend.RuntimeCommands
|
||||
|
||||
switch responseMessage := backendParamCheckResponse.(type) {
|
||||
case error:
|
||||
log.Warnf("Failed to get response for backend: %s", responseMessage.Error())
|
||||
|
||||
err = backend.Stop()
|
||||
|
||||
if err != nil {
|
||||
log.Warnf("Failed to stop backend: %s", err.Error())
|
||||
}
|
||||
|
||||
c.JSON(http.StatusInternalServerError, gin.H{
|
||||
"error": "Failed to get status response from backend",
|
||||
})
|
||||
|
||||
return
|
||||
case *commonbackend.CheckParametersResponse:
|
||||
if responseMessage.InResponseTo != "checkServerParameters" {
|
||||
log.Errorf("Got illegal response to CheckServerParameters: %s", responseMessage.InResponseTo)
|
||||
|
||||
err = backend.Stop()
|
||||
|
||||
if err != nil {
|
||||
log.Warnf("Failed to stop backend: %s", err.Error())
|
||||
}
|
||||
|
||||
c.JSON(http.StatusInternalServerError, gin.H{
|
||||
"error": "Failed to get status response from backend",
|
||||
})
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
if !responseMessage.IsValid {
|
||||
err = backend.Stop()
|
||||
|
||||
if err != nil {
|
||||
log.Warnf("Failed to stop backend: %s", err.Error())
|
||||
}
|
||||
|
||||
var errorMessage string
|
||||
|
||||
if responseMessage.Message == "" {
|
||||
errorMessage = "Unkown error while trying to parse connectionDetails"
|
||||
} else {
|
||||
errorMessage = fmt.Sprintf("Invalid backend parameters: %s", responseMessage.Message)
|
||||
}
|
||||
|
||||
c.JSON(http.StatusBadRequest, gin.H{
|
||||
"error": errorMessage,
|
||||
})
|
||||
|
||||
return
|
||||
}
|
||||
default:
|
||||
log.Warnf("Got illegal response type for backend: %T", responseMessage)
|
||||
}
|
||||
|
||||
log.Info("Passed backend checks successfully")
|
||||
|
||||
backendInDatabase := &dbcore.Backend{
|
||||
UserID: user.ID,
|
||||
Name: req.Name,
|
||||
Description: req.Description,
|
||||
Backend: req.Backend,
|
||||
BackendParameters: base64.StdEncoding.EncodeToString(backendParameters),
|
||||
}
|
||||
|
||||
if result := dbcore.DB.Create(&backendInDatabase); result.Error != nil {
|
||||
log.Warnf("Failed to create backend: %s", result.Error.Error())
|
||||
|
||||
err = backend.Stop()
|
||||
|
||||
if err != nil {
|
||||
log.Warnf("Failed to stop backend: %s", err.Error())
|
||||
}
|
||||
|
||||
c.JSON(http.StatusInternalServerError, gin.H{
|
||||
"error": "Failed to add backend into database",
|
||||
})
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
backend.RuntimeCommands <- &commonbackend.Start{
|
||||
Type: "start",
|
||||
Arguments: backendParameters,
|
||||
}
|
||||
|
||||
backendStartResponse := <-backend.RuntimeCommands
|
||||
|
||||
switch responseMessage := backendStartResponse.(type) {
|
||||
case error:
|
||||
log.Warnf("Failed to get response for backend: %s", responseMessage.Error())
|
||||
|
||||
err = backend.Stop()
|
||||
|
||||
if err != nil {
|
||||
log.Warnf("Failed to stop backend: %s", err.Error())
|
||||
}
|
||||
|
||||
c.JSON(http.StatusInternalServerError, gin.H{
|
||||
"error": "Failed to get status response from backend",
|
||||
})
|
||||
|
||||
return
|
||||
case *commonbackend.BackendStatusResponse:
|
||||
if !responseMessage.IsRunning {
|
||||
err = backend.Stop()
|
||||
|
||||
if err != nil {
|
||||
log.Warnf("Failed to start backend: %s", err.Error())
|
||||
}
|
||||
|
||||
var errorMessage string
|
||||
|
||||
if responseMessage.Message == "" {
|
||||
errorMessage = "Unkown error while trying to start the backend"
|
||||
} else {
|
||||
errorMessage = fmt.Sprintf("Failed to start backend: %s", responseMessage.Message)
|
||||
}
|
||||
|
||||
c.JSON(http.StatusBadRequest, gin.H{
|
||||
"error": errorMessage,
|
||||
})
|
||||
|
||||
return
|
||||
}
|
||||
default:
|
||||
log.Warnf("Got illegal response type for backend: %T", responseMessage)
|
||||
}
|
||||
|
||||
backendruntime.RunningBackends[backendInDatabase.ID] = backend
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"success": true,
|
||||
})
|
||||
}
|
|
@ -12,7 +12,10 @@ import (
|
|||
"github.com/golang-jwt/jwt/v5"
|
||||
)
|
||||
|
||||
var JWTKey []byte
|
||||
var (
|
||||
JWTKey []byte
|
||||
developmentMode bool
|
||||
)
|
||||
|
||||
func SetupJWT() error {
|
||||
var err error
|
||||
|
@ -32,6 +35,10 @@ func SetupJWT() error {
|
|||
JWTKey = []byte(jwtDataString)
|
||||
}
|
||||
|
||||
if os.Getenv("HERMES_DEVELOPMENT_MODE") != "" {
|
||||
developmentMode = true
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
|
@ -83,8 +90,14 @@ func GetUserFromJWT(token string) (*dbcore.User, error) {
|
|||
}
|
||||
|
||||
func Generate(uid uint) (string, error) {
|
||||
timeMultiplier := 3
|
||||
|
||||
if developmentMode {
|
||||
timeMultiplier = 60 * 24
|
||||
}
|
||||
|
||||
token := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.RegisteredClaims{
|
||||
ExpiresAt: jwt.NewNumericDate(time.Now().Add(3 * time.Minute)),
|
||||
ExpiresAt: jwt.NewNumericDate(time.Now().Add(time.Duration(timeMultiplier) * time.Minute)),
|
||||
IssuedAt: jwt.NewNumericDate(time.Now()),
|
||||
NotBefore: jwt.NewNumericDate(time.Now()),
|
||||
Audience: []string{strconv.Itoa(int(uid))},
|
||||
|
|
|
@ -1,44 +1,34 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"git.terah.dev/imterah/hermes/api/backendruntime"
|
||||
"git.terah.dev/imterah/hermes/api/controllers/v1/backends"
|
||||
"git.terah.dev/imterah/hermes/api/controllers/v1/users"
|
||||
"git.terah.dev/imterah/hermes/api/dbcore"
|
||||
"git.terah.dev/imterah/hermes/api/jwtcore"
|
||||
"git.terah.dev/imterah/hermes/commonbackend"
|
||||
"github.com/charmbracelet/log"
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/urfave/cli/v2"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
func main() {
|
||||
logLevel := os.Getenv("HERMES_LOG_LEVEL")
|
||||
func entrypoint(cCtx *cli.Context) error {
|
||||
developmentMode := false
|
||||
|
||||
if os.Getenv("HERMES_DEVELOPMENT_MODE") != "" {
|
||||
log.Warn("You have development mode enabled. This may weaken security.")
|
||||
developmentMode = true
|
||||
}
|
||||
|
||||
if logLevel != "" {
|
||||
switch logLevel {
|
||||
case "debug":
|
||||
log.SetLevel(log.DebugLevel)
|
||||
|
||||
case "info":
|
||||
log.SetLevel(log.InfoLevel)
|
||||
|
||||
case "warn":
|
||||
log.SetLevel(log.WarnLevel)
|
||||
|
||||
case "error":
|
||||
log.SetLevel(log.ErrorLevel)
|
||||
|
||||
case "fatal":
|
||||
log.SetLevel(log.FatalLevel)
|
||||
}
|
||||
}
|
||||
|
||||
log.Info("Hermes is initializing...")
|
||||
log.Debug("Initializing database and opening it...")
|
||||
|
||||
|
@ -51,13 +41,117 @@ func main() {
|
|||
log.Debug("Running database migrations...")
|
||||
|
||||
if err := dbcore.DoDatabaseMigrations(dbcore.DB); err != nil {
|
||||
log.Fatalf("Failed to run database migrations: %s", err)
|
||||
return fmt.Errorf("Failed to run database migrations: %s", err)
|
||||
}
|
||||
|
||||
log.Debug("Initializing the JWT subsystem...")
|
||||
|
||||
if err := jwtcore.SetupJWT(); err != nil {
|
||||
log.Fatalf("Failed to initialize the JWT subsystem: %s", err.Error())
|
||||
return fmt.Errorf("Failed to initialize the JWT subsystem: %s", err.Error())
|
||||
}
|
||||
|
||||
log.Debug("Initializing the backend subsystem...")
|
||||
|
||||
backendMetadataPath := cCtx.String("backends-path")
|
||||
backendMetadata, err := os.ReadFile(backendMetadataPath)
|
||||
|
||||
if err != nil {
|
||||
return fmt.Errorf("Failed to read backends: %s", err.Error())
|
||||
}
|
||||
|
||||
availableBackends := []*backendruntime.Backend{}
|
||||
err = json.Unmarshal(backendMetadata, &availableBackends)
|
||||
|
||||
if err != nil {
|
||||
return fmt.Errorf("Failed to parse backends: %s", err.Error())
|
||||
}
|
||||
|
||||
for _, backend := range availableBackends {
|
||||
backend.Path = path.Join(filepath.Dir(backendMetadataPath), backend.Path)
|
||||
}
|
||||
|
||||
backendruntime.Init(availableBackends)
|
||||
|
||||
log.Debug("Enumerating backends...")
|
||||
|
||||
backendList := []dbcore.Backend{}
|
||||
|
||||
if err := dbcore.DB.Find(&backendList).Error; err != nil {
|
||||
return fmt.Errorf("Failed to enumerate backends: %s", err.Error())
|
||||
}
|
||||
|
||||
for _, backend := range backendList {
|
||||
log.Infof("Starting up backend #%d: %s", backend.ID, backend.Name)
|
||||
|
||||
var backendRuntimeFilePath string
|
||||
|
||||
for _, runtime := range backendruntime.AvailableBackends {
|
||||
if runtime.Name == backend.Backend {
|
||||
backendRuntimeFilePath = runtime.Path
|
||||
}
|
||||
}
|
||||
|
||||
if backendRuntimeFilePath == "" {
|
||||
log.Errorf("Unsupported backend recieved for ID %d: %s", backend.ID, backend.Backend)
|
||||
continue
|
||||
}
|
||||
|
||||
backendInstance := backendruntime.NewBackend(backendRuntimeFilePath)
|
||||
err = backendInstance.Start()
|
||||
|
||||
if err != nil {
|
||||
log.Errorf("Failed to start backend #%d: %s", backend.ID, err.Error())
|
||||
continue
|
||||
}
|
||||
|
||||
backendParameters, err := base64.StdEncoding.DecodeString(backend.BackendParameters)
|
||||
|
||||
if err != nil {
|
||||
log.Errorf("Failed to decode backend parameters for backend #%d: %s", backend.ID, err.Error())
|
||||
continue
|
||||
}
|
||||
|
||||
backendInstance.RuntimeCommands <- &commonbackend.Start{
|
||||
Type: "start",
|
||||
Arguments: backendParameters,
|
||||
}
|
||||
|
||||
backendStartResponse := <-backendInstance.RuntimeCommands
|
||||
|
||||
switch responseMessage := backendStartResponse.(type) {
|
||||
case error:
|
||||
log.Warnf("Failed to get response for backend #%d: %s", backend.ID, responseMessage.Error())
|
||||
|
||||
err = backendInstance.Stop()
|
||||
|
||||
if err != nil {
|
||||
log.Warnf("Failed to stop backend: %s", err.Error())
|
||||
}
|
||||
|
||||
continue
|
||||
case *commonbackend.BackendStatusResponse:
|
||||
if !responseMessage.IsRunning {
|
||||
err = backendInstance.Stop()
|
||||
|
||||
if err != nil {
|
||||
log.Warnf("Failed to start backend: %s", err.Error())
|
||||
}
|
||||
|
||||
if responseMessage.Message == "" {
|
||||
log.Errorf("Unkown error while trying to start the backend #%d", backend.ID)
|
||||
} else {
|
||||
log.Errorf("Failed to start backend: %s", responseMessage.Message)
|
||||
}
|
||||
|
||||
continue
|
||||
}
|
||||
default:
|
||||
log.Errorf("Got illegal response type for backend #%d: %T", backend.ID, responseMessage)
|
||||
continue
|
||||
}
|
||||
|
||||
backendruntime.RunningBackends[backend.ID] = backendInstance
|
||||
log.Infof("Successfully started backend #%d", backend.ID)
|
||||
}
|
||||
|
||||
log.Debug("Initializing API...")
|
||||
|
@ -97,10 +191,55 @@ func main() {
|
|||
engine.POST("/api/v1/users/remove", users.RemoveUser)
|
||||
engine.POST("/api/v1/users/lookup", users.LookupUser)
|
||||
|
||||
log.Infof("Listening on: %s", listeningAddress)
|
||||
engine.POST("/api/v1/backends/create", backends.CreateBackend)
|
||||
|
||||
log.Infof("Listening on '%s'", listeningAddress)
|
||||
err = engine.Run(listeningAddress)
|
||||
|
||||
if err != nil {
|
||||
log.Fatalf("Error running web server: %s", err.Error())
|
||||
return fmt.Errorf("Error running web server: %s", err.Error())
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func main() {
|
||||
logLevel := os.Getenv("HERMES_LOG_LEVEL")
|
||||
|
||||
if logLevel != "" {
|
||||
switch logLevel {
|
||||
case "debug":
|
||||
log.SetLevel(log.DebugLevel)
|
||||
|
||||
case "info":
|
||||
log.SetLevel(log.InfoLevel)
|
||||
|
||||
case "warn":
|
||||
log.SetLevel(log.WarnLevel)
|
||||
|
||||
case "error":
|
||||
log.SetLevel(log.ErrorLevel)
|
||||
|
||||
case "fatal":
|
||||
log.SetLevel(log.FatalLevel)
|
||||
}
|
||||
}
|
||||
|
||||
app := &cli.App{
|
||||
Name: "hermes",
|
||||
Usage: "port forwarding across boundaries",
|
||||
Flags: []cli.Flag{
|
||||
&cli.StringFlag{
|
||||
Name: "backends-path",
|
||||
Aliases: []string{"b"},
|
||||
Usage: "path to the backend manifest file",
|
||||
Required: true,
|
||||
},
|
||||
},
|
||||
Action: entrypoint,
|
||||
}
|
||||
|
||||
if err := app.Run(os.Args); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue