Compare commits

32 Commits

Author SHA1 Message Date
9c140abc6d make a table 2025-10-11 12:49:53 +03:00
e90233aec4 complete part of readme 2025-10-11 12:44:48 +03:00
df1ef57769 fix bugs 2025-10-10 22:58:31 +03:00
4c840c40bb some changes 2025-10-10 22:54:29 +03:00
57f35e8f33 move go files to src/ 2025-10-10 22:46:24 +03:00
f0c591f325 delete old files 2025-10-10 22:28:55 +03:00
36ee320c45 comment upx 2025-10-10 22:22:34 +03:00
ee6fd205d5 fix the use of empty fields in the response 2025-10-10 22:22:15 +03:00
bed0471cc4 remove some messages 2025-10-10 22:03:21 +03:00
e3812a18a6 require bcrypt only if needed 2025-10-10 20:35:19 +03:00
b7d939d5d7 optimise db exec 2025-10-10 20:23:24 +03:00
c737e80b8f add password changing support 2025-10-10 20:02:17 +03:00
5783a756c3 add method update 2025-10-10 19:27:04 +03:00
ba47ee4219 move error messages to variables 2025-10-10 19:26:57 +03:00
5d49e0afc7 add _errors.lua 2025-10-10 19:26:35 +03:00
76fed578ff create fully functional get method 2025-10-09 20:26:25 +03:00
975c52b58e delete unused modules 2025-10-09 20:03:44 +03:00
4e75d48f1d small fixes 2025-10-09 20:00:48 +03:00
65af07fffa add delete method 2025-10-09 20:00:40 +03:00
1252634420 Change method separator to . and move separator symbol and regexp template to global variables 2025-10-09 19:56:41 +03:00
4a58845211 deledet some files 2025-10-05 19:10:53 +03:00
b0701632e6 add common function to Unit layer 2025-10-05 19:10:45 +03:00
9277aa9f1a add some files to ginignore 2025-10-05 19:10:31 +03:00
19654e1eca Ad some CRUD methods to manage units table 2025-10-05 19:09:58 +03:00
d4306a0d89 rename internal.sha256.sum to hash 2025-10-05 19:09:00 +03:00
73095a69e0 Merge branch 'main' of https://github.com/akyaiy/GoSally-mvp 2025-09-12 19:19:08 +03:00
0f82ce941b in 2025-09-12 19:18:22 +03:00
Aleksey
0ec8493ab4 Merge pull request #3 from akyaiy/auth-server
Auth server
2025-09-12 19:16:32 +03:00
Aleksey
24eef9eee0 Merge pull request #2 from akyaiy/dev
Dev
2025-08-04 15:15:00 +03:00
Aleksey
a6c9e5102f Merge branch 'main' into dev 2025-08-04 15:13:55 +03:00
f3c4b9e9b1 update config example 2025-07-30 12:02:12 +03:00
Aleksey
81359c036c Merge pull request #1 from akyaiy/dev
Dev
2025-07-29 16:51:18 +03:00
65 changed files with 481 additions and 726 deletions

4
.gitignore vendored
View File

@@ -11,3 +11,7 @@ com/_config.lua
.vscode
Taskfile.yml
config.yaml
# Garbage
com/_*
com/test.lua

View File

@@ -30,14 +30,14 @@ build:
@# @echo "CGO_CFLAGS is: '$(CGO_CFLAGS)'"
@# @echo "CGO_LDFLAGS is: '$(CGO_LDFLAGS)'"
@# CGO_CFLAGS="$(CGO_CFLAGS)" CGO_LDFLAGS="$(CGO_LDFLAGS)"
@go build -trimpath -ldflags "-w -s $(LDFLAGS)" -o $(BIN_DIR)/$(APP_NAME) ./
@if ! command -v upx >/dev/null 2>&1; then \
echo "upx not found, skipping compression."; \
elif upx -t $(BIN_DIR)/$(APP_NAME) >/dev/null 2>&1; then \
echo "$(BIN_DIR)/$(APP_NAME) already compressed, skipping."; \
else \
upx $(BIN_DIR)/$(APP_NAME) >/dev/null 2>&1 || true; \
fi
@cd src && go build -trimpath -ldflags "-w -s $(LDFLAGS)" -o ../$(BIN_DIR)/$(APP_NAME) ./
# @if ! command -v upx >/dev/null 2>&1; then \
# echo "upx not found, skipping compression."; \
# elif upx -t $(BIN_DIR)/$(APP_NAME) >/dev/null 2>&1; then \
# echo "$(BIN_DIR)/$(APP_NAME) already compressed, skipping."; \
# else \
# upx $(BIN_DIR)/$(APP_NAME) >/dev/null 2>&1 || true; \
# fi
run: build
@echo "Running!"

View File

