mirror of
https://github.com/akyaiy/GoSally-mvp.git
synced 2026-01-03 19:32:26 +00:00
Compare commits
13 Commits
54eb5eec6a
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| d4413c433f | |||
| d9a4bb7871 | |||
| 8e017af3ed | |||
| ef6023330d | |||
| 5474b22fc8 | |||
| 6cd678d9f1 | |||
| 856d3b418c | |||
| 5734ca7a67 | |||
| 608c5aed4a | |||
|
|
d4d04115f3 | ||
|
|
4b916f4fc9 | ||
| 54cc496c39 | |||
| f7b0014a37 |
26
Makefile
26
Makefile
@@ -55,30 +55,22 @@ pure-run:
|
|||||||
exec ./$(BIN_DIR)/$(APP_NAME)
|
exec ./$(BIN_DIR)/$(APP_NAME)
|
||||||
|
|
||||||
test:
|
test:
|
||||||
@go test ./... | grep -v '^?' || true
|
@cd src && go test ./... | grep -v '^?' || true
|
||||||
|
|
||||||
fmt:
|
fmt:
|
||||||
@go fmt ./internal/./...
|
@cd src && go fmt .
|
||||||
@go fmt ./cmd/./...
|
@cd src && $(GOPATH)/bin/goimports -w .
|
||||||
@go fmt ./hooks/./...
|
|
||||||
@$(GOPATH)/bin/goimports -w ./internal/
|
|
||||||
@$(GOPATH)/bin/goimports -w ./cmd/
|
|
||||||
@$(GOPATH)/bin/goimports -w ./hooks/
|
|
||||||
|
|
||||||
vet:
|
vet:
|
||||||
@go vet ./...
|
@cd src && go vet ./...
|
||||||
|
|
||||||
lint:
|
|
||||||
@$(GOPATH)/bin/golangci-lint run
|
|
||||||
|
|
||||||
check: fmt vet lint test
|
check: fmt vet lint test
|
||||||
|
lint:
|
||||||
licenses:
|
@cd src && $(GOPATH)/bin/golangci-lint run ./...
|
||||||
@$(GOPATH)/bin/go-licenses save ./... --save_path=third_party/licenses --force
|
@$(GOPATH)/bin/go-licenses save ./... --save_path=third_party/licenses --force
|
||||||
@echo "Licenses have been exported to third_party/licenses"
|
@echo "Licenses have been exported to third_party/licenses"
|
||||||
|
|
||||||
clean:
|
licenses:
|
||||||
@rm -rf bin
|
@cd src && $(GOPATH)/bin/go-licenses save ./... --save_path=../third_party/licenses --force
|
||||||
|
@echo "Licenses have been exported to third_party/licenses"
|
||||||
help:
|
help:
|
||||||
@echo "Available commands: $$(cat Makefile | grep -E '^[a-zA-Z_-]+:.*?' | grep -v -- '-setup:' | sed 's/:.*//g' | sort | uniq | tr '\n' ' ')"
|
@echo "Available commands: $$(cat Makefile | grep -E '^[a-zA-Z_-]+:.*?' | grep -v -- '-setup:' | sed 's/:.*//g' | sort | uniq | tr '\n' ' ')"
|
||||||
|
|||||||
78
README.md
78
README.md
@@ -1,11 +1,39 @@
|
|||||||
# Go Sally MVP (Minimum/Minimal Viable Product)
|
# Go Sally MVP (Minimum/Minimal Viable Product)
|
||||||
|
[]()
|
||||||
|
[](https://go.dev/)
|
||||||
|
[](https://www.lua.org/manual/5.1/)
|
||||||
|
[](https://pkg.go.dev/github.com/akyaiy/GoSally-mvp)
|
||||||
|
[](LICENSE)
|
||||||
|
|
||||||
|
[]()
|
||||||
|
[]()
|
||||||
|
[](https://github.com/akyaiy/GoSally-mvp/wiki)
|
||||||
|
|
||||||
|
|
||||||
|
> ⚡ **What, Why, Why Care?**
|
||||||
|
|
||||||
|
> **What:** Go Sally is a lightweight decentralized node system with Lua scripting and JSON-RPC2.0.
|
||||||
|
|
||||||
|
> **Why:** Large admin tools are too heavy, and Raspberry Pi and small servers require a lightweight, modular architecture.
|
||||||
|
|
||||||
|
> **Why Care:** Create, automate, and expand your infrastructure quickly, without unnecessary software or dependencies.
|
||||||
|
|
||||||
|
## Navigation
|
||||||
|
* [Core features](#core-features)
|
||||||
|
* [Quick start](#quick-start)
|
||||||
|
* [Test it](#test-it)
|
||||||
|
* [Concept](#concept)
|
||||||
|
* [API](#api)
|
||||||
|
* [License](#license)
|
||||||
|
* [Wiki →](https://github.com/akyaiy/GoSally-mvp/wiki)
|
||||||
|
|
||||||
> [!NOTE]
|
> [!NOTE]
|
||||||
> If you see "💡" in the text, it means the information below is about plans for the future of the project.
|
> If you see "💡" in the text, it means the information below is about plans for the future of the project.
|
||||||
|
|
||||||
### Features
|
## Core features
|
||||||
- **Decentralized nodes**<details>this means that *multiple GS[^1] nodes can be located on a single machine*, provided no attempt is made to disrupt, sabotage, or bypass the built-in protection mechanism against running a node under the same identifier as one already running in the system. Identification plays a role in node communication. 💡 In the future, we plan to create tools for conveniently building distributed systems using node identification.</details>
|
- **Decentralized nodes**<details>this means that *multiple GS[^1] nodes can be located on a single machine*, provided no attempt is made to disrupt, sabotage, or bypass the built-in protection mechanism against running a node under the same identifier as one already running in the system. Identification plays a role in node communication. 💡 In the future, we plan to create tools for conveniently building distributed systems using node identification.
|
||||||
- **RPC request processing**<details>the GS operates *using HTTP/https and the JSONRPC2.0 protocol.* Unlike gRPC, jsonrpc is extremely simple, allows for easy sending of requests from the browser, and does not require any additional code compilation.</details>
|
**Why Care?** Multiple nodes on one machine allow testing, experimentation, and scaling small infrastructures without extra hardware or complex setup.</details>
|
||||||
|
- **RPC request processing**<details>the GS operates *using HTTP/https and the JSONRPC2.0 protocol.* Unlike gRPC, jsonrpc is extremely simple, allows for easy sending of requests from the browser, and does not require any additional code compilation. **Why Care?** Easy-to-use API means you can control nodes from anywhere, including lightweight web clients, without compiling extra code.</details>
|
||||||
- **Lua script-based methods**<details>*The gopher-lua library is used, providing full support for Lua 5.1.* scripts implement libraries for interacting with sessions (receiving parameters and sending responses), hashing, logging, and more. This allows you to quickly write business logic on the fly without touching the lower layers of abstraction, which also eliminates unnecessary compilation and the risk of breaking the codebase.
|
- **Lua script-based methods**<details>*The gopher-lua library is used, providing full support for Lua 5.1.* scripts implement libraries for interacting with sessions (receiving parameters and sending responses), hashing, logging, and more. This allows you to quickly write business logic on the fly without touching the lower layers of abstraction, which also eliminates unnecessary compilation and the risk of breaking the codebase.
|
||||||
Example of the "echo" method:
|
Example of the "echo" method:
|
||||||
```lua
|
```lua
|
||||||
@@ -15,15 +43,50 @@
|
|||||||
session.response.send(session.request.params.get())
|
session.response.send(session.request.params.get())
|
||||||
-- send everything passed in the parameters in response.
|
-- send everything passed in the parameters in response.
|
||||||
```
|
```
|
||||||
|
**Why Care?** You can extend node behavior dynamically, write custom logic fast, and iterate without recompiling — perfect for experiments or automation.
|
||||||
</details>
|
</details>
|
||||||
- **Relatively flexible configuration** <details>
|
- **Relatively flexible configuration** <details>
|
||||||
you can configure the server port, address, name, node settings, and more. 💡 More settings are planned in the future.</details>
|
you can configure the server port, address, name, node settings, and more. 💡 More settings are planned in the future. **Why Care?** Configure nodes for any environment, from Raspberry Pi to VPS, without touching the source code. obviously :)</details>
|
||||||
- ***And more in the future***
|
- ***And more in the future***
|
||||||
|
|
||||||
> [!IMPORTANT]
|
> [!IMPORTANT]
|
||||||
> This is the beginning of the project's development, and some aspects of it may be unstable, unfinished, and the text about it may be overly ambitious. It's just a matter of time.
|
> This is the beginning of the project's development, and some aspects of it may be unstable, unfinished, and the text about it may be overly ambitious. It's just a matter of time.
|
||||||
|
|
||||||
## Why?
|
## Quick start
|
||||||
|
```bash
|
||||||
|
git clone https://github.com/akyaiy/GoSally-mvp.git && \
|
||||||
|
cd GoSally-mvp && \
|
||||||
|
make build && \
|
||||||
|
echo -e "node:\n com_dir: \"%path%/com\"" > config.yaml && \
|
||||||
|
mkdir -p com && \
|
||||||
|
echo -e 'local session = require("internal.session")\n\nsession.response.send(session.request.params.get())' > com/echo.lua && \
|
||||||
|
./bin/node run
|
||||||
|
```
|
||||||
|
|
||||||
|
If you have problems, make sure you have all [dependencies](https://github.com/akyaiy/GoSally-mvp/wiki/Getting-started#installing-dependencies) installed, otherwise [file an issue report](https://github.com/akyaiy/GoSally-mvp/issues)
|
||||||
|
|
||||||
|
### Test it
|
||||||
|
```bash
|
||||||
|
curl -X POST http://localhost:8080/com \
|
||||||
|
-d '{"jsonrpc":"2.0","context-version": "v1","method":"echo","params":["Hi!!"],"id":1}'
|
||||||
|
```
|
||||||
|
Expected response:
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"jsonrpc": "2.0",
|
||||||
|
"id": 1,
|
||||||
|
"result": [
|
||||||
|
"Hi!!"
|
||||||
|
],
|
||||||
|
"data": {
|
||||||
|
"responsible-node": "a0e1c440473ffd4d87e32cff2717f5b3",
|
||||||
|
"salt": "f26df732-a3be-4400-8e71-b8dc3ba705fc",
|
||||||
|
"checksum-md5": "cd8bec6a365d1b8ee90773567cb3ad0a"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Concept
|
||||||
The project was originally conceived as a tool for building infrastructure using relatively *small nodes with limited functionality*. 💡 In the future, we plan to create a *web interface for interacting with nodes, administration, and configuration*. The concept is simple: suppose we have a node that manages Bind9. It has all the necessary methods for interacting with the service: creating new zones, viewing zone status, changing configuration, and server operation status. All of this works only through manual configuration, with the exception of larger solutions like Webmin and the BIND DNS Server module. The big problem is that while we only needed web configuration for Bind9, we have to pull in a massive amount of software just to implement one module. What if the service is hosted on a low-power Raspberry Pi? That's where GS nodes come in. By default, GS nodes communicate only through API calls, so 💡 in the future, we plan to create a dedicated, also programmable, web node that will provide convenient access to node management.
|
The project was originally conceived as a tool for building infrastructure using relatively *small nodes with limited functionality*. 💡 In the future, we plan to create a *web interface for interacting with nodes, administration, and configuration*. The concept is simple: suppose we have a node that manages Bind9. It has all the necessary methods for interacting with the service: creating new zones, viewing zone status, changing configuration, and server operation status. All of this works only through manual configuration, with the exception of larger solutions like Webmin and the BIND DNS Server module. The big problem is that while we only needed web configuration for Bind9, we have to pull in a massive amount of software just to implement one module. What if the service is hosted on a low-power Raspberry Pi? That's where GS nodes come in. By default, GS nodes communicate only through API calls, so 💡 in the future, we plan to create a dedicated, also programmable, web node that will provide convenient access to node management.
|
||||||
|
|
||||||
There's an obvious advantage here: transparency. The project is *completely open source and aims to support community-driven node functionality*. 💡 In the future, we plan to create a "store" similar to Docker Hub, which will contain scripts for configuring bind9, openvpn, and even custom projects.
|
There's an obvious advantage here: transparency. The project is *completely open source and aims to support community-driven node functionality*. 💡 In the future, we plan to create a "store" similar to Docker Hub, which will contain scripts for configuring bind9, openvpn, and even custom projects.
|
||||||
@@ -63,4 +126,7 @@ In the result field, we see the echo method's response. Those familiar with the
|
|||||||
| `salt` | string | Random value for each request — can be used to check that the response is unique |
|
| `salt` | string | Random value for each request — can be used to check that the response is unique |
|
||||||
| `checksum-md5` | string | MD5 hash of the result field — can be used to avoid processing identical results separately |
|
| `checksum-md5` | string | MD5 hash of the result field — can be used to avoid processing identical results separately |
|
||||||
|
|
||||||
[^1]: Go Sally
|
## License
|
||||||
|
Distributed under the BSD 3-Clause License. See [`LICENSE`](./LICENSE) for more information.
|
||||||
|
|
||||||
|
[^1]: Go Sally
|
||||||
|
|||||||
22
com/Access/_common.lua
Normal file
22
com/Access/_common.lua
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
-- File com/Access/_common.lua
|
||||||
|
--
|
||||||
|
-- Created at 2025-21-10
|
||||||
|
--
|
||||||
|
-- Description:
|
||||||
|
--- Common functions for Unit module
|
||||||
|
|
||||||
|
local common = {}
|
||||||
|
|
||||||
|
function common.CheckMissingElement(arr, cmp)
|
||||||
|
local is_missing = {}
|
||||||
|
local ok = true
|
||||||
|
for _, key in ipairs(arr) do
|
||||||
|
if cmp[key] == nil then
|
||||||
|
table.insert(is_missing, key)
|
||||||
|
ok = false
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return ok, is_missing
|
||||||
|
end
|
||||||
|
|
||||||
|
return common
|
||||||
30
com/Access/_errors.lua
Normal file
30
com/Access/_errors.lua
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
-- File com/Access/_errors.lua
|
||||||
|
--
|
||||||
|
-- Created at 2025-21-10
|
||||||
|
-- Description:
|
||||||
|
--- Centralized error definitions for Access operations
|
||||||
|
--- to keep API responses consistent and clean.
|
||||||
|
|
||||||
|
local errors = {
|
||||||
|
-- Common validation
|
||||||
|
MISSING_PARAMS = { code = -32602, message = "Missing params" },
|
||||||
|
INVALID_FIELD_TYPE = { code = -32602, message = "'fields' must be a non-empty table" },
|
||||||
|
INVALID_BY_PARAM = { code = -32602, message = "Invalid 'by' param" },
|
||||||
|
NO_VALID_FIELDS = { code = -32604, message = "No valid fields to update" },
|
||||||
|
|
||||||
|
-- Existence / duplication
|
||||||
|
UNIT_NOT_FOUND = { code = -32102, message = "Unit is not exists" },
|
||||||
|
UNIT_EXISTS = { code = -32101, message = "Unit is already exists" },
|
||||||
|
|
||||||
|
-- Database & constraint
|
||||||
|
UNIQUE_CONSTRAINT = { code = -32602, message = "Unique constraint failed" },
|
||||||
|
DB_QUERY_FAILED = { code = -32001, message = "Database query failed" },
|
||||||
|
DB_EXEC_FAILED = { code = -32002, message = "Database execution failed" },
|
||||||
|
DB_INSERT_FAILED = { code = -32003, message = "Failed to create unit" },
|
||||||
|
DB_DELETE_FAILED = { code = -32004, message = "Failed to delete unit" },
|
||||||
|
|
||||||
|
-- Generic fallback
|
||||||
|
UNKNOWN = { code = -32099, message = "Unexpected internal error" },
|
||||||
|
}
|
||||||
|
|
||||||
|
return errors
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
// The cmd package is the main package where all the main hooks and methods are called.
|
// The cmd package is the main package where all the main hooks and methods are called.
|
||||||
// GoSally uses spf13/cobra to organize all the calls.
|
// GoSally uses spf13/cobra to organize all the calls.
|
||||||
package cmd
|
package cmd
|
||||||
|
|
||||||
@@ -24,7 +24,7 @@ scripts in a given directory. For more information, visit: https://gosally.oblat
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
// Execute prepares global log, loads cmdline args
|
// Execute prepares global log, loads cmdline args
|
||||||
// and executes rootCmd.Execute()
|
// and executes rootCmd.Execute()
|
||||||
func Execute() {
|
func Execute() {
|
||||||
log.SetOutput(os.Stdout)
|
log.SetOutput(os.Stdout)
|
||||||
|
|||||||
@@ -85,7 +85,7 @@ func InitUUIDHook(_ context.Context, cs *corestate.CoreState, x *app.AppX) {
|
|||||||
corestate.NODE_UUID = uuid32
|
corestate.NODE_UUID = uuid32
|
||||||
}
|
}
|
||||||
|
|
||||||
// The hook is responsible for checking the initialization stage
|
// The hook is responsible for checking the initialization stage
|
||||||
// and restarting in some cases
|
// and restarting in some cases
|
||||||
func InitRuntimeHook(_ context.Context, cs *corestate.CoreState, x *app.AppX) {
|
func InitRuntimeHook(_ context.Context, cs *corestate.CoreState, x *app.AppX) {
|
||||||
if *x.Config.Env.ParentStagePID != os.Getpid() {
|
if *x.Config.Env.ParentStagePID != os.Getpid() {
|
||||||
@@ -138,7 +138,7 @@ func InitRuntimeHook(_ context.Context, cs *corestate.CoreState, x *app.AppX) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// post-init stage
|
// post-init stage
|
||||||
// The hook creates a run.lock file, which contains information
|
// The hook creates a run.lock file, which contains information
|
||||||
// about the process and the node, in the runtime directory.
|
// about the process and the node, in the runtime directory.
|
||||||
func InitRunlockHook(_ context.Context, cs *corestate.CoreState, x *app.AppX) {
|
func InitRunlockHook(_ context.Context, cs *corestate.CoreState, x *app.AppX) {
|
||||||
NodeApp.Fallback(func(ctx context.Context, cs *corestate.CoreState, x *app.AppX) {
|
NodeApp.Fallback(func(ctx context.Context, cs *corestate.CoreState, x *app.AppX) {
|
||||||
@@ -192,7 +192,7 @@ func InitRunlockHook(_ context.Context, cs *corestate.CoreState, x *app.AppX) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// The hook reads the configuration and replaces special expressions
|
// The hook reads the configuration and replaces special expressions
|
||||||
// (%tmp% and so on) in string fields with the required data.
|
// (%tmp% and so on) in string fields with the required data.
|
||||||
func InitConfigReplHook(_ context.Context, cs *corestate.CoreState, x *app.AppX) {
|
func InitConfigReplHook(_ context.Context, cs *corestate.CoreState, x *app.AppX) {
|
||||||
if !slices.Contains(*x.Config.Conf.DisableWarnings, "--WNonStdTmpDir") && os.TempDir() != "/tmp" {
|
if !slices.Contains(*x.Config.Conf.DisableWarnings, "--WNonStdTmpDir") && os.TempDir() != "/tmp" {
|
||||||
@@ -218,7 +218,7 @@ func InitConfigReplHook(_ context.Context, cs *corestate.CoreState, x *app.AppX)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// The hook is responsible for outputting the
|
// The hook is responsible for outputting the
|
||||||
// final config and asking for confirmation.
|
// final config and asking for confirmation.
|
||||||
func InitConfigPrintHook(ctx context.Context, cs *corestate.CoreState, x *app.AppX) {
|
func InitConfigPrintHook(ctx context.Context, cs *corestate.CoreState, x *app.AppX) {
|
||||||
if *x.Config.Conf.Node.ShowConfig {
|
if *x.Config.Conf.Node.ShowConfig {
|
||||||
@@ -234,7 +234,11 @@ func InitConfigPrintHook(ctx context.Context, cs *corestate.CoreState, x *app.Ap
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
x.Log.Printf("Starting \"%s\" node", *x.Config.Conf.Node.Name)
|
if *x.Config.Conf.Node.Name == "noname" {
|
||||||
|
x.Log.Printf("Starting node")
|
||||||
|
} else {
|
||||||
|
x.Log.Printf("Starting \"%s\" node", *x.Config.Conf.Node.Name)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func InitSLogHook(_ context.Context, cs *corestate.CoreState, x *app.AppX) {
|
func InitSLogHook(_ context.Context, cs *corestate.CoreState, x *app.AppX) {
|
||||||
@@ -250,7 +254,7 @@ func InitSLogHook(_ context.Context, cs *corestate.CoreState, x *app.AppX) {
|
|||||||
*x.SLog = *newSlog
|
*x.SLog = *newSlog
|
||||||
}
|
}
|
||||||
|
|
||||||
// The method goes through the entire config structure through
|
// The method goes through the entire config structure through
|
||||||
// reflection and replaces string fields with the required ones.
|
// reflection and replaces string fields with the required ones.
|
||||||
func processConfig(conf any, replacements map[string]any) error {
|
func processConfig(conf any, replacements map[string]any) error {
|
||||||
val := reflect.ValueOf(conf)
|
val := reflect.ValueOf(conf)
|
||||||
|
|||||||
@@ -11,8 +11,8 @@ import (
|
|||||||
"regexp"
|
"regexp"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/akyaiy/GoSally-mvp/src/internal/core/corestate"
|
|
||||||
"github.com/akyaiy/GoSally-mvp/src/internal/colors"
|
"github.com/akyaiy/GoSally-mvp/src/internal/colors"
|
||||||
|
"github.com/akyaiy/GoSally-mvp/src/internal/core/corestate"
|
||||||
"github.com/akyaiy/GoSally-mvp/src/internal/core/run_manager"
|
"github.com/akyaiy/GoSally-mvp/src/internal/core/run_manager"
|
||||||
"github.com/akyaiy/GoSally-mvp/src/internal/core/update"
|
"github.com/akyaiy/GoSally-mvp/src/internal/core/update"
|
||||||
"github.com/akyaiy/GoSally-mvp/src/internal/core/utils"
|
"github.com/akyaiy/GoSally-mvp/src/internal/core/utils"
|
||||||
@@ -22,6 +22,7 @@ import (
|
|||||||
"github.com/akyaiy/GoSally-mvp/src/internal/server/gateway"
|
"github.com/akyaiy/GoSally-mvp/src/internal/server/gateway"
|
||||||
"github.com/akyaiy/GoSally-mvp/src/internal/server/session"
|
"github.com/akyaiy/GoSally-mvp/src/internal/server/session"
|
||||||
"github.com/akyaiy/GoSally-mvp/src/internal/server/sv1"
|
"github.com/akyaiy/GoSally-mvp/src/internal/server/sv1"
|
||||||
|
"github.com/akyaiy/GoSally-mvp/src/internal/server/sv2"
|
||||||
"github.com/go-chi/chi/v5"
|
"github.com/go-chi/chi/v5"
|
||||||
"github.com/go-chi/cors"
|
"github.com/go-chi/cors"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
@@ -65,13 +66,20 @@ func RunHook(ctx context.Context, cs *corestate.CoreState, x *app.AppX) error {
|
|||||||
Ver: "v1",
|
Ver: "v1",
|
||||||
})
|
})
|
||||||
|
|
||||||
|
sv2 := sv2.InitServer(&sv2.HandlerInitStruct{
|
||||||
|
X: x,
|
||||||
|
CS: cs,
|
||||||
|
AllowedCmd: regexp.MustCompile(AllowedCmdPattern),
|
||||||
|
Ver: "v2",
|
||||||
|
})
|
||||||
|
|
||||||
session_manager := session.New(*x.Config.Conf.HTTPServer.SessionTTL)
|
session_manager := session.New(*x.Config.Conf.HTTPServer.SessionTTL)
|
||||||
|
|
||||||
s := gateway.InitGateway(&gateway.GatewayServerInit{
|
s := gateway.InitGateway(&gateway.GatewayServerInit{
|
||||||
SM: session_manager,
|
SM: session_manager,
|
||||||
CS: cs,
|
CS: cs,
|
||||||
X: x,
|
X: x,
|
||||||
}, serverv1)
|
}, serverv1, sv2)
|
||||||
|
|
||||||
r := chi.NewRouter()
|
r := chi.NewRouter()
|
||||||
r.Use(cors.Handler(cors.Options{
|
r.Use(cors.Handler(cors.Options{
|
||||||
|
|||||||
@@ -61,6 +61,7 @@ func (c *Compositor) LoadConf(path string) error {
|
|||||||
v.SetDefault("log.json_format", "false")
|
v.SetDefault("log.json_format", "false")
|
||||||
v.SetDefault("log.level", "info")
|
v.SetDefault("log.level", "info")
|
||||||
v.SetDefault("log.output", "%2%")
|
v.SetDefault("log.output", "%2%")
|
||||||
|
v.SetDefault("disable_warnings", []string{})
|
||||||
|
|
||||||
if err := v.ReadInConfig(); err != nil {
|
if err := v.ReadInConfig(); err != nil {
|
||||||
return fmt.Errorf("error reading config: %w", err)
|
return fmt.Errorf("error reading config: %w", err)
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ func NewLuaPool() *LuaPool {
|
|||||||
pool: sync.Pool{
|
pool: sync.Pool{
|
||||||
New: func() any {
|
New: func() any {
|
||||||
L := lua.NewState()
|
L := lua.NewState()
|
||||||
|
|
||||||
return L
|
return L
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -30,6 +30,6 @@ func (lp *LuaPool) Put(L *lua.LState) {
|
|||||||
L.Close()
|
L.Close()
|
||||||
|
|
||||||
newL := lua.NewState()
|
newL := lua.NewState()
|
||||||
|
|
||||||
lp.pool.Put(newL)
|
lp.pool.Put(newL)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,13 +11,12 @@ import (
|
|||||||
type LuaEngineDeps struct {
|
type LuaEngineDeps struct {
|
||||||
HttpRequest *http.Request
|
HttpRequest *http.Request
|
||||||
JSONRPCRequest *rpc.RPCRequest
|
JSONRPCRequest *rpc.RPCRequest
|
||||||
SessionUUID string
|
SessionUUID string
|
||||||
ScriptPath string
|
ScriptPath string
|
||||||
}
|
}
|
||||||
|
|
||||||
type LuaEngineContract interface {
|
type LuaEngineContract interface {
|
||||||
Handle(deps *LuaEngineDeps) *rpc.RPCResponse
|
Handle(deps *LuaEngineDeps) *rpc.RPCResponse
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type LuaEngine struct {
|
type LuaEngine struct {
|
||||||
|
|||||||
@@ -80,10 +80,10 @@ func loadDBMod(llog *slog.Logger, sid string) func(*lua.LState) int {
|
|||||||
|
|
||||||
mt := L.NewTypeMetatable("gosally_db")
|
mt := L.NewTypeMetatable("gosally_db")
|
||||||
L.SetField(mt, "__index", L.SetFuncs(L.NewTable(), map[string]lua.LGFunction{
|
L.SetField(mt, "__index", L.SetFuncs(L.NewTable(), map[string]lua.LGFunction{
|
||||||
"exec": dbExec,
|
"exec": dbExec,
|
||||||
"query": dbQuery,
|
"query": dbQuery,
|
||||||
"query_row": dbQueryRow,
|
"query_row": dbQueryRow,
|
||||||
"close": dbClose,
|
"close": dbClose,
|
||||||
}))
|
}))
|
||||||
|
|
||||||
L.SetField(dbMod, "__seed", lua.LString(sid))
|
L.SetField(dbMod, "__seed", lua.LString(sid))
|
||||||
@@ -215,43 +215,43 @@ func dbExec(L *lua.LState) int {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func dbQueryRow(L *lua.LState) int {
|
func dbQueryRow(L *lua.LState) int {
|
||||||
ud := L.CheckUserData(1)
|
ud := L.CheckUserData(1)
|
||||||
conn, ok := ud.Value.(*DBConnection)
|
conn, ok := ud.Value.(*DBConnection)
|
||||||
if !ok {
|
if !ok {
|
||||||
L.Push(lua.LNil)
|
L.Push(lua.LNil)
|
||||||
L.Push(lua.LString("invalid database connection"))
|
L.Push(lua.LString("invalid database connection"))
|
||||||
return 2
|
return 2
|
||||||
}
|
}
|
||||||
|
|
||||||
query := L.CheckString(2)
|
query := L.CheckString(2)
|
||||||
|
|
||||||
var args []any
|
var args []any
|
||||||
if L.GetTop() >= 3 {
|
if L.GetTop() >= 3 {
|
||||||
params := L.CheckTable(3)
|
params := L.CheckTable(3)
|
||||||
params.ForEach(func(k lua.LValue, v lua.LValue) {
|
params.ForEach(func(k lua.LValue, v lua.LValue) {
|
||||||
args = append(args, ConvertLuaTypesToGolang(v))
|
args = append(args, ConvertLuaTypesToGolang(v))
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
if conn.log {
|
if conn.log {
|
||||||
conn.logger.Info("DB QueryRow",
|
conn.logger.Info("DB QueryRow",
|
||||||
slog.String("query", query),
|
slog.String("query", query),
|
||||||
slog.Any("params", args))
|
slog.Any("params", args))
|
||||||
}
|
}
|
||||||
|
|
||||||
mtx := getDBMutex(conn.dbPath)
|
mtx := getDBMutex(conn.dbPath)
|
||||||
mtx.RLock()
|
mtx.RLock()
|
||||||
defer mtx.RUnlock()
|
defer mtx.RUnlock()
|
||||||
|
|
||||||
db, err := sql.Open("sqlite", conn.dbPath+"?_busy_timeout=5000&_journal_mode=WAL&_sync=NORMAL&_cache_size=-10000")
|
db, err := sql.Open("sqlite", conn.dbPath+"?_busy_timeout=5000&_journal_mode=WAL&_sync=NORMAL&_cache_size=-10000")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
L.Push(lua.LNil)
|
L.Push(lua.LNil)
|
||||||
L.Push(lua.LString(err.Error()))
|
L.Push(lua.LString(err.Error()))
|
||||||
return 2
|
return 2
|
||||||
}
|
}
|
||||||
defer db.Close()
|
defer db.Close()
|
||||||
|
|
||||||
row := db.QueryRow(query, args...)
|
row := db.QueryRow(query, args...)
|
||||||
|
|
||||||
columns := []string{}
|
columns := []string{}
|
||||||
stmt, err := db.Prepare(query)
|
stmt, err := db.Prepare(query)
|
||||||
@@ -278,36 +278,36 @@ func dbQueryRow(L *lua.LState) int {
|
|||||||
columns = append(columns, c)
|
columns = append(columns, c)
|
||||||
}
|
}
|
||||||
|
|
||||||
colCount := len(columns)
|
colCount := len(columns)
|
||||||
values := make([]any, colCount)
|
values := make([]any, colCount)
|
||||||
valuePtrs := make([]any, colCount)
|
valuePtrs := make([]any, colCount)
|
||||||
for i := range columns {
|
for i := range columns {
|
||||||
valuePtrs[i] = &values[i]
|
valuePtrs[i] = &values[i]
|
||||||
}
|
}
|
||||||
|
|
||||||
err = row.Scan(valuePtrs...)
|
err = row.Scan(valuePtrs...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if err == sql.ErrNoRows {
|
if err == sql.ErrNoRows {
|
||||||
L.Push(lua.LNil)
|
L.Push(lua.LNil)
|
||||||
return 1
|
return 1
|
||||||
}
|
}
|
||||||
L.Push(lua.LNil)
|
L.Push(lua.LNil)
|
||||||
L.Push(lua.LString(fmt.Sprintf("scan failed: %v", err)))
|
L.Push(lua.LString(fmt.Sprintf("scan failed: %v", err)))
|
||||||
return 2
|
return 2
|
||||||
}
|
}
|
||||||
|
|
||||||
rowTable := L.NewTable()
|
rowTable := L.NewTable()
|
||||||
for i, col := range columns {
|
for i, col := range columns {
|
||||||
val := values[i]
|
val := values[i]
|
||||||
if val == nil {
|
if val == nil {
|
||||||
L.SetField(rowTable, col, lua.LNil)
|
L.SetField(rowTable, col, lua.LNil)
|
||||||
} else {
|
} else {
|
||||||
L.SetField(rowTable, col, ConvertGolangTypesToLua(L, val))
|
L.SetField(rowTable, col, ConvertGolangTypesToLua(L, val))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
L.Push(rowTable)
|
L.Push(rowTable)
|
||||||
return 1
|
return 1
|
||||||
}
|
}
|
||||||
|
|
||||||
func dbQuery(L *lua.LState) int {
|
func dbQuery(L *lua.LState) int {
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import (
|
|||||||
"github.com/akyaiy/GoSally-mvp/src/internal/engine/app"
|
"github.com/akyaiy/GoSally-mvp/src/internal/engine/app"
|
||||||
)
|
)
|
||||||
|
|
||||||
var SV1Version = "null"
|
var SV1Version = "v1"
|
||||||
|
|
||||||
// HandlerV1InitStruct structure is only for initialization
|
// HandlerV1InitStruct structure is only for initialization
|
||||||
type HandlerV1InitStruct struct {
|
type HandlerV1InitStruct struct {
|
||||||
|
|||||||
12
src/internal/server/sv2/handle.go
Normal file
12
src/internal/server/sv2/handle.go
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
package sv2
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/akyaiy/GoSally-mvp/src/internal/server/rpc"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (h *Handler) Handle(_ context.Context, sid string, r *http.Request, req *rpc.RPCRequest) *rpc.RPCResponse {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
43
src/internal/server/sv2/server.go
Normal file
43
src/internal/server/sv2/server.go
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
// SV2 works with binaries, scripts, and anything else that has access to stdin/stdout.
|
||||||
|
// Modules run in a separate process and communicate via I/O.
|
||||||
|
package sv2
|
||||||
|
|
||||||
|
import (
|
||||||
|
"regexp"
|
||||||
|
|
||||||
|
"github.com/akyaiy/GoSally-mvp/src/internal/core/corestate"
|
||||||
|
"github.com/akyaiy/GoSally-mvp/src/internal/engine/app"
|
||||||
|
)
|
||||||
|
|
||||||
|
// HandlerV2InitStruct structure is only for initialization
|
||||||
|
type HandlerInitStruct struct {
|
||||||
|
Ver string
|
||||||
|
CS *corestate.CoreState
|
||||||
|
X *app.AppX
|
||||||
|
AllowedCmd *regexp.Regexp
|
||||||
|
}
|
||||||
|
|
||||||
|
type Handler struct {
|
||||||
|
cs *corestate.CoreState
|
||||||
|
x *app.AppX
|
||||||
|
|
||||||
|
// allowedCmd and listAllowedCmd are regular expressions used to validate command names.
|
||||||
|
allowedCmd *regexp.Regexp
|
||||||
|
|
||||||
|
ver string
|
||||||
|
}
|
||||||
|
|
||||||
|
func InitServer(o *HandlerInitStruct) *Handler {
|
||||||
|
return &Handler{
|
||||||
|
cs: o.CS,
|
||||||
|
x: o.X,
|
||||||
|
allowedCmd: o.AllowedCmd,
|
||||||
|
ver: o.Ver,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetVersion returns the API version of the HandlerV1, which is set during initialization.
|
||||||
|
// This version is used to identify the API version in the request routing.
|
||||||
|
func (h *Handler) GetVersion() string {
|
||||||
|
return h.ver
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user