change how plugin preloading works and change gloomi plugin uploading

This commit is contained in:
Zoe
2025-05-14 20:06:39 -05:00
parent ea40eb98e0
commit c31f070b46
4 changed files with 89 additions and 28 deletions

View File

@@ -55,6 +55,9 @@ type PluginUpload struct {
} }
``` ```
> [!IMPORTANT]
> The Name field can only contain alphanumeric characters and underscores and dashes.
## GLoomI ## GLoomI
GLoomI is the included plugin management interface for GLoom, it utilizes the GLoom RPC, much like you would if you wanted to make your own management interface. By default, GLoomI is configured to use 127.0.0.1 as the hostname, but you con configure it to use a different hostname by setting the `GLOOMI_HOSTNAME` environment variable. The endpoints for GLoomI are as follows: GLoomI is the included plugin management interface for GLoom, it utilizes the GLoom RPC, much like you would if you wanted to make your own management interface. By default, GLoomI is configured to use 127.0.0.1 as the hostname, but you con configure it to use a different hostname by setting the `GLOOMI_HOSTNAME` environment variable. The endpoints for GLoomI are as follows:

View File

@@ -37,14 +37,17 @@ GLoom is a plugin-based web app manager written in Go (perhaps a pico-paas). GLo
zqdgr build:no-gloomi zqdgr build:no-gloomi
``` ```
and make sure to set the `DISABLE_GLOOMI` environment variable to `true` in the `.env` file. and make sure to clear the `PRELOAD_PLUGINS` environment variable and set it to your preferred management interface, or nothing at all if you don't want to use a management interface.
## Configuring ## Configuring
GLoom is configured using environment variables. The following environment variables are supported: GLoom is configured using environment variables. The following environment variables are supported:
- `DEBUG` - Enables debug logging. This is a boolean value, so you can set it to any truthy value to enable debug logging. - `DEBUG` - Enables debug logging. This is a boolean value, so you can set it to any truthy value to enable debug logging.
- `DISABLE_GLOOMI` - Disables the GLoomI plugin. This is a boolean value, so you can set it to any truthy value to disable the GLoomI plugin. - `PRELOAD_PLUGINS` - A json array of plugins to preload. The default value of this is `gloomi`, this is how GLoomI is loaded by default, and how replacement interfaces can be loaded. The format is in json, and the default value is:
```json
[{"file": "gloomi.so", "domains": ["localhost"]}]
```
- `PLUGINS_DIR` - The directory where plugins are stored. This is a string value, so you can set it to any directory path you want. The default value is `plugs`. - `PLUGINS_DIR` - The directory where plugins are stored. This is a string value, so you can set it to any directory path you want. The default value is `plugs`.
## Usage ## Usage

View File

