mirror of
https://github.com/akyaiy/GoSally-mvp.git
synced 2026-01-03 20:52:25 +00:00
Compare commits
52 Commits
cfa7724b68
...
dev
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a6c9e5102f | ||
| a72627d87c | |||
| 4a9719cdfb | |||
| 7de5ec5248 | |||
| e5f9105364 | |||
| ce2a23f9e6 | |||
| d56b022bf5 | |||
| ca38c10ec4 | |||
| 13dbd00bb7 | |||
| e7289dc9be | |||
| 5394178abc | |||
| 981551e944 | |||
| 27446adf3f | |||
| 2f071c25b2 | |||
| d23fd32e84 | |||
| 86d35a9ede | |||
| c77d51a95c | |||
| 3cbea14e84 | |||
| 6e59af1662 | |||
| 8684d178e0 | |||
| 945ab6c9cf | |||
| 520901c331 | |||
| 9a274250cd | |||
| 6d49d83ea7 | |||
| fb04b3bc46 | |||
| a60b75a4c0 | |||
| 041fda8522 | |||
| 6508f03d08 | |||
| 93cf53025c | |||
| 83912b6c28 | |||
| 6ed5a7f9e0 | |||
| 2f78e9367c | |||
| ac074ce0ff | |||
| 8bdf9197d6 | |||
| 4db8fa2360 | |||
| 2a48927a08 | |||
| 58027bb988 | |||
| 30a87fdb4c | |||
| 5cdfb2a543 | |||
| 08e96aa32a | |||
| 3b8390a0c8 | |||
| b6ad0f82a0 | |||
| 7009828e79 | |||
| 45e541ac00 | |||
| a5a7354061 | |||
| 20bb90e77a | |||
| 148ca53538 | |||
| 2951fd2da9 | |||
| f411637520 | |||
| 75ee6e10aa | |||
| f3c4b9e9b1 | |||
|
|
81359c036c |
2
.gitignore
vendored
2
.gitignore
vendored
@@ -5,6 +5,8 @@ tmp/
|
|||||||
.meta/
|
.meta/
|
||||||
db/
|
db/
|
||||||
|
|
||||||
|
com/test.lua
|
||||||
|
|
||||||
.vscode
|
.vscode
|
||||||
Taskfile.yml
|
Taskfile.yml
|
||||||
config.yaml
|
config.yaml
|
||||||
@@ -6,8 +6,8 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
|
|
||||||
"github.com/akyaiy/GoSally-mvp/hooks"
|
"github.com/akyaiy/GoSally-mvp/hooks"
|
||||||
|
"github.com/akyaiy/GoSally-mvp/internal/colors"
|
||||||
"github.com/akyaiy/GoSally-mvp/internal/core/corestate"
|
"github.com/akyaiy/GoSally-mvp/internal/core/corestate"
|
||||||
"github.com/akyaiy/GoSally-mvp/internal/engine/logs"
|
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -24,7 +24,7 @@ scripts in a given directory. For more information, visit: https://gosally.oblat
|
|||||||
|
|
||||||
func Execute() {
|
func Execute() {
|
||||||
log.SetOutput(os.Stdout)
|
log.SetOutput(os.Stdout)
|
||||||
log.SetPrefix(logs.SetBrightBlack(fmt.Sprintf("(%s) ", corestate.StageNotReady)))
|
log.SetPrefix(colors.SetBrightBlack(fmt.Sprintf("(%s) ", corestate.StageNotReady)))
|
||||||
log.SetFlags(log.Ldate | log.Ltime)
|
log.SetFlags(log.Ldate | log.Ltime)
|
||||||
hooks.Compositor.LoadCMDLine(rootCmd)
|
hooks.Compositor.LoadCMDLine(rootCmd)
|
||||||
_ = rootCmd.Execute()
|
_ = rootCmd.Execute()
|
||||||
|
|||||||
66
com/DB/Put.lua
Normal file
66
com/DB/Put.lua
Normal file
@@ -0,0 +1,66 @@
|
|||||||
|
---@diagnostic disable: redefined-local
|
||||||
|
local db = require("internal.database-sqlite").connect("db/test.db", {log = true})
|
||||||
|
local log = require("internal.log")
|
||||||
|
local session = require("internal.session")
|
||||||
|
|
||||||
|
if not (session.request.params.name and session.request.params.email) then
|
||||||
|
session.response.error = {
|
||||||
|
code = -32602,
|
||||||
|
message = "Name and email are required"
|
||||||
|
}
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
local existing, err = db:query("SELECT 1 FROM users WHERE email = ? LIMIT 1", {
|
||||||
|
session.request.params.email
|
||||||
|
})
|
||||||
|
if err ~= nil then
|
||||||
|
session.response.error = {
|
||||||
|
code = -32603,
|
||||||
|
message = "Database check failed: "..tostring(err)
|
||||||
|
}
|
||||||
|
log.error("Email check failed: "..tostring(err))
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
if existing and #existing > 0 then
|
||||||
|
session.response.error = {
|
||||||
|
code = -32604,
|
||||||
|
message = "Email already exists"
|
||||||
|
}
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
local ctx, err = db:exec(
|
||||||
|
"INSERT INTO users (name, email) VALUES (?, ?)",
|
||||||
|
{
|
||||||
|
session.request.params.name,
|
||||||
|
session.request.params.email
|
||||||
|
}
|
||||||
|
)
|
||||||
|
if err ~= nil then
|
||||||
|
session.response.error = {
|
||||||
|
code = -32605,
|
||||||
|
message = "Insert failed: "..tostring(err)
|
||||||
|
}
|
||||||
|
log.error("Insert failed: "..tostring(err))
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
local res, err = ctx:wait()
|
||||||
|
if err ~= nil then
|
||||||
|
session.response.error = {
|
||||||
|
code = -32606,
|
||||||
|
message = "Insert confirmation failed: "..tostring(err)
|
||||||
|
}
|
||||||
|
log.error("Insert confirmation failed: "..tostring(err))
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
session.response.result = {
|
||||||
|
success = true,
|
||||||
|
rows_affected = res,
|
||||||
|
message = "User created successfully"
|
||||||
|
}
|
||||||
|
|
||||||
|
db:close()
|
||||||
28
com/List.lua
28
com/List.lua
@@ -1,7 +1,9 @@
|
|||||||
-- com/List.lua
|
-- com/List.lua
|
||||||
|
|
||||||
if In.Params and In.Params.about then
|
local session = require("internal.session")
|
||||||
Out.Result = {
|
|
||||||
|
if session.request and session.request.params and session.request.params.about then
|
||||||
|
session.response.result = {
|
||||||
description = "Returns a list of available methods",
|
description = "Returns a list of available methods",
|
||||||
params = {
|
params = {
|
||||||
layer = "select which layer list to display"
|
layer = "select which layer list to display"
|
||||||
@@ -10,7 +12,7 @@ if In.Params and In.Params.about then
|
|||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
|
||||||
local function isValidCommand(name)
|
local function isValidName(name)
|
||||||
return name:match("^[%w]+$") ~= nil
|
return name:match("^[%w]+$") ~= nil
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -21,8 +23,20 @@ local function scanDirectory(basePath, targetPath)
|
|||||||
|
|
||||||
if handle then
|
if handle then
|
||||||
for filePath in handle:lines() do
|
for filePath in handle:lines() do
|
||||||
local fileName = filePath:match("([^/]+)%.lua$")
|
local parts = {}
|
||||||
if fileName and isValidCommand(fileName) then
|
for part in filePath:gsub(".lua$", ""):gmatch("[^/]+") do
|
||||||
|
table.insert(parts, part)
|
||||||
|
end
|
||||||
|
|
||||||
|
local allValid = true
|
||||||
|
for _, part in ipairs(parts) do
|
||||||
|
if not isValidName(part) then
|
||||||
|
allValid = false
|
||||||
|
break
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
if allValid then
|
||||||
local relPath = filePath:gsub("^"..basePath.."/", ""):gsub(".lua$", ""):gsub("/", ">")
|
local relPath = filePath:gsub("^"..basePath.."/", ""):gsub(".lua$", ""):gsub("/", ">")
|
||||||
table.insert(res, relPath)
|
table.insert(res, relPath)
|
||||||
end
|
end
|
||||||
@@ -34,8 +48,8 @@ local function scanDirectory(basePath, targetPath)
|
|||||||
end
|
end
|
||||||
|
|
||||||
local basePath = "com"
|
local basePath = "com"
|
||||||
local layer = In.Params and In.Params.layer and In.Params.layer:gsub(">", "/") or nil
|
local layer = session.request and session.request.params.layer and session.request.params.layer:gsub(">", "/") or nil
|
||||||
|
|
||||||
Out.Result = {
|
session.response.result = {
|
||||||
answer = layer and scanDirectory(basePath, layer) or scanDirectory(basePath, "")
|
answer = layer and scanDirectory(basePath, layer) or scanDirectory(basePath, "")
|
||||||
}
|
}
|
||||||
29
com/Net/GetExpl.lua
Normal file
29
com/Net/GetExpl.lua
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
local session = require("internal.session")
|
||||||
|
local net = require("internal.net")
|
||||||
|
|
||||||
|
local reqAddr
|
||||||
|
local logReq = true
|
||||||
|
|
||||||
|
if session.request.params and session.request.params.url then
|
||||||
|
reqAddr = session.request.params.url
|
||||||
|
else
|
||||||
|
session.response.error = {
|
||||||
|
code = -32602,
|
||||||
|
message = "no url provided"
|
||||||
|
}
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
local resp = net.http.get_request(logReq, reqAddr)
|
||||||
|
if resp then
|
||||||
|
session.response.result.answer = {
|
||||||
|
status = resp.status,
|
||||||
|
body = resp.body
|
||||||
|
}
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
session.response.error = {
|
||||||
|
data = "error while requesting"
|
||||||
|
}
|
||||||
|
|
||||||
35
com/Net/PostExpl.lua
Normal file
35
com/Net/PostExpl.lua
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
local session = require("internal.session")
|
||||||
|
local net = require("internal.net")
|
||||||
|
local log = require("internal.log")
|
||||||
|
|
||||||
|
local reqAddr
|
||||||
|
local logReq = true
|
||||||
|
local payload
|
||||||
|
|
||||||
|
log.debug(session.request.params)
|
||||||
|
|
||||||
|
if not (session.request.params and session.request.params.url) then
|
||||||
|
session.response.error = {
|
||||||
|
code = -32602,
|
||||||
|
message = "no url or payload provided"
|
||||||
|
}
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
reqAddr = session.request.params.url
|
||||||
|
payload = session.request.params.payload
|
||||||
|
|
||||||
|
local resp = net.http.post_request(logReq, reqAddr, "application/json", payload)
|
||||||
|
if resp then
|
||||||
|
session.response.result.answer = {
|
||||||
|
status = resp.status,
|
||||||
|
body = resp.body
|
||||||
|
}
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
session.response.error = {
|
||||||
|
data = "error while requesting"
|
||||||
|
}
|
||||||
@@ -1,24 +1,54 @@
|
|||||||
---@diagnostic disable: missing-fields, missing-return
|
--@diagnostic disable: missing-fields, missing-return
|
||||||
---@alias AnyTable table<string, any>
|
|
||||||
|
|
||||||
---@type AnyTable
|
---@alias Any any
|
||||||
In = {
|
---@alias AnyTable table<string, Any>
|
||||||
Params = {},
|
|
||||||
}
|
|
||||||
|
|
||||||
---@type AnyTable
|
--- Global session module interface
|
||||||
Out = {
|
---@class SessionIn
|
||||||
Result = {},
|
---@field params AnyTable Request parameters
|
||||||
}
|
|
||||||
|
|
||||||
---@class Log
|
---@class SessionOut
|
||||||
---@field Info fun(msg: string)
|
---@field result Any|string? Result payload (table or primitive)
|
||||||
---@field Debug fun(msg: string)
|
---@field error { code: integer, message: string, data: Any }? Optional error info
|
||||||
---@field Error fun(msg: string)
|
|
||||||
---@field Warn fun(msg: string)
|
|
||||||
---@field Event fun(msg: string)
|
|
||||||
---@field EventError fun(msg: string)
|
|
||||||
---@field EventWarn fun(msg: string)
|
|
||||||
|
|
||||||
---@type Log
|
---@class SessionModule
|
||||||
Log = {}
|
---@field request SessionIn Input context (read-only)
|
||||||
|
---@field response SessionOut Output context (write results/errors)
|
||||||
|
|
||||||
|
--- Global log module interface
|
||||||
|
---@class LogModule
|
||||||
|
---@field info fun(msg: string) Log informational message
|
||||||
|
---@field debug fun(msg: string) Log debug message
|
||||||
|
---@field error fun(msg: string) Log error message
|
||||||
|
---@field warn fun(msg: string) Log warning message
|
||||||
|
---@field event fun(msg: string) Log event (generic)
|
||||||
|
---@field event_error fun(msg: string) Log event error
|
||||||
|
---@field event_warn fun(msg: string) Log event warning
|
||||||
|
|
||||||
|
--- Global net module interface
|
||||||
|
---@class HttpResponse
|
||||||
|
---@field status integer HTTP status code
|
||||||
|
---@field status_text string HTTP status text
|
||||||
|
---@field body string Response body
|
||||||
|
---@field content_length integer Content length
|
||||||
|
---@field headers AnyTable Map of headers
|
||||||
|
|
||||||
|
---@class HttpModule
|
||||||
|
---@field get fun(log: boolean, url: string): HttpResponse, string? Perform GET
|
||||||
|
---@field post fun(log: boolean, url: string, content_type: string, payload: string): HttpResponse, string? Perform POST
|
||||||
|
|
||||||
|
---@class NetModule
|
||||||
|
---@field http HttpModule HTTP client functions
|
||||||
|
|
||||||
|
--- Global variables declaration
|
||||||
|
---@global
|
||||||
|
---@type SessionModule
|
||||||
|
_G.session = session or {}
|
||||||
|
|
||||||
|
---@global
|
||||||
|
---@type LogModule
|
||||||
|
_G.log = log or {}
|
||||||
|
|
||||||
|
---@global
|
||||||
|
---@type NetModule
|
||||||
|
_G.net = net or {}
|
||||||
@@ -1,21 +1,30 @@
|
|||||||
mode: "prod"
|
node:
|
||||||
|
mode: dev
|
||||||
|
name: "My gosally node"
|
||||||
|
show_config: true
|
||||||
|
com_dir: "%path%/com"
|
||||||
|
|
||||||
http_server:
|
http_server:
|
||||||
address: "0.0.0.0:8080"
|
address: "0.0.0.0"
|
||||||
api:
|
port: "8080"
|
||||||
latest-version: v1
|
session_ttl: 5s
|
||||||
layers:
|
timeout: 3s
|
||||||
- b1
|
idle_timeout: 30s
|
||||||
- s2
|
|
||||||
|
|
||||||
tls:
|
tls:
|
||||||
enabled: false
|
enabled: true
|
||||||
cert_file: "./cert/fullchain.pem"
|
cert_file: "%path%/cert/fullchain.pem"
|
||||||
key_file: "./cert/privkey.pem"
|
key_file: "%path%/cert/privkey.pem"
|
||||||
|
|
||||||
com_dir: "com/"
|
|
||||||
|
|
||||||
updates:
|
updates:
|
||||||
enabled: true
|
enabled: false
|
||||||
check-interval: 1h
|
check-interval: 1h
|
||||||
repository_url: "https://repo.serve.lv/raw/go-sally"
|
repository_url: "https://repo.serve.lv/raw/go-sally"
|
||||||
|
|
||||||
|
log:
|
||||||
|
json_format: false
|
||||||
|
level: "debug"
|
||||||
|
|
||||||
|
disable_warnings:
|
||||||
|
- --WNonStdTmpDir
|
||||||
|
- --WUndefLogLevel
|
||||||
3
go.mod
3
go.mod
@@ -4,6 +4,7 @@ go 1.24.4
|
|||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/go-chi/chi/v5 v5.2.2
|
github.com/go-chi/chi/v5 v5.2.2
|
||||||
|
github.com/google/uuid v1.6.0
|
||||||
github.com/spf13/cobra v1.9.1
|
github.com/spf13/cobra v1.9.1
|
||||||
github.com/spf13/viper v1.20.1
|
github.com/spf13/viper v1.20.1
|
||||||
github.com/yuin/gopher-lua v1.1.1
|
github.com/yuin/gopher-lua v1.1.1
|
||||||
@@ -17,7 +18,6 @@ require (
|
|||||||
github.com/dustin/go-humanize v1.0.1 // indirect
|
github.com/dustin/go-humanize v1.0.1 // indirect
|
||||||
github.com/fsnotify/fsnotify v1.9.0 // indirect
|
github.com/fsnotify/fsnotify v1.9.0 // indirect
|
||||||
github.com/go-viper/mapstructure/v2 v2.4.0 // indirect
|
github.com/go-viper/mapstructure/v2 v2.4.0 // indirect
|
||||||
github.com/google/uuid v1.6.0 // indirect
|
|
||||||
github.com/inconshreveable/mousetrap v1.1.0 // indirect
|
github.com/inconshreveable/mousetrap v1.1.0 // indirect
|
||||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||||
github.com/ncruces/go-strftime v0.1.9 // indirect
|
github.com/ncruces/go-strftime v0.1.9 // indirect
|
||||||
@@ -29,7 +29,6 @@ require (
|
|||||||
github.com/spf13/cast v1.9.2 // indirect
|
github.com/spf13/cast v1.9.2 // indirect
|
||||||
github.com/spf13/pflag v1.0.7 // indirect
|
github.com/spf13/pflag v1.0.7 // indirect
|
||||||
github.com/subosito/gotenv v1.6.0 // indirect
|
github.com/subosito/gotenv v1.6.0 // indirect
|
||||||
go.uber.org/multierr v1.11.0 // indirect
|
|
||||||
golang.org/x/exp v0.0.0-20250718183923-645b1fa84792 // indirect
|
golang.org/x/exp v0.0.0-20250718183923-645b1fa84792 // indirect
|
||||||
golang.org/x/sys v0.34.0 // indirect
|
golang.org/x/sys v0.34.0 // indirect
|
||||||
golang.org/x/text v0.27.0 // indirect
|
golang.org/x/text v0.27.0 // indirect
|
||||||
|
|||||||
32
go.sum
32
go.sum
@@ -15,6 +15,8 @@ github.com/go-viper/mapstructure/v2 v2.4.0 h1:EBsztssimR/CONLSZZ04E8qAkxNYq4Qp9L
|
|||||||
github.com/go-viper/mapstructure/v2 v2.4.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM=
|
github.com/go-viper/mapstructure/v2 v2.4.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM=
|
||||||
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
||||||
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||||
|
github.com/google/pprof v0.0.0-20250317173921-a4b03ec1a45e h1:ijClszYn+mADRFY17kjQEVQ1XRhq2/JR1M3sGqeJoxs=
|
||||||
|
github.com/google/pprof v0.0.0-20250317173921-a4b03ec1a45e/go.mod h1:boTsfXsheKC2y+lKOCMpSfarhxDeIzfZG1jqGcPl3cA=
|
||||||
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/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
|
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
|
||||||
@@ -36,12 +38,8 @@ github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qq
|
|||||||
github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8=
|
github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8=
|
||||||
github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
|
github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
|
||||||
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||||
github.com/sagikazarmark/locafero v0.9.0 h1:GbgQGNtTrEmddYDSAH9QLRyfAHY12md+8YFTqyMTC9k=
|
|
||||||
github.com/sagikazarmark/locafero v0.9.0/go.mod h1:UBUyz37V+EdMS3hDF3QWIiVr/2dPrx49OMO0Bn0hJqk=
|
|
||||||
github.com/sagikazarmark/locafero v0.10.0 h1:FM8Cv6j2KqIhM2ZK7HZjm4mpj9NBktLgowT1aN9q5Cc=
|
github.com/sagikazarmark/locafero v0.10.0 h1:FM8Cv6j2KqIhM2ZK7HZjm4mpj9NBktLgowT1aN9q5Cc=
|
||||||
github.com/sagikazarmark/locafero v0.10.0/go.mod h1:Ieo3EUsjifvQu4NZwV5sPd4dwvu0OCgEQV7vjc9yDjw=
|
github.com/sagikazarmark/locafero v0.10.0/go.mod h1:Ieo3EUsjifvQu4NZwV5sPd4dwvu0OCgEQV7vjc9yDjw=
|
||||||
github.com/sourcegraph/conc v0.3.0 h1:OQTbbt6P72L20UqAkXXuLOj79LfEanQ+YQFNpLA9ySo=
|
|
||||||
github.com/sourcegraph/conc v0.3.0/go.mod h1:Sdozi7LEKbFPqYX2/J+iBAM6HpqSLTASQIKqDmF7Mt0=
|
|
||||||
github.com/sourcegraph/conc v0.3.1-0.20240121214520-5f936abd7ae8 h1:+jumHNA0Wrelhe64i8F6HNlS8pkoyMv5sreGx2Ry5Rw=
|
github.com/sourcegraph/conc v0.3.1-0.20240121214520-5f936abd7ae8 h1:+jumHNA0Wrelhe64i8F6HNlS8pkoyMv5sreGx2Ry5Rw=
|
||||||
github.com/sourcegraph/conc v0.3.1-0.20240121214520-5f936abd7ae8/go.mod h1:3n1Cwaq1E1/1lhQhtRK2ts/ZwZEhjcQeJQ1RuC6Q/8U=
|
github.com/sourcegraph/conc v0.3.1-0.20240121214520-5f936abd7ae8/go.mod h1:3n1Cwaq1E1/1lhQhtRK2ts/ZwZEhjcQeJQ1RuC6Q/8U=
|
||||||
github.com/spf13/afero v1.14.0 h1:9tH6MapGnn/j0eb0yIXiLjERO8RB6xIVZRDCX7PtqWA=
|
github.com/spf13/afero v1.14.0 h1:9tH6MapGnn/j0eb0yIXiLjERO8RB6xIVZRDCX7PtqWA=
|
||||||
@@ -61,17 +59,21 @@ github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8
|
|||||||
github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU=
|
github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU=
|
||||||
github.com/yuin/gopher-lua v1.1.1 h1:kYKnWBjvbNP4XLT3+bPEwAXJx262OhaHDWDVOPjL46M=
|
github.com/yuin/gopher-lua v1.1.1 h1:kYKnWBjvbNP4XLT3+bPEwAXJx262OhaHDWDVOPjL46M=
|
||||||
github.com/yuin/gopher-lua v1.1.1/go.mod h1:GBR0iDaNXjAgGg9zfCvksxSRnQx76gclCIb7kdAd1Pw=
|
github.com/yuin/gopher-lua v1.1.1/go.mod h1:GBR0iDaNXjAgGg9zfCvksxSRnQx76gclCIb7kdAd1Pw=
|
||||||
go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
|
|
||||||
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
|
|
||||||
golang.org/x/exp v0.0.0-20250718183923-645b1fa84792 h1:R9PFI6EUdfVKgwKjZef7QIwGcBKu86OEFpJ9nUEP2l4=
|
golang.org/x/exp v0.0.0-20250718183923-645b1fa84792 h1:R9PFI6EUdfVKgwKjZef7QIwGcBKu86OEFpJ9nUEP2l4=
|
||||||
golang.org/x/exp v0.0.0-20250718183923-645b1fa84792/go.mod h1:A+z0yzpGtvnG90cToK5n2tu8UJVP2XUATh+r+sfOOOc=
|
golang.org/x/exp v0.0.0-20250718183923-645b1fa84792/go.mod h1:A+z0yzpGtvnG90cToK5n2tu8UJVP2XUATh+r+sfOOOc=
|
||||||
|
golang.org/x/mod v0.26.0 h1:EGMPT//Ezu+ylkCijjPc+f4Aih7sZvaAr+O3EHBxvZg=
|
||||||
|
golang.org/x/mod v0.26.0/go.mod h1:/j6NAhSk8iQ723BGAUyoAcn7SlD7s15Dp9Nd/SfeaFQ=
|
||||||
golang.org/x/net v0.42.0 h1:jzkYrhi3YQWD6MLBJcsklgQsoAcw89EcZbJw8Z614hs=
|
golang.org/x/net v0.42.0 h1:jzkYrhi3YQWD6MLBJcsklgQsoAcw89EcZbJw8Z614hs=
|
||||||
golang.org/x/net v0.42.0/go.mod h1:FF1RA5d3u7nAYA4z2TkclSCKh68eSXtiFwcWQpPXdt8=
|
golang.org/x/net v0.42.0/go.mod h1:FF1RA5d3u7nAYA4z2TkclSCKh68eSXtiFwcWQpPXdt8=
|
||||||
|
golang.org/x/sync v0.16.0 h1:ycBJEhp9p4vXvUZNszeOq0kGTPghopOL8q0fq3vstxw=
|
||||||
|
golang.org/x/sync v0.16.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
|
||||||
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.34.0 h1:H5Y5sJ2L2JRdyv7ROF1he/lPdvFsd0mJHFw2ThKHxLA=
|
golang.org/x/sys v0.34.0 h1:H5Y5sJ2L2JRdyv7ROF1he/lPdvFsd0mJHFw2ThKHxLA=
|
||||||
golang.org/x/sys v0.34.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
|
golang.org/x/sys v0.34.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
|
||||||
golang.org/x/text v0.27.0 h1:4fGWRpyh641NLlecmyl4LOe6yDdfaYNrGb2zdfo4JV4=
|
golang.org/x/text v0.27.0 h1:4fGWRpyh641NLlecmyl4LOe6yDdfaYNrGb2zdfo4JV4=
|
||||||
golang.org/x/text v0.27.0/go.mod h1:1D28KMCvyooCX9hBiosv5Tz/+YLxj0j7XhWjpSUF7CU=
|
golang.org/x/text v0.27.0/go.mod h1:1D28KMCvyooCX9hBiosv5Tz/+YLxj0j7XhWjpSUF7CU=
|
||||||
|
golang.org/x/tools v0.35.0 h1:mBffYraMEf7aa0sB+NuKnuCy8qI/9Bughn8dC2Gu5r0=
|
||||||
|
golang.org/x/tools v0.35.0/go.mod h1:NKdj5HkL/73byiZSJjqJgKn3ep7KjFkBOkR/Hps3VPw=
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo=
|
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo=
|
||||||
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
@@ -81,11 +83,29 @@ gopkg.in/natefinch/lumberjack.v2 v2.2.1 h1:bBRl1b0OH9s/DuPhuXpNl+VtCaJXFZ5/uEFST
|
|||||||
gopkg.in/natefinch/lumberjack.v2 v2.2.1/go.mod h1:YD8tP3GAjkrDg1eZH7EGmyESg/lsYskCTPBJVb9jqSc=
|
gopkg.in/natefinch/lumberjack.v2 v2.2.1/go.mod h1:YD8tP3GAjkrDg1eZH7EGmyESg/lsYskCTPBJVb9jqSc=
|
||||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
|
modernc.org/cc/v4 v4.26.3 h1:yEN8dzrkRFnn4PUUKXLYIqVf2PJYAEjMTFjO3BDGc3I=
|
||||||
|
modernc.org/cc/v4 v4.26.3/go.mod h1:uVtb5OGqUKpoLWhqwNQo/8LwvoiEBLvZXIQ/SmO6mL0=
|
||||||
|
modernc.org/ccgo/v4 v4.28.0 h1:rjznn6WWehKq7dG4JtLRKxb52Ecv8OUGah8+Z/SfpNU=
|
||||||
|
modernc.org/ccgo/v4 v4.28.0/go.mod h1:JygV3+9AV6SmPhDasu4JgquwU81XAKLd3OKTUDNOiKE=
|
||||||
|
modernc.org/fileutil v1.3.8 h1:qtzNm7ED75pd1C7WgAGcK4edm4fvhtBsEiI/0NQ54YM=
|
||||||
|
modernc.org/fileutil v1.3.8/go.mod h1:HxmghZSZVAz/LXcMNwZPA/DRrQZEVP9VX0V4LQGQFOc=
|
||||||
|
modernc.org/gc/v2 v2.6.5 h1:nyqdV8q46KvTpZlsw66kWqwXRHdjIlJOhG6kxiV/9xI=
|
||||||
|
modernc.org/gc/v2 v2.6.5/go.mod h1:YgIahr1ypgfe7chRuJi2gD7DBQiKSLMPgBQe9oIiito=
|
||||||
|
modernc.org/goabi0 v0.2.0 h1:HvEowk7LxcPd0eq6mVOAEMai46V+i7Jrj13t4AzuNks=
|
||||||
|
modernc.org/goabi0 v0.2.0/go.mod h1:CEFRnnJhKvWT1c1JTI3Avm+tgOWbkOu5oPA8eH8LnMI=
|
||||||
modernc.org/libc v1.66.6 h1:RyQpwAhM/19nXD8y3iejM/AjmKwY2TjxZTlUWTsWw2U=
|
modernc.org/libc v1.66.6 h1:RyQpwAhM/19nXD8y3iejM/AjmKwY2TjxZTlUWTsWw2U=
|
||||||
modernc.org/libc v1.66.6/go.mod h1:j8z0EYAuumoMQ3+cWXtmw6m+LYn3qm8dcZDFtFTSq+M=
|
modernc.org/libc v1.66.6/go.mod h1:j8z0EYAuumoMQ3+cWXtmw6m+LYn3qm8dcZDFtFTSq+M=
|
||||||
modernc.org/mathutil v1.7.1 h1:GCZVGXdaN8gTqB1Mf/usp1Y/hSqgI2vAGGP4jZMCxOU=
|
modernc.org/mathutil v1.7.1 h1:GCZVGXdaN8gTqB1Mf/usp1Y/hSqgI2vAGGP4jZMCxOU=
|
||||||
modernc.org/mathutil v1.7.1/go.mod h1:4p5IwJITfppl0G4sUEDtCr4DthTaT47/N3aT6MhfgJg=
|
modernc.org/mathutil v1.7.1/go.mod h1:4p5IwJITfppl0G4sUEDtCr4DthTaT47/N3aT6MhfgJg=
|
||||||
modernc.org/memory v1.11.0 h1:o4QC8aMQzmcwCK3t3Ux/ZHmwFPzE6hf2Y5LbkRs+hbI=
|
modernc.org/memory v1.11.0 h1:o4QC8aMQzmcwCK3t3Ux/ZHmwFPzE6hf2Y5LbkRs+hbI=
|
||||||
modernc.org/memory v1.11.0/go.mod h1:/JP4VbVC+K5sU2wZi9bHoq2MAkCnrt2r98UGeSK7Mjw=
|
modernc.org/memory v1.11.0/go.mod h1:/JP4VbVC+K5sU2wZi9bHoq2MAkCnrt2r98UGeSK7Mjw=
|
||||||
|
modernc.org/opt v0.1.4 h1:2kNGMRiUjrp4LcaPuLY2PzUfqM/w9N23quVwhKt5Qm8=
|
||||||
|
modernc.org/opt v0.1.4/go.mod h1:03fq9lsNfvkYSfxrfUhZCWPk1lm4cq4N+Bh//bEtgns=
|
||||||
|
modernc.org/sortutil v1.2.1 h1:+xyoGf15mM3NMlPDnFqrteY07klSFxLElE2PVuWIJ7w=
|
||||||
|
modernc.org/sortutil v1.2.1/go.mod h1:7ZI3a3REbai7gzCLcotuw9AC4VZVpYMjDzETGsSMqJE=
|
||||||
modernc.org/sqlite v1.38.2 h1:Aclu7+tgjgcQVShZqim41Bbw9Cho0y/7WzYptXqkEek=
|
modernc.org/sqlite v1.38.2 h1:Aclu7+tgjgcQVShZqim41Bbw9Cho0y/7WzYptXqkEek=
|
||||||
modernc.org/sqlite v1.38.2/go.mod h1:cPTJYSlgg3Sfg046yBShXENNtPrWrDX8bsbAQBzgQ5E=
|
modernc.org/sqlite v1.38.2/go.mod h1:cPTJYSlgg3Sfg046yBShXENNtPrWrDX8bsbAQBzgQ5E=
|
||||||
|
modernc.org/strutil v1.2.1 h1:UneZBkQA+DX2Rp35KcM69cSsNES9ly8mQWD71HKlOA0=
|
||||||
|
modernc.org/strutil v1.2.1/go.mod h1:EHkiggD70koQxjVdSBM3JKM7k6L0FbGE5eymy9i3B9A=
|
||||||
|
modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y=
|
||||||
|
modernc.org/token v1.1.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM=
|
||||||
|
|||||||
256
hooks/initial.go
256
hooks/initial.go
@@ -1,6 +1,8 @@
|
|||||||
package hooks
|
package hooks
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bufio"
|
||||||
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
@@ -8,12 +10,15 @@ import (
|
|||||||
"log"
|
"log"
|
||||||
"log/slog"
|
"log/slog"
|
||||||
"os"
|
"os"
|
||||||
|
"os/signal"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
"reflect"
|
||||||
"slices"
|
"slices"
|
||||||
"strings"
|
"strings"
|
||||||
"syscall"
|
"syscall"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/akyaiy/GoSally-mvp/internal/colors"
|
||||||
"github.com/akyaiy/GoSally-mvp/internal/core/corestate"
|
"github.com/akyaiy/GoSally-mvp/internal/core/corestate"
|
||||||
"github.com/akyaiy/GoSally-mvp/internal/core/run_manager"
|
"github.com/akyaiy/GoSally-mvp/internal/core/run_manager"
|
||||||
"github.com/akyaiy/GoSally-mvp/internal/core/utils"
|
"github.com/akyaiy/GoSally-mvp/internal/core/utils"
|
||||||
@@ -25,15 +30,15 @@ import (
|
|||||||
|
|
||||||
var Compositor *config.Compositor = config.NewCompositor()
|
var Compositor *config.Compositor = config.NewCompositor()
|
||||||
|
|
||||||
func Init0Hook(cs *corestate.CoreState, x *app.AppX) {
|
func InitGlobalLoggerHook(_ context.Context, cs *corestate.CoreState, x *app.AppX) {
|
||||||
x.Config = Compositor
|
x.Config = Compositor
|
||||||
x.Log.SetOutput(os.Stdout)
|
x.Log.SetOutput(os.Stdout)
|
||||||
x.Log.SetPrefix(logs.SetBrightBlack(fmt.Sprintf("(%s) ", cs.Stage)))
|
x.Log.SetPrefix(colors.SetBrightBlack(fmt.Sprintf("(%s) ", cs.Stage)))
|
||||||
x.Log.SetFlags(log.Ldate | log.Ltime)
|
x.Log.SetFlags(log.Ldate | log.Ltime)
|
||||||
}
|
}
|
||||||
|
|
||||||
// First stage: pre-init
|
// First stage: pre-init
|
||||||
func Init1Hook(cs *corestate.CoreState, x *app.AppX) {
|
func InitCorestateHook(_ context.Context, cs *corestate.CoreState, x *app.AppX) {
|
||||||
*cs = *corestate.NewCorestate(&corestate.CoreState{
|
*cs = *corestate.NewCorestate(&corestate.CoreState{
|
||||||
UUID32DirName: "uuid",
|
UUID32DirName: "uuid",
|
||||||
NodeBinName: filepath.Base(os.Args[0]),
|
NodeBinName: filepath.Base(os.Args[0]),
|
||||||
@@ -44,8 +49,8 @@ func Init1Hook(cs *corestate.CoreState, x *app.AppX) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func Init2Hook(cs *corestate.CoreState, x *app.AppX) {
|
func InitConfigLoadHook(_ context.Context, cs *corestate.CoreState, x *app.AppX) {
|
||||||
x.Log.SetPrefix(logs.SetBlue(fmt.Sprintf("(%s) ", cs.Stage)))
|
x.Log.SetPrefix(colors.SetYellow(fmt.Sprintf("(%s) ", cs.Stage)))
|
||||||
|
|
||||||
if err := x.Config.LoadEnv(); err != nil {
|
if err := x.Config.LoadEnv(); err != nil {
|
||||||
x.Log.Fatalf("env load error: %s", err)
|
x.Log.Fatalf("env load error: %s", err)
|
||||||
@@ -60,7 +65,7 @@ func Init2Hook(cs *corestate.CoreState, x *app.AppX) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func Init3Hook(cs *corestate.CoreState, x *app.AppX) {
|
func InitUUUDHook(_ context.Context, cs *corestate.CoreState, x *app.AppX) {
|
||||||
uuid32, err := corestate.GetNodeUUID(filepath.Join(cs.MetaDir, "uuid"))
|
uuid32, err := corestate.GetNodeUUID(filepath.Join(cs.MetaDir, "uuid"))
|
||||||
if errors.Is(err, fs.ErrNotExist) {
|
if errors.Is(err, fs.ErrNotExist) {
|
||||||
if err := corestate.SetNodeUUID(filepath.Join(cs.NodePath, cs.MetaDir, cs.UUID32DirName)); err != nil {
|
if err := corestate.SetNodeUUID(filepath.Join(cs.NodePath, cs.MetaDir, cs.UUID32DirName)); err != nil {
|
||||||
@@ -77,7 +82,7 @@ func Init3Hook(cs *corestate.CoreState, x *app.AppX) {
|
|||||||
cs.UUID32 = uuid32
|
cs.UUID32 = uuid32
|
||||||
}
|
}
|
||||||
|
|
||||||
func Init4Hook(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() {
|
||||||
// still pre-init stage
|
// still pre-init stage
|
||||||
runDir, err := run_manager.Create(cs.UUID32)
|
runDir, err := run_manager.Create(cs.UUID32)
|
||||||
@@ -128,9 +133,18 @@ func Init4Hook(cs *corestate.CoreState, x *app.AppX) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// post-init stage
|
// post-init stage
|
||||||
func Init5Hook(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) {
|
||||||
|
x.Log.Println("Cleaning up...")
|
||||||
|
|
||||||
|
if err := run_manager.Clean(); err != nil {
|
||||||
|
x.Log.Printf("%s: Cleanup error: %s", colors.PrintError(), err.Error())
|
||||||
|
}
|
||||||
|
x.Log.Println("bye!")
|
||||||
|
})
|
||||||
|
|
||||||
cs.Stage = corestate.StagePostInit
|
cs.Stage = corestate.StagePostInit
|
||||||
x.Log.SetPrefix(logs.SetYellow(fmt.Sprintf("(%s) ", cs.Stage)))
|
x.Log.SetPrefix(colors.SetBlue(fmt.Sprintf("(%s) ", cs.Stage)))
|
||||||
|
|
||||||
cs.RunDir = run_manager.Toggle()
|
cs.RunDir = run_manager.Toggle()
|
||||||
exist, err := utils.ExistsMatchingDirs(filepath.Join(os.TempDir(), fmt.Sprintf("/*-%s-%s", cs.UUID32, "gosally-runtime")), cs.RunDir)
|
exist, err := utils.ExistsMatchingDirs(filepath.Join(os.TempDir(), fmt.Sprintf("/*-%s-%s", cs.UUID32, "gosally-runtime")), cs.RunDir)
|
||||||
@@ -171,25 +185,50 @@ func Init5Hook(cs *corestate.CoreState, x *app.AppX) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func Init6Hook(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" {
|
||||||
x.Log.Printf("%s: %s", logs.PrintWarn(), "Non-standard value specified for temporary directory")
|
x.Log.Printf("%s: %s", colors.PrintWarn(), "Non-standard value specified for temporary directory")
|
||||||
}
|
}
|
||||||
if strings.Contains(*x.Config.Conf.Log.OutPath, `%tmp%`) {
|
|
||||||
replaced := strings.ReplaceAll(*x.Config.Conf.Log.OutPath, "%tmp%", filepath.Clean(run_manager.RuntimeDir()))
|
replacements := map[string]any{
|
||||||
x.Config.Conf.Log.OutPath = &replaced
|
"%tmp%": filepath.Clean(run_manager.RuntimeDir()),
|
||||||
|
"%path%": *x.Config.Env.NodePath,
|
||||||
|
"%stdout%": "_1STDout",
|
||||||
|
"%stderr%": "_2STDerr",
|
||||||
|
"%1%": "_1STDout",
|
||||||
|
"%2%": "_2STDerr",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
processConfig(&x.Config.Conf, replacements)
|
||||||
|
|
||||||
if !slices.Contains(logs.Levels.Available, *x.Config.Conf.Log.Level) {
|
if !slices.Contains(logs.Levels.Available, *x.Config.Conf.Log.Level) {
|
||||||
if !slices.Contains(*x.Config.Conf.DisableWarnings, "--WUndefLogLevel") {
|
if !slices.Contains(*x.Config.Conf.DisableWarnings, "--WUndefLogLevel") {
|
||||||
x.Log.Printf("%s: %s", logs.PrintWarn(), fmt.Sprintf("Unknown logging level %s, fallback level: %s", *x.Config.Conf.Log.Level, logs.Levels.Fallback))
|
x.Log.Printf("%s: %s", colors.PrintWarn(), fmt.Sprintf("Unknown logging level %s, fallback level: %s", *x.Config.Conf.Log.Level, logs.Levels.Fallback))
|
||||||
}
|
}
|
||||||
x.Config.Conf.Log.Level = &logs.Levels.Fallback
|
x.Config.Conf.Log.Level = &logs.Levels.Fallback
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func Init7Hook(cs *corestate.CoreState, x *app.AppX) {
|
func InitConfigPrintHook(ctx context.Context, cs *corestate.CoreState, x *app.AppX) {
|
||||||
|
if *x.Config.Conf.Node.ShowConfig {
|
||||||
|
fmt.Printf("Configuration from %s:\n", x.Config.CMDLine.Run.ConfigPath)
|
||||||
|
x.Config.Print(x.Config.Conf)
|
||||||
|
|
||||||
|
fmt.Printf("Environment:\n")
|
||||||
|
x.Config.Print(x.Config.Env)
|
||||||
|
|
||||||
|
if cs.UUID32 != "" && !askConfirm("Is that ok?", true) {
|
||||||
|
x.Log.Printf("Cancel launch")
|
||||||
|
NodeApp.CallFallback(ctx)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
x.Log.Printf("Starting \"%s\" node", *x.Config.Conf.Node.Name)
|
||||||
|
}
|
||||||
|
|
||||||
|
func InitSLogHook(_ context.Context, cs *corestate.CoreState, x *app.AppX) {
|
||||||
cs.Stage = corestate.StageReady
|
cs.Stage = corestate.StageReady
|
||||||
x.Log.SetPrefix(logs.SetGreen(fmt.Sprintf("(%s) ", cs.Stage)))
|
x.Log.SetPrefix(colors.SetGreen(fmt.Sprintf("(%s) ", cs.Stage)))
|
||||||
|
|
||||||
x.SLog = new(slog.Logger)
|
x.SLog = new(slog.Logger)
|
||||||
newSlog, err := logs.SetupLogger(x.Config.Conf.Log)
|
newSlog, err := logs.SetupLogger(x.Config.Conf.Log)
|
||||||
@@ -200,26 +239,165 @@ func Init7Hook(cs *corestate.CoreState, x *app.AppX) {
|
|||||||
*x.SLog = *newSlog
|
*x.SLog = *newSlog
|
||||||
}
|
}
|
||||||
|
|
||||||
// repl := map[string]string{
|
func processConfig(conf any, replacements map[string]any) error {
|
||||||
// "tmp": filepath.Clean(run_manager.RuntimeDir()),
|
val := reflect.ValueOf(conf)
|
||||||
// }
|
if val.Kind() == reflect.Ptr {
|
||||||
// re := regexp.MustCompile(`%(\w+)%`)
|
val = val.Elem()
|
||||||
// result := re.ReplaceAllStringFunc(x.Config.Conf.Log.OutPath, func(match string) string {
|
}
|
||||||
// sub := re.FindStringSubmatch(match)
|
|
||||||
// if len(sub) < 2 {
|
|
||||||
// return match
|
|
||||||
// }
|
|
||||||
// key := sub[1]
|
|
||||||
// if val, ok := repl[key]; ok {
|
|
||||||
// return val
|
|
||||||
// }
|
|
||||||
// return match
|
|
||||||
// })
|
|
||||||
|
|
||||||
// if strings.Contains(x.Config.Conf.Log.OutPath, "%tmp%") {
|
switch val.Kind() {
|
||||||
// relPath := strings.TrimPrefix(result, filepath.Clean(run_manager.RuntimeDir()))
|
case reflect.Struct:
|
||||||
// if err := run_manager.SetDir(relPath); err != nil {
|
for i := 0; i < val.NumField(); i++ {
|
||||||
// _ = run_manager.Clean()
|
field := val.Field(i)
|
||||||
// x.Log.Fatalf("Unexpected failure: %s", err.Error())
|
if field.CanAddr() && field.CanSet() {
|
||||||
// }
|
if err := processConfig(field.Addr().Interface(), replacements); err != nil {
|
||||||
// }
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
case reflect.Slice:
|
||||||
|
for i := 0; i < val.Len(); i++ {
|
||||||
|
elem := val.Index(i)
|
||||||
|
if elem.CanAddr() && elem.CanSet() {
|
||||||
|
if err := processConfig(elem.Addr().Interface(), replacements); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
case reflect.Map:
|
||||||
|
for _, key := range val.MapKeys() {
|
||||||
|
elem := val.MapIndex(key)
|
||||||
|
if elem.CanInterface() {
|
||||||
|
newVal := reflect.New(elem.Type()).Elem()
|
||||||
|
newVal.Set(elem)
|
||||||
|
|
||||||
|
if err := processConfig(newVal.Addr().Interface(), replacements); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
val.SetMapIndex(key, newVal)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
case reflect.String:
|
||||||
|
str := val.String()
|
||||||
|
|
||||||
|
if replacement, exists := replacements[str]; exists {
|
||||||
|
if err := setValue(val, replacement); err != nil {
|
||||||
|
return fmt.Errorf("failed to set %q: %v", str, err)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
for placeholder, replacement := range replacements {
|
||||||
|
if strings.Contains(str, placeholder) {
|
||||||
|
replacementStr, err := toString(replacement)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("invalid replacement for %q: %v", placeholder, err)
|
||||||
|
}
|
||||||
|
newStr := strings.ReplaceAll(str, placeholder, replacementStr)
|
||||||
|
val.SetString(newStr)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
case reflect.Ptr:
|
||||||
|
if !val.IsNil() {
|
||||||
|
elem := val.Elem()
|
||||||
|
if elem.Kind() == reflect.String {
|
||||||
|
str := elem.String()
|
||||||
|
if replacement, exists := replacements[str]; exists {
|
||||||
|
strVal, err := toString(replacement)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("cannot convert replacement to string: %v", err)
|
||||||
|
}
|
||||||
|
elem.SetString(strVal)
|
||||||
|
} else {
|
||||||
|
for placeholder, replacement := range replacements {
|
||||||
|
if strings.Contains(str, placeholder) {
|
||||||
|
replacementStr, err := toString(replacement)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("invalid replacement for %q: %v", placeholder, err)
|
||||||
|
}
|
||||||
|
newStr := strings.ReplaceAll(str, placeholder, replacementStr)
|
||||||
|
elem.SetString(newStr)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return processConfig(elem.Addr().Interface(), replacements)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func setValue(val reflect.Value, replacement any) error {
|
||||||
|
if !val.CanSet() {
|
||||||
|
return fmt.Errorf("value is not settable")
|
||||||
|
}
|
||||||
|
|
||||||
|
replacementVal := reflect.ValueOf(replacement)
|
||||||
|
if replacementVal.Type().AssignableTo(val.Type()) {
|
||||||
|
val.Set(replacementVal)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if val.Kind() == reflect.String {
|
||||||
|
str, err := toString(replacement)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("cannot convert replacement to string: %v", err)
|
||||||
|
}
|
||||||
|
val.SetString(str)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return fmt.Errorf("type mismatch: cannot assign %T to %v", replacement, val.Type())
|
||||||
|
}
|
||||||
|
|
||||||
|
func toString(v any) (string, error) {
|
||||||
|
switch s := v.(type) {
|
||||||
|
case string:
|
||||||
|
return s, nil
|
||||||
|
case fmt.Stringer:
|
||||||
|
return s.String(), nil
|
||||||
|
default:
|
||||||
|
return fmt.Sprint(v), nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func askConfirm(prompt string, defaultYes bool) bool {
|
||||||
|
ctx, _ := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGINT, syscall.SIGTERM, syscall.SIGQUIT)
|
||||||
|
|
||||||
|
fmt.Print(prompt)
|
||||||
|
if defaultYes {
|
||||||
|
fmt.Printf(" (%s/%s): ", colors.SetBrightGreen("Y"), colors.SetBrightRed("n"))
|
||||||
|
} else {
|
||||||
|
fmt.Printf(" (%s/%s): ", colors.SetBrightGreen("n"), colors.SetBrightRed("Y"))
|
||||||
|
}
|
||||||
|
|
||||||
|
inputChan := make(chan string, 1)
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
reader := bufio.NewReader(os.Stdin)
|
||||||
|
text, _ := reader.ReadString('\n')
|
||||||
|
inputChan <- text
|
||||||
|
}()
|
||||||
|
|
||||||
|
select {
|
||||||
|
case <-ctx.Done():
|
||||||
|
fmt.Println("")
|
||||||
|
NodeApp.CallFallback(ctx)
|
||||||
|
os.Exit(3)
|
||||||
|
case text := <-inputChan:
|
||||||
|
text = strings.TrimSpace(strings.ToLower(text))
|
||||||
|
if text == "" {
|
||||||
|
return defaultYes
|
||||||
|
}
|
||||||
|
if text == "y" || text == "yes" {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return defaultYes
|
||||||
|
}
|
||||||
|
|||||||
42
hooks/run.go
42
hooks/run.go
@@ -5,11 +5,13 @@ import (
|
|||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
"log"
|
||||||
|
"log/slog"
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
"regexp"
|
"regexp"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/akyaiy/GoSally-mvp/internal/colors"
|
||||||
"github.com/akyaiy/GoSally-mvp/internal/core/corestate"
|
"github.com/akyaiy/GoSally-mvp/internal/core/corestate"
|
||||||
"github.com/akyaiy/GoSally-mvp/internal/core/run_manager"
|
"github.com/akyaiy/GoSally-mvp/internal/core/run_manager"
|
||||||
"github.com/akyaiy/GoSally-mvp/internal/core/update"
|
"github.com/akyaiy/GoSally-mvp/internal/core/update"
|
||||||
@@ -18,6 +20,7 @@ import (
|
|||||||
"github.com/akyaiy/GoSally-mvp/internal/engine/config"
|
"github.com/akyaiy/GoSally-mvp/internal/engine/config"
|
||||||
"github.com/akyaiy/GoSally-mvp/internal/engine/logs"
|
"github.com/akyaiy/GoSally-mvp/internal/engine/logs"
|
||||||
"github.com/akyaiy/GoSally-mvp/internal/server/gateway"
|
"github.com/akyaiy/GoSally-mvp/internal/server/gateway"
|
||||||
|
"github.com/akyaiy/GoSally-mvp/internal/server/session"
|
||||||
"github.com/akyaiy/GoSally-mvp/internal/server/sv1"
|
"github.com/akyaiy/GoSally-mvp/internal/server/sv1"
|
||||||
"github.com/go-chi/chi/v5"
|
"github.com/go-chi/chi/v5"
|
||||||
"github.com/go-chi/cors"
|
"github.com/go-chi/cors"
|
||||||
@@ -25,16 +28,16 @@ import (
|
|||||||
"golang.org/x/net/netutil"
|
"golang.org/x/net/netutil"
|
||||||
)
|
)
|
||||||
|
|
||||||
var nodeApp = app.New()
|
var NodeApp = app.New()
|
||||||
|
|
||||||
func Run(cmd *cobra.Command, args []string) {
|
func Run(cmd *cobra.Command, args []string) {
|
||||||
nodeApp.InitialHooks(
|
NodeApp.InitialHooks(
|
||||||
Init0Hook, Init1Hook, Init2Hook,
|
InitGlobalLoggerHook, InitCorestateHook, InitConfigLoadHook,
|
||||||
Init3Hook, Init4Hook, Init5Hook,
|
InitUUUDHook, InitRuntimeHook, InitRunlockHook,
|
||||||
Init6Hook, Init7Hook,
|
InitConfigReplHook, InitConfigPrintHook, InitSLogHook,
|
||||||
)
|
)
|
||||||
|
|
||||||
nodeApp.Run(RunHook)
|
NodeApp.Run(RunHook)
|
||||||
}
|
}
|
||||||
|
|
||||||
func RunHook(ctx context.Context, cs *corestate.CoreState, x *app.AppX) error {
|
func RunHook(ctx context.Context, cs *corestate.CoreState, x *app.AppX) error {
|
||||||
@@ -61,7 +64,10 @@ func RunHook(ctx context.Context, cs *corestate.CoreState, x *app.AppX) error {
|
|||||||
Ver: "v1",
|
Ver: "v1",
|
||||||
})
|
})
|
||||||
|
|
||||||
|
session_manager := session.New(*x.Config.Conf.HTTPServer.SessionTTL)
|
||||||
|
|
||||||
s := gateway.InitGateway(&gateway.GatewayServerInit{
|
s := gateway.InitGateway(&gateway.GatewayServerInit{
|
||||||
|
SM: session_manager,
|
||||||
CS: cs,
|
CS: cs,
|
||||||
X: x,
|
X: x,
|
||||||
}, serverv1)
|
}, serverv1)
|
||||||
@@ -69,8 +75,8 @@ func RunHook(ctx context.Context, cs *corestate.CoreState, x *app.AppX) error {
|
|||||||
r := chi.NewRouter()
|
r := chi.NewRouter()
|
||||||
r.Use(cors.Handler(cors.Options{
|
r.Use(cors.Handler(cors.Options{
|
||||||
AllowedOrigins: []string{"*"},
|
AllowedOrigins: []string{"*"},
|
||||||
AllowedMethods: []string{"GET", "POST", "OPTIONS"},
|
AllowedMethods: []string{"POST"},
|
||||||
AllowedHeaders: []string{"Accept", "Authorization", "Content-Type", "X-CSRF-Token"},
|
AllowedHeaders: []string{"Accept", "Authorization", "Content-Type", "X-CSRF-Token", "X-Session-UUID"},
|
||||||
AllowCredentials: true,
|
AllowCredentials: true,
|
||||||
MaxAge: 300,
|
MaxAge: 300,
|
||||||
}))
|
}))
|
||||||
@@ -86,13 +92,13 @@ func RunHook(ctx context.Context, cs *corestate.CoreState, x *app.AppX) error {
|
|||||||
Handler: r,
|
Handler: r,
|
||||||
ErrorLog: log.New(&logs.SlogWriter{
|
ErrorLog: log.New(&logs.SlogWriter{
|
||||||
Logger: x.SLog,
|
Logger: x.SLog,
|
||||||
Level: logs.GlobalLevel,
|
Level: slog.LevelError,
|
||||||
}, "", 0),
|
}, "", 0),
|
||||||
}
|
}
|
||||||
|
|
||||||
nodeApp.Fallback(func(ctx context.Context, cs *corestate.CoreState, x *app.AppX) {
|
NodeApp.Fallback(func(ctx context.Context, cs *corestate.CoreState, x *app.AppX) {
|
||||||
if err := srv.Shutdown(ctxMain); err != nil {
|
if err := srv.Shutdown(ctxMain); err != nil {
|
||||||
x.Log.Printf("%s: Failed to stop the server gracefully: %s", logs.PrintError(), err.Error())
|
x.Log.Printf("%s: Failed to stop the server gracefully: %s", colors.PrintError(), err.Error())
|
||||||
} else {
|
} else {
|
||||||
x.Log.Printf("Server stopped gracefully")
|
x.Log.Printf("Server stopped gracefully")
|
||||||
}
|
}
|
||||||
@@ -100,7 +106,7 @@ func RunHook(ctx context.Context, cs *corestate.CoreState, x *app.AppX) error {
|
|||||||
x.Log.Println("Cleaning up...")
|
x.Log.Println("Cleaning up...")
|
||||||
|
|
||||||
if err := run_manager.Clean(); err != nil {
|
if err := run_manager.Clean(); err != nil {
|
||||||
x.Log.Printf("%s: Cleanup error: %s", logs.PrintError(), err.Error())
|
x.Log.Printf("%s: Cleanup error: %s", colors.PrintError(), err.Error())
|
||||||
}
|
}
|
||||||
x.Log.Println("bye!")
|
x.Log.Println("bye!")
|
||||||
})
|
})
|
||||||
@@ -110,32 +116,34 @@ func RunHook(ctx context.Context, cs *corestate.CoreState, x *app.AppX) error {
|
|||||||
if *x.Config.Conf.TLS.TlsEnabled {
|
if *x.Config.Conf.TLS.TlsEnabled {
|
||||||
listener, err := net.Listen("tcp", fmt.Sprintf("%s:%s", *x.Config.Conf.HTTPServer.Address, *x.Config.Conf.HTTPServer.Port))
|
listener, err := net.Listen("tcp", fmt.Sprintf("%s:%s", *x.Config.Conf.HTTPServer.Address, *x.Config.Conf.HTTPServer.Port))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
x.Log.Printf("%s: Failed to start TLS listener: %s", logs.PrintError(), err.Error())
|
x.Log.Printf("%s: Failed to start TLS listener: %s", colors.PrintError(), err.Error())
|
||||||
cancelMain()
|
cancelMain()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
x.Log.Printf("Serving on %s port %s with TLS... (https://%s%s)", *x.Config.Conf.HTTPServer.Address, *x.Config.Conf.HTTPServer.Port, fmt.Sprintf("%s:%s", *x.Config.Conf.HTTPServer.Address, *x.Config.Conf.HTTPServer.Port), config.ComDirRoute)
|
x.Log.Printf("Serving on %s port %s with TLS... (https://%s%s)", *x.Config.Conf.HTTPServer.Address, *x.Config.Conf.HTTPServer.Port, fmt.Sprintf("%s:%s", *x.Config.Conf.HTTPServer.Address, *x.Config.Conf.HTTPServer.Port), config.ComDirRoute)
|
||||||
limitedListener := netutil.LimitListener(listener, 100)
|
limitedListener := netutil.LimitListener(listener, 100)
|
||||||
if err := srv.ServeTLS(limitedListener, *x.Config.Conf.TLS.CertFile, *x.Config.Conf.TLS.KeyFile); err != nil && !errors.Is(err, http.ErrServerClosed) {
|
if err := srv.ServeTLS(limitedListener, *x.Config.Conf.TLS.CertFile, *x.Config.Conf.TLS.KeyFile); err != nil && !errors.Is(err, http.ErrServerClosed) {
|
||||||
x.Log.Printf("%s: Failed to start HTTPS server: %s", logs.PrintError(), err.Error())
|
x.Log.Printf("%s: Failed to start HTTPS server: %s", colors.PrintError(), err.Error())
|
||||||
cancelMain()
|
cancelMain()
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
x.Log.Printf("Serving on %s port %s... (http://%s%s)", *x.Config.Conf.HTTPServer.Address, *x.Config.Conf.HTTPServer.Port, fmt.Sprintf("%s:%s", *x.Config.Conf.HTTPServer.Address, *x.Config.Conf.HTTPServer.Port), config.ComDirRoute)
|
x.Log.Printf("Serving on %s port %s... (http://%s%s)", *x.Config.Conf.HTTPServer.Address, *x.Config.Conf.HTTPServer.Port, fmt.Sprintf("%s:%s", *x.Config.Conf.HTTPServer.Address, *x.Config.Conf.HTTPServer.Port), config.ComDirRoute)
|
||||||
listener, err := net.Listen("tcp", fmt.Sprintf("%s:%s", *x.Config.Conf.HTTPServer.Address, *x.Config.Conf.HTTPServer.Port))
|
listener, err := net.Listen("tcp", fmt.Sprintf("%s:%s", *x.Config.Conf.HTTPServer.Address, *x.Config.Conf.HTTPServer.Port))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
x.Log.Printf("%s: Failed to start listener: %s", logs.PrintError(), err.Error())
|
x.Log.Printf("%s: Failed to start listener: %s", colors.PrintError(), err.Error())
|
||||||
cancelMain()
|
cancelMain()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
limitedListener := netutil.LimitListener(listener, 100)
|
limitedListener := netutil.LimitListener(listener, 100)
|
||||||
if err := srv.Serve(limitedListener); err != nil && !errors.Is(err, http.ErrServerClosed) {
|
if err := srv.Serve(limitedListener); err != nil && !errors.Is(err, http.ErrServerClosed) {
|
||||||
x.Log.Printf("%s: Failed to start HTTP server: %s", logs.PrintError(), err.Error())
|
x.Log.Printf("%s: Failed to start HTTP server: %s", colors.PrintError(), err.Error())
|
||||||
cancelMain()
|
cancelMain()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
|
session_manager.StartCleanup(5 * time.Second)
|
||||||
|
|
||||||
if *x.Config.Conf.Updates.UpdatesEnabled {
|
if *x.Config.Conf.Updates.UpdatesEnabled {
|
||||||
go func() {
|
go func() {
|
||||||
defer utils.CatchPanicWithCancel(cancelMain)
|
defer utils.CatchPanicWithCancel(cancelMain)
|
||||||
@@ -163,6 +171,6 @@ func RunHook(ctx context.Context, cs *corestate.CoreState, x *app.AppX) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
<-ctxMain.Done()
|
<-ctxMain.Done()
|
||||||
nodeApp.CallFallback(ctx)
|
NodeApp.CallFallback(ctx)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
package logs
|
package colors
|
||||||
|
|
||||||
import "fmt"
|
import "fmt"
|
||||||
|
|
||||||
@@ -14,7 +14,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type AppContract interface {
|
type AppContract interface {
|
||||||
InitialHooks(fn ...func(cs *corestate.CoreState, x *AppX))
|
InitialHooks(fn ...func(ctx context.Context, cs *corestate.CoreState, x *AppX))
|
||||||
Run(fn func(ctx context.Context, cs *corestate.CoreState, x *AppX) error)
|
Run(fn func(ctx context.Context, cs *corestate.CoreState, x *AppX) error)
|
||||||
Fallback(fn func(ctx context.Context, cs *corestate.CoreState, x *AppX))
|
Fallback(fn func(ctx context.Context, cs *corestate.CoreState, x *AppX))
|
||||||
|
|
||||||
@@ -22,7 +22,7 @@ type AppContract interface {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type App struct {
|
type App struct {
|
||||||
initHooks []func(cs *corestate.CoreState, x *AppX)
|
initHooks []func(ctx context.Context, cs *corestate.CoreState, x *AppX)
|
||||||
runHook func(ctx context.Context, cs *corestate.CoreState, x *AppX) error
|
runHook func(ctx context.Context, cs *corestate.CoreState, x *AppX) error
|
||||||
fallback func(ctx context.Context, cs *corestate.CoreState, x *AppX)
|
fallback func(ctx context.Context, cs *corestate.CoreState, x *AppX)
|
||||||
|
|
||||||
@@ -47,7 +47,7 @@ func New() AppContract {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *App) InitialHooks(fn ...func(cs *corestate.CoreState, x *AppX)) {
|
func (a *App) InitialHooks(fn ...func(ctx context.Context, cs *corestate.CoreState, x *AppX)) {
|
||||||
a.initHooks = append(a.initHooks, fn...)
|
a.initHooks = append(a.initHooks, fn...)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -58,13 +58,13 @@ func (a *App) Fallback(fn func(ctx context.Context, cs *corestate.CoreState, x *
|
|||||||
func (a *App) Run(fn func(ctx context.Context, cs *corestate.CoreState, x *AppX) error) {
|
func (a *App) Run(fn func(ctx context.Context, cs *corestate.CoreState, x *AppX) error) {
|
||||||
a.runHook = fn
|
a.runHook = fn
|
||||||
|
|
||||||
for _, hook := range a.initHooks {
|
|
||||||
hook(a.Corestate, a.AppX)
|
|
||||||
}
|
|
||||||
|
|
||||||
ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGINT, syscall.SIGTERM, syscall.SIGQUIT)
|
ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGINT, syscall.SIGTERM, syscall.SIGQUIT)
|
||||||
defer stop()
|
defer stop()
|
||||||
|
|
||||||
|
for _, hook := range a.initHooks {
|
||||||
|
hook(ctx, a.Corestate, a.AppX)
|
||||||
|
}
|
||||||
|
|
||||||
defer func() {
|
defer func() {
|
||||||
if r := recover(); r != nil {
|
if r := recover(); r != nil {
|
||||||
a.AppX.Log.Printf("PANIC recovered: %v", r)
|
a.AppX.Log.Printf("PANIC recovered: %v", r)
|
||||||
@@ -90,5 +90,6 @@ func (a *App) CallFallback(ctx context.Context) {
|
|||||||
if a.fallback != nil {
|
if a.fallback != nil {
|
||||||
a.fallback(ctx, a.Corestate, a.AppX)
|
a.fallback(ctx, a.Corestate, a.AppX)
|
||||||
}
|
}
|
||||||
|
os.Exit(0)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -43,10 +43,13 @@ func (c *Compositor) LoadConf(path string) error {
|
|||||||
v.SetConfigType("yaml")
|
v.SetConfigType("yaml")
|
||||||
|
|
||||||
// defaults
|
// defaults
|
||||||
v.SetDefault("mode", "dev")
|
v.SetDefault("node.name", "noname")
|
||||||
v.SetDefault("com_dir", "./com/")
|
v.SetDefault("node.mode", "dev")
|
||||||
|
v.SetDefault("node.show_config", "false")
|
||||||
|
v.SetDefault("node.com_dir", "./com/")
|
||||||
v.SetDefault("http_server.address", "0.0.0.0")
|
v.SetDefault("http_server.address", "0.0.0.0")
|
||||||
v.SetDefault("http_server.port", "8080")
|
v.SetDefault("http_server.port", "8080")
|
||||||
|
v.SetDefault("http_server.session_ttl", "30m")
|
||||||
v.SetDefault("http_server.timeout", "5s")
|
v.SetDefault("http_server.timeout", "5s")
|
||||||
v.SetDefault("http_server.idle_timeout", "60s")
|
v.SetDefault("http_server.idle_timeout", "60s")
|
||||||
v.SetDefault("tls.enabled", false)
|
v.SetDefault("tls.enabled", false)
|
||||||
@@ -55,8 +58,9 @@ func (c *Compositor) LoadConf(path string) error {
|
|||||||
v.SetDefault("updates.enabled", false)
|
v.SetDefault("updates.enabled", false)
|
||||||
v.SetDefault("updates.check_interval", "2h")
|
v.SetDefault("updates.check_interval", "2h")
|
||||||
v.SetDefault("updates.wanted_version", "latest-stable")
|
v.SetDefault("updates.wanted_version", "latest-stable")
|
||||||
|
v.SetDefault("log.json_format", "false")
|
||||||
v.SetDefault("log.level", "info")
|
v.SetDefault("log.level", "info")
|
||||||
v.SetDefault("log.out_path", "")
|
v.SetDefault("log.output", "%2%")
|
||||||
|
|
||||||
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)
|
||||||
|
|||||||
@@ -18,8 +18,7 @@ type Compositor struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type Conf struct {
|
type Conf struct {
|
||||||
Mode *string `mapstructure:"mode"`
|
Node *Node `mapstructure:"node"`
|
||||||
ComDir *string `mapstructure:"com_dir"`
|
|
||||||
HTTPServer *HTTPServer `mapstructure:"http_server"`
|
HTTPServer *HTTPServer `mapstructure:"http_server"`
|
||||||
TLS *TLS `mapstructure:"tls"`
|
TLS *TLS `mapstructure:"tls"`
|
||||||
Updates *Updates `mapstructure:"updates"`
|
Updates *Updates `mapstructure:"updates"`
|
||||||
@@ -27,9 +26,17 @@ type Conf struct {
|
|||||||
DisableWarnings *[]string `mapstructure:"disable_warnings"`
|
DisableWarnings *[]string `mapstructure:"disable_warnings"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type Node struct {
|
||||||
|
Mode *string `mapstructure:"mode"`
|
||||||
|
Name *string `mapstructure:"name"`
|
||||||
|
ShowConfig *bool `mapstructure:"show_config"`
|
||||||
|
ComDir *string `mapstructure:"com_dir"`
|
||||||
|
}
|
||||||
|
|
||||||
type HTTPServer struct {
|
type HTTPServer struct {
|
||||||
Address *string `mapstructure:"address"`
|
Address *string `mapstructure:"address"`
|
||||||
Port *string `mapstructure:"port"`
|
Port *string `mapstructure:"port"`
|
||||||
|
SessionTTL *time.Duration `mapstructure:"session_ttl"`
|
||||||
Timeout *time.Duration `mapstructure:"timeout"`
|
Timeout *time.Duration `mapstructure:"timeout"`
|
||||||
IdleTimeout *time.Duration `mapstructure:"idle_timeout"`
|
IdleTimeout *time.Duration `mapstructure:"idle_timeout"`
|
||||||
}
|
}
|
||||||
@@ -48,8 +55,9 @@ type Updates struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type Log struct {
|
type Log struct {
|
||||||
|
JSON *bool `mapstructure:"json_format"`
|
||||||
Level *string `mapstructure:"level"`
|
Level *string `mapstructure:"level"`
|
||||||
OutPath *string `mapstructure:"out_path"`
|
OutPath *string `mapstructure:"output"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// ConfigEnv structure for environment variables
|
// ConfigEnv structure for environment variables
|
||||||
|
|||||||
72
internal/engine/config/print.go
Normal file
72
internal/engine/config/print.go
Normal file
@@ -0,0 +1,72 @@
|
|||||||
|
package config
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"reflect"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/akyaiy/GoSally-mvp/internal/colors"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (c *Compositor) Print(v any) {
|
||||||
|
c.printConfig(v, " ")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Compositor) printConfig(v any, prefix string) {
|
||||||
|
val := reflect.ValueOf(v)
|
||||||
|
if val.Kind() == reflect.Ptr {
|
||||||
|
val = val.Elem()
|
||||||
|
}
|
||||||
|
|
||||||
|
typ := val.Type()
|
||||||
|
|
||||||
|
for i := 0; i < val.NumField(); i++ {
|
||||||
|
field := val.Field(i)
|
||||||
|
fieldType := typ.Field(i)
|
||||||
|
|
||||||
|
fieldName := fieldType.Name
|
||||||
|
if tag, ok := fieldType.Tag.Lookup("mapstructure"); ok {
|
||||||
|
if tag != "" {
|
||||||
|
fieldName = tag
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
coloredFieldName := colors.SetBrightCyan(fieldName)
|
||||||
|
|
||||||
|
if field.Kind() == reflect.Ptr {
|
||||||
|
if field.IsNil() {
|
||||||
|
fmt.Printf("%s%s: %s\n", prefix, coloredFieldName, colors.SetBrightRed("<nil>"))
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
field = field.Elem()
|
||||||
|
}
|
||||||
|
|
||||||
|
if field.Kind() == reflect.Struct {
|
||||||
|
if field.Type() == reflect.TypeOf(time.Duration(0)) {
|
||||||
|
duration := field.Interface().(time.Duration)
|
||||||
|
fmt.Printf("%s%s: %s\n",
|
||||||
|
prefix,
|
||||||
|
coloredFieldName,
|
||||||
|
colors.SetBrightYellow(duration.String()))
|
||||||
|
} else {
|
||||||
|
fmt.Printf("%s%s:\n", prefix, coloredFieldName)
|
||||||
|
c.printConfig(field.Addr().Interface(), prefix+" ")
|
||||||
|
}
|
||||||
|
} else if field.Kind() == reflect.Slice {
|
||||||
|
fmt.Printf("%s%s: %s\n",
|
||||||
|
prefix,
|
||||||
|
coloredFieldName,
|
||||||
|
colors.SetBrightYellow(fmt.Sprintf("%v", field.Interface())))
|
||||||
|
} else {
|
||||||
|
value := field.Interface()
|
||||||
|
valueStr := fmt.Sprintf("%v", value)
|
||||||
|
if field.Kind() == reflect.String {
|
||||||
|
valueStr = fmt.Sprintf("\"%s\"", value)
|
||||||
|
}
|
||||||
|
fmt.Printf("%s%s: %s\n",
|
||||||
|
prefix,
|
||||||
|
coloredFieldName,
|
||||||
|
colors.SetBrightYellow(valueStr))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -16,6 +16,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
var GlobalLevel slog.Level
|
var GlobalLevel slog.Level
|
||||||
|
|
||||||
type levelsStruct struct {
|
type levelsStruct struct {
|
||||||
Available []string
|
Available []string
|
||||||
Fallback string
|
Fallback string
|
||||||
@@ -56,7 +57,12 @@ func SetupLogger(o *config.Log) (*slog.Logger, error) {
|
|||||||
handlerOpts.Level = slog.LevelInfo
|
handlerOpts.Level = slog.LevelInfo
|
||||||
}
|
}
|
||||||
|
|
||||||
if *o.OutPath != "" {
|
switch *o.OutPath {
|
||||||
|
case "_1STDout":
|
||||||
|
writer = os.Stdout
|
||||||
|
case "_2STDerr":
|
||||||
|
writer = os.Stderr
|
||||||
|
default:
|
||||||
logFile := &lumberjack.Logger{
|
logFile := &lumberjack.Logger{
|
||||||
Filename: filepath.Join(*o.OutPath, "event.log"),
|
Filename: filepath.Join(*o.OutPath, "event.log"),
|
||||||
MaxSize: 10,
|
MaxSize: 10,
|
||||||
@@ -67,6 +73,13 @@ func SetupLogger(o *config.Log) (*slog.Logger, error) {
|
|||||||
writer = logFile
|
writer = logFile
|
||||||
}
|
}
|
||||||
|
|
||||||
log := slog.New(slog.NewJSONHandler(writer, &handlerOpts))
|
var handler slog.Handler
|
||||||
|
|
||||||
|
if *o.JSON {
|
||||||
|
handler = slog.NewJSONHandler(writer, &handlerOpts)
|
||||||
|
} else {
|
||||||
|
handler = slog.NewTextHandler(writer, &handlerOpts)
|
||||||
|
}
|
||||||
|
log := slog.New(handler)
|
||||||
return log, nil
|
return log, nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,11 +1,13 @@
|
|||||||
package gateway
|
package gateway
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"github.com/akyaiy/GoSally-mvp/internal/core/corestate"
|
"github.com/akyaiy/GoSally-mvp/internal/core/corestate"
|
||||||
"github.com/akyaiy/GoSally-mvp/internal/engine/app"
|
"github.com/akyaiy/GoSally-mvp/internal/engine/app"
|
||||||
"github.com/akyaiy/GoSally-mvp/internal/server/rpc"
|
"github.com/akyaiy/GoSally-mvp/internal/server/rpc"
|
||||||
|
"github.com/akyaiy/GoSally-mvp/internal/server/session"
|
||||||
)
|
)
|
||||||
|
|
||||||
// serversApiVer is a type alias for string, used to represent API version strings in the GeneralServer.
|
// serversApiVer is a type alias for string, used to represent API version strings in the GeneralServer.
|
||||||
@@ -13,7 +15,7 @@ type serversApiVer string
|
|||||||
|
|
||||||
type ServerApiContract interface {
|
type ServerApiContract interface {
|
||||||
GetVersion() string
|
GetVersion() string
|
||||||
Handle(r *http.Request, req *rpc.RPCRequest) *rpc.RPCResponse
|
Handle(ctx context.Context, sid string, r *http.Request, req *rpc.RPCRequest) *rpc.RPCResponse
|
||||||
}
|
}
|
||||||
|
|
||||||
// GeneralServer implements the GeneralServerApiContract and serves as a router for different API versions.
|
// GeneralServer implements the GeneralServerApiContract and serves as a router for different API versions.
|
||||||
@@ -22,6 +24,7 @@ type GatewayServer struct {
|
|||||||
// The key is the version string, and the value is the server implementing GeneralServerApi
|
// The key is the version string, and the value is the server implementing GeneralServerApi
|
||||||
servers map[serversApiVer]ServerApiContract
|
servers map[serversApiVer]ServerApiContract
|
||||||
|
|
||||||
|
sm *session.SessionManager
|
||||||
cs *corestate.CoreState
|
cs *corestate.CoreState
|
||||||
x *app.AppX
|
x *app.AppX
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,10 +5,12 @@ import (
|
|||||||
|
|
||||||
"github.com/akyaiy/GoSally-mvp/internal/core/corestate"
|
"github.com/akyaiy/GoSally-mvp/internal/core/corestate"
|
||||||
"github.com/akyaiy/GoSally-mvp/internal/engine/app"
|
"github.com/akyaiy/GoSally-mvp/internal/engine/app"
|
||||||
|
"github.com/akyaiy/GoSally-mvp/internal/server/session"
|
||||||
)
|
)
|
||||||
|
|
||||||
// GeneralServerInit structure only for initialization general server.
|
// GeneralServerInit structure only for initialization general server.
|
||||||
type GatewayServerInit struct {
|
type GatewayServerInit struct {
|
||||||
|
SM *session.SessionManager
|
||||||
CS *corestate.CoreState
|
CS *corestate.CoreState
|
||||||
X *app.AppX
|
X *app.AppX
|
||||||
}
|
}
|
||||||
@@ -17,6 +19,7 @@ type GatewayServerInit struct {
|
|||||||
func InitGateway(o *GatewayServerInit, servers ...ServerApiContract) *GatewayServer {
|
func InitGateway(o *GatewayServerInit, servers ...ServerApiContract) *GatewayServer {
|
||||||
general := &GatewayServer{
|
general := &GatewayServer{
|
||||||
servers: make(map[serversApiVer]ServerApiContract),
|
servers: make(map[serversApiVer]ServerApiContract),
|
||||||
|
sm: o.SM,
|
||||||
cs: o.CS,
|
cs: o.CS,
|
||||||
x: o.X,
|
x: o.X,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package gateway
|
package gateway
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"io"
|
"io"
|
||||||
"log/slog"
|
"log/slog"
|
||||||
@@ -9,12 +10,35 @@ import (
|
|||||||
|
|
||||||
"github.com/akyaiy/GoSally-mvp/internal/core/utils"
|
"github.com/akyaiy/GoSally-mvp/internal/core/utils"
|
||||||
"github.com/akyaiy/GoSally-mvp/internal/server/rpc"
|
"github.com/akyaiy/GoSally-mvp/internal/server/rpc"
|
||||||
|
"github.com/google/uuid"
|
||||||
)
|
)
|
||||||
|
|
||||||
func (gs *GatewayServer) Handle(w http.ResponseWriter, r *http.Request) {
|
func (gs *GatewayServer) Handle(w http.ResponseWriter, r *http.Request) {
|
||||||
|
ctx := r.Context() // TODO
|
||||||
|
|
||||||
w.Header().Set("Content-Type", "application/json")
|
w.Header().Set("Content-Type", "application/json")
|
||||||
|
sessionUUID := r.Header.Get("X-Session-UUID")
|
||||||
|
if sessionUUID == "" {
|
||||||
|
sessionUUID = uuid.New().String()
|
||||||
|
}
|
||||||
|
gs.x.SLog.Debug("new request", slog.String("session-uuid", sessionUUID), slog.Group("connection", slog.String("ip", r.RemoteAddr)))
|
||||||
|
|
||||||
|
w.Header().Set("X-Session-UUID", sessionUUID)
|
||||||
|
if !gs.sm.Add(sessionUUID) {
|
||||||
|
gs.x.SLog.Debug("session is busy", slog.String("session-uuid", sessionUUID))
|
||||||
|
rpc.WriteError(w, &rpc.RPCResponse{
|
||||||
|
Error: map[string]any{
|
||||||
|
"code": rpc.ErrSessionIsBusy,
|
||||||
|
"message": rpc.ErrSessionIsBusyS,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer gs.sm.Delete(sessionUUID)
|
||||||
|
|
||||||
body, err := io.ReadAll(r.Body)
|
body, err := io.ReadAll(r.Body)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
gs.x.SLog.Debug("failed to read body", slog.String("err", err.Error()))
|
||||||
w.WriteHeader(http.StatusBadRequest)
|
w.WriteHeader(http.StatusBadRequest)
|
||||||
rpc.WriteError(w, &rpc.RPCResponse{
|
rpc.WriteError(w, &rpc.RPCResponse{
|
||||||
JSONRPC: rpc.JSONRPCVersion,
|
JSONRPC: rpc.JSONRPCVersion,
|
||||||
@@ -34,6 +58,7 @@ func (gs *GatewayServer) Handle(w http.ResponseWriter, r *http.Request) {
|
|||||||
var single rpc.RPCRequest
|
var single rpc.RPCRequest
|
||||||
if batch == nil {
|
if batch == nil {
|
||||||
if err := json.Unmarshal(body, &single); err != nil {
|
if err := json.Unmarshal(body, &single); err != nil {
|
||||||
|
gs.x.SLog.Debug("failed to parse json", slog.String("err", err.Error()))
|
||||||
w.WriteHeader(http.StatusBadRequest)
|
w.WriteHeader(http.StatusBadRequest)
|
||||||
rpc.WriteError(w, &rpc.RPCResponse{
|
rpc.WriteError(w, &rpc.RPCResponse{
|
||||||
JSONRPC: rpc.JSONRPCVersion,
|
JSONRPC: rpc.JSONRPCVersion,
|
||||||
@@ -46,7 +71,11 @@ func (gs *GatewayServer) Handle(w http.ResponseWriter, r *http.Request) {
|
|||||||
gs.x.SLog.Info("invalid request received", slog.String("issue", rpc.ErrParseErrorS))
|
gs.x.SLog.Info("invalid request received", slog.String("issue", rpc.ErrParseErrorS))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
resp := gs.Route(r, &single)
|
resp := gs.Route(ctx, sessionUUID, r, &single)
|
||||||
|
if resp == nil {
|
||||||
|
w.Write([]byte(""))
|
||||||
|
return
|
||||||
|
}
|
||||||
rpc.WriteResponse(w, resp)
|
rpc.WriteResponse(w, resp)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -58,7 +87,7 @@ func (gs *GatewayServer) Handle(w http.ResponseWriter, r *http.Request) {
|
|||||||
wg.Add(1)
|
wg.Add(1)
|
||||||
go func(req rpc.RPCRequest) {
|
go func(req rpc.RPCRequest) {
|
||||||
defer wg.Done()
|
defer wg.Done()
|
||||||
res := gs.Route(r, &req)
|
res := gs.Route(ctx, sessionUUID, r, &req)
|
||||||
if res != nil {
|
if res != nil {
|
||||||
responses <- *res
|
responses <- *res
|
||||||
}
|
}
|
||||||
@@ -73,29 +102,31 @@ func (gs *GatewayServer) Handle(w http.ResponseWriter, r *http.Request) {
|
|||||||
}
|
}
|
||||||
if len(result) > 0 {
|
if len(result) > 0 {
|
||||||
json.NewEncoder(w).Encode(result)
|
json.NewEncoder(w).Encode(result)
|
||||||
|
} else {
|
||||||
|
w.Write([]byte("[]"))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (gs *GatewayServer) Route(r *http.Request, req *rpc.RPCRequest) (resp *rpc.RPCResponse) {
|
func (gs *GatewayServer) Route(ctx context.Context, sid string, r *http.Request, req *rpc.RPCRequest) (resp *rpc.RPCResponse) {
|
||||||
defer utils.CatchPanicWithFallback(func(rec any) {
|
defer utils.CatchPanicWithFallback(func(rec any) {
|
||||||
gs.x.SLog.Error("panic caught in handler", slog.Any("error", rec))
|
gs.x.SLog.Error("panic caught in handler", slog.Any("error", rec))
|
||||||
resp = rpc.NewError(rpc.ErrInternalError, "Internal server error (panic)", req.ID)
|
resp = rpc.NewError(rpc.ErrInternalError, "Internal server error (panic)", nil, req.ID)
|
||||||
})
|
})
|
||||||
if req.JSONRPC != rpc.JSONRPCVersion {
|
if req.JSONRPC != rpc.JSONRPCVersion {
|
||||||
gs.x.SLog.Info("invalid request received", slog.String("issue", rpc.ErrInvalidRequestS), slog.String("requested-version", req.JSONRPC))
|
gs.x.SLog.Info("invalid request received", slog.String("issue", rpc.ErrInvalidRequestS), slog.String("requested-version", req.JSONRPC))
|
||||||
return rpc.NewError(rpc.ErrInvalidRequest, rpc.ErrInvalidRequestS, req.ID)
|
return rpc.NewError(rpc.ErrInvalidRequest, rpc.ErrInvalidRequestS, nil, req.ID)
|
||||||
}
|
}
|
||||||
|
|
||||||
server, ok := gs.servers[serversApiVer(req.ContextVersion)]
|
server, ok := gs.servers[serversApiVer(req.ContextVersion)]
|
||||||
if !ok {
|
if !ok {
|
||||||
gs.x.SLog.Info("invalid request received", slog.String("issue", rpc.ErrContextVersionS), slog.String("requested-version", req.ContextVersion))
|
gs.x.SLog.Info("invalid request received", slog.String("issue", rpc.ErrContextVersionS), slog.String("requested-version", req.ContextVersion))
|
||||||
return rpc.NewError(rpc.ErrContextVersion, rpc.ErrContextVersionS, req.ID)
|
return rpc.NewError(rpc.ErrContextVersion, rpc.ErrContextVersionS, nil, req.ID)
|
||||||
}
|
}
|
||||||
|
|
||||||
resp = server.Handle(r, req)
|
|
||||||
// checks if request is notification
|
// checks if request is notification
|
||||||
if req.ID == nil {
|
if req.ID == nil {
|
||||||
|
go server.Handle(ctx, sid, r, req)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
return resp
|
return server.Handle(ctx, sid, r, req)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,4 +24,7 @@ const (
|
|||||||
|
|
||||||
ErrMethodIsMissing = -32020
|
ErrMethodIsMissing = -32020
|
||||||
ErrMethodIsMissingS = "Method is missing"
|
ErrMethodIsMissingS = "Method is missing"
|
||||||
|
|
||||||
|
ErrSessionIsBusy = -32030
|
||||||
|
ErrSessionIsBusyS = "The session is busy"
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -2,7 +2,18 @@ package rpc
|
|||||||
|
|
||||||
import "encoding/json"
|
import "encoding/json"
|
||||||
|
|
||||||
func NewError(code int, message string, id *json.RawMessage) *RPCResponse {
|
func NewError(code int, message string, data any, id *json.RawMessage) *RPCResponse {
|
||||||
|
if data != nil {
|
||||||
|
return &RPCResponse{
|
||||||
|
JSONRPC: JSONRPCVersion,
|
||||||
|
ID: id,
|
||||||
|
Error: map[string]any{
|
||||||
|
"code": code,
|
||||||
|
"message": message,
|
||||||
|
"data": data,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
return &RPCResponse{
|
return &RPCResponse{
|
||||||
JSONRPC: JSONRPCVersion,
|
JSONRPC: JSONRPCVersion,
|
||||||
ID: id,
|
ID: id,
|
||||||
|
|||||||
47
internal/server/session/manager.go
Normal file
47
internal/server/session/manager.go
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
package session
|
||||||
|
|
||||||
|
import (
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
type SessionManagerContract interface {
|
||||||
|
Add(uuid string) bool
|
||||||
|
Delete(uuid string)
|
||||||
|
StartCleanup(interval time.Duration)
|
||||||
|
}
|
||||||
|
|
||||||
|
type SessionManager struct {
|
||||||
|
sessions sync.Map
|
||||||
|
ttl time.Duration
|
||||||
|
}
|
||||||
|
|
||||||
|
func New(ttl time.Duration) *SessionManager {
|
||||||
|
return &SessionManager{
|
||||||
|
ttl: ttl,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sm *SessionManager) Add(uuid string) bool {
|
||||||
|
_, loaded := sm.sessions.LoadOrStore(uuid, time.Now().Add(sm.ttl))
|
||||||
|
return !loaded
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sm *SessionManager) Delete(uuid string) {
|
||||||
|
sm.sessions.Delete(uuid)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sm *SessionManager) StartCleanup(interval time.Duration) {
|
||||||
|
go func() {
|
||||||
|
ticker := time.NewTicker(interval)
|
||||||
|
for range ticker.C {
|
||||||
|
sm.sessions.Range(func(key, value any) bool {
|
||||||
|
expiry := value.(time.Time)
|
||||||
|
if time.Now().After(expiry) {
|
||||||
|
sm.sessions.Delete(key)
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
}
|
||||||
@@ -1,28 +1,29 @@
|
|||||||
package sv1
|
package sv1
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"log/slog"
|
"log/slog"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"github.com/akyaiy/GoSally-mvp/internal/server/rpc"
|
"github.com/akyaiy/GoSally-mvp/internal/server/rpc"
|
||||||
)
|
)
|
||||||
|
|
||||||
func (h *HandlerV1) Handle(r *http.Request, req *rpc.RPCRequest) *rpc.RPCResponse {
|
func (h *HandlerV1) Handle(_ context.Context, sid string, r *http.Request, req *rpc.RPCRequest) *rpc.RPCResponse {
|
||||||
if req.Method == "" {
|
if req.Method == "" {
|
||||||
h.x.SLog.Info("invalid request received", slog.String("issue", rpc.ErrMethodNotFoundS), slog.String("requested-method", req.Method))
|
h.x.SLog.Info("invalid request received", slog.String("issue", rpc.ErrMethodNotFoundS), slog.String("requested-method", req.Method))
|
||||||
return rpc.NewError(rpc.ErrMethodIsMissing, rpc.ErrMethodIsMissingS, req.ID)
|
return rpc.NewError(rpc.ErrMethodIsMissing, rpc.ErrMethodIsMissingS, nil, req.ID)
|
||||||
}
|
}
|
||||||
|
|
||||||
method, err := h.resolveMethodPath(req.Method)
|
method, err := h.resolveMethodPath(req.Method)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if err.Error() == rpc.ErrInvalidMethodFormatS {
|
if err.Error() == rpc.ErrInvalidMethodFormatS {
|
||||||
h.x.SLog.Info("invalid request received", slog.String("issue", rpc.ErrInvalidMethodFormatS), slog.String("requested-method", req.Method))
|
h.x.SLog.Info("invalid request received", slog.String("issue", rpc.ErrInvalidMethodFormatS), slog.String("requested-method", req.Method))
|
||||||
return rpc.NewError(rpc.ErrInvalidMethodFormat, rpc.ErrInvalidMethodFormatS, req.ID)
|
return rpc.NewError(rpc.ErrInvalidMethodFormat, rpc.ErrInvalidMethodFormatS, nil, req.ID)
|
||||||
} else if err.Error() == rpc.ErrMethodNotFoundS {
|
} else if err.Error() == rpc.ErrMethodNotFoundS {
|
||||||
h.x.SLog.Info("invalid request received", slog.String("issue", rpc.ErrMethodNotFoundS), slog.String("requested-method", req.Method))
|
h.x.SLog.Info("invalid request received", slog.String("issue", rpc.ErrMethodNotFoundS), slog.String("requested-method", req.Method))
|
||||||
return rpc.NewError(rpc.ErrMethodNotFound, rpc.ErrMethodNotFoundS, req.ID)
|
return rpc.NewError(rpc.ErrMethodNotFound, rpc.ErrMethodNotFoundS, nil, req.ID)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return h.handleLUA(method, req)
|
return h.handleLUA(sid, r, req, method)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,118 +1,653 @@
|
|||||||
package sv1
|
package sv1
|
||||||
|
|
||||||
|
// TODO: make a lua state pool using sync.Pool
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"database/sql"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"io"
|
||||||
"log/slog"
|
"log/slog"
|
||||||
|
"math/rand/v2"
|
||||||
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"sync"
|
||||||
|
|
||||||
"github.com/akyaiy/GoSally-mvp/internal/engine/logs"
|
"github.com/akyaiy/GoSally-mvp/internal/colors"
|
||||||
"github.com/akyaiy/GoSally-mvp/internal/server/rpc"
|
"github.com/akyaiy/GoSally-mvp/internal/server/rpc"
|
||||||
lua "github.com/yuin/gopher-lua"
|
lua "github.com/yuin/gopher-lua"
|
||||||
|
_ "modernc.org/sqlite"
|
||||||
)
|
)
|
||||||
|
|
||||||
func (h *HandlerV1) handleLUA(path string, req *rpc.RPCRequest) *rpc.RPCResponse {
|
type DBConnection struct {
|
||||||
|
dbPath string
|
||||||
|
log bool
|
||||||
|
logger *slog.Logger
|
||||||
|
writeChan chan *dbWriteRequest
|
||||||
|
closeChan chan struct{}
|
||||||
|
}
|
||||||
|
|
||||||
|
type dbWriteRequest struct {
|
||||||
|
query string
|
||||||
|
args []interface{}
|
||||||
|
resCh chan *dbWriteResult
|
||||||
|
}
|
||||||
|
|
||||||
|
type dbWriteResult struct {
|
||||||
|
rowsAffected int64
|
||||||
|
err error
|
||||||
|
}
|
||||||
|
|
||||||
|
var dbMutexMap = make(map[string]*sync.RWMutex)
|
||||||
|
var dbGlobalMutex sync.Mutex
|
||||||
|
|
||||||
|
func getDBMutex(dbPath string) *sync.RWMutex {
|
||||||
|
dbGlobalMutex.Lock()
|
||||||
|
defer dbGlobalMutex.Unlock()
|
||||||
|
|
||||||
|
if mtx, ok := dbMutexMap[dbPath]; ok {
|
||||||
|
return mtx
|
||||||
|
}
|
||||||
|
|
||||||
|
mtx := &sync.RWMutex{}
|
||||||
|
dbMutexMap[dbPath] = mtx
|
||||||
|
return mtx
|
||||||
|
}
|
||||||
|
|
||||||
|
func loadDBMod(llog *slog.Logger) func(*lua.LState) int {
|
||||||
|
llog.Debug("import module db-sqlite")
|
||||||
|
return func(L *lua.LState) int {
|
||||||
|
dbMod := L.NewTable()
|
||||||
|
|
||||||
|
L.SetField(dbMod, "connect", L.NewFunction(func(L *lua.LState) int {
|
||||||
|
dbPath := L.CheckString(1)
|
||||||
|
|
||||||
|
logQueries := false
|
||||||
|
if L.GetTop() >= 2 {
|
||||||
|
opts := L.CheckTable(2)
|
||||||
|
if val := opts.RawGetString("log"); val != lua.LNil {
|
||||||
|
logQueries = lua.LVAsBool(val)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
conn := &DBConnection{
|
||||||
|
dbPath: dbPath,
|
||||||
|
log: logQueries,
|
||||||
|
logger: llog,
|
||||||
|
writeChan: make(chan *dbWriteRequest, 100),
|
||||||
|
closeChan: make(chan struct{}),
|
||||||
|
}
|
||||||
|
|
||||||
|
go conn.processWrites()
|
||||||
|
|
||||||
|
ud := L.NewUserData()
|
||||||
|
ud.Value = conn
|
||||||
|
L.SetMetatable(ud, L.GetTypeMetatable("gosally_db"))
|
||||||
|
|
||||||
|
L.Push(ud)
|
||||||
|
return 1
|
||||||
|
}))
|
||||||
|
|
||||||
|
mt := L.NewTypeMetatable("gosally_db")
|
||||||
|
L.SetField(mt, "__index", L.SetFuncs(L.NewTable(), map[string]lua.LGFunction{
|
||||||
|
"exec": dbExec,
|
||||||
|
"query": dbQuery,
|
||||||
|
"close": dbClose,
|
||||||
|
}))
|
||||||
|
|
||||||
|
L.SetField(dbMod, "__gosally_internal", lua.LString("0"))
|
||||||
|
L.Push(dbMod)
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (conn *DBConnection) processWrites() {
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case req := <-conn.writeChan:
|
||||||
|
mtx := getDBMutex(conn.dbPath)
|
||||||
|
mtx.Lock()
|
||||||
|
|
||||||
|
db, err := sql.Open("sqlite", conn.dbPath+"?_busy_timeout=5000&_journal_mode=WAL&_sync=NORMAL&_cache_size=-10000")
|
||||||
|
if err == nil {
|
||||||
|
_, err = db.Exec("PRAGMA journal_mode=WAL;")
|
||||||
|
if err == nil {
|
||||||
|
res, execErr := db.Exec(req.query, req.args...)
|
||||||
|
if execErr == nil {
|
||||||
|
rows, _ := res.RowsAffected()
|
||||||
|
req.resCh <- &dbWriteResult{rowsAffected: rows}
|
||||||
|
} else {
|
||||||
|
req.resCh <- &dbWriteResult{err: execErr}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
db.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
req.resCh <- &dbWriteResult{err: err}
|
||||||
|
}
|
||||||
|
|
||||||
|
mtx.Unlock()
|
||||||
|
case <-conn.closeChan:
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func dbExec(L *lua.LState) int {
|
||||||
|
ud := L.CheckUserData(1)
|
||||||
|
conn, ok := ud.Value.(*DBConnection)
|
||||||
|
if !ok {
|
||||||
|
L.Push(lua.LNil)
|
||||||
|
L.Push(lua.LString("invalid database connection"))
|
||||||
|
return 2
|
||||||
|
}
|
||||||
|
|
||||||
|
query := L.CheckString(2)
|
||||||
|
|
||||||
|
var args []any
|
||||||
|
if L.GetTop() >= 3 {
|
||||||
|
params := L.CheckTable(3)
|
||||||
|
params.ForEach(func(k lua.LValue, v lua.LValue) {
|
||||||
|
args = append(args, ConvertLuaTypesToGolang(v))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
if conn.log {
|
||||||
|
conn.logger.Info("DB Exec",
|
||||||
|
slog.String("query", query),
|
||||||
|
slog.Any("params", args))
|
||||||
|
}
|
||||||
|
|
||||||
|
resCh := make(chan *dbWriteResult, 1)
|
||||||
|
conn.writeChan <- &dbWriteRequest{
|
||||||
|
query: query,
|
||||||
|
args: args,
|
||||||
|
resCh: resCh,
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx := L.NewTable()
|
||||||
|
L.SetField(ctx, "done", lua.LBool(false))
|
||||||
|
|
||||||
|
var result lua.LValue = lua.LNil
|
||||||
|
var errorMsg lua.LValue = lua.LNil
|
||||||
|
|
||||||
|
L.SetField(ctx, "wait", L.NewFunction(func(lL *lua.LState) int {
|
||||||
|
res := <-resCh
|
||||||
|
L.SetField(ctx, "done", lua.LBool(true))
|
||||||
|
|
||||||
|
if res.err != nil {
|
||||||
|
errorMsg = lua.LString(res.err.Error())
|
||||||
|
result = lua.LNil
|
||||||
|
} else {
|
||||||
|
result = lua.LNumber(res.rowsAffected)
|
||||||
|
errorMsg = lua.LNil
|
||||||
|
}
|
||||||
|
|
||||||
|
if res.err != nil {
|
||||||
|
lL.Push(lua.LNil)
|
||||||
|
lL.Push(lua.LString(res.err.Error()))
|
||||||
|
return 2
|
||||||
|
}
|
||||||
|
lL.Push(lua.LNumber(res.rowsAffected))
|
||||||
|
lL.Push(lua.LNil)
|
||||||
|
return 2
|
||||||
|
}))
|
||||||
|
|
||||||
|
L.SetField(ctx, "check", L.NewFunction(func(lL *lua.LState) int {
|
||||||
|
select {
|
||||||
|
case res := <-resCh:
|
||||||
|
lL.SetField(ctx, "done", lua.LBool(true))
|
||||||
|
if res.err != nil {
|
||||||
|
errorMsg = lua.LString(res.err.Error())
|
||||||
|
result = lua.LNil
|
||||||
|
lL.Push(lua.LNil)
|
||||||
|
lL.Push(lua.LString(res.err.Error()))
|
||||||
|
return 2
|
||||||
|
} else {
|
||||||
|
result = lua.LNumber(res.rowsAffected)
|
||||||
|
errorMsg = lua.LNil
|
||||||
|
lL.Push(lua.LNumber(res.rowsAffected))
|
||||||
|
lL.Push(lua.LNil)
|
||||||
|
return 2
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
lL.Push(result)
|
||||||
|
lL.Push(errorMsg)
|
||||||
|
return 2
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
|
||||||
|
L.Push(ctx)
|
||||||
|
L.Push(lua.LNil)
|
||||||
|
return 2
|
||||||
|
}
|
||||||
|
|
||||||
|
func dbQuery(L *lua.LState) int {
|
||||||
|
ud := L.CheckUserData(1)
|
||||||
|
conn, ok := ud.Value.(*DBConnection)
|
||||||
|
if !ok {
|
||||||
|
L.Push(lua.LNil)
|
||||||
|
L.Push(lua.LString("invalid database connection"))
|
||||||
|
return 2
|
||||||
|
}
|
||||||
|
|
||||||
|
query := L.CheckString(2)
|
||||||
|
|
||||||
|
var args []any
|
||||||
|
if L.GetTop() >= 3 {
|
||||||
|
params := L.CheckTable(3)
|
||||||
|
params.ForEach(func(k lua.LValue, v lua.LValue) {
|
||||||
|
args = append(args, ConvertLuaTypesToGolang(v))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
if conn.log {
|
||||||
|
conn.logger.Info("DB Query",
|
||||||
|
slog.String("query", query),
|
||||||
|
slog.Any("params", args))
|
||||||
|
}
|
||||||
|
|
||||||
|
mtx := getDBMutex(conn.dbPath)
|
||||||
|
mtx.RLock()
|
||||||
|
defer mtx.RUnlock()
|
||||||
|
|
||||||
|
db, err := sql.Open("sqlite", conn.dbPath+"?_busy_timeout=5000&_journal_mode=WAL&_sync=NORMAL&_cache_size=-10000")
|
||||||
|
if err != nil {
|
||||||
|
L.Push(lua.LNil)
|
||||||
|
L.Push(lua.LString(err.Error()))
|
||||||
|
return 2
|
||||||
|
}
|
||||||
|
defer db.Close()
|
||||||
|
|
||||||
|
rows, err := db.Query(query, args...)
|
||||||
|
if err != nil {
|
||||||
|
L.Push(lua.LNil)
|
||||||
|
L.Push(lua.LString(fmt.Sprintf("query failed: %v", err)))
|
||||||
|
return 2
|
||||||
|
}
|
||||||
|
defer rows.Close()
|
||||||
|
|
||||||
|
columns, err := rows.Columns()
|
||||||
|
if err != nil {
|
||||||
|
L.Push(lua.LNil)
|
||||||
|
L.Push(lua.LString(fmt.Sprintf("get columns failed: %v", err)))
|
||||||
|
return 2
|
||||||
|
}
|
||||||
|
|
||||||
|
result := L.NewTable()
|
||||||
|
colCount := len(columns)
|
||||||
|
values := make([]any, colCount)
|
||||||
|
valuePtrs := make([]any, colCount)
|
||||||
|
|
||||||
|
for rows.Next() {
|
||||||
|
for i := range columns {
|
||||||
|
valuePtrs[i] = &values[i]
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := rows.Scan(valuePtrs...); err != nil {
|
||||||
|
L.Push(lua.LNil)
|
||||||
|
L.Push(lua.LString(fmt.Sprintf("scan failed: %v", err)))
|
||||||
|
return 2
|
||||||
|
}
|
||||||
|
|
||||||
|
rowTable := L.NewTable()
|
||||||
|
for i, col := range columns {
|
||||||
|
val := values[i]
|
||||||
|
if val == nil {
|
||||||
|
L.SetField(rowTable, col, lua.LNil)
|
||||||
|
} else {
|
||||||
|
L.SetField(rowTable, col, ConvertGolangTypesToLua(L, val))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
result.Append(rowTable)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := rows.Err(); err != nil {
|
||||||
|
L.Push(lua.LNil)
|
||||||
|
L.Push(lua.LString(fmt.Sprintf("rows iteration failed: %v", err)))
|
||||||
|
return 2
|
||||||
|
}
|
||||||
|
|
||||||
|
L.Push(result)
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
func dbClose(L *lua.LState) int {
|
||||||
|
ud := L.CheckUserData(1)
|
||||||
|
conn, ok := ud.Value.(*DBConnection)
|
||||||
|
if !ok {
|
||||||
|
L.Push(lua.LFalse)
|
||||||
|
L.Push(lua.LString("invalid database connection"))
|
||||||
|
return 2
|
||||||
|
}
|
||||||
|
|
||||||
|
close(conn.closeChan)
|
||||||
|
L.Push(lua.LTrue)
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
func addInitiatorHeaders(sid string, req *http.Request, headers http.Header) {
|
||||||
|
clientIP := req.RemoteAddr
|
||||||
|
if forwardedFor := req.Header.Get("X-Forwarded-For"); forwardedFor != "" {
|
||||||
|
clientIP = forwardedFor
|
||||||
|
}
|
||||||
|
headers.Set("X-Initiator-IP", clientIP)
|
||||||
|
headers.Set("X-Session-UUID", sid)
|
||||||
|
headers.Set("X-Initiator-Host", req.Host)
|
||||||
|
headers.Set("X-Initiator-User-Agent", req.UserAgent())
|
||||||
|
headers.Set("X-Initiator-Referer", req.Referer())
|
||||||
|
}
|
||||||
|
|
||||||
|
// A small reminder: this code is only at the MVP stage,
|
||||||
|
// and some parts of the code may cause shock from the
|
||||||
|
// incompetence of the developer. But, in the end,
|
||||||
|
// this code is just an idea. If there is a desire to
|
||||||
|
// contribute to the development of the code,
|
||||||
|
// I will be only glad.
|
||||||
|
// TODO: make this huge function more harmonious by dividing responsibilities
|
||||||
|
func (h *HandlerV1) handleLUA(sid string, r *http.Request, req *rpc.RPCRequest, path string) *rpc.RPCResponse {
|
||||||
|
llog := h.x.SLog.With(slog.String("session-id", sid))
|
||||||
|
llog.Debug("handling LUA")
|
||||||
L := lua.NewState()
|
L := lua.NewState()
|
||||||
defer L.Close()
|
defer L.Close()
|
||||||
|
|
||||||
inTable := L.NewTable()
|
seed := rand.Int()
|
||||||
paramsTable := L.NewTable()
|
|
||||||
if fetchedParams, ok := req.Params.(map[string]any); ok {
|
loadSessionMod := func(lL *lua.LState) int {
|
||||||
for k, v := range fetchedParams {
|
llog.Debug("import module session", slog.String("script", path))
|
||||||
L.SetField(paramsTable, k, ConvertGolangTypesToLua(L, v))
|
sessionMod := lL.NewTable()
|
||||||
|
inTable := lL.NewTable()
|
||||||
|
paramsTable := lL.NewTable()
|
||||||
|
if fetchedParams, ok := req.Params.(map[string]any); ok {
|
||||||
|
for k, v := range fetchedParams {
|
||||||
|
lL.SetField(paramsTable, k, ConvertGolangTypesToLua(lL, v))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
lL.SetField(inTable, "params", paramsTable)
|
||||||
L.SetField(inTable, "Params", paramsTable)
|
|
||||||
L.SetGlobal("In", inTable)
|
|
||||||
|
|
||||||
outTable := L.NewTable()
|
outTable := lL.NewTable()
|
||||||
resultTable := L.NewTable()
|
resultTable := lL.NewTable()
|
||||||
L.SetField(outTable, "Result", resultTable)
|
lL.SetField(outTable, "result", resultTable)
|
||||||
L.SetGlobal("Out", outTable)
|
|
||||||
|
|
||||||
logTable := L.NewTable()
|
lL.SetField(inTable, "address", lua.LString(r.RemoteAddr))
|
||||||
|
lL.SetField(sessionMod, "request", inTable)
|
||||||
|
lL.SetField(sessionMod, "response", outTable)
|
||||||
|
|
||||||
logFuncs := map[string]func(string, ...any){
|
lL.SetField(sessionMod, "id", lua.LString(sid))
|
||||||
"Info": h.x.SLog.Info,
|
|
||||||
"Debug": h.x.SLog.Debug,
|
lL.SetField(sessionMod, "__gosally_internal", lua.LString(fmt.Sprint(seed)))
|
||||||
"Error": h.x.SLog.Error,
|
lL.Push(sessionMod)
|
||||||
"Warn": h.x.SLog.Warn,
|
return 1
|
||||||
}
|
}
|
||||||
|
|
||||||
for name, logFunc := range logFuncs {
|
loadLogMod := func(lL *lua.LState) int {
|
||||||
L.SetField(logTable, name, L.NewFunction(func(L *lua.LState) int {
|
llog.Debug("import module log", slog.String("script", path))
|
||||||
msg := L.ToString(1)
|
logMod := lL.NewTable()
|
||||||
logFunc(fmt.Sprintf("the script says: %s", msg), slog.String("script", path))
|
|
||||||
return 0
|
logFuncs := map[string]func(string, ...any){
|
||||||
|
"info": llog.Info,
|
||||||
|
"debug": llog.Debug,
|
||||||
|
"error": llog.Error,
|
||||||
|
"warn": llog.Warn,
|
||||||
|
}
|
||||||
|
|
||||||
|
for name, logFunc := range logFuncs {
|
||||||
|
fun := logFunc
|
||||||
|
lL.SetField(logMod, name, lL.NewFunction(func(lL *lua.LState) int {
|
||||||
|
msg := lL.Get(1)
|
||||||
|
converted := ConvertLuaTypesToGolang(msg)
|
||||||
|
fun(fmt.Sprintf("the script says: %s", converted), slog.String("script", path))
|
||||||
|
return 0
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, fn := range []struct {
|
||||||
|
field string
|
||||||
|
pfunc func(string, ...any)
|
||||||
|
color func() string
|
||||||
|
}{
|
||||||
|
{"event", h.x.Log.Printf, nil},
|
||||||
|
{"event_error", h.x.Log.Printf, colors.PrintError},
|
||||||
|
{"event_warn", h.x.Log.Printf, colors.PrintWarn},
|
||||||
|
} {
|
||||||
|
lL.SetField(logMod, fn.field, lL.NewFunction(func(lL *lua.LState) int {
|
||||||
|
msg := lL.Get(1)
|
||||||
|
converted := ConvertLuaTypesToGolang(msg)
|
||||||
|
if fn.color != nil {
|
||||||
|
h.x.Log.Printf("%s: %s: %s", fn.color(), path, converted)
|
||||||
|
} else {
|
||||||
|
h.x.Log.Printf("%s: %s", path, converted)
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
lL.SetField(logMod, "__gosally_internal", lua.LString(fmt.Sprint(seed)))
|
||||||
|
lL.Push(logMod)
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
loadNetMod := func(lL *lua.LState) int {
|
||||||
|
llog.Debug("import module net", slog.String("script", path))
|
||||||
|
netMod := lL.NewTable()
|
||||||
|
netModhttp := lL.NewTable()
|
||||||
|
|
||||||
|
lL.SetField(netModhttp, "get_request", lL.NewFunction(func(lL *lua.LState) int {
|
||||||
|
logRequest := lL.ToBool(1)
|
||||||
|
url := lL.ToString(2)
|
||||||
|
|
||||||
|
req, err := http.NewRequest("GET", url, nil)
|
||||||
|
if err != nil {
|
||||||
|
lL.Push(lua.LNil)
|
||||||
|
lL.Push(lua.LString(err.Error()))
|
||||||
|
return 2
|
||||||
|
}
|
||||||
|
|
||||||
|
addInitiatorHeaders(sid, r, req.Header)
|
||||||
|
|
||||||
|
client := &http.Client{}
|
||||||
|
resp, err := client.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
lL.Push(lua.LNil)
|
||||||
|
lL.Push(lua.LString(err.Error()))
|
||||||
|
return 2
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
body, err := io.ReadAll(resp.Body)
|
||||||
|
if err != nil {
|
||||||
|
lL.Push(lua.LNil)
|
||||||
|
lL.Push(lua.LString(err.Error()))
|
||||||
|
return 2
|
||||||
|
}
|
||||||
|
|
||||||
|
if logRequest {
|
||||||
|
llog.Info("HTTP GET request",
|
||||||
|
slog.String("script", path),
|
||||||
|
slog.String("url", url),
|
||||||
|
slog.Int("status", resp.StatusCode),
|
||||||
|
slog.String("status_text", resp.Status),
|
||||||
|
slog.String("initiator_ip", req.Header.Get("X-Initiator-IP")),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
result := lL.NewTable()
|
||||||
|
lL.SetField(result, "status", lua.LNumber(resp.StatusCode))
|
||||||
|
lL.SetField(result, "status_text", lua.LString(resp.Status))
|
||||||
|
lL.SetField(result, "body", lua.LString(body))
|
||||||
|
lL.SetField(result, "content_length", lua.LNumber(resp.ContentLength))
|
||||||
|
|
||||||
|
headers := lL.NewTable()
|
||||||
|
for k, v := range resp.Header {
|
||||||
|
lL.SetField(headers, k, ConvertGolangTypesToLua(lL, v))
|
||||||
|
}
|
||||||
|
lL.SetField(result, "headers", headers)
|
||||||
|
|
||||||
|
lL.Push(result)
|
||||||
|
return 1
|
||||||
}))
|
}))
|
||||||
|
|
||||||
|
lL.SetField(netModhttp, "post_request", lL.NewFunction(func(lL *lua.LState) int {
|
||||||
|
logRequest := lL.ToBool(1)
|
||||||
|
url := lL.ToString(2)
|
||||||
|
contentType := lL.ToString(3)
|
||||||
|
payload := lL.ToString(4)
|
||||||
|
|
||||||
|
body := strings.NewReader(payload)
|
||||||
|
|
||||||
|
req, err := http.NewRequest("POST", url, body)
|
||||||
|
if err != nil {
|
||||||
|
lL.Push(lua.LNil)
|
||||||
|
lL.Push(lua.LString(err.Error()))
|
||||||
|
return 2
|
||||||
|
}
|
||||||
|
|
||||||
|
req.Header.Set("Content-Type", contentType)
|
||||||
|
|
||||||
|
addInitiatorHeaders(sid, r, req.Header)
|
||||||
|
|
||||||
|
client := &http.Client{}
|
||||||
|
resp, err := client.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
lL.Push(lua.LNil)
|
||||||
|
lL.Push(lua.LString(err.Error()))
|
||||||
|
return 2
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
respBody, err := io.ReadAll(resp.Body)
|
||||||
|
if err != nil {
|
||||||
|
lL.Push(lua.LNil)
|
||||||
|
lL.Push(lua.LString(err.Error()))
|
||||||
|
return 2
|
||||||
|
}
|
||||||
|
|
||||||
|
if logRequest {
|
||||||
|
llog.Info("HTTP POST request",
|
||||||
|
slog.String("script", path),
|
||||||
|
slog.String("url", url),
|
||||||
|
slog.String("content_type", contentType),
|
||||||
|
slog.Int("status", resp.StatusCode),
|
||||||
|
slog.String("status_text", resp.Status),
|
||||||
|
slog.String("initiator_ip", req.Header.Get("X-Initiator-IP")),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
result := lL.NewTable()
|
||||||
|
lL.SetField(result, "status", lua.LNumber(resp.StatusCode))
|
||||||
|
lL.SetField(result, "status_text", lua.LString(resp.Status))
|
||||||
|
lL.SetField(result, "body", lua.LString(respBody))
|
||||||
|
lL.SetField(result, "content_length", lua.LNumber(resp.ContentLength))
|
||||||
|
|
||||||
|
headers := lL.NewTable()
|
||||||
|
for k, v := range resp.Header {
|
||||||
|
lL.SetField(headers, k, ConvertGolangTypesToLua(lL, v))
|
||||||
|
}
|
||||||
|
lL.SetField(result, "headers", headers)
|
||||||
|
|
||||||
|
lL.Push(result)
|
||||||
|
return 1
|
||||||
|
}))
|
||||||
|
|
||||||
|
lL.SetField(netMod, "http", netModhttp)
|
||||||
|
|
||||||
|
lL.SetField(netMod, "__gosally_internal", lua.LString(fmt.Sprint(seed)))
|
||||||
|
lL.Push(netMod)
|
||||||
|
return 1
|
||||||
}
|
}
|
||||||
|
|
||||||
L.SetField(logTable, "Event", L.NewFunction(func(L *lua.LState) int {
|
L.PreloadModule("internal.session", loadSessionMod)
|
||||||
msg := L.ToString(1)
|
L.PreloadModule("internal.log", loadLogMod)
|
||||||
h.x.Log.Printf("%s: %s", path, msg)
|
L.PreloadModule("internal.net", loadNetMod)
|
||||||
return 0
|
L.PreloadModule("internal.database-sqlite", loadDBMod(llog))
|
||||||
}))
|
|
||||||
|
|
||||||
L.SetField(logTable, "EventError", L.NewFunction(func(L *lua.LState) int {
|
llog.Debug("preparing environment")
|
||||||
msg := L.ToString(1)
|
prep := filepath.Join(*h.x.Config.Conf.Node.ComDir, "_prepare.lua")
|
||||||
h.x.Log.Printf("%s: %s: %s", logs.PrintError(), path, msg)
|
|
||||||
return 0
|
|
||||||
}))
|
|
||||||
|
|
||||||
L.SetField(logTable, "EventWarn", L.NewFunction(func(L *lua.LState) int {
|
|
||||||
msg := L.ToString(1)
|
|
||||||
h.x.Log.Printf("%s: %s: %s", logs.PrintWarn(), path, msg)
|
|
||||||
return 0
|
|
||||||
}))
|
|
||||||
|
|
||||||
L.SetGlobal("Log", logTable)
|
|
||||||
|
|
||||||
prep := filepath.Join(*h.x.Config.Conf.ComDir, "_prepare.lua")
|
|
||||||
if _, err := os.Stat(prep); err == nil {
|
if _, err := os.Stat(prep); err == nil {
|
||||||
if err := L.DoFile(prep); err != nil {
|
if err := L.DoFile(prep); err != nil {
|
||||||
return rpc.NewError(rpc.ErrInternalError, err.Error(), req.ID)
|
llog.Error("script error", slog.String("script", path), slog.String("error", err.Error()))
|
||||||
|
return rpc.NewError(rpc.ErrInternalError, rpc.ErrInternalErrorS, nil, req.ID)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
llog.Debug("executing script", slog.String("script", path))
|
||||||
if err := L.DoFile(path); err != nil {
|
if err := L.DoFile(path); err != nil {
|
||||||
return rpc.NewError(rpc.ErrInternalError, err.Error(), req.ID)
|
llog.Error("script error", slog.String("script", path), slog.String("error", err.Error()))
|
||||||
|
return rpc.NewError(rpc.ErrInternalError, rpc.ErrInternalErrorS, nil, req.ID)
|
||||||
}
|
}
|
||||||
|
|
||||||
lv := L.GetGlobal("Out")
|
pkg := L.GetGlobal("package")
|
||||||
outTbl, ok := lv.(*lua.LTable)
|
pkgTbl, ok := pkg.(*lua.LTable)
|
||||||
if !ok {
|
if !ok {
|
||||||
return rpc.NewError(rpc.ErrInternalError, "Out is not a table", req.ID)
|
llog.Error("script error", slog.String("script", path), slog.String("error", "package not found"))
|
||||||
|
return rpc.NewError(rpc.ErrInternalError, rpc.ErrInternalErrorS, nil, req.ID)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if Out.Error exists
|
loaded := pkgTbl.RawGetString("loaded")
|
||||||
if errVal := outTbl.RawGetString("Error"); errVal != lua.LNil {
|
loadedTbl, ok := loaded.(*lua.LTable)
|
||||||
|
if !ok {
|
||||||
|
llog.Error("script error", slog.String("script", path), slog.String("error", "package.loaded not found"))
|
||||||
|
return rpc.NewError(rpc.ErrInternalError, rpc.ErrInternalErrorS, nil, req.ID)
|
||||||
|
}
|
||||||
|
|
||||||
|
sessionVal := loadedTbl.RawGetString("internal.session")
|
||||||
|
sessionTbl, ok := sessionVal.(*lua.LTable)
|
||||||
|
if !ok {
|
||||||
|
return rpc.NewResponse(map[string]any{
|
||||||
|
"responsible-node": h.cs.UUID32,
|
||||||
|
}, req.ID)
|
||||||
|
}
|
||||||
|
|
||||||
|
tag := sessionTbl.RawGetString("__gosally_internal")
|
||||||
|
if tag.Type() != lua.LTString || tag.String() != fmt.Sprint(seed) {
|
||||||
|
llog.Debug("stock session module is not imported: wrong seed", slog.String("script", path))
|
||||||
|
return rpc.NewResponse(map[string]any{
|
||||||
|
"responsible-node": h.cs.UUID32,
|
||||||
|
}, req.ID)
|
||||||
|
}
|
||||||
|
|
||||||
|
outVal := sessionTbl.RawGetString("response")
|
||||||
|
outTbl, ok := outVal.(*lua.LTable)
|
||||||
|
if !ok {
|
||||||
|
llog.Error("script error", slog.String("script", path), slog.String("error", "response is not a table"))
|
||||||
|
return rpc.NewError(rpc.ErrInternalError, rpc.ErrInternalErrorS, nil, req.ID)
|
||||||
|
}
|
||||||
|
|
||||||
|
if errVal := outTbl.RawGetString("error"); errVal != lua.LNil {
|
||||||
|
llog.Debug("catch error table", slog.String("script", path))
|
||||||
if errTbl, ok := errVal.(*lua.LTable); ok {
|
if errTbl, ok := errVal.(*lua.LTable); ok {
|
||||||
code := rpc.ErrInternalError
|
code := rpc.ErrInternalError
|
||||||
message := "Internal script error"
|
message := rpc.ErrInternalErrorS
|
||||||
|
data := make(map[string]any)
|
||||||
if c := errTbl.RawGetString("code"); c.Type() == lua.LTNumber {
|
if c := errTbl.RawGetString("code"); c.Type() == lua.LTNumber {
|
||||||
code = int(c.(lua.LNumber))
|
code = int(c.(lua.LNumber))
|
||||||
}
|
}
|
||||||
if msg := errTbl.RawGetString("message"); msg.Type() == lua.LTString {
|
if msg := errTbl.RawGetString("message"); msg.Type() == lua.LTString {
|
||||||
message = msg.String()
|
message = msg.String()
|
||||||
}
|
}
|
||||||
h.x.SLog.Error("the script terminated with an error", slog.String("code", strconv.Itoa(code)), slog.String("message", message))
|
rawData := errTbl.RawGetString("data")
|
||||||
return rpc.NewError(code, message, req.ID)
|
|
||||||
|
if tbl, ok := rawData.(*lua.LTable); ok {
|
||||||
|
tbl.ForEach(func(k, v lua.LValue) { data[k.String()] = ConvertLuaTypesToGolang(v) })
|
||||||
|
} else {
|
||||||
|
llog.Error("the script terminated with an error", slog.String("code", strconv.Itoa(code)), slog.String("message", message))
|
||||||
|
return rpc.NewError(code, message, ConvertLuaTypesToGolang(rawData), req.ID)
|
||||||
|
}
|
||||||
|
llog.Error("the script terminated with an error", slog.String("code", strconv.Itoa(code)), slog.String("message", message))
|
||||||
|
return rpc.NewError(code, message, data, req.ID)
|
||||||
}
|
}
|
||||||
return rpc.NewError(rpc.ErrInternalError, "Out.Error is not a table", req.ID)
|
return rpc.NewError(rpc.ErrInternalError, rpc.ErrInternalErrorS, nil, req.ID)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Otherwise, parse Out.Result
|
resultVal := outTbl.RawGetString("result")
|
||||||
resultVal := outTbl.RawGetString("Result")
|
payload := make(map[string]any)
|
||||||
resultTbl, ok := resultVal.(*lua.LTable)
|
if tbl, ok := resultVal.(*lua.LTable); ok {
|
||||||
if !ok {
|
tbl.ForEach(func(k, v lua.LValue) { payload[k.String()] = ConvertLuaTypesToGolang(v) })
|
||||||
return rpc.NewError(rpc.ErrInternalError, "Out.Result is not a table", req.ID)
|
} else {
|
||||||
|
payload["message"] = ConvertLuaTypesToGolang(resultVal)
|
||||||
}
|
}
|
||||||
|
payload["responsible-node"] = h.cs.UUID32
|
||||||
out := make(map[string]any)
|
return rpc.NewResponse(payload, req.ID)
|
||||||
resultTbl.ForEach(func(key lua.LValue, value lua.LValue) {
|
|
||||||
out[key.String()] = ConvertLuaTypesToGolang(value)
|
|
||||||
})
|
|
||||||
|
|
||||||
out["responsible-node"] = h.cs.UUID32
|
|
||||||
return rpc.NewResponse(out, req.ID)
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ func (h *HandlerV1) resolveMethodPath(method string) (string, error) {
|
|||||||
|
|
||||||
parts := strings.Split(method, ">")
|
parts := strings.Split(method, ">")
|
||||||
relPath := filepath.Join(parts...) + ".lua"
|
relPath := filepath.Join(parts...) + ".lua"
|
||||||
fullPath := filepath.Join(*h.x.Config.Conf.ComDir, relPath)
|
fullPath := filepath.Join(*h.x.Config.Conf.Node.ComDir, relPath)
|
||||||
|
|
||||||
if _, err := os.Stat(fullPath); os.IsNotExist(err) {
|
if _, err := os.Stat(fullPath); os.IsNotExist(err) {
|
||||||
return "", errors.New(rpc.ErrMethodNotFoundS)
|
return "", errors.New(rpc.ErrMethodNotFoundS)
|
||||||
|
|||||||
Reference in New Issue
Block a user