Flesh out GLoom
I have documented GLoom's new RPC, how a plugin developer might create a plugin, and more. I have also created GLoomI, the included management interface for GLoom and added a LICENSE.
This commit is contained in:
2
.env.example
Normal file
2
.env.example
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
DISABLE_GLOOMI=false
|
||||||
|
GLOOMI_HOSTNAME=localhost
|
||||||
6
.gitignore
vendored
6
.gitignore
vendored
@@ -1 +1,5 @@
|
|||||||
plugs/**
|
plugs/**
|
||||||
|
gloom
|
||||||
|
**/*.so
|
||||||
|
.env
|
||||||
|
gloom.db
|
||||||
56
DOCUMENTATION.md
Normal file
56
DOCUMENTATION.md
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
# GLoom Documentation
|
||||||
|
|
||||||
|
## Plugins
|
||||||
|
|
||||||
|
Plugins are the core of GLoom, they are responsible for handling requests and providing routes.
|
||||||
|
|
||||||
|
### Plugin Interface
|
||||||
|
|
||||||
|
The `Plugin` interface is the main interface for plugins to implement. It has three methods:
|
||||||
|
|
||||||
|
- `Init()` - This method is called when the plugin is loaded. It is the function that is initially called when the plugin is loaded.
|
||||||
|
- `Name()` - 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.
|
||||||
|
|
||||||
|
An example plugin is provided in the `plugin` directory and can be built using the following command:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
zqdgr build
|
||||||
|
```
|
||||||
|
|
||||||
|
This will generate a `plugin.so` file in the `plugin` directory.
|
||||||
|
|
||||||
|
## RPC
|
||||||
|
|
||||||
|
GLoom exposes an RPC server on port 7143. This server is used for GLoom's plugin management system. Gloom currently provides two methods:
|
||||||
|
|
||||||
|
- `ListPlugins(struct{}, reply *[]PluginData) error` - This method returns a list of all registered plugins and their domains.
|
||||||
|
- `UploadPlugin(PluginUpload, reply *string) error` - This method uploads a plugin to GLoom.
|
||||||
|
|
||||||
|
PluginData is a struct that looks like this:
|
||||||
|
|
||||||
|
```go
|
||||||
|
type PluginData struct {
|
||||||
|
Name string `json:"name"`
|
||||||
|
Domains []string `json:"domains"`
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
PluginUpload is a struct that looks like this:
|
||||||
|
|
||||||
|
```go
|
||||||
|
type PluginUpload struct {
|
||||||
|
Name string `json:"name"`
|
||||||
|
Domains []string `json:"domains"`
|
||||||
|
Data []byte `json:"data"`
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 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:
|
||||||
|
|
||||||
|
- `GET /api/plugins` - This endpoint returns a list of all registered plugins and their domains.
|
||||||
|
- `POST /api/plugins` - This endpoint uploads a plugin to GLoom. it takes a multipart/form-data request with the following fields:
|
||||||
|
- `plugin` - The plugin file to upload.
|
||||||
|
- `domains` - A comma-separated list of domains to associate with the plugin.
|
||||||
21
LICENSE
Normal file
21
LICENSE
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2025 juls0730
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
||||||
49
README.md
Normal file
49
README.md
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
# GLoom
|
||||||
|
|
||||||
|
GLoom is a plugin-based web server written in Go. GLoom's focus is to provide and simple and efficient way to host micro-web apps easily.
|
||||||
|
|
||||||
|
## Features
|
||||||
|
|
||||||
|
- Plugin-based architecture
|
||||||
|
- RPC-based communication between GLoom and plugins
|
||||||
|
- Built-in plugin management system
|
||||||
|
- Built-in plugin management UI
|
||||||
|
|
||||||
|
## Getting Started
|
||||||
|
|
||||||
|
### Prerequisites
|
||||||
|
|
||||||
|
- Go 1.20 or higher
|
||||||
|
- [zqdgr](https://github.com/juls0730/zqdgr)
|
||||||
|
|
||||||
|
### Installation
|
||||||
|
|
||||||
|
1. Clone the repository:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git clone https://github.com/juls0730/gloom.git
|
||||||
|
```
|
||||||
|
|
||||||
|
2. Run the project:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
zqdgr run
|
||||||
|
```
|
||||||
|
|
||||||
|
or if you want to build the project:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
zqdgr build
|
||||||
|
```
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
please read [DOCUMENTATION.md](DOCUMENTATION.md)
|
||||||
|
|
||||||
|
## Contributing
|
||||||
|
|
||||||
|
Contributions are welcome!
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
GLoom is licensed under the MIT License.
|
||||||
3
gloomi/README.md
Normal file
3
gloomi/README.md
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
# GLoomI
|
||||||
|
|
||||||
|
GLoomI is the management Interface for GLoom. This plugin is responsible for managing GLoom's plugins and domains, and utilizes gloom's built in plugin system. It uses RPC to communicate with GLoom.
|
||||||
26
gloomi/go.mod
Normal file
26
gloomi/go.mod
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
module github.com/juls0730/gloomi
|
||||||
|
|
||||||
|
go 1.23.4
|
||||||
|
|
||||||
|
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
|
||||||
|
)
|
||||||
42
gloomi/go.sum
Normal file
42
gloomi/go.sum
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
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/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/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.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=
|
||||||
108
gloomi/main.go
Normal file
108
gloomi/main.go
Normal file
@@ -0,0 +1,108 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"mime/multipart"
|
||||||
|
"net/rpc"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/gofiber/fiber/v3"
|
||||||
|
)
|
||||||
|
|
||||||
|
type GLoomI struct {
|
||||||
|
client *rpc.Client
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *GLoomI) Init() error {
|
||||||
|
// Connect to the RPC server
|
||||||
|
client, err := rpc.Dial("tcp", "localhost:7143")
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to connect to Gloom RPC server: %w", err)
|
||||||
|
}
|
||||||
|
p.client = client
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *GLoomI) Name() string {
|
||||||
|
return "GLoomI"
|
||||||
|
}
|
||||||
|
|
||||||
|
type PluginData struct {
|
||||||
|
Name string `json:"name"`
|
||||||
|
Domains []string `json:"domains"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetPlugins(client *rpc.Client) ([]PluginData, error) {
|
||||||
|
var plugins []PluginData
|
||||||
|
err := client.Call("GloomRPC.ListPlugins", struct{}{}, &plugins)
|
||||||
|
|
||||||
|
return plugins, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *GLoomI) RegisterRoutes(router fiber.Router) {
|
||||||
|
apiRouter := router.Group("/api")
|
||||||
|
{
|
||||||
|
apiRouter.Get("/plugins", func(c fiber.Ctx) error {
|
||||||
|
plugins, err := GetPlugins(p.client)
|
||||||
|
if err != nil {
|
||||||
|
return c.Status(fiber.StatusInternalServerError).SendString("Failed to list plugins: " + err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.Status(fiber.StatusOK).JSON(plugins)
|
||||||
|
})
|
||||||
|
|
||||||
|
type UploadRequest struct {
|
||||||
|
Domains string `form:"domains"`
|
||||||
|
}
|
||||||
|
|
||||||
|
apiRouter.Post("/plugins", func(c fiber.Ctx) error {
|
||||||
|
pluginUpload := new(UploadRequest)
|
||||||
|
if err := c.Bind().Form(pluginUpload); err != nil {
|
||||||
|
return c.Status(fiber.StatusBadRequest).SendString("Failed to bind form data: " + err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
if pluginUpload.Domains == "" {
|
||||||
|
return c.Status(fiber.StatusBadRequest).SendString("No domains provided")
|
||||||
|
}
|
||||||
|
|
||||||
|
domains := make([]string, 0)
|
||||||
|
for _, domain := range strings.Split(pluginUpload.Domains, ",") {
|
||||||
|
domains = append(domains, strings.TrimSpace(domain))
|
||||||
|
}
|
||||||
|
|
||||||
|
var pluginFile *multipart.FileHeader
|
||||||
|
pluginFile, err := c.FormFile("plugin")
|
||||||
|
if err != nil {
|
||||||
|
return c.Status(fiber.StatusBadRequest).SendString("Failed to get plugin file: " + err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
pluginData, err := pluginFile.Open()
|
||||||
|
if err != nil {
|
||||||
|
return c.Status(fiber.StatusInternalServerError).SendString("Failed to open plugin file: " + err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
var pluginUploadStruct struct {
|
||||||
|
Domains []string `json:"domains"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
Data []byte `json:"data"`
|
||||||
|
}
|
||||||
|
pluginUploadStruct.Name = pluginFile.Filename
|
||||||
|
pluginUploadStruct.Domains = domains
|
||||||
|
pluginUploadStruct.Data, err = io.ReadAll(pluginData)
|
||||||
|
if err != nil {
|
||||||
|
return c.Status(fiber.StatusInternalServerError).SendString("Failed to read plugin file: " + err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
err = p.client.Call("GloomRPC.UploadPlugin", pluginUploadStruct, nil)
|
||||||
|
if err != nil {
|
||||||
|
return c.Status(fiber.StatusInternalServerError).SendString("Failed to upload plugin: " + err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.Status(fiber.StatusOK).SendString("Plugin uploaded successfully")
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Exported symbol
|
||||||
|
var Plugin GLoomI
|
||||||
12
gloomi/zqdgr.config.json
Normal file
12
gloomi/zqdgr.config.json
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
{
|
||||||
|
"name": "Go Project",
|
||||||
|
"version": "0.0.1",
|
||||||
|
"description": "Example description",
|
||||||
|
"author": "you",
|
||||||
|
"license": "BSL-1.0",
|
||||||
|
"scripts": {
|
||||||
|
"build": "go build -buildmode=plugin -o ../plugs/gloomi.so main.go"
|
||||||
|
},
|
||||||
|
"pattern": "**/*.go",
|
||||||
|
"excluded_dirs": []
|
||||||
|
}
|
||||||
3
go.mod
3
go.mod
@@ -2,6 +2,8 @@ module github.com/juls0730/gloom
|
|||||||
|
|
||||||
go 1.23.4
|
go 1.23.4
|
||||||
|
|
||||||
|
require github.com/mattn/go-sqlite3 v1.14.24
|
||||||
|
|
||||||
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
|
||||||
@@ -9,6 +11,7 @@ require (
|
|||||||
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
|
||||||
|
github.com/joho/godotenv v1.5.1 // indirect
|
||||||
github.com/klauspost/compress v1.17.11 // indirect
|
github.com/klauspost/compress v1.17.11 // indirect
|
||||||
github.com/mattn/go-colorable v0.1.13 // 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
|
||||||
|
|||||||
4
go.sum
4
go.sum
@@ -10,6 +10,8 @@ github.com/gofiber/utils/v2 v2.0.0-beta.7 h1:NnHFrRHvhrufPABdWajcKZejz9HnCWmT/as
|
|||||||
github.com/gofiber/utils/v2 v2.0.0-beta.7/go.mod h1:J/M03s+HMdZdvhAeyh76xT72IfVqBzuz/OJkrMa7cwU=
|
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 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||||
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
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/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
|
||||||
github.com/klauspost/compress v1.17.11 h1:In6xLpyWOi1+C7tXUUWv2ot1QvBjxevKAaI6IXrJmUc=
|
github.com/klauspost/compress v1.17.11 h1:In6xLpyWOi1+C7tXUUWv2ot1QvBjxevKAaI6IXrJmUc=
|
||||||
github.com/klauspost/compress v1.17.11/go.mod h1:pMDklpSncoRMuLFrf1W9Ss9KT+0rH90U12bZKk7uwG0=
|
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 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
|
||||||
@@ -17,6 +19,8 @@ github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovk
|
|||||||
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
|
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/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
|
||||||
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/tinylib/msgp v1.2.5 h1:WeQg1whrXRFiZusidTQqzETkRpGjFjcIhW6uqWH09po=
|
github.com/tinylib/msgp v1.2.5 h1:WeQg1whrXRFiZusidTQqzETkRpGjFjcIhW6uqWH09po=
|
||||||
|
|||||||
181
libs/syncmap.go
Normal file
181
libs/syncmap.go
Normal file
@@ -0,0 +1,181 @@
|
|||||||
|
// https://gist.github.com/tarampampam/f96538257ff125ab71785710d48b3118
|
||||||
|
package libs
|
||||||
|
|
||||||
|
import "sync"
|
||||||
|
|
||||||
|
// SyncMap is like a Go sync.Map but type-safe using generics.
|
||||||
|
//
|
||||||
|
// The zero SyncMap is empty and ready for use. A SyncMap must not be copied after first use.
|
||||||
|
type SyncMap[K comparable, V any] struct {
|
||||||
|
mu sync.Mutex
|
||||||
|
m map[K]V
|
||||||
|
}
|
||||||
|
|
||||||
|
// Grow grows the map to the given size. It can be called before the first write operation used.
|
||||||
|
func (s *SyncMap[K, V]) Grow(size int) {
|
||||||
|
s.mu.Lock()
|
||||||
|
s.grow(size)
|
||||||
|
s.mu.Unlock()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *SyncMap[K, V]) grow(size ...int) {
|
||||||
|
if s.m == nil {
|
||||||
|
if len(size) == 0 {
|
||||||
|
s.m = make(map[K]V) // let runtime decide the needed map size
|
||||||
|
} else {
|
||||||
|
s.m = make(map[K]V, size[0])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clone returns a copy (clone) of current SyncMap.
|
||||||
|
func (s *SyncMap[K, V]) Clone() SyncMap[K, V] {
|
||||||
|
s.mu.Lock()
|
||||||
|
defer s.mu.Unlock()
|
||||||
|
|
||||||
|
var clone = make(map[K]V, len(s.m))
|
||||||
|
|
||||||
|
for k, v := range s.m {
|
||||||
|
clone[k] = v
|
||||||
|
}
|
||||||
|
|
||||||
|
return SyncMap[K, V]{m: clone}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load returns the value stored in the map for a key, or nil if no value is present.
|
||||||
|
// The ok result indicates whether value was found in the map.
|
||||||
|
func (s *SyncMap[K, V]) Load(key K) (value V, loaded bool) {
|
||||||
|
s.mu.Lock()
|
||||||
|
defer s.mu.Unlock()
|
||||||
|
|
||||||
|
if s.m == nil { // fast operation terminator
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
value, loaded = s.m[key]
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Store sets the value for a key.
|
||||||
|
func (s *SyncMap[K, V]) Store(key K, value V) {
|
||||||
|
s.mu.Lock()
|
||||||
|
defer s.mu.Unlock()
|
||||||
|
|
||||||
|
s.grow()
|
||||||
|
|
||||||
|
s.m[key] = value
|
||||||
|
}
|
||||||
|
|
||||||
|
// LoadOrStore returns the existing value for the key if present. Otherwise, it stores and returns the given value.
|
||||||
|
// The loaded result is true if the value was loaded, false if stored.
|
||||||
|
func (s *SyncMap[K, V]) LoadOrStore(key K, value V) (actual V, loaded bool) {
|
||||||
|
s.mu.Lock()
|
||||||
|
defer s.mu.Unlock()
|
||||||
|
|
||||||
|
if actual, loaded = s.m[key]; !loaded {
|
||||||
|
s.grow()
|
||||||
|
|
||||||
|
s.m[key], actual = value, value
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// LoadAndDelete deletes the value for a key, returning the previous value if any. The loaded result reports whether
|
||||||
|
// the key was present.
|
||||||
|
func (s *SyncMap[K, V]) LoadAndDelete(key K) (value V, loaded bool) {
|
||||||
|
s.mu.Lock()
|
||||||
|
defer s.mu.Unlock()
|
||||||
|
|
||||||
|
if s.m == nil { // fast operation terminator
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
s.grow()
|
||||||
|
|
||||||
|
if value, loaded = s.m[key]; loaded {
|
||||||
|
delete(s.m, key)
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete deletes the value for a key.
|
||||||
|
func (s *SyncMap[K, V]) Delete(key K) {
|
||||||
|
s.mu.Lock()
|
||||||
|
defer s.mu.Unlock()
|
||||||
|
|
||||||
|
if s.m == nil { // fast operation terminator
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
s.grow()
|
||||||
|
|
||||||
|
delete(s.m, key)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Range calls f sequentially for each key and value present in the map. If f returns false, range stops the iteration.
|
||||||
|
//
|
||||||
|
// Range does not necessarily correspond to any consistent snapshot of the Map's contents: no key will be visited more
|
||||||
|
// than once. Range does not block other methods on the receiver; even f itself may call any method on m.
|
||||||
|
func (s *SyncMap[K, V]) Range(f func(key K, value V) (shouldContinue bool)) {
|
||||||
|
s.mu.Lock()
|
||||||
|
|
||||||
|
if s.m == nil { // fast operation terminator
|
||||||
|
s.mu.Unlock()
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
s.grow()
|
||||||
|
|
||||||
|
for k, v := range s.m {
|
||||||
|
s.mu.Unlock()
|
||||||
|
|
||||||
|
if !f(k, v) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
s.mu.Lock()
|
||||||
|
}
|
||||||
|
|
||||||
|
s.mu.Unlock()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Len returns the count of values in the map.
|
||||||
|
func (s *SyncMap[K, V]) Len() (l int) {
|
||||||
|
s.mu.Lock()
|
||||||
|
l = len(s.m)
|
||||||
|
s.mu.Unlock()
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Keys return slice with all map keys.
|
||||||
|
func (s *SyncMap[K, V]) Keys() []K {
|
||||||
|
s.mu.Lock()
|
||||||
|
defer s.mu.Unlock()
|
||||||
|
|
||||||
|
var keys, i = make([]K, len(s.m)), 0
|
||||||
|
|
||||||
|
for k := range s.m {
|
||||||
|
keys[i], i = k, i+1
|
||||||
|
}
|
||||||
|
|
||||||
|
return keys
|
||||||
|
}
|
||||||
|
|
||||||
|
// Values return slice with all map values.
|
||||||
|
func (s *SyncMap[K, V]) Values() []V {
|
||||||
|
s.mu.Lock()
|
||||||
|
defer s.mu.Unlock()
|
||||||
|
|
||||||
|
var values, i = make([]V, len(s.m)), 0
|
||||||
|
|
||||||
|
for _, v := range s.m {
|
||||||
|
values[i], i = v, i+1
|
||||||
|
}
|
||||||
|
|
||||||
|
return values
|
||||||
|
}
|
||||||
301
main.go
301
main.go
@@ -1,90 +1,265 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"database/sql"
|
||||||
|
"embed"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"log/slog"
|
||||||
|
"net"
|
||||||
|
"net/rpc"
|
||||||
"os"
|
"os"
|
||||||
"plugin"
|
"plugin"
|
||||||
"sync"
|
"strings"
|
||||||
|
|
||||||
"github.com/gofiber/fiber/v3"
|
"github.com/gofiber/fiber/v3"
|
||||||
"github.com/gofiber/fiber/v3/middleware/logger"
|
"github.com/gofiber/fiber/v3/middleware/logger"
|
||||||
|
"github.com/joho/godotenv"
|
||||||
|
"github.com/juls0730/gloom/libs"
|
||||||
|
_ "github.com/mattn/go-sqlite3"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
//go:embed schema.sql
|
||||||
|
var embeddedAssets embed.FS
|
||||||
|
|
||||||
type Plugin interface {
|
type Plugin interface {
|
||||||
Name() string
|
|
||||||
Init() error
|
Init() error
|
||||||
Domains() []string
|
Name() string
|
||||||
RegisterRoutes(app fiber.Router)
|
RegisterRoutes(app fiber.Router)
|
||||||
}
|
}
|
||||||
|
|
||||||
var domainMap sync.Map // Maps domains to plugins
|
type PluginInstance struct {
|
||||||
|
Plugin Plugin
|
||||||
func main() {
|
Name string
|
||||||
app := fiber.New()
|
Router *fiber.App
|
||||||
|
|
||||||
app.Use(logger.New())
|
|
||||||
|
|
||||||
plugins := loadPlugins()
|
|
||||||
|
|
||||||
for _, p := range plugins {
|
|
||||||
for _, domain := range p.Domains() {
|
|
||||||
fmt.Printf("Registering domain: %s for plugin: %s\n", domain, p.Name())
|
|
||||||
domainMap.Store(domain, p)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
app.Use(func(c fiber.Ctx) error {
|
|
||||||
host := c.Host()
|
|
||||||
if value, ok := domainMap.Load(host); ok {
|
|
||||||
plugin := value.(Plugin)
|
|
||||||
|
|
||||||
pluginRouter := fiber.New()
|
|
||||||
plugin.RegisterRoutes(pluginRouter)
|
|
||||||
|
|
||||||
pluginRouter.Handler()(c.RequestCtx())
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return c.Status(404).SendString("Domain not found")
|
|
||||||
})
|
|
||||||
|
|
||||||
fmt.Println("Server running at http://localhost:3000")
|
|
||||||
app.Listen(":3000")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func loadPlugins() []Plugin {
|
type GLoom struct {
|
||||||
|
Plugins []PluginInstance
|
||||||
|
domainMap libs.SyncMap[string, *PluginInstance]
|
||||||
|
DB *sql.DB
|
||||||
|
fiber *fiber.App
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewGloom(app *fiber.App) (*GLoom, error) {
|
||||||
if err := os.MkdirAll("plugs", 0755); err != nil {
|
if err := os.MkdirAll("plugs", 0755); err != nil {
|
||||||
if os.IsNotExist(err) {
|
if os.IsNotExist(err) {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pluginPaths := []string{"plugs/example.so"}
|
db, err := sql.Open("sqlite3", "gloom.db")
|
||||||
var plugins []Plugin
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
for _, path := range pluginPaths {
|
|
||||||
p, err := plugin.Open(path)
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
symbol, err := p.Lookup("Plugin")
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
pluginInstance, ok := symbol.(Plugin)
|
|
||||||
if !ok {
|
|
||||||
panic("Invalid plugin type")
|
|
||||||
}
|
|
||||||
|
|
||||||
err = pluginInstance.Init()
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
plugins = append(plugins, pluginInstance)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return plugins
|
schema, err := embeddedAssets.ReadFile("schema.sql")
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = db.Exec(string(schema))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
gloom := &GLoom{
|
||||||
|
Plugins: []PluginInstance{},
|
||||||
|
domainMap: libs.SyncMap[string, *PluginInstance]{},
|
||||||
|
DB: db,
|
||||||
|
fiber: app,
|
||||||
|
}
|
||||||
|
|
||||||
|
plugins, err := db.Query("SELECT path, domains FROM plugins")
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer plugins.Close()
|
||||||
|
|
||||||
|
for plugins.Next() {
|
||||||
|
var plugin struct {
|
||||||
|
Path string
|
||||||
|
Domain string
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := plugins.Scan(&plugin.Path, &plugin.Domain); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
domains := strings.Split(plugin.Domain, ",")
|
||||||
|
|
||||||
|
gloom.RegisterPlugin(plugin.Path, domains)
|
||||||
|
}
|
||||||
|
|
||||||
|
return gloom, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (gloom *GLoom) RegisterPlugin(pluginPath string, domains []string) {
|
||||||
|
slog.Info("Registering plugin", "pluginPath", pluginPath, "domains", domains)
|
||||||
|
p, err := plugin.Open(pluginPath)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
symbol, err := p.Lookup("Plugin")
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
pluginLib, ok := symbol.(Plugin)
|
||||||
|
if !ok {
|
||||||
|
panic("Plugin is not a Plugin")
|
||||||
|
}
|
||||||
|
|
||||||
|
err = pluginLib.Init()
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
router := fiber.New()
|
||||||
|
pluginLib.RegisterRoutes(router)
|
||||||
|
|
||||||
|
pluginInstance := PluginInstance{
|
||||||
|
Plugin: pluginLib,
|
||||||
|
Name: pluginLib.Name(),
|
||||||
|
Router: router,
|
||||||
|
}
|
||||||
|
|
||||||
|
gloom.Plugins = append(gloom.Plugins, pluginInstance)
|
||||||
|
pluginPtr := &gloom.Plugins[len(gloom.Plugins)-1]
|
||||||
|
for _, domain := range domains {
|
||||||
|
gloom.domainMap.Store(domain, pluginPtr)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (gloom *GLoom) StartRPCServer() error {
|
||||||
|
rpcServer := &GloomRPC{gloom: gloom}
|
||||||
|
err := rpc.Register(rpcServer)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
listener, err := net.Listen("tcp", ":7143")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Printf("RPC server running on port 7143\n")
|
||||||
|
go func() {
|
||||||
|
for {
|
||||||
|
conn, err := listener.Accept()
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println("RPC connection error:", err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
go rpc.ServeConn(conn)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type GloomRPC struct {
|
||||||
|
gloom *GLoom
|
||||||
|
}
|
||||||
|
|
||||||
|
type PluginData struct {
|
||||||
|
Name string `json:"name"`
|
||||||
|
Domains []string `json:"domains"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Example RPC method: List all registered plugins
|
||||||
|
func (rpc *GloomRPC) ListPlugins(_ struct{}, reply *[]PluginData) error {
|
||||||
|
var plugins []PluginData = make([]PluginData, 0)
|
||||||
|
var domains map[string][]string = make(map[string][]string)
|
||||||
|
|
||||||
|
rpc.gloom.domainMap.Range(func(domain string, plugin *PluginInstance) bool {
|
||||||
|
domains[plugin.Name] = append(domains[plugin.Name], domain)
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
|
||||||
|
for _, plugin := range rpc.gloom.Plugins {
|
||||||
|
var pluginDataStruct PluginData
|
||||||
|
pluginDataStruct.Name = plugin.Name
|
||||||
|
pluginDataStruct.Domains = domains[plugin.Name]
|
||||||
|
|
||||||
|
plugins = append(plugins, pluginDataStruct)
|
||||||
|
}
|
||||||
|
|
||||||
|
*reply = plugins
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type PluginUpload struct {
|
||||||
|
Name string `json:"name"`
|
||||||
|
Domains []string `json:"domains"`
|
||||||
|
Data []byte `json:"data"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (rpc *GloomRPC) UploadPlugin(plugin PluginUpload, reply *string) error {
|
||||||
|
slog.Info("Uploading plugin", "plugin", plugin.Name, "domains", plugin.Domains)
|
||||||
|
if err := os.WriteFile(fmt.Sprintf("plugs/%s", plugin.Name), plugin.Data, 0644); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Print("Plugin uploaded successfully")
|
||||||
|
|
||||||
|
rpc.gloom.DB.Exec("INSERT INTO plugins (path, domains) VALUES (?, ?)", "plugs/"+plugin.Name, strings.Join(plugin.Domains, ","))
|
||||||
|
rpc.gloom.RegisterPlugin("plugs/"+plugin.Name, plugin.Domains)
|
||||||
|
*reply = "Plugin uploaded successfully"
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
if err := godotenv.Load(); err != nil {
|
||||||
|
fmt.Println("No .env file found")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
app := fiber.New(fiber.Config{
|
||||||
|
BodyLimit: 1024 * 1024 * 1024 * 5, // 5GB
|
||||||
|
})
|
||||||
|
|
||||||
|
app.Use(logger.New(logger.Config{
|
||||||
|
CustomTags: map[string]logger.LogFunc{
|
||||||
|
"app": func(output logger.Buffer, c fiber.Ctx, data *logger.Data, extraParam string) (int, error) {
|
||||||
|
output.WriteString(c.Host())
|
||||||
|
return len(output.Bytes()), nil
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Format: " ${time} | ${status} | ${latency} | ${ip} | ${method} | ${app} | ${path}\n",
|
||||||
|
}))
|
||||||
|
|
||||||
|
gloom, err := NewGloom(app)
|
||||||
|
if err != nil {
|
||||||
|
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 {
|
||||||
|
panic("Failed to start RPC server: " + err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
if os.Getenv("DISABLE_GLOOMI") != "true" {
|
||||||
|
hostname := os.Getenv("GLOOMI_HOSTNAME")
|
||||||
|
if hostname == "" {
|
||||||
|
hostname = "127.0.0.1"
|
||||||
|
}
|
||||||
|
|
||||||
|
gloom.RegisterPlugin("plugs/gloomi.so", []string{hostname})
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Println("Server running at http://localhost:3000")
|
||||||
|
if err := app.Listen(":3000"); err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,21 +4,17 @@ import "github.com/gofiber/fiber/v3"
|
|||||||
|
|
||||||
type MyPlugin struct{}
|
type MyPlugin struct{}
|
||||||
|
|
||||||
func (p MyPlugin) Name() string {
|
func (p *MyPlugin) Init() error {
|
||||||
return "my plugin"
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p MyPlugin) Init() error {
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p MyPlugin) Domains() []string {
|
func (p *MyPlugin) Name() string {
|
||||||
return []string{"myplugin.local"}
|
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.SendString("Welcome to MyPlugin!")
|
return c.Status(fiber.StatusTeapot).SendString("Welcome to MyPlugin!")
|
||||||
})
|
})
|
||||||
|
|
||||||
router.Get("/hello", func(c fiber.Ctx) error {
|
router.Get("/hello", func(c fiber.Ctx) error {
|
||||||
|
|||||||
@@ -5,8 +5,7 @@
|
|||||||
"author": "you",
|
"author": "you",
|
||||||
"license": "BSL-1.0",
|
"license": "BSL-1.0",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "go build -buildmode=plugin -o ../plugs/example.so main.go",
|
"build": "go build -buildmode=plugin -o plugin.so main.go"
|
||||||
"dev": "go run main.go"
|
|
||||||
},
|
},
|
||||||
"pattern": "**/*.go",
|
"pattern": "**/*.go",
|
||||||
"excluded_dirs": []
|
"excluded_dirs": []
|
||||||
|
|||||||
5
schema.sql
Normal file
5
schema.sql
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
CREATE TABLE IF NOT EXISTS plugins (
|
||||||
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
|
path TEXT NOT NULL,
|
||||||
|
domains TEXT NOT NULL
|
||||||
|
);
|
||||||
@@ -5,8 +5,8 @@
|
|||||||
"author": "you",
|
"author": "you",
|
||||||
"license": "BSL-1.0",
|
"license": "BSL-1.0",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "go build",
|
"build": "sh -c \"cd gloomi; zqdgr build\" && go build",
|
||||||
"dev": "go run main.go"
|
"dev": "sh -c \"cd gloomi; zqdgr build\" && go run main.go"
|
||||||
},
|
},
|
||||||
"pattern": "**/*.go",
|
"pattern": "**/*.go",
|
||||||
"excluded_dirs": []
|
"excluded_dirs": []
|
||||||
|
|||||||
Reference in New Issue
Block a user