add plugin hosts to seperate plugins from gloomi
This adds a plugin host that seperates plugins from the gloomi process, allowing for plugins to be unloaded and loaded. This commit also has a fair amount of other changes, nice to haves and bug fixes, some notable changes are: - Highly available reverse proxy from my Flux project - Improved gloomi functionality
This commit is contained in:
@@ -1,2 +1,3 @@
|
|||||||
DISABLE_GLOOMI=false
|
DISABLE_GLOOMI=false
|
||||||
GLOOMI_HOSTNAME=localhost
|
GLOOMI_HOSTNAME=localhost
|
||||||
|
PLUGINS_DIR=plugs
|
||||||
|
|||||||
3
.gitignore
vendored
3
.gitignore
vendored
@@ -1,5 +1,6 @@
|
|||||||
plugs/**
|
plugs
|
||||||
gloom
|
gloom
|
||||||
|
host
|
||||||
**/*.so
|
**/*.so
|
||||||
.env
|
.env
|
||||||
gloom.db
|
gloom.db
|
||||||
@@ -2,6 +2,9 @@
|
|||||||
|
|
||||||
## Plugins
|
## Plugins
|
||||||
|
|
||||||
|
> [!IMPORTANT]
|
||||||
|
> Plugins __must__ be compiled with the same version of Go that GLoom was compiled with. This is a limitation of Golang's plugin system, which is what GLoom uses to load plugins.
|
||||||
|
|
||||||
Plugins are the core of GLoom, they are responsible for handling requests and providing routes. When building a plugin, it's expected that all the assets you need will be bundled with the plugin. However, you are allowed to create directories for assets like file uploads, but we urge you to create a specific directory for assets (ie a database or a public directory) to avoid cluttering the root directory of gloom.
|
Plugins are the core of GLoom, they are responsible for handling requests and providing routes. When building a plugin, it's expected that all the assets you need will be bundled with the plugin. However, you are allowed to create directories for assets like file uploads, but we urge you to create a specific directory for assets (ie a database or a public directory) to avoid cluttering the root directory of gloom.
|
||||||
|
|
||||||
### Plugin Interface
|
### Plugin Interface
|
||||||
@@ -9,7 +12,6 @@ Plugins are the core of GLoom, they are responsible for handling requests and pr
|
|||||||
The `Plugin` interface is the main interface for plugins to implement. It has three methods:
|
The `Plugin` interface is the main interface for plugins to implement. It has three methods:
|
||||||
|
|
||||||
- `Init() (*fiber.Config, error)` - This method is called when the plugin is loaded. It is the function that is initially called when the plugin is loaded.
|
- `Init() (*fiber.Config, error)` - This method is called when the plugin is loaded. It is the function that is initially called when the plugin is loaded.
|
||||||
- `Name() string` - This method returns the name of the plugin.
|
|
||||||
- `RegisterRoutes(router fiber.Router)` - This method is called when the plugin is loaded and is responsible for registering routes to the router.
|
- `RegisterRoutes(router fiber.Router)` - This method is called when the plugin is loaded and is responsible for registering routes to the router.
|
||||||
|
|
||||||
Furthermore, your plugin should export a symbol named `Plugin` that implements the `Plugin` interface. The easiest way to do this in Go is simply
|
Furthermore, your plugin should export a symbol named `Plugin` that implements the `Plugin` interface. The easiest way to do this in Go is simply
|
||||||
|
|||||||
26
README.md
26
README.md
@@ -1,18 +1,12 @@
|
|||||||
# GLoom
|
# GLoom
|
||||||
|
|
||||||
GLoom is a plugin-based web app manager written in Go. GLoom's focus is to provide and simple and efficient way to host micro-web apps easily. Currently, GLoom is a fun little proof of concept, but it suffers from a few issues:
|
GLoom is a plugin-based web app manager written in Go (perhaps a pico-paas). GLoom's focus is to provide and simple and efficient way to host micro-web apps easily. Currently, GLoom is a fun little proof of concept, and now even supports unloading plugins, and gracefully handles plugins that crash, but it is not yet ready for production use, and may not ever be. GLoom is still in early development, so expect some rough edges and bugs and at its heart, GLoom is just a proof of concept, fun to write, and fun to use, but not production ready.
|
||||||
|
|
||||||
- Incorrectly confgiured plugins will cause GLoom to crash
|
|
||||||
- GLoom plugins are cannot be reloaded when they are updated
|
|
||||||
|
|
||||||
As far as I see it, these issues are unfixable currently, Go Plugins __cannot__ be unloaded, and there's no way to separate GLoom plugins from the host proces, thus meaning if a plugin crashes, GLoom will crash as well.
|
|
||||||
|
|
||||||
## Features
|
## Features
|
||||||
|
|
||||||
- Plugin-based architecture
|
- Plugin-based architecture
|
||||||
- RPC-based communication between GLoom and plugins
|
- RPC-based communication between GLoom and plugins
|
||||||
- Built-in plugin management system
|
- Built-in plugin management system
|
||||||
- Built-in plugin management UI
|
|
||||||
|
|
||||||
## Getting Started
|
## Getting Started
|
||||||
|
|
||||||
@@ -24,23 +18,35 @@ As far as I see it, these issues are unfixable currently, Go Plugins __cannot__
|
|||||||
### Installation
|
### Installation
|
||||||
|
|
||||||
1. Clone the repository:
|
1. Clone the repository:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
git clone https://github.com/juls0730/gloom.git
|
git clone https://github.com/juls0730/gloom.git
|
||||||
```
|
```
|
||||||
|
|
||||||
2. Run the project:
|
2. Run the project:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
zqdgr run
|
zqdgr run
|
||||||
```
|
```
|
||||||
|
|
||||||
or if you want to build the project:
|
or if you want to build the project:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
zqdgr build
|
zqdgr build
|
||||||
```
|
```
|
||||||
|
|
||||||
|
and if you want to build the project without the GLoom management Interface (you will not be able to manage plugins wunless you have another interface like GLoomI):
|
||||||
|
```bash
|
||||||
|
zqdgr build:no-gloomi
|
||||||
|
```
|
||||||
|
|
||||||
|
and make sure to set the `DISABLE_GLOOMI` environment variable to `true` in the `.env` file.
|
||||||
|
|
||||||
|
## Configuring
|
||||||
|
|
||||||
|
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.
|
||||||
|
- `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.
|
||||||
|
- `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
|
||||||
|
|
||||||
please read [DOCUMENTATION.md](DOCUMENTATION.md)
|
please read [DOCUMENTATION.md](DOCUMENTATION.md)
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
github.com/andybalholm/brotli v1.1.1 h1:PR2pgnyFznKEugtsUo0xLdDop5SKXd5Qf5ysW+7XdTA=
|
github.com/andybalholm/brotli v1.1.1 h1:PR2pgnyFznKEugtsUo0xLdDop5SKXd5Qf5ysW+7XdTA=
|
||||||
github.com/andybalholm/brotli v1.1.1/go.mod h1:05ib4cKhjx3OQYUY22hTVd34Bc8upXjOLL2rKwwZBoA=
|
github.com/andybalholm/brotli v1.1.1/go.mod h1:05ib4cKhjx3OQYUY22hTVd34Bc8upXjOLL2rKwwZBoA=
|
||||||
|
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||||
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/fxamacker/cbor/v2 v2.7.0 h1:iM5WgngdRBanHcxugY4JySA0nk1wZorNOpTgCMedv5E=
|
github.com/fxamacker/cbor/v2 v2.7.0 h1:iM5WgngdRBanHcxugY4JySA0nk1wZorNOpTgCMedv5E=
|
||||||
github.com/fxamacker/cbor/v2 v2.7.0/go.mod h1:pxXPTn3joSm21Gbwsv0w9OSA2y1HFR9qXEeXQVeNoDQ=
|
github.com/fxamacker/cbor/v2 v2.7.0/go.mod h1:pxXPTn3joSm21Gbwsv0w9OSA2y1HFR9qXEeXQVeNoDQ=
|
||||||
github.com/gofiber/fiber/v3 v3.0.0-beta.4 h1:KzDSavvhG7m81NIsmnu5l3ZDbVS4feCidl4xlIfu6V0=
|
github.com/gofiber/fiber/v3 v3.0.0-beta.4 h1:KzDSavvhG7m81NIsmnu5l3ZDbVS4feCidl4xlIfu6V0=
|
||||||
@@ -19,6 +21,10 @@ github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWE
|
|||||||
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||||
github.com/philhofer/fwd v1.1.3-0.20240916144458-20a13a1f6b7c h1:dAMKvw0MlJT1GshSTtih8C2gDs04w8dReiOGXrGLNoY=
|
github.com/philhofer/fwd v1.1.3-0.20240916144458-20a13a1f6b7c h1:dAMKvw0MlJT1GshSTtih8C2gDs04w8dReiOGXrGLNoY=
|
||||||
github.com/philhofer/fwd v1.1.3-0.20240916144458-20a13a1f6b7c/go.mod h1:RqIHx9QI14HlwKwm98g9Re5prTQ6LdeRQn+gXJFxsJM=
|
github.com/philhofer/fwd v1.1.3-0.20240916144458-20a13a1f6b7c/go.mod h1:RqIHx9QI14HlwKwm98g9Re5prTQ6LdeRQn+gXJFxsJM=
|
||||||
|
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||||
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
|
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
|
||||||
|
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||||
github.com/tinylib/msgp v1.2.5 h1:WeQg1whrXRFiZusidTQqzETkRpGjFjcIhW6uqWH09po=
|
github.com/tinylib/msgp v1.2.5 h1:WeQg1whrXRFiZusidTQqzETkRpGjFjcIhW6uqWH09po=
|
||||||
github.com/tinylib/msgp v1.2.5/go.mod h1:ykjzy2wzgrlvpDCRc4LA8UXy6D8bzMSuAF3WD57Gok0=
|
github.com/tinylib/msgp v1.2.5/go.mod h1:ykjzy2wzgrlvpDCRc4LA8UXy6D8bzMSuAF3WD57Gok0=
|
||||||
github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
|
github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
|
||||||
@@ -29,6 +35,7 @@ github.com/valyala/tcplisten v1.0.0 h1:rBHj/Xf+E1tRGZyWIWwJDiRY0zc1Js+CV5DqwacVS
|
|||||||
github.com/valyala/tcplisten v1.0.0/go.mod h1:T0xQ8SeCZGxckz9qRXTfG43PvQ/mcWh7FwZEA7Ioqkc=
|
github.com/valyala/tcplisten v1.0.0/go.mod h1:T0xQ8SeCZGxckz9qRXTfG43PvQ/mcWh7FwZEA7Ioqkc=
|
||||||
github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM=
|
github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM=
|
||||||
github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg=
|
github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg=
|
||||||
|
github.com/xyproto/randomstring v1.0.5 h1:YtlWPoRdgMu3NZtP45drfy1GKoojuR7hmRcnhZqKjWU=
|
||||||
github.com/xyproto/randomstring v1.0.5/go.mod h1:rgmS5DeNXLivK7YprL0pY+lTuhNQW3iGxZ18UQApw/E=
|
github.com/xyproto/randomstring v1.0.5/go.mod h1:rgmS5DeNXLivK7YprL0pY+lTuhNQW3iGxZ18UQApw/E=
|
||||||
golang.org/x/crypto v0.31.0 h1:ihbySMvVjLAeSH1IbfcRTkD/iNscyz8rGzjF/E5hV6U=
|
golang.org/x/crypto v0.31.0 h1:ihbySMvVjLAeSH1IbfcRTkD/iNscyz8rGzjF/E5hV6U=
|
||||||
golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk=
|
golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk=
|
||||||
@@ -40,3 +47,5 @@ golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA=
|
|||||||
golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||||
golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo=
|
golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo=
|
||||||
golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
|
golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
|
||||||
|
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||||
|
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
|
|||||||
@@ -21,11 +21,9 @@ func (p *GLoomI) Init() (*fiber.Config, error) {
|
|||||||
return nil, fmt.Errorf("failed to connect to Gloom RPC server: %w", err)
|
return nil, fmt.Errorf("failed to connect to Gloom RPC server: %w", err)
|
||||||
}
|
}
|
||||||
p.client = client
|
p.client = client
|
||||||
return nil, nil
|
return &fiber.Config{
|
||||||
}
|
BodyLimit: 1024 * 1024 * 1024 * 5, // 5GB
|
||||||
|
}, nil
|
||||||
func (p *GLoomI) Name() string {
|
|
||||||
return "GLoomI"
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type PluginData struct {
|
type PluginData struct {
|
||||||
@@ -95,12 +93,13 @@ func (p *GLoomI) RegisterRoutes(router fiber.Router) {
|
|||||||
return c.Status(fiber.StatusInternalServerError).SendString("Failed to read plugin file: " + err.Error())
|
return c.Status(fiber.StatusInternalServerError).SendString("Failed to read plugin file: " + err.Error())
|
||||||
}
|
}
|
||||||
|
|
||||||
err = p.client.Call("GloomRPC.UploadPlugin", pluginUploadStruct, nil)
|
reply := new(string)
|
||||||
|
err = p.client.Call("GloomRPC.UploadPlugin", pluginUploadStruct, reply)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return c.Status(fiber.StatusInternalServerError).SendString("Failed to upload plugin: " + err.Error())
|
return c.Status(fiber.StatusInternalServerError).SendString("Failed to upload plugin: " + err.Error())
|
||||||
}
|
}
|
||||||
|
|
||||||
return c.Status(fiber.StatusOK).SendString("Plugin uploaded successfully")
|
return c.Status(fiber.StatusOK).SendString(*reply)
|
||||||
})
|
})
|
||||||
|
|
||||||
apiRouter.Delete("/plugins/:pluginName", func(c fiber.Ctx) error {
|
apiRouter.Delete("/plugins/:pluginName", func(c fiber.Ctx) error {
|
||||||
|
|||||||
27
go.mod
27
go.mod
@@ -2,27 +2,14 @@ module github.com/juls0730/gloom
|
|||||||
|
|
||||||
go 1.23.4
|
go 1.23.4
|
||||||
|
|
||||||
require github.com/mattn/go-sqlite3 v1.14.24
|
require (
|
||||||
|
github.com/joho/godotenv v1.5.1
|
||||||
|
github.com/mattn/go-sqlite3 v1.14.24
|
||||||
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/andybalholm/brotli v1.1.1 // indirect
|
github.com/mattn/go-colorable v0.1.14 // indirect
|
||||||
github.com/fxamacker/cbor/v2 v2.7.0 // indirect
|
|
||||||
github.com/gofiber/fiber/v3 v3.0.0-beta.4 // indirect
|
|
||||||
github.com/gofiber/schema v1.2.0 // indirect
|
|
||||||
github.com/gofiber/utils/v2 v2.0.0-beta.7 // indirect
|
|
||||||
github.com/google/uuid v1.6.0 // indirect
|
|
||||||
github.com/joho/godotenv v1.5.1 // indirect
|
|
||||||
github.com/klauspost/compress v1.17.11 // indirect
|
|
||||||
github.com/mattn/go-colorable v0.1.13 // indirect
|
|
||||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||||
github.com/philhofer/fwd v1.1.3-0.20240916144458-20a13a1f6b7c // indirect
|
github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d // indirect
|
||||||
github.com/tinylib/msgp v1.2.5 // indirect
|
golang.org/x/sys v0.29.0 // indirect
|
||||||
github.com/valyala/bytebufferpool v1.0.0 // indirect
|
|
||||||
github.com/valyala/fasthttp v1.58.0 // indirect
|
|
||||||
github.com/valyala/tcplisten v1.0.0 // indirect
|
|
||||||
github.com/x448/float16 v0.8.4 // indirect
|
|
||||||
golang.org/x/crypto v0.31.0 // indirect
|
|
||||||
golang.org/x/net v0.31.0 // indirect
|
|
||||||
golang.org/x/sys v0.28.0 // indirect
|
|
||||||
golang.org/x/text v0.21.0 // indirect
|
|
||||||
)
|
)
|
||||||
|
|||||||
45
go.sum
45
go.sum
@@ -1,46 +1,13 @@
|
|||||||
github.com/andybalholm/brotli v1.1.1 h1:PR2pgnyFznKEugtsUo0xLdDop5SKXd5Qf5ysW+7XdTA=
|
|
||||||
github.com/andybalholm/brotli v1.1.1/go.mod h1:05ib4cKhjx3OQYUY22hTVd34Bc8upXjOLL2rKwwZBoA=
|
|
||||||
github.com/fxamacker/cbor/v2 v2.7.0 h1:iM5WgngdRBanHcxugY4JySA0nk1wZorNOpTgCMedv5E=
|
|
||||||
github.com/fxamacker/cbor/v2 v2.7.0/go.mod h1:pxXPTn3joSm21Gbwsv0w9OSA2y1HFR9qXEeXQVeNoDQ=
|
|
||||||
github.com/gofiber/fiber/v3 v3.0.0-beta.4 h1:KzDSavvhG7m81NIsmnu5l3ZDbVS4feCidl4xlIfu6V0=
|
|
||||||
github.com/gofiber/fiber/v3 v3.0.0-beta.4/go.mod h1:/WFUoHRkZEsGHyy2+fYcdqi109IVOFbVwxv1n1RU+kk=
|
|
||||||
github.com/gofiber/schema v1.2.0 h1:j+ZRrNnUa/0ZuWrn/6kAtAufEr4jCJ+JuTURAMxNSZg=
|
|
||||||
github.com/gofiber/schema v1.2.0/go.mod h1:YYwj01w3hVfaNjhtJzaqetymL56VW642YS3qZPhuE6c=
|
|
||||||
github.com/gofiber/utils/v2 v2.0.0-beta.7 h1:NnHFrRHvhrufPABdWajcKZejz9HnCWmT/asoxRsiEbQ=
|
|
||||||
github.com/gofiber/utils/v2 v2.0.0-beta.7/go.mod h1:J/M03s+HMdZdvhAeyh76xT72IfVqBzuz/OJkrMa7cwU=
|
|
||||||
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
|
||||||
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
|
||||||
github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0=
|
github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0=
|
||||||
github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
|
github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
|
||||||
github.com/klauspost/compress v1.17.11 h1:In6xLpyWOi1+C7tXUUWv2ot1QvBjxevKAaI6IXrJmUc=
|
github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE=
|
||||||
github.com/klauspost/compress v1.17.11/go.mod h1:pMDklpSncoRMuLFrf1W9Ss9KT+0rH90U12bZKk7uwG0=
|
github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8=
|
||||||
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
|
|
||||||
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
|
|
||||||
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
|
|
||||||
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
||||||
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||||
github.com/mattn/go-sqlite3 v1.14.24 h1:tpSp2G2KyMnnQu99ngJ47EIkWVmliIizyZBfPrBWDRM=
|
github.com/mattn/go-sqlite3 v1.14.24 h1:tpSp2G2KyMnnQu99ngJ47EIkWVmliIizyZBfPrBWDRM=
|
||||||
github.com/mattn/go-sqlite3 v1.14.24/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
|
github.com/mattn/go-sqlite3 v1.14.24/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
|
||||||
github.com/philhofer/fwd v1.1.3-0.20240916144458-20a13a1f6b7c h1:dAMKvw0MlJT1GshSTtih8C2gDs04w8dReiOGXrGLNoY=
|
github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d h1:5PJl274Y63IEHC+7izoQE9x6ikvDFZS2mDVS3drnohI=
|
||||||
github.com/philhofer/fwd v1.1.3-0.20240916144458-20a13a1f6b7c/go.mod h1:RqIHx9QI14HlwKwm98g9Re5prTQ6LdeRQn+gXJFxsJM=
|
github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE=
|
||||||
github.com/tinylib/msgp v1.2.5 h1:WeQg1whrXRFiZusidTQqzETkRpGjFjcIhW6uqWH09po=
|
|
||||||
github.com/tinylib/msgp v1.2.5/go.mod h1:ykjzy2wzgrlvpDCRc4LA8UXy6D8bzMSuAF3WD57Gok0=
|
|
||||||
github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
|
|
||||||
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
|
|
||||||
github.com/valyala/fasthttp v1.58.0 h1:GGB2dWxSbEprU9j0iMJHgdKYJVDyjrOwF9RE59PbRuE=
|
|
||||||
github.com/valyala/fasthttp v1.58.0/go.mod h1:SYXvHHaFp7QZHGKSHmoMipInhrI5StHrhDTYVEjK/Kw=
|
|
||||||
github.com/valyala/tcplisten v1.0.0 h1:rBHj/Xf+E1tRGZyWIWwJDiRY0zc1Js+CV5DqwacVSA8=
|
|
||||||
github.com/valyala/tcplisten v1.0.0/go.mod h1:T0xQ8SeCZGxckz9qRXTfG43PvQ/mcWh7FwZEA7Ioqkc=
|
|
||||||
github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM=
|
|
||||||
github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg=
|
|
||||||
github.com/xyproto/randomstring v1.0.5/go.mod h1:rgmS5DeNXLivK7YprL0pY+lTuhNQW3iGxZ18UQApw/E=
|
|
||||||
golang.org/x/crypto v0.31.0 h1:ihbySMvVjLAeSH1IbfcRTkD/iNscyz8rGzjF/E5hV6U=
|
|
||||||
golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk=
|
|
||||||
golang.org/x/net v0.31.0 h1:68CPQngjLL0r2AlUKiSxtQFKvzRVbnzLwMUn5SzcLHo=
|
|
||||||
golang.org/x/net v0.31.0/go.mod h1:P4fl1q7dY2hnZFxEk4pPSkDHF+QqjitcnDjUQyMM+pM=
|
|
||||||
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
|
||||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA=
|
golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU=
|
||||||
golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||||
golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo=
|
|
||||||
golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
|
|
||||||
|
|||||||
662
main.go
662
main.go
@@ -1,48 +1,66 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bufio"
|
||||||
|
"context"
|
||||||
"database/sql"
|
"database/sql"
|
||||||
"embed"
|
"embed"
|
||||||
"fmt"
|
"fmt"
|
||||||
"log/slog"
|
"log/slog"
|
||||||
|
"math/rand/v2"
|
||||||
"net"
|
"net"
|
||||||
|
"net/http"
|
||||||
|
"net/http/httputil"
|
||||||
"net/rpc"
|
"net/rpc"
|
||||||
|
"net/url"
|
||||||
"os"
|
"os"
|
||||||
"plugin"
|
"os/exec"
|
||||||
|
"path"
|
||||||
|
"path/filepath"
|
||||||
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
"sync"
|
||||||
|
"sync/atomic"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/gofiber/fiber/v3"
|
|
||||||
"github.com/gofiber/fiber/v3/middleware/logger"
|
|
||||||
"github.com/joho/godotenv"
|
"github.com/joho/godotenv"
|
||||||
"github.com/juls0730/gloom/libs"
|
"github.com/juls0730/gloom/libs"
|
||||||
_ "github.com/mattn/go-sqlite3"
|
_ "github.com/mattn/go-sqlite3"
|
||||||
)
|
)
|
||||||
|
|
||||||
//go:embed schema.sql
|
//go:embed schema.sql host
|
||||||
var embeddedAssets embed.FS
|
var embeddedAssets embed.FS
|
||||||
|
|
||||||
type Plugin interface {
|
type PluginHost struct {
|
||||||
Init() (*fiber.Config, error)
|
UnixSocket string
|
||||||
RegisterRoutes(app fiber.Router)
|
Process *os.Process
|
||||||
Name() string
|
Domains []string
|
||||||
}
|
|
||||||
|
|
||||||
type PluginInstance struct {
|
|
||||||
Plugin Plugin
|
|
||||||
Name string
|
|
||||||
Path string
|
|
||||||
Router *fiber.App
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type GLoom struct {
|
type GLoom struct {
|
||||||
Plugins []PluginInstance
|
// path to the pluginHost binary
|
||||||
domainMap libs.SyncMap[string, *PluginInstance]
|
tmpDir string
|
||||||
|
pluginDir string
|
||||||
|
|
||||||
|
plugins libs.SyncMap[string, *PluginHost]
|
||||||
|
hostMap libs.SyncMap[string, bool]
|
||||||
|
|
||||||
DB *sql.DB
|
DB *sql.DB
|
||||||
fiber *fiber.App
|
ProxyManager *ProxyManager
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewGloom(app *fiber.App) (*GLoom, error) {
|
func NewGloom(proxyManager *ProxyManager) (*GLoom, error) {
|
||||||
if err := os.MkdirAll("plugs", 0755); err != nil {
|
pluginsDir := os.Getenv("PLUGINS_DIR")
|
||||||
|
if pluginsDir == "" {
|
||||||
|
pluginsDir = "plugs"
|
||||||
|
}
|
||||||
|
|
||||||
|
pluginsDir, err := filepath.Abs(pluginsDir)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := os.MkdirAll(pluginsDir, 0755); err != nil {
|
||||||
if os.IsNotExist(err) {
|
if os.IsNotExist(err) {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
@@ -63,18 +81,35 @@ func NewGloom(app *fiber.App) (*GLoom, error) {
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pluginHost, err := embeddedAssets.ReadFile("host")
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
tmpDir, err := os.MkdirTemp(os.TempDir(), "gloom")
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if err = os.WriteFile(tmpDir+"/pluginHost", pluginHost, 0755); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
slog.Debug("Wrote pluginHost", "dir", tmpDir+"/pluginHost")
|
||||||
|
|
||||||
gloom := &GLoom{
|
gloom := &GLoom{
|
||||||
Plugins: []PluginInstance{},
|
tmpDir: tmpDir,
|
||||||
domainMap: libs.SyncMap[string, *PluginInstance]{},
|
pluginDir: pluginsDir,
|
||||||
|
plugins: libs.SyncMap[string, *PluginHost]{},
|
||||||
DB: db,
|
DB: db,
|
||||||
fiber: app,
|
ProxyManager: proxyManager,
|
||||||
}
|
}
|
||||||
|
|
||||||
return gloom, nil
|
return gloom, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (gloom *GLoom) LoadInitialPlugins() error {
|
func (gloom *GLoom) LoadInitialPlugins() error {
|
||||||
plugins, err := gloom.DB.Query("SELECT path, domains FROM plugins")
|
slog.Debug("Loading initial plugins")
|
||||||
|
|
||||||
|
plugins, err := gloom.DB.Query("SELECT path, domains, name FROM plugins")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -84,15 +119,16 @@ func (gloom *GLoom) LoadInitialPlugins() error {
|
|||||||
var plugin struct {
|
var plugin struct {
|
||||||
Path string
|
Path string
|
||||||
Domain string
|
Domain string
|
||||||
|
Name string
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := plugins.Scan(&plugin.Path, &plugin.Domain); err != nil {
|
if err := plugins.Scan(&plugin.Path, &plugin.Domain, &plugin.Name); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
domains := strings.Split(plugin.Domain, ",")
|
domains := strings.Split(plugin.Domain, ",")
|
||||||
|
|
||||||
if err := gloom.RegisterPlugin(plugin.Path, domains); err != nil {
|
if err := gloom.RegisterPlugin(plugin.Path, plugin.Name, domains); err != nil {
|
||||||
slog.Warn("Failed to register plugin", "pluginPath", plugin.Path, "error", err)
|
slog.Warn("Failed to register plugin", "pluginPath", plugin.Path, "error", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -100,59 +136,177 @@ func (gloom *GLoom) LoadInitialPlugins() error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (gloom *GLoom) RegisterPlugin(pluginPath string, domains []string) error {
|
var ErrLocked = fmt.Errorf("item is locked")
|
||||||
|
|
||||||
|
type MutexLock[T comparable] struct {
|
||||||
|
mu sync.Mutex
|
||||||
|
deployed map[T]context.CancelFunc
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewMutexLock[T comparable]() *MutexLock[T] {
|
||||||
|
return &MutexLock[T]{
|
||||||
|
deployed: make(map[T]context.CancelFunc),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (dt *MutexLock[T]) Lock(id T, ctx context.Context) (context.Context, error) {
|
||||||
|
dt.mu.Lock()
|
||||||
|
defer dt.mu.Unlock()
|
||||||
|
|
||||||
|
// Check if the object is locked
|
||||||
|
if _, exists := dt.deployed[id]; exists {
|
||||||
|
slog.Debug("Item is locked", "id", id)
|
||||||
|
return nil, ErrLocked
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create a context that can be cancelled
|
||||||
|
ctx, cancel := context.WithCancel(ctx)
|
||||||
|
|
||||||
|
// Store the cancel function
|
||||||
|
dt.deployed[id] = cancel
|
||||||
|
|
||||||
|
return ctx, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (dt *MutexLock[T]) Unlock(id T) {
|
||||||
|
dt.mu.Lock()
|
||||||
|
defer dt.mu.Unlock()
|
||||||
|
|
||||||
|
// Remove the app from deployed tracking
|
||||||
|
if cancel, exists := dt.deployed[id]; exists {
|
||||||
|
// Cancel the context
|
||||||
|
cancel()
|
||||||
|
// Remove from map
|
||||||
|
delete(dt.deployed, id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var deploymentLock = NewMutexLock[string]()
|
||||||
|
|
||||||
|
func (gloom *GLoom) RegisterPlugin(pluginPath string, name string, domains []string) (err error) {
|
||||||
slog.Info("Registering plugin", "pluginPath", pluginPath, "domains", domains)
|
slog.Info("Registering plugin", "pluginPath", pluginPath, "domains", domains)
|
||||||
|
|
||||||
p, err := plugin.Open(pluginPath)
|
pathStr := strconv.FormatUint(uint64(rand.Uint64()), 16)
|
||||||
|
socketPath := path.Join(gloom.tmpDir, pathStr+".sock")
|
||||||
|
controlPath := path.Join(gloom.tmpDir, pathStr+"-control.sock")
|
||||||
|
|
||||||
|
slog.Debug("Starting pluginHost", "pluginPath", pluginPath, "socketPath", socketPath)
|
||||||
|
|
||||||
|
processPath := path.Join(gloom.tmpDir, "pluginHost")
|
||||||
|
args := []string{pluginPath, socketPath, controlPath}
|
||||||
|
slog.Debug("Starting pluginHost", "args", args)
|
||||||
|
|
||||||
|
cmd := exec.Command(processPath, args...)
|
||||||
|
if err := cmd.Start(); err != nil {
|
||||||
|
return fmt.Errorf("failed to start pluginHost: %w", err)
|
||||||
|
}
|
||||||
|
process := cmd.Process
|
||||||
|
|
||||||
|
for {
|
||||||
|
_, err := os.Stat(controlPath)
|
||||||
|
if err == nil {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
time.Sleep(100 * time.Millisecond)
|
||||||
|
}
|
||||||
|
|
||||||
|
conn, err := net.DialTimeout("unix", controlPath, 5*time.Second)
|
||||||
|
if err != nil {
|
||||||
|
_ = process.Signal(os.Interrupt)
|
||||||
|
return fmt.Errorf("failed to connect to plugin control socket: %w", err)
|
||||||
|
}
|
||||||
|
defer conn.Close()
|
||||||
|
|
||||||
|
reader := bufio.NewReader(conn)
|
||||||
|
readTimeout := time.After(30 * time.Second)
|
||||||
|
|
||||||
|
select {
|
||||||
|
case <-readTimeout:
|
||||||
|
_ = process.Signal(os.Interrupt)
|
||||||
|
return fmt.Errorf("timed out waiting for plugin status")
|
||||||
|
default:
|
||||||
|
status, err := reader.ReadString('\n')
|
||||||
|
if err != nil {
|
||||||
|
_ = process.Signal(os.Interrupt)
|
||||||
|
return fmt.Errorf("error reading plugin status: %w", err)
|
||||||
|
}
|
||||||
|
status = strings.TrimSpace(status)
|
||||||
|
|
||||||
|
if status == "ready" {
|
||||||
|
slog.Debug("PluginHost ported ready", "pluginPath", pluginPath)
|
||||||
|
break
|
||||||
|
} else if strings.HasPrefix(status, "Error: ") {
|
||||||
|
errorMessage := strings.TrimPrefix(status, "Error: ")
|
||||||
|
_ = process.Signal(os.Interrupt)
|
||||||
|
return fmt.Errorf("plugin reported error: %s", errorMessage)
|
||||||
|
} else {
|
||||||
|
_ = process.Signal(os.Interrupt)
|
||||||
|
return fmt.Errorf("received unknown status from plugin: %s", status)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
proxy, err := NewDeploymentProxy(socketPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
symbol, err := p.Lookup("Plugin")
|
var oldProxy *Proxy
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
pluginLib, ok := symbol.(Plugin)
|
|
||||||
if !ok {
|
|
||||||
return fmt.Errorf("plugin is not a Plugin")
|
|
||||||
}
|
|
||||||
|
|
||||||
fiberConfig, err := pluginLib.Init()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if fiberConfig == nil {
|
|
||||||
fiberConfig = &fiber.Config{}
|
|
||||||
}
|
|
||||||
|
|
||||||
router := fiber.New(*fiberConfig)
|
|
||||||
pluginLib.RegisterRoutes(router)
|
|
||||||
|
|
||||||
pluginInstance := PluginInstance{
|
|
||||||
Plugin: pluginLib,
|
|
||||||
Name: pluginLib.Name(),
|
|
||||||
Path: pluginPath,
|
|
||||||
Router: router,
|
|
||||||
}
|
|
||||||
|
|
||||||
gloom.Plugins = append(gloom.Plugins, pluginInstance)
|
|
||||||
pluginPtr := &gloom.Plugins[len(gloom.Plugins)-1]
|
|
||||||
for _, domain := range domains {
|
for _, domain := range domains {
|
||||||
gloom.domainMap.Store(domain, pluginPtr)
|
var ok bool
|
||||||
|
oldProxy, ok = gloom.ProxyManager.Load(domain)
|
||||||
|
// there can only be one in a set of domains. If a is the domains already attached to the proxy, and b is
|
||||||
|
// a superset of a, but the new members of b are not in any other set, then we can be sure there is just one
|
||||||
|
if ok {
|
||||||
|
break
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// this will replace the old proxy with a new one
|
||||||
|
for _, domain := range domains {
|
||||||
|
gloom.ProxyManager.AddProxy(domain, proxy)
|
||||||
|
}
|
||||||
|
|
||||||
|
plugHost := &PluginHost{
|
||||||
|
UnixSocket: socketPath,
|
||||||
|
Process: process,
|
||||||
|
Domains: domains,
|
||||||
|
}
|
||||||
|
|
||||||
|
gloom.plugins.Store(pluginPath, plugHost)
|
||||||
|
|
||||||
|
if oldProxy != nil {
|
||||||
|
go func() {
|
||||||
|
oldProxy.GracefulShutdown(nil)
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
|
||||||
|
slog.Debug("Registered plugin", "pluginPath", pluginPath, "domains", domains)
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (gloom *GLoom) DeletePlugin(pluginName string) {
|
// removes plugin from proxy and kills the process
|
||||||
gloom.domainMap.Range(func(domain string, plugin *PluginInstance) bool {
|
func (gloom *GLoom) DeletePlugin(pluginName string) error {
|
||||||
if plugin.Name == pluginName {
|
slog.Debug("Deleting plugin", "pluginName", pluginName)
|
||||||
gloom.domainMap.Delete(domain)
|
|
||||||
|
plug, ok := gloom.plugins.Load(pluginName)
|
||||||
|
if !ok {
|
||||||
|
return fmt.Errorf("plugin not found")
|
||||||
}
|
}
|
||||||
return true
|
|
||||||
})
|
for _, domain := range plug.Domains {
|
||||||
|
gloom.ProxyManager.RemoveDeployment(domain)
|
||||||
|
gloom.hostMap.Store(domain, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
plug.Process.Signal(os.Interrupt)
|
||||||
|
for _, domain := range plug.Domains {
|
||||||
|
gloom.ProxyManager.RemoveDeployment(domain)
|
||||||
|
}
|
||||||
|
|
||||||
|
gloom.plugins.Delete(pluginName)
|
||||||
|
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (gloom *GLoom) StartRPCServer() error {
|
func (gloom *GLoom) StartRPCServer() error {
|
||||||
@@ -192,23 +346,17 @@ type PluginData struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (rpc *GloomRPC) ListPlugins(_ struct{}, reply *[]PluginData) error {
|
func (rpc *GloomRPC) ListPlugins(_ struct{}, reply *[]PluginData) error {
|
||||||
var plugins []PluginData = make([]PluginData, 0)
|
var pluginsArray []PluginData = make([]PluginData, 0, len(rpc.gloom.plugins.Keys()))
|
||||||
var domains map[string][]string = make(map[string][]string)
|
rpc.gloom.plugins.Range(func(key string, value *PluginHost) (shouldContinue bool) {
|
||||||
|
pluginData := PluginData{
|
||||||
rpc.gloom.domainMap.Range(func(domain string, plugin *PluginInstance) bool {
|
Name: key,
|
||||||
domains[plugin.Name] = append(domains[plugin.Name], domain)
|
Domains: value.Domains,
|
||||||
|
}
|
||||||
|
pluginsArray = append(pluginsArray, pluginData)
|
||||||
return true
|
return true
|
||||||
})
|
})
|
||||||
|
|
||||||
for _, plugin := range rpc.gloom.Plugins {
|
*reply = pluginsArray
|
||||||
var pluginDataStruct PluginData
|
|
||||||
pluginDataStruct.Name = plugin.Name
|
|
||||||
pluginDataStruct.Domains = domains[plugin.Name]
|
|
||||||
|
|
||||||
plugins = append(plugins, pluginDataStruct)
|
|
||||||
}
|
|
||||||
|
|
||||||
*reply = plugins
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -219,85 +367,143 @@ type PluginUpload struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (rpc *GloomRPC) UploadPlugin(plugin PluginUpload, reply *string) error {
|
func (rpc *GloomRPC) UploadPlugin(plugin PluginUpload, reply *string) error {
|
||||||
|
_, err := deploymentLock.Lock(plugin.Name, context.Background())
|
||||||
|
if err != nil && err == ErrLocked {
|
||||||
|
*reply = "Plugin is already being updated"
|
||||||
|
return fmt.Errorf("plugin is already being updated")
|
||||||
|
}
|
||||||
|
defer deploymentLock.Unlock(plugin.Name)
|
||||||
|
|
||||||
slog.Info("Uploading plugin", "plugin", plugin.Name, "domains", plugin.Domains)
|
slog.Info("Uploading plugin", "plugin", plugin.Name, "domains", plugin.Domains)
|
||||||
|
pluginPath, err := filepath.Abs(fmt.Sprintf("plugs/%s", plugin.Name))
|
||||||
|
if err != nil {
|
||||||
|
*reply = "Plugin upload failed"
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
var plugExists bool
|
var plugExists bool
|
||||||
rpc.gloom.DB.QueryRow("SELECT path FROM plugins WHERE path = ?", "plugs/"+plugin.Name).Scan(&plugExists)
|
// TODO: make name a consistent identifier
|
||||||
|
slog.Debug("Checking if plugin exists", "pluginPath", pluginPath, "pluginName", plugin.Name)
|
||||||
|
rpc.gloom.DB.QueryRow("SELECT 1 FROM plugins WHERE name = ?", plugin.Name).Scan(&plugExists)
|
||||||
|
slog.Debug("Plugin exists", "pluginExists", plugExists)
|
||||||
|
|
||||||
var domains []string
|
var domains []string
|
||||||
|
var newDomains []string
|
||||||
if plugExists {
|
if plugExists {
|
||||||
// if plugin exists, we need to not check for domains that this plug has already registered, but instead check for new domains this plugin is registering
|
// if plugin exists, we need to not check for domains that this plug has already registered, but instead check for new domains this plugin is registering
|
||||||
domains = make([]string, 0)
|
domainsMap := map[string]bool{}
|
||||||
var existingDomains []string
|
newDomains = make([]string, 0)
|
||||||
err := rpc.gloom.DB.QueryRow("SELECT domains FROM plugins WHERE path = ?", "plugs/"+plugin.Name).Scan(&existingDomains)
|
removedDomains := make([]string, 0)
|
||||||
|
var sqlDomains string
|
||||||
|
err := rpc.gloom.DB.QueryRow("SELECT domains FROM plugins WHERE name = ?", plugin.Name).Scan(&sqlDomains)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, domain := range existingDomains {
|
// domains that are already related to the plugin
|
||||||
var found bool
|
existingDomains := strings.Split(sqlDomains, ",")
|
||||||
for _, domainToCheck := range plugin.Domains {
|
|
||||||
if domain == domainToCheck {
|
for _, domain := range plugin.Domains {
|
||||||
found = true
|
domainsMap[domain] = true
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if !found {
|
|
||||||
domains = append(domains, domain)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
found = false
|
for _, domain := range existingDomains {
|
||||||
|
if _, ok := domainsMap[domain]; !ok {
|
||||||
|
removedDomains = append(removedDomains, domain)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, domain := range removedDomains {
|
||||||
|
slog.Debug("Removing domain from plugin", "domain", domain, "plugin", plugin.Name)
|
||||||
|
rpc.gloom.ProxyManager.RemoveDeployment(domain)
|
||||||
|
}
|
||||||
|
|
||||||
|
for domain := range domainsMap {
|
||||||
|
if exists, _ := rpc.gloom.hostMap.Load(domain); !exists {
|
||||||
|
newDomains = append(newDomains, domain)
|
||||||
|
}
|
||||||
|
|
||||||
|
slog.Debug("Adding domain to plugin", "domain", domain, "plugin", plugin.Name)
|
||||||
|
domains = append(domains, domain)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
domains = plugin.Domains
|
domains = plugin.Domains
|
||||||
|
newDomains = plugin.Domains
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, domain := range domains {
|
for _, domain := range newDomains {
|
||||||
_, ok := rpc.gloom.domainMap.Load(domain)
|
_, ok := rpc.gloom.hostMap.Load(domain)
|
||||||
if ok {
|
if ok {
|
||||||
*reply = fmt.Sprintf("Domain %s already exists", domain)
|
*reply = fmt.Sprintf("Domain %s already exists", domain)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
plugsDir := "plugs"
|
||||||
|
|
||||||
|
if os.Getenv("PLUGINS_DIR") != "" {
|
||||||
|
plugsDir = os.Getenv("PLUGINS_DIR")
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err := os.Stat(plugsDir); os.IsNotExist(err) {
|
||||||
|
if err := os.Mkdir(plugsDir, 0755); err != nil {
|
||||||
|
*reply = "Plugin upload failed"
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// regardless of if plugin exists or not, we'll upload the file since this could be an update to an existing plugin
|
// regardless of if plugin exists or not, we'll upload the file since this could be an update to an existing plugin
|
||||||
if err := os.WriteFile(fmt.Sprintf("plugs/%s", plugin.Name), plugin.Data, 0644); err != nil {
|
if err := os.WriteFile(pluginPath, plugin.Data, 0644); err != nil {
|
||||||
|
*reply = "Plugin upload failed"
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Println("Plugin uploaded successfully")
|
fmt.Println("Plugin uploaded successfully")
|
||||||
|
|
||||||
|
if err := rpc.gloom.RegisterPlugin(pluginPath, plugin.Name, domains); err != nil {
|
||||||
|
os.Remove(pluginPath)
|
||||||
|
slog.Warn("Failed to register uplaoded plguin", "pluginPath", pluginPath, "error", err)
|
||||||
|
*reply = fmt.Sprintf("Plugin upload failed: %v", err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if !plugExists {
|
||||||
|
_, err = rpc.gloom.DB.Exec("INSERT INTO plugins (path, name, domains) VALUES (?, ?, ?)", pluginPath, plugin.Name, strings.Join(plugin.Domains, ","))
|
||||||
|
if err != nil {
|
||||||
|
*reply = fmt.Sprintf("Plugin upload failed: %v", err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
_, err = rpc.gloom.DB.Exec("UPDATE plugins SET domains = ?, path = ? WHERE name = ?", strings.Join(plugin.Domains, ","), pluginPath, plugin.Name)
|
||||||
|
if err != nil {
|
||||||
|
*reply = fmt.Sprintf("Plugin upload failed: %v", err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if plugExists {
|
if plugExists {
|
||||||
// exit out early otherwise we risk creating multiple of the same plugin and causing undefined behavior
|
// exit out early otherwise we risk creating multiple of the same plugin and causing undefined behavior
|
||||||
*reply = "Plugin updated successfully"
|
*reply = "Plugin updated successfully"
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := rpc.gloom.RegisterPlugin("plugs/"+plugin.Name, plugin.Domains); err != nil {
|
|
||||||
slog.Warn("Failed to register uplaoded plguin", "pluginPath", "plugs/"+plugin.Name, "error", err)
|
|
||||||
*reply = "Plugin upload failed"
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
rpc.gloom.DB.Exec("INSERT INTO plugins (path, domains) VALUES (?, ?)", "plugs/"+plugin.Name, strings.Join(plugin.Domains, ","))
|
|
||||||
*reply = "Plugin uploaded successfully"
|
*reply = "Plugin uploaded successfully"
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (rpc *GloomRPC) DeletePlugin(pluginName string, reply *string) error {
|
func (rpc *GloomRPC) DeletePlugin(pluginName string, reply *string) error {
|
||||||
var targetPlugin PluginInstance
|
if pluginName == "GLoomI" {
|
||||||
for _, plugin := range rpc.gloom.Plugins {
|
*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 plugin.Name == pluginName {
|
return nil
|
||||||
targetPlugin = plugin
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err := rpc.gloom.DB.Exec("DELETE FROM plugins WHERE path = ?", targetPlugin.Path)
|
_, ok := rpc.gloom.plugins.Load(pluginName)
|
||||||
if err != nil {
|
if !ok {
|
||||||
*reply = "Plugin not found"
|
*reply = "Plugin not found"
|
||||||
return err
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
err = os.Remove(targetPlugin.Path)
|
_, err := rpc.gloom.DB.Exec("DELETE FROM plugins WHERE name = ?", pluginName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
*reply = "Plugin not found"
|
*reply = "Plugin not found"
|
||||||
return err
|
return err
|
||||||
@@ -316,54 +522,230 @@ func init() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
app := fiber.New(fiber.Config{
|
debug, err := strconv.ParseBool(os.Getenv("DEBUG"))
|
||||||
BodyLimit: 1024 * 1024 * 1024 * 5, // 5GB
|
if err != nil {
|
||||||
})
|
debug = false
|
||||||
|
}
|
||||||
|
|
||||||
app.Use(logger.New(logger.Config{
|
level := slog.LevelInfo
|
||||||
CustomTags: map[string]logger.LogFunc{
|
if debug {
|
||||||
"app": func(output logger.Buffer, c fiber.Ctx, data *logger.Data, extraParam string) (int, error) {
|
level = slog.LevelDebug
|
||||||
output.WriteString(c.Host())
|
}
|
||||||
return len(output.Bytes()), nil
|
|
||||||
},
|
|
||||||
},
|
|
||||||
Format: " ${time} | ${status} | ${latency} | ${ip} | ${method} | ${app} | ${path}\n",
|
|
||||||
}))
|
|
||||||
|
|
||||||
gloom, err := NewGloom(app)
|
logger := slog.New(slog.NewTextHandler(os.Stdout, &slog.HandlerOptions{Level: level}))
|
||||||
|
slog.SetDefault(logger)
|
||||||
|
|
||||||
|
proxyManager := NewProxyManager()
|
||||||
|
|
||||||
|
gloom, err := NewGloom(proxyManager)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
app.Use(func(c fiber.Ctx) error {
|
|
||||||
host := c.Host()
|
|
||||||
if plugin, ok := gloom.domainMap.Load(host); ok {
|
|
||||||
plugin.Router.Handler()(c.RequestCtx())
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return c.Status(404).SendString("Domain not found")
|
|
||||||
})
|
|
||||||
|
|
||||||
if err := gloom.StartRPCServer(); err != nil {
|
if err := gloom.StartRPCServer(); err != nil {
|
||||||
panic("Failed to start RPC server: " + err.Error())
|
panic("Failed to start RPC server: " + err.Error())
|
||||||
}
|
}
|
||||||
|
|
||||||
gloom.LoadInitialPlugins()
|
gloom.LoadInitialPlugins()
|
||||||
|
|
||||||
if os.Getenv("DISABLE_GLOOMI") != "true" {
|
enableGloomi, err := strconv.ParseBool(os.Getenv("ENABLE_GLOOMI"))
|
||||||
|
if err != nil {
|
||||||
|
enableGloomi = true
|
||||||
|
}
|
||||||
|
|
||||||
|
if enableGloomi {
|
||||||
hostname := os.Getenv("GLOOMI_HOSTNAME")
|
hostname := os.Getenv("GLOOMI_HOSTNAME")
|
||||||
if hostname == "" {
|
if hostname == "" {
|
||||||
hostname = "127.0.0.1"
|
hostname = "127.0.0.1"
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := gloom.RegisterPlugin("plugs/gloomi.so", []string{hostname}); err != nil {
|
if err := gloom.RegisterPlugin("plugs/gloomi.so", "GLoomI", []string{hostname}); err != nil {
|
||||||
panic("Failed to register GLoomI: " + err.Error())
|
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 := app.Listen(":3000"); err != nil {
|
if err := gloom.ProxyManager.ListenAndServe("127.0.0.1:3000"); err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// this is the object that oversees the proxying of requests to the correct deployment
|
||||||
|
type ProxyManager struct {
|
||||||
|
libs.SyncMap[string, *Proxy]
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewProxyManager() *ProxyManager {
|
||||||
|
return &ProxyManager{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (proxyManager *ProxyManager) ListenAndServe(host string) error {
|
||||||
|
slog.Info("Proxy server starting", "url", host)
|
||||||
|
if err := http.ListenAndServe(host, proxyManager); err != nil && err != http.ErrServerClosed {
|
||||||
|
return fmt.Errorf("failed to start proxy server: %v", err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Stops forwarding traffic to a deployment
|
||||||
|
func (proxyManager *ProxyManager) RemoveDeployment(host string) {
|
||||||
|
slog.Info("Removing proxy", "host", host)
|
||||||
|
proxyManager.Delete(host)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Starts forwarding traffic to a deployment. The deployment must be ready to recieve requests before this is called.
|
||||||
|
func (proxyManager *ProxyManager) AddProxy(host string, proxy *Proxy) {
|
||||||
|
slog.Debug("Adding proxy", "host", host)
|
||||||
|
proxyManager.Store(host, proxy)
|
||||||
|
}
|
||||||
|
|
||||||
|
// This function is responsible for taking an http request and forwarding it to the correct deployment
|
||||||
|
func (proxyManager *ProxyManager) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||||
|
start := time.Now()
|
||||||
|
host := r.Host
|
||||||
|
path := r.URL.Path
|
||||||
|
method := r.Method
|
||||||
|
ip := getClientIP(r)
|
||||||
|
|
||||||
|
slog.Debug("Proxying request", "host", host, "path", path, "method", method, "ip", ip)
|
||||||
|
proxy, ok := proxyManager.Load(host)
|
||||||
|
if !ok {
|
||||||
|
http.Error(w, "Not found", http.StatusNotFound)
|
||||||
|
logRequest(host, http.StatusNotFound, time.Since(start), ip, method, path)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create a custom ResponseWriter to capture the status code
|
||||||
|
rw := &ResponseWriterInterceptor{ResponseWriter: w, statusCode: http.StatusOK}
|
||||||
|
|
||||||
|
proxy.proxyFunc.ServeHTTP(rw, r)
|
||||||
|
|
||||||
|
latency := time.Since(start)
|
||||||
|
statusCode := rw.statusCode
|
||||||
|
|
||||||
|
logRequest(host, statusCode, latency, ip, method, path)
|
||||||
|
}
|
||||||
|
|
||||||
|
// getClientIP retrieves the client's IP address from the request.
|
||||||
|
// It handles cases where the IP might be forwarded by proxies.
|
||||||
|
func getClientIP(r *http.Request) string {
|
||||||
|
if forwarded := r.Header.Get("X-Forwarded-For"); forwarded != "" {
|
||||||
|
return forwarded
|
||||||
|
}
|
||||||
|
return r.RemoteAddr
|
||||||
|
}
|
||||||
|
|
||||||
|
// ResponseWriterInterceptor is a custom http.ResponseWriter that captures the status code.
|
||||||
|
type ResponseWriterInterceptor struct {
|
||||||
|
http.ResponseWriter
|
||||||
|
statusCode int
|
||||||
|
}
|
||||||
|
|
||||||
|
func (rw *ResponseWriterInterceptor) WriteHeader(code int) {
|
||||||
|
rw.statusCode = code
|
||||||
|
rw.ResponseWriter.WriteHeader(code)
|
||||||
|
}
|
||||||
|
|
||||||
|
func logRequest(app string, status int, latency time.Duration, ip, method, path string) {
|
||||||
|
slog.Info("Proxy Request",
|
||||||
|
slog.String("time", time.Now().Format(time.RFC3339)),
|
||||||
|
slog.Int("status", status),
|
||||||
|
slog.Duration("latency", latency),
|
||||||
|
slog.String("ip", ip),
|
||||||
|
slog.String("method", method),
|
||||||
|
slog.String("app", app),
|
||||||
|
slog.String("path", path),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
type unixDialer struct {
|
||||||
|
socketPath string
|
||||||
|
}
|
||||||
|
|
||||||
|
// dialContext implements DialContext but ignored everthing and just gives you a connection to the unix socket
|
||||||
|
func (d *unixDialer) DialContext(ctx context.Context, network, address string) (net.Conn, error) {
|
||||||
|
return net.Dial("unix", d.socketPath)
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewUnixSocketTransport(socketPath string) *http.Transport {
|
||||||
|
return &http.Transport{
|
||||||
|
DialContext: (&unixDialer{socketPath: socketPath}).DialContext,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type Proxy struct {
|
||||||
|
socket string
|
||||||
|
proxyFunc *httputil.ReverseProxy
|
||||||
|
shutdownTimeout time.Duration
|
||||||
|
activeRequests int64
|
||||||
|
}
|
||||||
|
|
||||||
|
const PROXY_SHUTDOWN_TIMEOUT = 30 * time.Second
|
||||||
|
|
||||||
|
// Creates a proxy for a given deployment
|
||||||
|
func NewDeploymentProxy(socket string) (*Proxy, error) {
|
||||||
|
proxy := &Proxy{
|
||||||
|
socket: socket,
|
||||||
|
shutdownTimeout: PROXY_SHUTDOWN_TIMEOUT,
|
||||||
|
activeRequests: 0,
|
||||||
|
}
|
||||||
|
|
||||||
|
transport := &http.Transport{
|
||||||
|
DialContext: (&unixDialer{socketPath: socket}).DialContext,
|
||||||
|
MaxIdleConns: 100,
|
||||||
|
IdleConnTimeout: 90 * time.Second,
|
||||||
|
MaxIdleConnsPerHost: 100,
|
||||||
|
ForceAttemptHTTP2: false,
|
||||||
|
}
|
||||||
|
|
||||||
|
proxy.proxyFunc = &httputil.ReverseProxy{
|
||||||
|
Director: func(req *http.Request) {
|
||||||
|
req.URL = &url.URL{
|
||||||
|
Scheme: "http",
|
||||||
|
Host: req.Host,
|
||||||
|
Path: req.URL.Path,
|
||||||
|
}
|
||||||
|
atomic.AddInt64(&proxy.activeRequests, 1)
|
||||||
|
},
|
||||||
|
Transport: transport,
|
||||||
|
ModifyResponse: func(resp *http.Response) error {
|
||||||
|
atomic.AddInt64(&proxy.activeRequests, -1)
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
ErrorHandler: func(w http.ResponseWriter, r *http.Request, err error) {
|
||||||
|
slog.Error("Proxy error", "error", err)
|
||||||
|
atomic.AddInt64(&proxy.activeRequests, -1)
|
||||||
|
w.WriteHeader(http.StatusInternalServerError)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
return proxy, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Proxy) GracefulShutdown(shutdownFunc func()) {
|
||||||
|
slog.Debug("Shutting down proxy", "socket", p.socket)
|
||||||
|
|
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), p.shutdownTimeout)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
done := false
|
||||||
|
for !done {
|
||||||
|
select {
|
||||||
|
case <-ctx.Done():
|
||||||
|
slog.Debug("Proxy shutdown timed out", "socket", p.socket)
|
||||||
|
|
||||||
|
done = true
|
||||||
|
default:
|
||||||
|
if atomic.LoadInt64(&p.activeRequests) == 0 {
|
||||||
|
slog.Debug("Proxy shutdown completed successfully", "socket", p.socket)
|
||||||
|
done = true
|
||||||
|
}
|
||||||
|
|
||||||
|
time.Sleep(time.Second)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if shutdownFunc != nil {
|
||||||
|
shutdownFunc()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -2,10 +2,11 @@ module github.com/juls0730/gloom-plugin
|
|||||||
|
|
||||||
go 1.23.4
|
go 1.23.4
|
||||||
|
|
||||||
|
require github.com/gofiber/fiber/v3 v3.0.0-beta.4
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/andybalholm/brotli v1.1.1 // indirect
|
github.com/andybalholm/brotli v1.1.1 // indirect
|
||||||
github.com/fxamacker/cbor/v2 v2.7.0 // indirect
|
github.com/fxamacker/cbor/v2 v2.7.0 // indirect
|
||||||
github.com/gofiber/fiber/v3 v3.0.0-beta.4 // indirect
|
|
||||||
github.com/gofiber/schema v1.2.0 // indirect
|
github.com/gofiber/schema v1.2.0 // indirect
|
||||||
github.com/gofiber/utils/v2 v2.0.0-beta.7 // indirect
|
github.com/gofiber/utils/v2 v2.0.0-beta.7 // indirect
|
||||||
github.com/google/uuid v1.6.0 // indirect
|
github.com/google/uuid v1.6.0 // indirect
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
github.com/andybalholm/brotli v1.1.1 h1:PR2pgnyFznKEugtsUo0xLdDop5SKXd5Qf5ysW+7XdTA=
|
github.com/andybalholm/brotli v1.1.1 h1:PR2pgnyFznKEugtsUo0xLdDop5SKXd5Qf5ysW+7XdTA=
|
||||||
github.com/andybalholm/brotli v1.1.1/go.mod h1:05ib4cKhjx3OQYUY22hTVd34Bc8upXjOLL2rKwwZBoA=
|
github.com/andybalholm/brotli v1.1.1/go.mod h1:05ib4cKhjx3OQYUY22hTVd34Bc8upXjOLL2rKwwZBoA=
|
||||||
|
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||||
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/fxamacker/cbor/v2 v2.7.0 h1:iM5WgngdRBanHcxugY4JySA0nk1wZorNOpTgCMedv5E=
|
github.com/fxamacker/cbor/v2 v2.7.0 h1:iM5WgngdRBanHcxugY4JySA0nk1wZorNOpTgCMedv5E=
|
||||||
github.com/fxamacker/cbor/v2 v2.7.0/go.mod h1:pxXPTn3joSm21Gbwsv0w9OSA2y1HFR9qXEeXQVeNoDQ=
|
github.com/fxamacker/cbor/v2 v2.7.0/go.mod h1:pxXPTn3joSm21Gbwsv0w9OSA2y1HFR9qXEeXQVeNoDQ=
|
||||||
github.com/gofiber/fiber/v3 v3.0.0-beta.4 h1:KzDSavvhG7m81NIsmnu5l3ZDbVS4feCidl4xlIfu6V0=
|
github.com/gofiber/fiber/v3 v3.0.0-beta.4 h1:KzDSavvhG7m81NIsmnu5l3ZDbVS4feCidl4xlIfu6V0=
|
||||||
@@ -19,6 +21,10 @@ github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWE
|
|||||||
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||||
github.com/philhofer/fwd v1.1.3-0.20240916144458-20a13a1f6b7c h1:dAMKvw0MlJT1GshSTtih8C2gDs04w8dReiOGXrGLNoY=
|
github.com/philhofer/fwd v1.1.3-0.20240916144458-20a13a1f6b7c h1:dAMKvw0MlJT1GshSTtih8C2gDs04w8dReiOGXrGLNoY=
|
||||||
github.com/philhofer/fwd v1.1.3-0.20240916144458-20a13a1f6b7c/go.mod h1:RqIHx9QI14HlwKwm98g9Re5prTQ6LdeRQn+gXJFxsJM=
|
github.com/philhofer/fwd v1.1.3-0.20240916144458-20a13a1f6b7c/go.mod h1:RqIHx9QI14HlwKwm98g9Re5prTQ6LdeRQn+gXJFxsJM=
|
||||||
|
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||||
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
|
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
|
||||||
|
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||||
github.com/tinylib/msgp v1.2.5 h1:WeQg1whrXRFiZusidTQqzETkRpGjFjcIhW6uqWH09po=
|
github.com/tinylib/msgp v1.2.5 h1:WeQg1whrXRFiZusidTQqzETkRpGjFjcIhW6uqWH09po=
|
||||||
github.com/tinylib/msgp v1.2.5/go.mod h1:ykjzy2wzgrlvpDCRc4LA8UXy6D8bzMSuAF3WD57Gok0=
|
github.com/tinylib/msgp v1.2.5/go.mod h1:ykjzy2wzgrlvpDCRc4LA8UXy6D8bzMSuAF3WD57Gok0=
|
||||||
github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
|
github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
|
||||||
@@ -29,6 +35,7 @@ github.com/valyala/tcplisten v1.0.0 h1:rBHj/Xf+E1tRGZyWIWwJDiRY0zc1Js+CV5DqwacVS
|
|||||||
github.com/valyala/tcplisten v1.0.0/go.mod h1:T0xQ8SeCZGxckz9qRXTfG43PvQ/mcWh7FwZEA7Ioqkc=
|
github.com/valyala/tcplisten v1.0.0/go.mod h1:T0xQ8SeCZGxckz9qRXTfG43PvQ/mcWh7FwZEA7Ioqkc=
|
||||||
github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM=
|
github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM=
|
||||||
github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg=
|
github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg=
|
||||||
|
github.com/xyproto/randomstring v1.0.5 h1:YtlWPoRdgMu3NZtP45drfy1GKoojuR7hmRcnhZqKjWU=
|
||||||
github.com/xyproto/randomstring v1.0.5/go.mod h1:rgmS5DeNXLivK7YprL0pY+lTuhNQW3iGxZ18UQApw/E=
|
github.com/xyproto/randomstring v1.0.5/go.mod h1:rgmS5DeNXLivK7YprL0pY+lTuhNQW3iGxZ18UQApw/E=
|
||||||
golang.org/x/crypto v0.31.0 h1:ihbySMvVjLAeSH1IbfcRTkD/iNscyz8rGzjF/E5hV6U=
|
golang.org/x/crypto v0.31.0 h1:ihbySMvVjLAeSH1IbfcRTkD/iNscyz8rGzjF/E5hV6U=
|
||||||
golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk=
|
golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk=
|
||||||
@@ -40,3 +47,5 @@ golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA=
|
|||||||
golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||||
golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo=
|
golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo=
|
||||||
golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
|
golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
|
||||||
|
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||||
|
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
|
|||||||
@@ -8,13 +8,9 @@ func (p *MyPlugin) Init() (*fiber.Config, error) {
|
|||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *MyPlugin) Name() string {
|
|
||||||
return "MyPlugin"
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *MyPlugin) RegisterRoutes(router fiber.Router) {
|
func (p *MyPlugin) RegisterRoutes(router fiber.Router) {
|
||||||
router.Get("/", func(c fiber.Ctx) error {
|
router.Get("/", func(c fiber.Ctx) error {
|
||||||
return c.Status(fiber.StatusTeapot).SendString("Welcome to MyPlugin!")
|
return c.Status(fiber.StatusOK).SendString("Welcome to MyPlugin!")
|
||||||
})
|
})
|
||||||
|
|
||||||
router.Get("/hello", func(c fiber.Ctx) error {
|
router.Get("/hello", func(c fiber.Ctx) error {
|
||||||
|
|||||||
26
pluginHost/README.md
Normal file
26
pluginHost/README.md
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
# Plugin Host
|
||||||
|
This is the plugin host for GLoom. This is a small program that is responsible for loading and managing plugins. It is responsible for starting the plugin and forwarding requests to it. This is meant to be used with GLoom, but can be used as a standalone program if you so choose. The Plugin Host is built automatically when you build GLoom via `zqdgr build`.
|
||||||
|
|
||||||
|
## Building
|
||||||
|
To build the plugin host standalone, run the following command in the `pluginHost` directory:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
zqdgr build
|
||||||
|
```
|
||||||
|
|
||||||
|
or run the following command in the project root:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
zqdgr build:pluginHost
|
||||||
|
```
|
||||||
|
|
||||||
|
## Running
|
||||||
|
To run the plugin host, run the following command:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
./host <pluginPath> <socketPath> [controlPath]
|
||||||
|
```
|
||||||
|
|
||||||
|
- `pluginPath` - The path to the plugin to load.
|
||||||
|
- `socketPath` - The path to the socket that the plugin will use to listen for http requests through.
|
||||||
|
- `controlPath` - (Optional) The path to the control socket. If not provided, the host will not send errors or status messages to the control socket and instead log them to stdout and stderr.
|
||||||
26
pluginHost/go.mod
Normal file
26
pluginHost/go.mod
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
module github.com/juls0730/gloom/pluginHost
|
||||||
|
|
||||||
|
go 1.24.2
|
||||||
|
|
||||||
|
require github.com/gofiber/fiber/v3 v3.0.0-beta.4
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/andybalholm/brotli v1.1.1 // indirect
|
||||||
|
github.com/fxamacker/cbor/v2 v2.7.0 // indirect
|
||||||
|
github.com/gofiber/schema v1.2.0 // indirect
|
||||||
|
github.com/gofiber/utils/v2 v2.0.0-beta.7 // indirect
|
||||||
|
github.com/google/uuid v1.6.0 // indirect
|
||||||
|
github.com/klauspost/compress v1.17.11 // indirect
|
||||||
|
github.com/mattn/go-colorable v0.1.13 // indirect
|
||||||
|
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||||
|
github.com/philhofer/fwd v1.1.3-0.20240916144458-20a13a1f6b7c // indirect
|
||||||
|
github.com/tinylib/msgp v1.2.5 // indirect
|
||||||
|
github.com/valyala/bytebufferpool v1.0.0 // indirect
|
||||||
|
github.com/valyala/fasthttp v1.58.0 // indirect
|
||||||
|
github.com/valyala/tcplisten v1.0.0 // indirect
|
||||||
|
github.com/x448/float16 v0.8.4 // indirect
|
||||||
|
golang.org/x/crypto v0.31.0 // indirect
|
||||||
|
golang.org/x/net v0.31.0 // indirect
|
||||||
|
golang.org/x/sys v0.28.0 // indirect
|
||||||
|
golang.org/x/text v0.21.0 // indirect
|
||||||
|
)
|
||||||
51
pluginHost/go.sum
Normal file
51
pluginHost/go.sum
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
github.com/andybalholm/brotli v1.1.1 h1:PR2pgnyFznKEugtsUo0xLdDop5SKXd5Qf5ysW+7XdTA=
|
||||||
|
github.com/andybalholm/brotli v1.1.1/go.mod h1:05ib4cKhjx3OQYUY22hTVd34Bc8upXjOLL2rKwwZBoA=
|
||||||
|
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||||
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
|
github.com/fxamacker/cbor/v2 v2.7.0 h1:iM5WgngdRBanHcxugY4JySA0nk1wZorNOpTgCMedv5E=
|
||||||
|
github.com/fxamacker/cbor/v2 v2.7.0/go.mod h1:pxXPTn3joSm21Gbwsv0w9OSA2y1HFR9qXEeXQVeNoDQ=
|
||||||
|
github.com/gofiber/fiber/v3 v3.0.0-beta.4 h1:KzDSavvhG7m81NIsmnu5l3ZDbVS4feCidl4xlIfu6V0=
|
||||||
|
github.com/gofiber/fiber/v3 v3.0.0-beta.4/go.mod h1:/WFUoHRkZEsGHyy2+fYcdqi109IVOFbVwxv1n1RU+kk=
|
||||||
|
github.com/gofiber/schema v1.2.0 h1:j+ZRrNnUa/0ZuWrn/6kAtAufEr4jCJ+JuTURAMxNSZg=
|
||||||
|
github.com/gofiber/schema v1.2.0/go.mod h1:YYwj01w3hVfaNjhtJzaqetymL56VW642YS3qZPhuE6c=
|
||||||
|
github.com/gofiber/utils/v2 v2.0.0-beta.7 h1:NnHFrRHvhrufPABdWajcKZejz9HnCWmT/asoxRsiEbQ=
|
||||||
|
github.com/gofiber/utils/v2 v2.0.0-beta.7/go.mod h1:J/M03s+HMdZdvhAeyh76xT72IfVqBzuz/OJkrMa7cwU=
|
||||||
|
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||||
|
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||||
|
github.com/klauspost/compress v1.17.11 h1:In6xLpyWOi1+C7tXUUWv2ot1QvBjxevKAaI6IXrJmUc=
|
||||||
|
github.com/klauspost/compress v1.17.11/go.mod h1:pMDklpSncoRMuLFrf1W9Ss9KT+0rH90U12bZKk7uwG0=
|
||||||
|
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
|
||||||
|
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
|
||||||
|
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
|
||||||
|
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
||||||
|
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||||
|
github.com/philhofer/fwd v1.1.3-0.20240916144458-20a13a1f6b7c h1:dAMKvw0MlJT1GshSTtih8C2gDs04w8dReiOGXrGLNoY=
|
||||||
|
github.com/philhofer/fwd v1.1.3-0.20240916144458-20a13a1f6b7c/go.mod h1:RqIHx9QI14HlwKwm98g9Re5prTQ6LdeRQn+gXJFxsJM=
|
||||||
|
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||||
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
|
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
|
||||||
|
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||||
|
github.com/tinylib/msgp v1.2.5 h1:WeQg1whrXRFiZusidTQqzETkRpGjFjcIhW6uqWH09po=
|
||||||
|
github.com/tinylib/msgp v1.2.5/go.mod h1:ykjzy2wzgrlvpDCRc4LA8UXy6D8bzMSuAF3WD57Gok0=
|
||||||
|
github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
|
||||||
|
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
|
||||||
|
github.com/valyala/fasthttp v1.58.0 h1:GGB2dWxSbEprU9j0iMJHgdKYJVDyjrOwF9RE59PbRuE=
|
||||||
|
github.com/valyala/fasthttp v1.58.0/go.mod h1:SYXvHHaFp7QZHGKSHmoMipInhrI5StHrhDTYVEjK/Kw=
|
||||||
|
github.com/valyala/tcplisten v1.0.0 h1:rBHj/Xf+E1tRGZyWIWwJDiRY0zc1Js+CV5DqwacVSA8=
|
||||||
|
github.com/valyala/tcplisten v1.0.0/go.mod h1:T0xQ8SeCZGxckz9qRXTfG43PvQ/mcWh7FwZEA7Ioqkc=
|
||||||
|
github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM=
|
||||||
|
github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg=
|
||||||
|
github.com/xyproto/randomstring v1.0.5 h1:YtlWPoRdgMu3NZtP45drfy1GKoojuR7hmRcnhZqKjWU=
|
||||||
|
github.com/xyproto/randomstring v1.0.5/go.mod h1:rgmS5DeNXLivK7YprL0pY+lTuhNQW3iGxZ18UQApw/E=
|
||||||
|
golang.org/x/crypto v0.31.0 h1:ihbySMvVjLAeSH1IbfcRTkD/iNscyz8rGzjF/E5hV6U=
|
||||||
|
golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk=
|
||||||
|
golang.org/x/net v0.31.0 h1:68CPQngjLL0r2AlUKiSxtQFKvzRVbnzLwMUn5SzcLHo=
|
||||||
|
golang.org/x/net v0.31.0/go.mod h1:P4fl1q7dY2hnZFxEk4pPSkDHF+QqjitcnDjUQyMM+pM=
|
||||||
|
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA=
|
||||||
|
golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||||
|
golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo=
|
||||||
|
golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
|
||||||
|
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||||
|
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
145
pluginHost/main.go
Normal file
145
pluginHost/main.go
Normal file
@@ -0,0 +1,145 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"log"
|
||||||
|
"net"
|
||||||
|
"os"
|
||||||
|
"os/signal"
|
||||||
|
"path/filepath"
|
||||||
|
"plugin"
|
||||||
|
|
||||||
|
"github.com/gofiber/fiber/v3"
|
||||||
|
)
|
||||||
|
|
||||||
|
var pluginPath string
|
||||||
|
var socketPath string
|
||||||
|
var controlPath string
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
if len(os.Args) < 3 {
|
||||||
|
fmt.Fprintf(os.Stderr, "Usage: pluginHost <pluginPath> <socketPath>")
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
pluginPath = os.Args[1]
|
||||||
|
socketPath = os.Args[2]
|
||||||
|
if len(os.Args) > 3 {
|
||||||
|
controlPath = os.Args[3]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type Plugin interface {
|
||||||
|
Init() (*fiber.Config, error)
|
||||||
|
RegisterRoutes(app fiber.Router)
|
||||||
|
// Name() string
|
||||||
|
}
|
||||||
|
|
||||||
|
type PluginInstance struct {
|
||||||
|
Plugin Plugin
|
||||||
|
Name string
|
||||||
|
Path string
|
||||||
|
Router *fiber.App
|
||||||
|
}
|
||||||
|
|
||||||
|
// Init is the entry point for a container process
|
||||||
|
func (p *PluginInstance) Run(pluginName string) {
|
||||||
|
log.Printf("Starting container with plugin %s", pluginName)
|
||||||
|
// Load and initialize the plugin here
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
signalChan := make(chan os.Signal, 1)
|
||||||
|
signal.Notify(signalChan, os.Interrupt)
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
<-signalChan
|
||||||
|
// TODO: maybe do something graceful here
|
||||||
|
fmt.Println("Received SIGINT, shutting down...")
|
||||||
|
os.Exit(0)
|
||||||
|
}()
|
||||||
|
|
||||||
|
var writer io.Writer
|
||||||
|
writer = os.Stderr
|
||||||
|
if controlPath != "" {
|
||||||
|
fmt.Printf("Waiting for control connection on %s\n", controlPath)
|
||||||
|
|
||||||
|
controlListener, err := net.Listen("unix", controlPath)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("Error listening on control socket: %v", err)
|
||||||
|
}
|
||||||
|
defer controlListener.Close()
|
||||||
|
|
||||||
|
conn, err := controlListener.Accept()
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("Error accepting control connection: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer conn.Close()
|
||||||
|
|
||||||
|
var ok bool
|
||||||
|
writer, ok = conn.(io.Writer)
|
||||||
|
if !ok {
|
||||||
|
log.Printf("Control connection is not a writer")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err := os.Stat(socketPath); err == nil {
|
||||||
|
fmt.Fprintf(writer, "Error: Socket %s already exists\n", socketPath)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
realPluginPath, err := filepath.Abs(pluginPath)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Fprintf(writer, "Error: could not get absolute plugin path: %v\n", err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
p, err := plugin.Open(realPluginPath)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Fprintf(writer, "Error: could not open plugin %s: %v\n", realPluginPath, err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
symbol, err := p.Lookup("Plugin")
|
||||||
|
if err != nil {
|
||||||
|
fmt.Fprintf(writer, "Error: could not find 'Plugin' symbol in %s: %v\n", realPluginPath, err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
pluginLib, ok := symbol.(Plugin)
|
||||||
|
if !ok {
|
||||||
|
fmt.Fprintf(writer, "Error: symbol 'Plugin' in %s is not a Plugin interface\n", realPluginPath)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
pluginConfig, err := pluginLib.Init()
|
||||||
|
if err != nil {
|
||||||
|
fmt.Fprintf(writer, "Error: error initializing plugin %s: %v\n", realPluginPath, err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
config := fiber.Config{}
|
||||||
|
if pluginConfig != nil {
|
||||||
|
config = *pluginConfig
|
||||||
|
}
|
||||||
|
router := fiber.New(config)
|
||||||
|
|
||||||
|
pluginLib.RegisterRoutes(router)
|
||||||
|
|
||||||
|
// listen for connections on the socket
|
||||||
|
listener, err := net.Listen("unix", socketPath)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Fprintf(writer, "Error: error listening on socket %s: %v\n", socketPath, err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Fprintf(writer, "ready\n")
|
||||||
|
|
||||||
|
// technically this can still error
|
||||||
|
router.Listener(listener, fiber.ListenConfig{
|
||||||
|
DisableStartupMessage: true,
|
||||||
|
})
|
||||||
|
}
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
CREATE TABLE IF NOT EXISTS plugins (
|
CREATE TABLE IF NOT EXISTS plugins (
|
||||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
path TEXT NOT NULL UNIQUE,
|
path TEXT NOT NULL UNIQUE,
|
||||||
|
name TEXT NOT NULL UNIQUE,
|
||||||
domains TEXT NOT NULL
|
domains TEXT NOT NULL
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,12 +1,16 @@
|
|||||||
{
|
{
|
||||||
"name": "Go Project",
|
"name": "GLoom",
|
||||||
"version": "0.0.1",
|
"version": "0.0.1",
|
||||||
"description": "Example description",
|
"description": "GLoom is a plugin-based web app manager",
|
||||||
"author": "you",
|
"author": "juls0730",
|
||||||
"license": "BSL-1.0",
|
"license": "MIT",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "sh -c \"cd gloomi; zqdgr build\" && go build",
|
"build": "zqdgr build:gloomi && zqdgr build:pluginHost && go build",
|
||||||
"dev": "sh -c \"cd gloomi; zqdgr build\" && go run main.go"
|
"build:pluginHost": "sh -c \"cd pluginHost; go build -o ../host main.go\"",
|
||||||
|
"build:gloomi": "sh -c \"cd gloomi; zqdgr build\"",
|
||||||
|
"build:no-gloomi": "go build",
|
||||||
|
"clean": "rm -rf plugs && rm -rf host && rm -rf gloom.db && rm -rf plugin/plugin.so && rm -rf gloom",
|
||||||
|
"dev": "zqdgr build && ./gloom"
|
||||||
},
|
},
|
||||||
"pattern": "**/*.go",
|
"pattern": "**/*.go",
|
||||||
"excluded_dirs": []
|
"excluded_dirs": []
|
||||||
|
|||||||
Reference in New Issue
Block a user