@@ -1,51 +1,59 @@
# Go Sally MVP (Minimum/Minimal Viable Product)
### What is this?
System that allows you to build your own infrastructure based on identical nodes and various scripts written using built-in Lua 5.1, shebang scripts (scripts that start with the `#!` symbols), compiled binaries.
### Features
Go Sally is not viable at the moment, but it already has the ability to run embedded scripts, log slog events to stdout, handle RPC like requests, and independent automatic update from the repository (my pride, to be honest).
- **Decentralized nodes**<details>this means that *multiple GS nodes can be located on a single machine*, provided no attempt is made to disrupt, sabotage, or bypass the built-in protection mechanism against running a node under the same identifier as one already running in the system. Identification plays a role in node communication. 💡 In the future, we plan to create tools for conveniently building distributed systems using node identification.</details>
- **RPC request processing**<details>the gs operates *using HTTP/https and the JSONRPC2.0 protocol.* Unlike gRPC, jsonrpc is extremely simple, allows for easy sending of requests from the browser, and does not require any additional code compilation.</details>
- **Lua script-based methods**<details>*The gopher-lua library is used, providing full support for Lua 5.1.* scripts implement libraries for interacting with sessions (receiving parameters and sending responses), hashing, logging, and more. This allows you to quickly write business logic on the fly without touching the lower layers of abstraction, which also eliminates unnecessary compilation and the risk of breaking the codebase.
Example of the "echo" method:
```lua
local session = require("internal.session")
-- import the internal library for interacting with sessions
### Example of use
The basic directory tree looks something like this
```
.
├── bin
│   └── node Node core binary file
├── com
│   ├── echo.lua
│   ├── _globals.lua Declaring global variables and functions for all internal scripts (also required for luarc to work correctly)
│   └── _prepare.lua Script that is executed before each script launch
└── config.yaml
session.response.send(session.request.params.get())
-- send everything passed in the parameters in response.
```
</details>
- **Relatively flexible configuration** <details>
you can configure the server port, address, name, node settings, and more. 💡 More settings are planned in the future.</details>
- ***And more in the future***
3 directories, 5 files
## Why?
The project was originally conceived as a tool for building infrastructure using relatively *small nodes with limited functionality*. 💡 In the future, we plan to create a *web interface for interacting with nodes, administration, and configuration*. The concept is simple: suppose we have a node that manages Bind9. It has all the necessary methods for interacting with the service: creating new zones, viewing zone status, changing configuration, and server operation status. All of this works only through manual configuration, with the exception of larger solutions like Webmin and the BIND DNS Server module. The big problem is that while we only needed web configuration for Bind9, we have to pull in a massive amount of software just to implement one module. What if the service is hosted on a low-power Raspberry Pi? That's where GS nodes come in. By default, GS nodes communicate only through API calls, so 💡 in the future, we plan to create a dedicated, also programmable, web node that will provide convenient access to node management.
```
Launch by command
```bash
./bin/node run
```
or for structured logs
```bash
./bin/node run | jq
```
There's an obvious advantage here: transparency. The project is *completely open source and aims to support community-driven node functionality*. 💡 In the future, we plan to create a "store" similar to Docker Hub, which will contain scripts for configuring bind9, openvpn, and even custom projects.
Example of GET request to server
```bash
curl -s http://localhost:8080/api/v1/com/echo?msg=Hello
```
Then the response from the server
## API
As mentioned earlier, *the server handles [jsonrpc2.0](https://www.jsonrpc.org/specification) requests*
```json
{
"ResponsibleAgentUUID": "4593a87000bbe088f4e79c477e9c90d3",
"RequestedCommand": "echo",
"Response": {
"answer": "Hello",
"status": "ok"
}
"jsonrpc": "2.0",
"context-version": "v1",
"method": "test",
"params": [
"Hi!!"
],
"id": 1
}
```
This is a typical example of a request using the jsonrpc2.0 protocol.
```json
{
"jsonrpc": "2.0",
"id": 1,
"result": [
"Hi!!"
],
"data": {
"responsible-node": "2ad6ebeaf579a7c52801fb6c9dd1b83d",
"salt": "e7a81115-01c1-45b1-9618-0eae0ff26451",
"checksum-md5": "cd8bec6a365d1b8ee90773567cb3ad0a"
}
}
```
In the result field, we see the echo method's response. Those familiar with the jsonrpc2.0 specification will notice that the data structure here is unclear. This is my extension, which has three functions:
| Field | Type | Description |
|--------|------|-------------|
| `responsible-node` | string | ID of the node that executed the task |
| `salt` | string | Random value for each request — can be used to check that the response is unique |
| `checksum-md5` | string | MD5 hash of the result field — can be used to avoid processing identical results separately |
### How to install
**You don't need it now, but you can figure it out with the Makefile**

View File

@@ -1,77 +0,0 @@
local session = require("internal.session")
local log = require("internal.log")
local jwt = require("internal.crypt.jwt")
local bc = require("internal.crypt.bcrypt")
local db = require("internal.database.sqlite").connect("db/root.db", {log = true})
local sha256 = require("internal.crypt.sha256")
log.info("Someone at "..session.request.address.." trying to get master access")
local function close_db()
if db then
db:close()
db = nil
end
end
local params = session.request.params.get()
local function check_missing(arr, p)
local is_missing = {}
local ok = true
for _, key in ipairs(arr) do
if p[key] == nil then
table.insert(is_missing, key)
ok = false
end
end
return ok, is_missing
end
local ok, mp = check_missing({"master_secret", "master_name", "my_key"}, params)
if not ok then
close_db()
session.response.send_error(-32602, "Missing params", mp)
end
if type(params.master_secret) ~= "string" then
close_db()
session.response.send_error(-32050, "Access denied")
end
if type(params.master_name) ~= "string" then
close_db()
session.response.send_error(-32050, "Access denied")
end
local master, err = db:query_row("SELECT * FROM master_units WHERE master_name = ?", {params.master_name})
if not master then
log.event("DB query failed:", err)
close_db()
session.response.send_error(-32050, "Access denied")
end
local ok = bc.compare(master.master_secret, params.master_secret)
if not ok then
log.warn("Login failed: wrong password")
close_db()
session.response.send_error(-32050, "Access denied")
end
local token = jwt.encode({
secret = require("_config").token(),
payload = {
session_uuid = session.id,
master_id = master.id,
key = sha256.sum(params.my_key)
},
expires_in = 3600
})
close_db()
session.response.send({
token = token
})
-- G7HgOgl72o7t7u7r

68
com/Unit/Create.lua Normal file
View File

@@ -0,0 +1,68 @@
-- File com/Unit/Create.lua
--
-- Created at 2025-05-10 18:23
--
-- Updated at -
-- Description:
--- Creates a record in the unit.db database without
--- requiring additional permissions. Requires username,
--- password (hashing occurs at the server level), and email fields.
local log = require("internal.log")
local db = require("internal.database.sqlite").connect("db/unit.db", {log = true})
local session = require("internal.session")
local crypt = require("internal.crypt.bcrypt")
local sha256 = require("internal.crypt.sha256")
local common = require("com/Unit/_common")
local errors = require("com/Unit/_errors")
-- Preparing for first db query
local function close_db()
if db then
log.debug("Closing DB connection")
db:close()
db = nil
end
end
local params = session.request.params.get()
local ok, mp = common.CheckMissingElement({"username", "password", "email"}, params)
if not ok then
close_db()
session.response.send_error(errors.MISSING_PARAMS.code, errors.MISSING_PARAMS.message, mp)
end
local hashPass = crypt.generate(params.password, crypt.DefaultCost)
local unitID = string.sub(sha256.hash(session.__seed), 1, 16)
local ctx, err = db:exec(
"INSERT INTO units (user_id, username, email, password) VALUES (?, ?, ?, ?)",
{
unitID,
params.username,
params.email,
hashPass,
}
)
if err ~= nil then
log.error("Insert failed: "..tostring(err))
close_db()
session.response.send_error(errors.DB_INSERT_FAILED.code, errors.DB_INSERT_FAILED.message)
end
local _, err = ctx:wait()
if err ~= nil then
close_db()
if tostring(err):match("UNIQUE constraint failed") then
session.response.send_error(errors.UNIT_EXISTS.code, errors.UNIT_EXISTS.message)
else
log.error("Insert confirmation failed: "..tostring(err))
session.response.send_error()
end
end
close_db()
session.response.send({unit_id = unitID})

77
com/Unit/Delete.lua Normal file
View File

@@ -0,0 +1,77 @@
-- File com/Unit/Delete.lua
--
-- Created at 2025-05-10 19:18
--
-- Updated at -
local log = require("internal.log")
local db = require("internal.database.sqlite").connect("db/unit.db", {log = true})
local session = require("internal.session")
local common = require("com/Unit/_common")
local errors = require("com/Unit/_errors")
-- Preparing for first db query
local function close_db()
if db then
log.debug("Closing DB connection")
db:close()
db = nil
end
end
local params = session.request.params.get()
local ok, mp = common.CheckMissingElement({"user_id"}, params)
if not ok then
close_db()
session.response.send_error(errors.MISSING_PARAMS.code, errors.MISSING_PARAMS.message, mp)
end
local existing, err = db:query([[
SELECT 1
FROM units
WHERE user_id = ?
AND entry_status != 'deleted'
AND deleted_at IS NULL
LIMIT 1
]], {
params.user_id
})
if err ~= nil then
log.error("Email check failed: "..tostring(err))
close_db()
session.response.send_error()
end
if existing and #existing == 0 then
close_db()
session.response.send_error(errors.UNIT_NOT_FOUND.code, errors.UNIT_NOT_FOUND.message)
end
local ctx, err = db:exec(
[[
UPDATE units
SET entry_status = 'deleted',
deleted_at = CURRENT_TIMESTAMP
WHERE user_id = ? AND deleted_at is NULL
]],
{ params.user_id }
)
if err ~= nil then
log.error("Soft delete failed: " .. tostring(err))
close_db()
session.response.send_error(errors.DB_DELETE_FAILED.code, errors.DB_DELETE_FAILED.message)
end
local res, err = ctx:wait()
if err ~= nil then
log.error("Soft delete confirmation failed: " .. tostring(err))
close_db()
session.response.send_error(errors.DB_DELETE_FAILED.code, errors.DB_DELETE_FAILED.message)
end
close_db()
session.response.send()

55
com/Unit/Get.lua Normal file
View File

@@ -0,0 +1,55 @@
-- File com/Unit/Get.lua
--
-- Created at 2025-09-25 20:04
--
-- Updated at -
local log = require("internal.log")
local db = require("internal.database.sqlite").connect("db/unit.db", {log = true})
local session = require("internal.session")
local common = require("com/Unit/_common")
local errors = require("com/Unit/_errors")
-- Preparing for first db query
local function close_db()
if db then
log.debug("Closing DB connection")
db:close()
db = nil
end
end
local params = session.request.params.get()
local ok, mp = common.CheckMissingElement({"by", "value"}, params)
if not ok then
close_db()
session.response.send_error(errors.MISSING_PARAMS.code, errors.MISSING_PARAMS.message, mp)
end
if not (params.by == "email" or params.by == "username" or params.by == "user_id") then
close_db()
session.response.send_error(errors.INVALID_BY_PARAM.code, errors.INVALID_BY_PARAM.message)
end
local unit, err = db:query_row(
"SELECT user_id, username, email, created_at, updated_at, deleted_at, entry_status FROM units WHERE "..params.by.." = ? AND deleted_at IS NULL LIMIT 1",
{
params.value
}
)
if err then
close_db()
log.error("DB query error: " .. tostring(err))
session.response.send_error()
end
if not unit then
close_db()
session.response.send_error(errors.UNIT_NOT_FOUND.code, errors.UNIT_NOT_FOUND.message)
end
close_db()
session.response.send(unit)

102
com/Unit/Update.lua Normal file
View File

@@ -0,0 +1,102 @@
-- File com/Unit/Update.lua
--
-- Created at 2025-10-10
--
local log = require("internal.log")
local db = require("internal.database.sqlite").connect("db/unit.db", { log = true })
local session = require("internal.session")
local common = require("com/Unit/_common")
local errors = require("com/Unit/_errors")
local function close_db()
if db then
log.debug("Closing DB connection")
db:close()
db = nil
end
end
local params = session.request.params.get()
local ok, mp = common.CheckMissingElement({"user_id", "fields"}, params)
if not ok then
close_db()
session.response.send_error(errors.MISSING_PARAMS.code, errors.MISSING_PARAMS.message, mp)
end
if type(params.fields) ~= "table" or next(params.fields) == nil then
close_db()
session.response.send_error(errors.INVALID_FIELD_TYPE.code, errors.INVALID_FIELD_TYPE.message)
end
local allowed = {
username = true,
email = true,
password = true,
entry_status = true
}
local exists = db:query_row(
"SELECT 1 FROM units WHERE user_id = ? AND deleted_at IS NULL LIMIT 1",
{ params.user_id }
)
if not exists then
close_db()
session.response.send_error(errors.UNIT_NOT_FOUND.code, errors.UNIT_NOT_FOUND.message)
end
local set_clauses = {}
local values = {}
for k, v in pairs(params.fields) do
if allowed[k] then
if k == "password" then
local crypt = require("internal.crypt.bcrypt")
v = crypt.generate(v, crypt.DefaultCost)
end
table.insert(set_clauses, k .. " = ?")
table.insert(values, v)
else
log.warn("Ignoring unsupported field: " .. k)
end
end
if #set_clauses == 0 then
close_db()
session.response.send_error(errors.NO_VALID_FIELDS.code, errors.NO_VALID_FIELDS.message)
end
table.insert(set_clauses, "updated_at = CURRENT_TIMESTAMP")
local query = "UPDATE units SET " .. table.concat(set_clauses, ", ")
.. " WHERE user_id = ? AND deleted_at IS NULL"
table.insert(values, params.user_id)
local ctx, err = db:exec(query, values)
if not ctx then
close_db()
if tostring(err):match("UNIQUE constraint failed") then
session.response.send_error(errors.UNIQUE_CONSTRAINT.code, errors.UNIQUE_CONSTRAINT.message)
else
session.response.send_error()
end
end
local _, err = ctx:wait()
if err ~= nil then
close_db()
if tostring(err):match("UNIQUE constraint failed") then
session.response.send_error(errors.UNIQUE_CONSTRAINT.code, errors.UNIQUE_CONSTRAINT.message)
else
log.error("Insert confirmation failed: "..tostring(err))
session.response.send_error()
end
end
close_db()
session.response.send()

23
com/Unit/_common.lua Normal file
View File

@@ -0,0 +1,23 @@
-- File com/Unit/_common.lua
--
-- Created at 2025-05-10 18:23
--
-- Updated at -
-- Description:
--- Common functions for Unit module
local common = {}
function common.CheckMissingElement(arr, cmp)
local is_missing = {}
local ok = true
for _, key in ipairs(arr) do
if cmp[key] == nil then
table.insert(is_missing, key)
ok = false
end
end
return ok, is_missing
end
return common

30
com/Unit/_errors.lua Normal file
View File

@@ -0,0 +1,30 @@
-- File com/Unit/_errors.lua
--
-- Created at 2025-10-10
-- Description:
--- Centralized error definitions for Unit operations
--- to keep API responses consistent and clean.
local errors = {
-- Common validation
MISSING_PARAMS = { code = -32602, message = "Missing params" },
INVALID_FIELD_TYPE = { code = -32602, message = "'fields' must be a non-empty table" },
INVALID_BY_PARAM = { code = -32602, message = "Invalid 'by' param" },
NO_VALID_FIELDS = { code = -32604, message = "No valid fields to update" },
-- Existence / duplication
UNIT_NOT_FOUND = { code = -32102, message = "Unit is not exists" },
UNIT_EXISTS = { code = -32101, message = "Unit is already exists" },
-- Database & constraint
UNIQUE_CONSTRAINT = { code = -32602, message = "Unique constraint failed" },
DB_QUERY_FAILED = { code = -32001, message = "Database query failed" },
DB_EXEC_FAILED = { code = -32002, message = "Database execution failed" },
DB_INSERT_FAILED = { code = -32003, message = "Failed to create unit" },
DB_DELETE_FAILED = { code = -32004, message = "Failed to delete unit" },
-- Generic fallback
UNKNOWN = { code = -32099, message = "Unexpected internal error" },
}
return errors

View File

@@ -1,69 +0,0 @@
local session = require("internal.session")
local log = require("internal.log")
local jwt = require("internal.crypt.jwt")
local bc = require("internal.crypt.bcrypt")
local sha256 = require("internal.crypt.sha256")
local dbdriver = require("internal.database.sqlite")
local db_root = dbdriver.connect("db/root.db", {log = true})
local db_zone = nil
local function close_db()
if db_root then
db_root:close()
db_root = nil
end
if db_zone then
db_zone:close()
db_zone = nil
end
end
local token = session.request.headers.get("authorization")
if not token or type(token) ~= "string" then
close_db()
session.response.send_error(-32050, "Access denied")
end
local prefix = "Bearer "
if token:sub(1, #prefix) ~= prefix then
close_db()
session.response.send_error(-32052, "Invalid Authorization scheme")
end
local access_token = token:sub(#prefix + 1)
local err, data = jwt.decode(access_token, { secret = require("_config").token() })
if err or not data then
close_db()
session.response.send_error(-32053, "Cannod parse JWT", {err})
end
if data.master_id then
end
local params = session.request.params.get()
local function check_missing(arr, p)
local is_missing = {}
local ok = true
for _, key in ipairs(arr) do
if p[key] == nil then
table.insert(is_missing, key)
ok = false
end
end
return ok, is_missing
end
local ok, mp = check_missing({"zone_name"}, params)
if not ok then
close_db()
session.response.send_error(-32602, "Missing params", mp)
end
close_db()

View File

@@ -1,119 +0,0 @@
-- com/DeleteUnit.lua
---@diagnostic disable: redefined-local
local db = require("internal.database.sqlite").connect("db/user-database.db", {log = true})
local log = require("internal.log")
local session = require("internal.session")
local crypt = require("internal.crypt.bcrypt")
local jwt = require("internal.crypt.jwt")
local sha256 = require("internal.crypt.sha256")
local params = session.request.params.get()
local token = session.request.headers.get("authorization")
local function close_db()
if db then
db:close()
db = nil
end
end
local function error_response(message, code, data)
session.response.error = {
code = code or nil,
message = message,
data = data or nil
}
close_db()
end
if not token or type(token) ~= "string" then
return error_response("Access denied")
end
local prefix = "Bearer "
if token:sub(1, #prefix) ~= prefix then
return error_response("Invalid Authorization scheme")
end
local access_token = token:sub(#prefix + 1)
local err, data = jwt.decode(access_token, { secret = require("_config").token() })
if err or not data then
session.response.error = {
message = err
}
return
end
-- if data.session_uuid ~= session.id then
-- return error_response("Access denied")
-- end
-- if data.key ~= sha256.sum(session.request.address .. session.id .. session.request.headers.get("user-agent", "noagent")) then
-- return error_response("Access denied")
-- end
if not params then
return error_response("no params provided")
end
if not (params.username and params.email and params.password) then
return error_response("no username/email/password provided")
end
local existing, err = db:query(
"SELECT password FROM users WHERE email = ? AND username = ? AND deleted = 0 LIMIT 1",
{
params.email,
params.username
}
)
if err ~= nil then
log.error("Password fetch failed: " .. tostring(err))
return error_response("Database query failed: " .. tostring(err))
end
if not existing or #existing == 0 then
return error_response("Unit not found")
end
local hashed_password = existing[1].password
local ok = crypt.compare(hashed_password, params.password)
if not ok then
log.warn("Wrong password attempt for: " .. params.username)
return error_response("Invalid password")
end
local ctx, err = db:exec(
[[
UPDATE users
SET deleted = 1,
deleted_at = CURRENT_TIMESTAMP
WHERE email = ? AND username = ? AND deleted = 0
]],
{ params.email, params.username }
)
if err ~= nil then
log.error("Soft delete failed: " .. tostring(err))
return error_response("Soft delete failed: " .. tostring(err))
end
local res, err = ctx:wait()
if err ~= nil then
log.error("Soft delete confirmation failed: " .. tostring(err))
return error_response("Soft delete confirmation failed: " .. tostring(err))
end
session.response.result = {
rows_affected = res,
message = "Unit soft-deleted successfully"
}
log.info("user " .. params.username .. " soft-deleted successfully")
close_db()

View File

@@ -1,76 +0,0 @@
-- com/GetAccess
---@diagnostic disable: redefined-local
local db = require("internal.database.sqlite").connect("db/user-database.db", {log = true})
local log = require("internal.log")
local session = require("internal.session")
local crypt = require("internal.crypt.bcrypt")
local jwt = require("internal.crypt.jwt")
local sha256 = require("internal.crypt.sha256")
local params = session.request.params.get()
local secret = require("_config").token()
local function close_db()
if db then
db:close()
db = nil
end
end
local function error_response(message, code, data)
session.response.error = {
code = code or nil,
message = message,
data = data or nil
}
close_db()
end
if not params then
return error_response("No params provided")
end
if not (params.username and params.email and params.password) then
return error_response("Missing username, email or password")
end
local unit, err = db:query(
"SELECT id, username, email, password, created_at FROM users WHERE email = ? AND username = ? AND deleted = 0 LIMIT 1",
{
params.email,
params.username
}
)
if err then
log.error("DB query error: " .. tostring(err))
return error_response("Database query failed")
end
if not unit or #unit == 0 then
return error_response("Unit not found")
end
unit = unit[1]
local ok = crypt.compare(unit.password, params.password)
if not ok then
log.warn("Login failed: wrong password for " .. params.username)
return error_response("Invalid password")
end
local token = jwt.encode({
secret = secret,
payload = { session_uuid = session.id,
admin_user = params.username,
key = sha256.sum(session.request.address .. session.id .. session.request.headers.get("user-agent", "noagent"))
},
expires_in = 3600
})
session.response.result = {
access_token = token
}
close_db()

View File

@@ -1,109 +0,0 @@
-- com/PutNewUnit.lua
---@diagnostic disable: redefined-local
local db = require("internal.database.sqlite").connect("db/user-database.db", {log = true})
local log = require("internal.log")
local session = require("internal.session")
local crypt = require("internal.crypt.bcrypt")
local jwt = require("internal.crypt.jwt")
local sha256 = require("internal.crypt.sha256")
local params = session.request.params.get()
local token = session.request.headers.get("authorization")
local function close_db()
if db then
db:close()
db = nil
end
end
local function error_response(message, code, data)
session.response.error = {
code = code or nil,
message = message,
data = data or nil
}
close_db()
end
if not token or type(token) ~= "string" then
return error_response("Access denied")
end
local prefix = "Bearer "
if token:sub(1, #prefix) ~= prefix then
return error_response("Invalid Authorization scheme")
end
local access_token = token:sub(#prefix + 1)
local err, data = jwt.decode(access_token, { secret = require("_config").token() })
if err or not data then
session.response.error = {
message = err
}
return
end
if data.session_uuid ~= session.id then
return error_response("Access denied")
end
if data.key ~= sha256.sum(session.request.address .. session.id .. session.request.headers.get("user-agent", "noagent")) then
return error_response("Access denied")
end
if not params then
return error_response("no params provided")
end
if not (params.username and params.email and params.password) then
return error_response("no username/email/password provided")
end
local hashPass = crypt.generate(params.password, crypt.DefaultCost)
local existing, err = db:query("SELECT 1 FROM users WHERE deleted = 0 AND (email = ? OR username = ?) LIMIT 1", {
params.email,
params.username
})
if err ~= nil then
log.error("Email check failed: "..tostring(err))
return error_response("Database check failed: "..tostring(err))
end
if existing and #existing > 0 then
return error_response("Unit already exists")
end
local ctx, err = db:exec(
"INSERT INTO users (username, email, password, first_name, last_name, phone_number) VALUES (?, ?, ?, ?, ?, ?)",
{
params.username,
params.email,
hashPass,
params.first_name or "",
params.last_name or "",
params.phone_number or ""
}
)
if err ~= nil then
log.error("Insert failed: "..tostring(err))
return error_response("Insert failed: "..tostring(err))
end
local res, err = ctx:wait()
if err ~= nil then
log.error("Insert confirmation failed: "..tostring(err))
return error_response("Insert confirmation failed: "..tostring(err))
end
session.response.result = {
rows_affected = res,
message = "Unit created successfully"
}
close_db()

View File

@@ -1,66 +0,0 @@
---@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()

View File

@@ -1,29 +0,0 @@
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"
}

View File

@@ -1,35 +0,0 @@
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"
}

View File

@@ -1,30 +0,0 @@
node:
mode: dev
name: "My gosally node"
show_config: true
com_dir: "%path%/com"
http_server:
address: "0.0.0.0"
port: "8080"
session_ttl: 5s
timeout: 3s
idle_timeout: 30s
tls:
enabled: true
cert_file: "%path%/cert/fullchain.pem"
key_file: "%path%/cert/privkey.pem"
updates:
enabled: false
check-interval: 1h
repository_url: "https://repo.serve.lv/raw/go-sally"
log:
json_format: false
level: "debug"
disable_warnings:
- --WNonStdTmpDir
- --WUndefLogLevel

View File

@@ -7,9 +7,9 @@ import (
"log"
"os"
"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/src/hooks"
"github.com/akyaiy/GoSally-mvp/src/internal/colors"
"github.com/akyaiy/GoSally-mvp/src/internal/core/corestate"
"github.com/spf13/cobra"
)

View File

@@ -1,7 +1,7 @@
package cmd
import (
"github.com/akyaiy/GoSally-mvp/hooks"
"github.com/akyaiy/GoSally-mvp/src/hooks"
"github.com/spf13/cobra"
)

View File

@@ -4,7 +4,7 @@ import (
"fmt"
"runtime"
"github.com/akyaiy/GoSally-mvp/internal/engine/config"
"github.com/akyaiy/GoSally-mvp/src/internal/engine/config"
"github.com/spf13/cobra"
)

View File

@@ -1,4 +1,4 @@
module github.com/akyaiy/GoSally-mvp
module github.com/akyaiy/GoSally-mvp/src
go 1.24.4

View File

@@ -13,8 +13,6 @@ github.com/go-chi/cors v1.2.2 h1:Jmey33TE+b+rB7fT8MUy1u0I4L+NARQlK6LhzKPSyQE=
github.com/go-chi/cors v1.2.2/go.mod h1:sSbTewc+6wYHBBCW7ytsFSn836hqM7JxpglAy2Vzc58=
github.com/go-viper/mapstructure/v2 v2.4.0 h1:EBsztssimR/CONLSZZ04E8qAkxNYq4Qp9LvH92wZUgs=
github.com/go-viper/mapstructure/v2 v2.4.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM=
github.com/golang-jwt/jwt v3.2.2+incompatible h1:IfV12K8xAKAnZqdXVzCZ+TOjboZ2keLg81eXfW3O+oY=
github.com/golang-jwt/jwt v3.2.2+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I=
github.com/golang-jwt/jwt/v5 v5.3.0 h1:pv4AsKCKKZuqlgs5sUmn4x8UlGa0kEVt/puTpKx9vvo=
github.com/golang-jwt/jwt/v5 v5.3.0/go.mod h1:fxCRLWMO43lRc8nhHWY6LGqRcf+1gQWArsqaEUEa5bE=
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=

View File

@@ -18,13 +18,13 @@ import (
"syscall"
"time"
"github.com/akyaiy/GoSally-mvp/internal/colors"
"github.com/akyaiy/GoSally-mvp/internal/core/corestate"
"github.com/akyaiy/GoSally-mvp/internal/core/run_manager"
"github.com/akyaiy/GoSally-mvp/internal/core/utils"
"github.com/akyaiy/GoSally-mvp/internal/engine/app"
"github.com/akyaiy/GoSally-mvp/internal/engine/config"
"github.com/akyaiy/GoSally-mvp/internal/engine/logs"
"github.com/akyaiy/GoSally-mvp/src/internal/colors"
"github.com/akyaiy/GoSally-mvp/src/internal/core/corestate"
"github.com/akyaiy/GoSally-mvp/src/internal/core/run_manager"
"github.com/akyaiy/GoSally-mvp/src/internal/core/utils"
"github.com/akyaiy/GoSally-mvp/src/internal/engine/app"
"github.com/akyaiy/GoSally-mvp/src/internal/engine/config"
"github.com/akyaiy/GoSally-mvp/src/internal/engine/logs"
"gopkg.in/ini.v1"
)

View File

@@ -11,17 +11,17 @@ import (
"regexp"
"time"
"github.com/akyaiy/GoSally-mvp/internal/colors"
"github.com/akyaiy/GoSally-mvp/internal/core/corestate"
"github.com/akyaiy/GoSally-mvp/internal/core/run_manager"
"github.com/akyaiy/GoSally-mvp/internal/core/update"
"github.com/akyaiy/GoSally-mvp/internal/core/utils"
"github.com/akyaiy/GoSally-mvp/internal/engine/app"
"github.com/akyaiy/GoSally-mvp/internal/engine/config"
"github.com/akyaiy/GoSally-mvp/internal/engine/logs"
"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/src/internal/core/corestate"
"github.com/akyaiy/GoSally-mvp/src/internal/colors"
"github.com/akyaiy/GoSally-mvp/src/internal/core/run_manager"
"github.com/akyaiy/GoSally-mvp/src/internal/core/update"
"github.com/akyaiy/GoSally-mvp/src/internal/core/utils"
"github.com/akyaiy/GoSally-mvp/src/internal/engine/app"
"github.com/akyaiy/GoSally-mvp/src/internal/engine/config"
"github.com/akyaiy/GoSally-mvp/src/internal/engine/logs"
"github.com/akyaiy/GoSally-mvp/src/internal/server/gateway"
"github.com/akyaiy/GoSally-mvp/src/internal/server/session"
"github.com/akyaiy/GoSally-mvp/src/internal/server/sv1"
"github.com/go-chi/chi/v5"
"github.com/go-chi/cors"
"github.com/spf13/cobra"
@@ -29,6 +29,7 @@ import (
)
var NodeApp = app.New()
var AllowedCmdPattern = `^[a-zA-Z0-9]+(\.[a-zA-Z0-9]+)*$`
func Run(cmd *cobra.Command, args []string) {
NodeApp.InitialHooks(
@@ -60,7 +61,7 @@ func RunHook(ctx context.Context, cs *corestate.CoreState, x *app.AppX) error {
serverv1 := sv1.InitV1Server(&sv1.HandlerV1InitStruct{
X: x,
CS: cs,
AllowedCmd: regexp.MustCompile(`^[a-zA-Z0-9]+(>[a-zA-Z0-9]+)*$`),
AllowedCmd: regexp.MustCompile(AllowedCmdPattern),
Ver: "v1",
})

View File

@@ -7,8 +7,8 @@ import (
"path/filepath"
"strings"
"github.com/akyaiy/GoSally-mvp/internal/core/utils"
"github.com/akyaiy/GoSally-mvp/internal/engine/config"
"github.com/akyaiy/GoSally-mvp/src/internal/core/utils"
"github.com/akyaiy/GoSally-mvp/src/internal/engine/config"
)
// GetNodeUUID outputs the correct uuid from the file at the path specified in the arguments.

View File

@@ -6,7 +6,7 @@ import (
"os"
"path/filepath"
"github.com/akyaiy/GoSally-mvp/internal/core/utils"
"github.com/akyaiy/GoSally-mvp/src/internal/core/utils"
)
type RunManagerContract interface {

View File

@@ -13,10 +13,10 @@ import (
"strings"
"syscall"
"github.com/akyaiy/GoSally-mvp/internal/core/run_manager"
"github.com/akyaiy/GoSally-mvp/internal/core/utils"
"github.com/akyaiy/GoSally-mvp/internal/engine/app"
"github.com/akyaiy/GoSally-mvp/internal/engine/config"
"github.com/akyaiy/GoSally-mvp/src/internal/core/run_manager"
"github.com/akyaiy/GoSally-mvp/src/internal/core/utils"
"github.com/akyaiy/GoSally-mvp/src/internal/engine/app"
"github.com/akyaiy/GoSally-mvp/src/internal/engine/config"
"golang.org/x/net/context"
)

View File

@@ -5,7 +5,7 @@ import (
"encoding/hex"
"errors"
"github.com/akyaiy/GoSally-mvp/internal/engine/config"
"github.com/akyaiy/GoSally-mvp/src/internal/engine/config"
)
func NewUUIDRaw(length int) ([]byte, error) {

View File

@@ -9,8 +9,8 @@ import (
"sync"
"syscall"
"github.com/akyaiy/GoSally-mvp/internal/core/corestate"
"github.com/akyaiy/GoSally-mvp/internal/engine/config"
"github.com/akyaiy/GoSally-mvp/src/internal/core/corestate"
"github.com/akyaiy/GoSally-mvp/src/internal/engine/config"
)
type AppContract interface {

View File

@@ -5,7 +5,7 @@ import (
"reflect"
"time"
"github.com/akyaiy/GoSally-mvp/internal/colors"
"github.com/akyaiy/GoSally-mvp/src/internal/colors"
)
func (c *Compositor) Print(v any) {

View File

@@ -11,7 +11,7 @@ import (
"os"
"path/filepath"
"github.com/akyaiy/GoSally-mvp/internal/engine/config"
"github.com/akyaiy/GoSally-mvp/src/internal/engine/config"
"gopkg.in/natefinch/lumberjack.v2"
)

View File

@@ -3,9 +3,9 @@ package lua
import (
"net/http"
"github.com/akyaiy/GoSally-mvp/internal/core/corestate"
"github.com/akyaiy/GoSally-mvp/internal/engine/app"
"github.com/akyaiy/GoSally-mvp/internal/server/rpc"
"github.com/akyaiy/GoSally-mvp/src/internal/core/corestate"
"github.com/akyaiy/GoSally-mvp/src/internal/engine/app"
"github.com/akyaiy/GoSally-mvp/src/internal/server/rpc"
)
type LuaEngineDeps struct {

View File

@@ -4,10 +4,10 @@ import (
"context"
"net/http"
"github.com/akyaiy/GoSally-mvp/internal/core/corestate"
"github.com/akyaiy/GoSally-mvp/internal/engine/app"
"github.com/akyaiy/GoSally-mvp/internal/server/rpc"
"github.com/akyaiy/GoSally-mvp/internal/server/session"
"github.com/akyaiy/GoSally-mvp/src/internal/core/corestate"
"github.com/akyaiy/GoSally-mvp/src/internal/engine/app"
"github.com/akyaiy/GoSally-mvp/src/internal/server/rpc"
"github.com/akyaiy/GoSally-mvp/src/internal/server/session"
)
// serversApiVer is a type alias for string, used to represent API version strings in the GeneralServer.

View File

@@ -3,9 +3,9 @@ package gateway
import (
"errors"
"github.com/akyaiy/GoSally-mvp/internal/core/corestate"
"github.com/akyaiy/GoSally-mvp/internal/engine/app"
"github.com/akyaiy/GoSally-mvp/internal/server/session"
"github.com/akyaiy/GoSally-mvp/src/internal/core/corestate"
"github.com/akyaiy/GoSally-mvp/src/internal/engine/app"
"github.com/akyaiy/GoSally-mvp/src/internal/server/session"
)
// GeneralServerInit structure only for initialization general server.

View File

@@ -8,8 +8,8 @@ import (
"net/http"
"sync"
"github.com/akyaiy/GoSally-mvp/internal/core/utils"
"github.com/akyaiy/GoSally-mvp/internal/server/rpc"
"github.com/akyaiy/GoSally-mvp/src/internal/core/utils"
"github.com/akyaiy/GoSally-mvp/src/internal/server/rpc"
"github.com/google/uuid"
)

View File

@@ -5,7 +5,7 @@ import (
"encoding/json"
"fmt"
"github.com/akyaiy/GoSally-mvp/internal/core/corestate"
"github.com/akyaiy/GoSally-mvp/src/internal/core/corestate"
"github.com/google/uuid"
)
@@ -37,7 +37,9 @@ func NewError(code int, message string, data any, id *json.RawMessage) *RPCRespo
Error = map[string]any{
"code": code,
"message": message,
"data": data,
}
if data != nil {
Error["data"] = data
}
return &RPCResponse{

View File

@@ -5,7 +5,7 @@ import (
"log/slog"
"net/http"
"github.com/akyaiy/GoSally-mvp/internal/server/rpc"
"github.com/akyaiy/GoSally-mvp/src/internal/server/rpc"
)
func (h *HandlerV1) Handle(_ context.Context, sid string, r *http.Request, req *rpc.RPCRequest) *rpc.RPCResponse {

View File

@@ -17,8 +17,8 @@ import (
"golang.org/x/crypto/bcrypt"
"github.com/akyaiy/GoSally-mvp/internal/colors"
"github.com/akyaiy/GoSally-mvp/internal/server/rpc"
"github.com/akyaiy/GoSally-mvp/src/internal/colors"
"github.com/akyaiy/GoSally-mvp/src/internal/server/rpc"
lua "github.com/yuin/gopher-lua"
_ "modernc.org/sqlite"
)
@@ -186,11 +186,6 @@ func (h *HandlerV1) handleLUA(sid string, r *http.Request, req *rpc.RPCRequest,
L.SetField(scriptDataTable, "result", resTable)
L.SetField(outTable, "send", L.NewFunction(func(L *lua.LState) int {
res := L.Get(1)
if res == lua.LNil {
__exit = 0
L.RaiseError("__successfull")
return 0
}
resFTable := scriptDataTable.RawGetString("result")
if resPTable, ok := res.(*lua.LTable); ok {
@@ -542,7 +537,7 @@ func (h *HandlerV1) handleLUA(sid string, r *http.Request, req *rpc.RPCRequest,
llog.Debug("import module crypt.sha256", slog.String("script", path))
sha265mod := L.NewTable()
L.SetField(sha265mod, "sum", L.NewFunction(func(l *lua.LState) int {
L.SetField(sha265mod, "hash", L.NewFunction(func(l *lua.LState) int {
data := ConvertLuaTypesToGolang(L.Get(1))
var dataStr = fmt.Sprint(data)

View File

@@ -6,15 +6,17 @@ import (
"path/filepath"
"strings"
"github.com/akyaiy/GoSally-mvp/internal/server/rpc"
"github.com/akyaiy/GoSally-mvp/src/internal/server/rpc"
)
var RPCMethodSeparator = "."
func (h *HandlerV1) resolveMethodPath(method string) (string, error) {
if !h.allowedCmd.MatchString(method) {
return "", errors.New(rpc.ErrInvalidMethodFormatS)
}
parts := strings.Split(method, ">")
parts := strings.Split(method, RPCMethodSeparator)
relPath := filepath.Join(parts...) + ".lua"
fullPath := filepath.Join(*h.x.Config.Conf.Node.ComDir, relPath)

View File

@@ -5,8 +5,8 @@ package sv1
import (
"regexp"
"github.com/akyaiy/GoSally-mvp/internal/core/corestate"
"github.com/akyaiy/GoSally-mvp/internal/engine/app"
"github.com/akyaiy/GoSally-mvp/src/internal/core/corestate"
"github.com/akyaiy/GoSally-mvp/src/internal/engine/app"
)
// HandlerV1InitStruct structure is only for initialization

View File

@@ -2,7 +2,7 @@
package main
import (
"github.com/akyaiy/GoSally-mvp/cmd"
"github.com/akyaiy/GoSally-mvp/src/cmd"
_ "modernc.org/sqlite"
)