@@ -52,6 +52,7 @@ func (p *GLoomI) RegisterRoutes(router fiber.Router) {
}) })
type UploadRequest struct { type UploadRequest struct {
Name string `form:"name"`
Domains string `form:"domains"` Domains string `form:"domains"`
} }
@@ -65,6 +66,17 @@ func (p *GLoomI) RegisterRoutes(router fiber.Router) {
return c.Status(fiber.StatusBadRequest).SendString("No domains provided") return c.Status(fiber.StatusBadRequest).SendString("No domains provided")
} }
if pluginUpload.Name == "" {
return c.Status(fiber.StatusBadRequest).SendString("No name provided")
}
// check if string is alphanumeric
for _, char := range pluginUpload.Name {
if !((char >= 'a' && char <= 'z') || (char >= 'A' && char <= 'Z') || (char >= '0' && char <= '9') || char == '-' || char == '_') {
return c.Status(fiber.StatusBadRequest).SendString("Invalid name provided")
}
}
domains := make([]string, 0) domains := make([]string, 0)
for _, domain := range strings.Split(pluginUpload.Domains, ",") { for _, domain := range strings.Split(pluginUpload.Domains, ",") {
domains = append(domains, strings.TrimSpace(domain)) domains = append(domains, strings.TrimSpace(domain))
@@ -86,7 +98,7 @@ func (p *GLoomI) RegisterRoutes(router fiber.Router) {
Name string `json:"name"` Name string `json:"name"`
Data []byte `json:"data"` Data []byte `json:"data"`
} }
pluginUploadStruct.Name = pluginFile.Filename pluginUploadStruct.Name = pluginUpload.Name
pluginUploadStruct.Domains = domains pluginUploadStruct.Domains = domains
pluginUploadStruct.Data, err = io.ReadAll(pluginData) pluginUploadStruct.Data, err = io.ReadAll(pluginData)
if err != nil { if err != nil {

93
main.go
View File

@@ -5,6 +5,7 @@ import (
"context" "context"
"database/sql" "database/sql"
"embed" "embed"
"encoding/json"
"fmt" "fmt"
"log/slog" "log/slog"
"math/rand/v2" "math/rand/v2"
@@ -37,11 +38,18 @@ type PluginHost struct {
Domains []string Domains []string
} }
type PreloadPlugin struct {
File string `json:"file"`
Domains []string `json:"domains"`
}
type GLoom struct { type GLoom struct {
// path to the pluginHost binary // path to the pluginHost binary
tmpDir string tmpDir string
pluginDir string pluginDir string
preloadPlugins []PreloadPlugin
plugins libs.SyncMap[string, *PluginHost] plugins libs.SyncMap[string, *PluginHost]
hostMap libs.SyncMap[string, bool] hostMap libs.SyncMap[string, bool]
@@ -95,12 +103,29 @@ func NewGloom(proxyManager *ProxyManager) (*GLoom, error) {
} }
slog.Debug("Wrote pluginHost", "dir", tmpDir+"/pluginHost") slog.Debug("Wrote pluginHost", "dir", tmpDir+"/pluginHost")
var preloadPlugins []PreloadPlugin
preloadPluginsEnv, ok := os.LookupEnv("PRELOAD_PLUGINS")
if ok {
err = json.Unmarshal([]byte(preloadPluginsEnv), &preloadPlugins)
if err != nil {
panic(err)
}
} else {
preloadPlugins = []PreloadPlugin{
{
File: "gloomi.so",
Domains: []string{"localhost"},
},
}
}
gloom := &GLoom{ gloom := &GLoom{
tmpDir: tmpDir, tmpDir: tmpDir,
pluginDir: pluginsDir, pluginDir: pluginsDir,
plugins: libs.SyncMap[string, *PluginHost]{}, preloadPlugins: preloadPlugins,
DB: db, plugins: libs.SyncMap[string, *PluginHost]{},
ProxyManager: proxyManager, DB: db,
ProxyManager: proxyManager,
} }
return gloom, nil return gloom, nil
@@ -109,6 +134,12 @@ func NewGloom(proxyManager *ProxyManager) (*GLoom, error) {
func (gloom *GLoom) LoadInitialPlugins() error { func (gloom *GLoom) LoadInitialPlugins() error {
slog.Debug("Loading initial plugins") slog.Debug("Loading initial plugins")
for _, plugin := range gloom.preloadPlugins {
if err := gloom.RegisterPlugin(filepath.Join(gloom.pluginDir, plugin.File), plugin.File, plugin.Domains); err != nil {
slog.Warn("Failed to register plugin", "pluginPath", plugin.File, "error", err)
}
}
plugins, err := gloom.DB.Query("SELECT path, domains, name FROM plugins") plugins, err := gloom.DB.Query("SELECT path, domains, name FROM plugins")
if err != nil { if err != nil {
return err return err
@@ -272,7 +303,7 @@ func (gloom *GLoom) RegisterPlugin(pluginPath string, name string, domains []str
Domains: domains, Domains: domains,
} }
gloom.plugins.Store(pluginPath, plugHost) gloom.plugins.Store(name, plugHost)
if oldProxy != nil { if oldProxy != nil {
go func() { go func() {
@@ -367,6 +398,32 @@ type PluginUpload struct {
} }
func (rpc *GloomRPC) UploadPlugin(plugin PluginUpload, reply *string) error { func (rpc *GloomRPC) UploadPlugin(plugin PluginUpload, reply *string) error {
for _, preloadPlugin := range rpc.gloom.preloadPlugins {
if plugin.Name == preloadPlugin.File {
*reply = "Plugin is preloaded"
return nil
}
}
if plugin.Name == "" {
*reply = "Plugin name cannot be empty"
return fmt.Errorf("plugin name cannot be empty")
}
for _, char := range plugin.Name {
if !((char >= 'a' && char <= 'z') || (char >= 'A' && char <= 'Z') || (char >= '0' && char <= '9') || char == '-' || char == '_') {
*reply = "Invalid plugin name"
return fmt.Errorf("invalid plugin name")
}
}
for _, domain := range plugin.Domains {
if domain == "" {
*reply = "Domain cannot be empty"
return fmt.Errorf("domain cannot be empty")
}
}
_, err := deploymentLock.Lock(plugin.Name, context.Background()) _, err := deploymentLock.Lock(plugin.Name, context.Background())
if err != nil && err == ErrLocked { if err != nil && err == ErrLocked {
*reply = "Plugin is already being updated" *reply = "Plugin is already being updated"
@@ -492,9 +549,11 @@ func (rpc *GloomRPC) UploadPlugin(plugin PluginUpload, reply *string) error {
} }
func (rpc *GloomRPC) DeletePlugin(pluginName string, reply *string) error { func (rpc *GloomRPC) DeletePlugin(pluginName string, reply *string) error {
if pluginName == "GLoomI" { for _, preloadPlugin := range rpc.gloom.preloadPlugins {
*reply = "GLoomI cannot be deleted since it is not a plugin that is loaded by a user. If you wish to disable GLoomI, set DISABLE_GLOOMI=true in your .env file" if pluginName == preloadPlugin.File {
return nil *reply = "Plugin is preloaded"
return nil
}
} }
_, ok := rpc.gloom.plugins.Load(pluginName) _, ok := rpc.gloom.plugins.Load(pluginName)
@@ -548,22 +607,6 @@ func main() {
gloom.LoadInitialPlugins() gloom.LoadInitialPlugins()
enableGloomi, err := strconv.ParseBool(os.Getenv("ENABLE_GLOOMI"))
if err != nil {
enableGloomi = true
}
if enableGloomi {
hostname := os.Getenv("GLOOMI_HOSTNAME")
if hostname == "" {
hostname = "127.0.0.1"
}
if err := gloom.RegisterPlugin("plugs/gloomi.so", "GLoomI", []string{hostname}); err != nil {
panic("Failed to register GLoomI: " + err.Error())
}
}
fmt.Println("Server running at http://localhost:3000") fmt.Println("Server running at http://localhost:3000")
if err := gloom.ProxyManager.ListenAndServe("127.0.0.1:3000"); err != nil { if err := gloom.ProxyManager.ListenAndServe("127.0.0.1:3000"); err != nil {
panic(err) panic(err)