This commit is contained in:
@@ -0,0 +1,81 @@
|
|||||||
|
# yaml-language-server: $schema=https://goreleaser.com/static/schema.json
|
||||||
|
version: 2
|
||||||
|
project_name: BanForge
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
gitea_urls:
|
||||||
|
api: https://gitea.d3m0k1d.ru/api/v1
|
||||||
|
download: https://gitea.d3m0k1d.ru/d3m0k1d/HellreigN/releases/download
|
||||||
|
skip_tls_verify: false
|
||||||
|
|
||||||
|
|
||||||
|
builds:
|
||||||
|
- id: banforge
|
||||||
|
main: ./cmd/banforge/main.go
|
||||||
|
binary: banforge
|
||||||
|
ignore:
|
||||||
|
- goos: windows
|
||||||
|
- goos: darwin
|
||||||
|
- goos: freebsd
|
||||||
|
goos:
|
||||||
|
- linux
|
||||||
|
goarch:
|
||||||
|
- amd64
|
||||||
|
- arm64
|
||||||
|
ldflags:
|
||||||
|
- "-s -w"
|
||||||
|
env:
|
||||||
|
- CGO_ENABLED=0
|
||||||
|
archives:
|
||||||
|
- formats: [tar.gz]
|
||||||
|
name_template: "{{ .ProjectName }}_{{ .Version }}_{{ .Os }}_{{ .Arch }}"
|
||||||
|
|
||||||
|
nfpms:
|
||||||
|
- id: banforge
|
||||||
|
package_name: banforge
|
||||||
|
file_name_template: "{{ .PackageName }}_{{ .Version }}_{{ .Os }}_{{ .Arch }}"
|
||||||
|
homepage: https://gitea.d3m0k1d.ru/d3m0k1d/HellreigN
|
||||||
|
description: HellreigN agent
|
||||||
|
maintainer: d3m0k1d <contact@d3m0k1d.ru>
|
||||||
|
license: GPLv3.0
|
||||||
|
formats:
|
||||||
|
- apk
|
||||||
|
- deb
|
||||||
|
- rpm
|
||||||
|
- archlinux
|
||||||
|
bindir: /usr/bin
|
||||||
|
scripts:
|
||||||
|
postinstall: build/postinstall.sh
|
||||||
|
postremove: build/postremove.sh
|
||||||
|
contents:
|
||||||
|
- src: docs/man/banforge.1
|
||||||
|
dst: /usr/share/man/man1/banforge.1
|
||||||
|
file_info:
|
||||||
|
mode: 0644
|
||||||
|
- src: docs/man/banforge.5
|
||||||
|
dst: /usr/share/man/man5/banforge.5
|
||||||
|
file_info:
|
||||||
|
mode: 0644
|
||||||
|
release:
|
||||||
|
gitea:
|
||||||
|
owner: d3m0k1d
|
||||||
|
name: BanForge
|
||||||
|
mode: keep-existing
|
||||||
|
|
||||||
|
changelog:
|
||||||
|
sort: asc
|
||||||
|
filters:
|
||||||
|
exclude:
|
||||||
|
- "^docs:"
|
||||||
|
- "^test:"
|
||||||
|
checksum:
|
||||||
|
name_template: "{{ .ProjectName }}_{{ .Version }}_checksums.txt"
|
||||||
|
algorithm: sha256
|
||||||
|
|
||||||
|
sboms:
|
||||||
|
- artifacts: archive
|
||||||
|
documents:
|
||||||
|
- "{{ .ArtifactName }}.spdx.json"
|
||||||
|
cmd: syft
|
||||||
|
args: ["$artifact", "--output", "spdx-json=$document"]
|
||||||
+13
-1
@@ -92,10 +92,14 @@ func main() {
|
|||||||
if err := scriptRepo.Init(context.Background()); err != nil {
|
if err := scriptRepo.Init(context.Background()); err != nil {
|
||||||
log.Printf("Warning: failed to initialize script interpreters table: %v", err)
|
log.Printf("Warning: failed to initialize script interpreters table: %v", err)
|
||||||
}
|
}
|
||||||
scriptSvc := service.NewScriptService(scriptRepo)
|
scriptSvc := service.NewScriptServiceWithInterpreters(h.Repo, scriptRepo)
|
||||||
scriptHandlers := handlers.NewScriptHandlers(scriptSvc, cmdr)
|
scriptHandlers := handlers.NewScriptHandlers(scriptSvc, cmdr)
|
||||||
jobsHandlers := handlers.NewJobsHandlers(cmdr, scriptSvc)
|
jobsHandlers := handlers.NewJobsHandlers(cmdr, scriptSvc)
|
||||||
|
|
||||||
|
// Initialize script management service and handlers
|
||||||
|
scriptManageSvc := service.NewScriptService(h.Repo)
|
||||||
|
scriptManageHandlers := handlers.NewScriptHandlersGroup(scriptManageSvc, cmdr)
|
||||||
|
|
||||||
agents := handlers.NewAgentsGroup(h, coll)
|
agents := handlers.NewAgentsGroup(h, coll)
|
||||||
auth := handlers.AuthGroup{Handlers: h}
|
auth := handlers.AuthGroup{Handlers: h}
|
||||||
agentReg := handlers.NewAgentRegistrationGroup(h)
|
agentReg := handlers.NewAgentRegistrationGroup(h)
|
||||||
@@ -239,6 +243,14 @@ func main() {
|
|||||||
scriptsGroup.GET("/interpreters/:id", scriptHandlers.GetInterpreter)
|
scriptsGroup.GET("/interpreters/:id", scriptHandlers.GetInterpreter)
|
||||||
scriptsGroup.PUT("/interpreters/:id", scriptHandlers.UpdateInterpreter)
|
scriptsGroup.PUT("/interpreters/:id", scriptHandlers.UpdateInterpreter)
|
||||||
scriptsGroup.DELETE("/interpreters/:id", scriptHandlers.DeleteInterpreter)
|
scriptsGroup.DELETE("/interpreters/:id", scriptHandlers.DeleteInterpreter)
|
||||||
|
|
||||||
|
// Script management (tree, CRUD)
|
||||||
|
scriptsGroup.GET("/tree", scriptManageHandlers.GetTree)
|
||||||
|
scriptsGroup.POST("", scriptManageHandlers.CreateScript)
|
||||||
|
scriptsGroup.GET("/:id", scriptManageHandlers.GetScript)
|
||||||
|
scriptsGroup.PUT("/:id", scriptManageHandlers.UpdateScript)
|
||||||
|
scriptsGroup.DELETE("/:id", scriptManageHandlers.DeleteScript)
|
||||||
|
scriptsGroup.POST("/:id/run", scriptManageHandlers.RunScriptByID)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1304,6 +1304,282 @@ const docTemplate = `{
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"/scripts": {
|
||||||
|
"post": {
|
||||||
|
"security": [
|
||||||
|
{
|
||||||
|
"Bearer": []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "Creates a new script with path, content, and interpreter binding",
|
||||||
|
"consumes": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"produces": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"scripts"
|
||||||
|
],
|
||||||
|
"summary": "Create script",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"description": "Script data",
|
||||||
|
"name": "body",
|
||||||
|
"in": "body",
|
||||||
|
"required": true,
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/gitea_d3m0k1d_ru_d3m0k1d_HellreigN_backend_internal_repository.ScriptCreate"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"201": {
|
||||||
|
"description": "Created",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/gitea_d3m0k1d_ru_d3m0k1d_HellreigN_backend_internal_repository.Script"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"/scripts/:id": {
|
||||||
|
"get": {
|
||||||
|
"security": [
|
||||||
|
{
|
||||||
|
"Bearer": []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "Returns a script by its ID",
|
||||||
|
"produces": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"scripts"
|
||||||
|
],
|
||||||
|
"summary": "Get script",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"type": "integer",
|
||||||
|
"description": "Script ID",
|
||||||
|
"name": "id",
|
||||||
|
"in": "path",
|
||||||
|
"required": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "OK",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/gitea_d3m0k1d_ru_d3m0k1d_HellreigN_backend_internal_repository.Script"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"400": {
|
||||||
|
"description": "Bad Request",
|
||||||
|
"schema": {
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"404": {
|
||||||
|
"description": "Not Found",
|
||||||
|
"schema": {
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"put": {
|
||||||
|
"security": [
|
||||||
|
{
|
||||||
|
"Bearer": []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "Updates a script's path, content, or interpreter",
|
||||||
|
"consumes": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"produces": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"scripts"
|
||||||
|
],
|
||||||
|
"summary": "Update script",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"type": "integer",
|
||||||
|
"description": "Script ID",
|
||||||
|
"name": "id",
|
||||||
|
"in": "path",
|
||||||
|
"required": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "Script data",
|
||||||
|
"name": "body",
|
||||||
|
"in": "body",
|
||||||
|
"required": true,
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/gitea_d3m0k1d_ru_d3m0k1d_HellreigN_backend_internal_repository.ScriptUpdate"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "OK",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/gitea_d3m0k1d_ru_d3m0k1d_HellreigN_backend_internal_repository.Script"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"400": {
|
||||||
|
"description": "Bad Request",
|
||||||
|
"schema": {
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"404": {
|
||||||
|
"description": "Not Found",
|
||||||
|
"schema": {
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"delete": {
|
||||||
|
"security": [
|
||||||
|
{
|
||||||
|
"Bearer": []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "Deletes a script by its ID",
|
||||||
|
"tags": [
|
||||||
|
"scripts"
|
||||||
|
],
|
||||||
|
"summary": "Delete script",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"type": "integer",
|
||||||
|
"description": "Script ID",
|
||||||
|
"name": "id",
|
||||||
|
"in": "path",
|
||||||
|
"required": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "OK",
|
||||||
|
"schema": {
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"400": {
|
||||||
|
"description": "Bad Request",
|
||||||
|
"schema": {
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"404": {
|
||||||
|
"description": "Not Found",
|
||||||
|
"schema": {
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"/scripts/:id/run": {
|
||||||
|
"post": {
|
||||||
|
"security": [
|
||||||
|
{
|
||||||
|
"Bearer": []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "Loads a script from storage, resolves interpreter command, and executes on the specified agent",
|
||||||
|
"consumes": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"produces": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"scripts"
|
||||||
|
],
|
||||||
|
"summary": "Run script by ID",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"type": "integer",
|
||||||
|
"description": "Script ID",
|
||||||
|
"name": "id",
|
||||||
|
"in": "path",
|
||||||
|
"required": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "Agent token and optional stdin",
|
||||||
|
"name": "body",
|
||||||
|
"in": "body",
|
||||||
|
"required": true,
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/internal_handlers.RunStoredScriptIn"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"201": {
|
||||||
|
"description": "Created",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/internal_handlers.RunScriptOut"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"400": {
|
||||||
|
"description": "Bad Request",
|
||||||
|
"schema": {
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"404": {
|
||||||
|
"description": "Not Found",
|
||||||
|
"schema": {
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"500": {
|
||||||
|
"description": "Internal Server Error",
|
||||||
|
"schema": {
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"/scripts/interpreters": {
|
"/scripts/interpreters": {
|
||||||
"get": {
|
"get": {
|
||||||
"security": [
|
"security": [
|
||||||
@@ -1511,6 +1787,34 @@ const docTemplate = `{
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"/scripts/tree": {
|
||||||
|
"get": {
|
||||||
|
"security": [
|
||||||
|
{
|
||||||
|
"Bearer": []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "Returns a hierarchical tree of all scripts organized by their paths",
|
||||||
|
"produces": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"scripts"
|
||||||
|
],
|
||||||
|
"summary": "Get script directory tree",
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "OK",
|
||||||
|
"schema": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"$ref": "#/definitions/gitea_d3m0k1d_ru_d3m0k1d_HellreigN_backend_internal_repository.ScriptTreeNode"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"definitions": {
|
"definitions": {
|
||||||
@@ -1704,6 +2008,47 @@ const docTemplate = `{
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"gitea_d3m0k1d_ru_d3m0k1d_HellreigN_backend_internal_repository.Script": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"content": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"created_at": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"id": {
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"interpreter_id": {
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"path": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"updated_at": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"gitea_d3m0k1d_ru_d3m0k1d_HellreigN_backend_internal_repository.ScriptCreate": {
|
||||||
|
"type": "object",
|
||||||
|
"required": [
|
||||||
|
"interpreter_id",
|
||||||
|
"path"
|
||||||
|
],
|
||||||
|
"properties": {
|
||||||
|
"content": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"interpreter_id": {
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"path": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"gitea_d3m0k1d_ru_d3m0k1d_HellreigN_backend_internal_repository.ScriptInterpreter": {
|
"gitea_d3m0k1d_ru_d3m0k1d_HellreigN_backend_internal_repository.ScriptInterpreter": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
@@ -1769,6 +2114,47 @@ const docTemplate = `{
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"gitea_d3m0k1d_ru_d3m0k1d_HellreigN_backend_internal_repository.ScriptTreeNode": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"children": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"$ref": "#/definitions/gitea_d3m0k1d_ru_d3m0k1d_HellreigN_backend_internal_repository.ScriptTreeNode"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"content": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"id": {
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"interpreter_id": {
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"name": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"type": {
|
||||||
|
"description": "\"folder\" or \"file\"",
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"gitea_d3m0k1d_ru_d3m0k1d_HellreigN_backend_internal_repository.ScriptUpdate": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"content": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"interpreter_id": {
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"path": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"gitea_d3m0k1d_ru_d3m0k1d_HellreigN_backend_internal_repository.TokenCreate": {
|
"gitea_d3m0k1d_ru_d3m0k1d_HellreigN_backend_internal_repository.TokenCreate": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"required": [
|
"required": [
|
||||||
@@ -2109,6 +2495,20 @@ const docTemplate = `{
|
|||||||
"type": "string"
|
"type": "string"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"internal_handlers.RunStoredScriptIn": {
|
||||||
|
"type": "object",
|
||||||
|
"required": [
|
||||||
|
"token"
|
||||||
|
],
|
||||||
|
"properties": {
|
||||||
|
"stdin": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"token": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"securityDefinitions": {
|
"securityDefinitions": {
|
||||||
|
|||||||
@@ -1293,6 +1293,282 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"/scripts": {
|
||||||
|
"post": {
|
||||||
|
"security": [
|
||||||
|
{
|
||||||
|
"Bearer": []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "Creates a new script with path, content, and interpreter binding",
|
||||||
|
"consumes": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"produces": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"scripts"
|
||||||
|
],
|
||||||
|
"summary": "Create script",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"description": "Script data",
|
||||||
|
"name": "body",
|
||||||
|
"in": "body",
|
||||||
|
"required": true,
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/gitea_d3m0k1d_ru_d3m0k1d_HellreigN_backend_internal_repository.ScriptCreate"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"201": {
|
||||||
|
"description": "Created",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/gitea_d3m0k1d_ru_d3m0k1d_HellreigN_backend_internal_repository.Script"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"/scripts/:id": {
|
||||||
|
"get": {
|
||||||
|
"security": [
|
||||||
|
{
|
||||||
|
"Bearer": []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "Returns a script by its ID",
|
||||||
|
"produces": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"scripts"
|
||||||
|
],
|
||||||
|
"summary": "Get script",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"type": "integer",
|
||||||
|
"description": "Script ID",
|
||||||
|
"name": "id",
|
||||||
|
"in": "path",
|
||||||
|
"required": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "OK",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/gitea_d3m0k1d_ru_d3m0k1d_HellreigN_backend_internal_repository.Script"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"400": {
|
||||||
|
"description": "Bad Request",
|
||||||
|
"schema": {
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"404": {
|
||||||
|
"description": "Not Found",
|
||||||
|
"schema": {
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"put": {
|
||||||
|
"security": [
|
||||||
|
{
|
||||||
|
"Bearer": []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "Updates a script's path, content, or interpreter",
|
||||||
|
"consumes": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"produces": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"scripts"
|
||||||
|
],
|
||||||
|
"summary": "Update script",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"type": "integer",
|
||||||
|
"description": "Script ID",
|
||||||
|
"name": "id",
|
||||||
|
"in": "path",
|
||||||
|
"required": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "Script data",
|
||||||
|
"name": "body",
|
||||||
|
"in": "body",
|
||||||
|
"required": true,
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/gitea_d3m0k1d_ru_d3m0k1d_HellreigN_backend_internal_repository.ScriptUpdate"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "OK",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/gitea_d3m0k1d_ru_d3m0k1d_HellreigN_backend_internal_repository.Script"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"400": {
|
||||||
|
"description": "Bad Request",
|
||||||
|
"schema": {
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"404": {
|
||||||
|
"description": "Not Found",
|
||||||
|
"schema": {
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"delete": {
|
||||||
|
"security": [
|
||||||
|
{
|
||||||
|
"Bearer": []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "Deletes a script by its ID",
|
||||||
|
"tags": [
|
||||||
|
"scripts"
|
||||||
|
],
|
||||||
|
"summary": "Delete script",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"type": "integer",
|
||||||
|
"description": "Script ID",
|
||||||
|
"name": "id",
|
||||||
|
"in": "path",
|
||||||
|
"required": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "OK",
|
||||||
|
"schema": {
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"400": {
|
||||||
|
"description": "Bad Request",
|
||||||
|
"schema": {
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"404": {
|
||||||
|
"description": "Not Found",
|
||||||
|
"schema": {
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"/scripts/:id/run": {
|
||||||
|
"post": {
|
||||||
|
"security": [
|
||||||
|
{
|
||||||
|
"Bearer": []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "Loads a script from storage, resolves interpreter command, and executes on the specified agent",
|
||||||
|
"consumes": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"produces": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"scripts"
|
||||||
|
],
|
||||||
|
"summary": "Run script by ID",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"type": "integer",
|
||||||
|
"description": "Script ID",
|
||||||
|
"name": "id",
|
||||||
|
"in": "path",
|
||||||
|
"required": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "Agent token and optional stdin",
|
||||||
|
"name": "body",
|
||||||
|
"in": "body",
|
||||||
|
"required": true,
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/internal_handlers.RunStoredScriptIn"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"201": {
|
||||||
|
"description": "Created",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/internal_handlers.RunScriptOut"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"400": {
|
||||||
|
"description": "Bad Request",
|
||||||
|
"schema": {
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"404": {
|
||||||
|
"description": "Not Found",
|
||||||
|
"schema": {
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"500": {
|
||||||
|
"description": "Internal Server Error",
|
||||||
|
"schema": {
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"/scripts/interpreters": {
|
"/scripts/interpreters": {
|
||||||
"get": {
|
"get": {
|
||||||
"security": [
|
"security": [
|
||||||
@@ -1500,6 +1776,34 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"/scripts/tree": {
|
||||||
|
"get": {
|
||||||
|
"security": [
|
||||||
|
{
|
||||||
|
"Bearer": []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "Returns a hierarchical tree of all scripts organized by their paths",
|
||||||
|
"produces": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"scripts"
|
||||||
|
],
|
||||||
|
"summary": "Get script directory tree",
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "OK",
|
||||||
|
"schema": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"$ref": "#/definitions/gitea_d3m0k1d_ru_d3m0k1d_HellreigN_backend_internal_repository.ScriptTreeNode"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"definitions": {
|
"definitions": {
|
||||||
@@ -1693,6 +1997,47 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"gitea_d3m0k1d_ru_d3m0k1d_HellreigN_backend_internal_repository.Script": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"content": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"created_at": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"id": {
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"interpreter_id": {
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"path": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"updated_at": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"gitea_d3m0k1d_ru_d3m0k1d_HellreigN_backend_internal_repository.ScriptCreate": {
|
||||||
|
"type": "object",
|
||||||
|
"required": [
|
||||||
|
"interpreter_id",
|
||||||
|
"path"
|
||||||
|
],
|
||||||
|
"properties": {
|
||||||
|
"content": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"interpreter_id": {
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"path": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"gitea_d3m0k1d_ru_d3m0k1d_HellreigN_backend_internal_repository.ScriptInterpreter": {
|
"gitea_d3m0k1d_ru_d3m0k1d_HellreigN_backend_internal_repository.ScriptInterpreter": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
@@ -1758,6 +2103,47 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"gitea_d3m0k1d_ru_d3m0k1d_HellreigN_backend_internal_repository.ScriptTreeNode": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"children": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"$ref": "#/definitions/gitea_d3m0k1d_ru_d3m0k1d_HellreigN_backend_internal_repository.ScriptTreeNode"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"content": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"id": {
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"interpreter_id": {
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"name": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"type": {
|
||||||
|
"description": "\"folder\" or \"file\"",
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"gitea_d3m0k1d_ru_d3m0k1d_HellreigN_backend_internal_repository.ScriptUpdate": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"content": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"interpreter_id": {
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"path": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"gitea_d3m0k1d_ru_d3m0k1d_HellreigN_backend_internal_repository.TokenCreate": {
|
"gitea_d3m0k1d_ru_d3m0k1d_HellreigN_backend_internal_repository.TokenCreate": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"required": [
|
"required": [
|
||||||
@@ -2098,6 +2484,20 @@
|
|||||||
"type": "string"
|
"type": "string"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"internal_handlers.RunStoredScriptIn": {
|
||||||
|
"type": "object",
|
||||||
|
"required": [
|
||||||
|
"token"
|
||||||
|
],
|
||||||
|
"properties": {
|
||||||
|
"stdin": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"token": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"securityDefinitions": {
|
"securityDefinitions": {
|
||||||
|
|||||||
@@ -130,6 +130,33 @@ definitions:
|
|||||||
required:
|
required:
|
||||||
- label
|
- label
|
||||||
type: object
|
type: object
|
||||||
|
gitea_d3m0k1d_ru_d3m0k1d_HellreigN_backend_internal_repository.Script:
|
||||||
|
properties:
|
||||||
|
content:
|
||||||
|
type: string
|
||||||
|
created_at:
|
||||||
|
type: string
|
||||||
|
id:
|
||||||
|
type: integer
|
||||||
|
interpreter_id:
|
||||||
|
type: integer
|
||||||
|
path:
|
||||||
|
type: string
|
||||||
|
updated_at:
|
||||||
|
type: string
|
||||||
|
type: object
|
||||||
|
gitea_d3m0k1d_ru_d3m0k1d_HellreigN_backend_internal_repository.ScriptCreate:
|
||||||
|
properties:
|
||||||
|
content:
|
||||||
|
type: string
|
||||||
|
interpreter_id:
|
||||||
|
type: integer
|
||||||
|
path:
|
||||||
|
type: string
|
||||||
|
required:
|
||||||
|
- interpreter_id
|
||||||
|
- path
|
||||||
|
type: object
|
||||||
gitea_d3m0k1d_ru_d3m0k1d_HellreigN_backend_internal_repository.ScriptInterpreter:
|
gitea_d3m0k1d_ru_d3m0k1d_HellreigN_backend_internal_repository.ScriptInterpreter:
|
||||||
properties:
|
properties:
|
||||||
argv:
|
argv:
|
||||||
@@ -173,6 +200,33 @@ definitions:
|
|||||||
name:
|
name:
|
||||||
type: string
|
type: string
|
||||||
type: object
|
type: object
|
||||||
|
gitea_d3m0k1d_ru_d3m0k1d_HellreigN_backend_internal_repository.ScriptTreeNode:
|
||||||
|
properties:
|
||||||
|
children:
|
||||||
|
items:
|
||||||
|
$ref: '#/definitions/gitea_d3m0k1d_ru_d3m0k1d_HellreigN_backend_internal_repository.ScriptTreeNode'
|
||||||
|
type: array
|
||||||
|
content:
|
||||||
|
type: string
|
||||||
|
id:
|
||||||
|
type: integer
|
||||||
|
interpreter_id:
|
||||||
|
type: integer
|
||||||
|
name:
|
||||||
|
type: string
|
||||||
|
type:
|
||||||
|
description: '"folder" or "file"'
|
||||||
|
type: string
|
||||||
|
type: object
|
||||||
|
gitea_d3m0k1d_ru_d3m0k1d_HellreigN_backend_internal_repository.ScriptUpdate:
|
||||||
|
properties:
|
||||||
|
content:
|
||||||
|
type: string
|
||||||
|
interpreter_id:
|
||||||
|
type: integer
|
||||||
|
path:
|
||||||
|
type: string
|
||||||
|
type: object
|
||||||
gitea_d3m0k1d_ru_d3m0k1d_HellreigN_backend_internal_repository.TokenCreate:
|
gitea_d3m0k1d_ru_d3m0k1d_HellreigN_backend_internal_repository.TokenCreate:
|
||||||
properties:
|
properties:
|
||||||
is_active:
|
is_active:
|
||||||
@@ -402,6 +456,15 @@ definitions:
|
|||||||
stdout:
|
stdout:
|
||||||
type: string
|
type: string
|
||||||
type: object
|
type: object
|
||||||
|
internal_handlers.RunStoredScriptIn:
|
||||||
|
properties:
|
||||||
|
stdin:
|
||||||
|
type: string
|
||||||
|
token:
|
||||||
|
type: string
|
||||||
|
required:
|
||||||
|
- token
|
||||||
|
type: object
|
||||||
info:
|
info:
|
||||||
contact: {}
|
contact: {}
|
||||||
paths:
|
paths:
|
||||||
@@ -1228,6 +1291,183 @@ paths:
|
|||||||
summary: Get distinct services
|
summary: Get distinct services
|
||||||
tags:
|
tags:
|
||||||
- logs
|
- logs
|
||||||
|
/scripts:
|
||||||
|
post:
|
||||||
|
consumes:
|
||||||
|
- application/json
|
||||||
|
description: Creates a new script with path, content, and interpreter binding
|
||||||
|
parameters:
|
||||||
|
- description: Script data
|
||||||
|
in: body
|
||||||
|
name: body
|
||||||
|
required: true
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/gitea_d3m0k1d_ru_d3m0k1d_HellreigN_backend_internal_repository.ScriptCreate'
|
||||||
|
produces:
|
||||||
|
- application/json
|
||||||
|
responses:
|
||||||
|
"201":
|
||||||
|
description: Created
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/gitea_d3m0k1d_ru_d3m0k1d_HellreigN_backend_internal_repository.Script'
|
||||||
|
security:
|
||||||
|
- Bearer: []
|
||||||
|
summary: Create script
|
||||||
|
tags:
|
||||||
|
- scripts
|
||||||
|
/scripts/:id:
|
||||||
|
delete:
|
||||||
|
description: Deletes a script by its ID
|
||||||
|
parameters:
|
||||||
|
- description: Script ID
|
||||||
|
in: path
|
||||||
|
name: id
|
||||||
|
required: true
|
||||||
|
type: integer
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: OK
|
||||||
|
schema:
|
||||||
|
additionalProperties:
|
||||||
|
type: string
|
||||||
|
type: object
|
||||||
|
"400":
|
||||||
|
description: Bad Request
|
||||||
|
schema:
|
||||||
|
additionalProperties:
|
||||||
|
type: string
|
||||||
|
type: object
|
||||||
|
"404":
|
||||||
|
description: Not Found
|
||||||
|
schema:
|
||||||
|
additionalProperties:
|
||||||
|
type: string
|
||||||
|
type: object
|
||||||
|
security:
|
||||||
|
- Bearer: []
|
||||||
|
summary: Delete script
|
||||||
|
tags:
|
||||||
|
- scripts
|
||||||
|
get:
|
||||||
|
description: Returns a script by its ID
|
||||||
|
parameters:
|
||||||
|
- description: Script ID
|
||||||
|
in: path
|
||||||
|
name: id
|
||||||
|
required: true
|
||||||
|
type: integer
|
||||||
|
produces:
|
||||||
|
- application/json
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: OK
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/gitea_d3m0k1d_ru_d3m0k1d_HellreigN_backend_internal_repository.Script'
|
||||||
|
"400":
|
||||||
|
description: Bad Request
|
||||||
|
schema:
|
||||||
|
additionalProperties:
|
||||||
|
type: string
|
||||||
|
type: object
|
||||||
|
"404":
|
||||||
|
description: Not Found
|
||||||
|
schema:
|
||||||
|
additionalProperties:
|
||||||
|
type: string
|
||||||
|
type: object
|
||||||
|
security:
|
||||||
|
- Bearer: []
|
||||||
|
summary: Get script
|
||||||
|
tags:
|
||||||
|
- scripts
|
||||||
|
put:
|
||||||
|
consumes:
|
||||||
|
- application/json
|
||||||
|
description: Updates a script's path, content, or interpreter
|
||||||
|
parameters:
|
||||||
|
- description: Script ID
|
||||||
|
in: path
|
||||||
|
name: id
|
||||||
|
required: true
|
||||||
|
type: integer
|
||||||
|
- description: Script data
|
||||||
|
in: body
|
||||||
|
name: body
|
||||||
|
required: true
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/gitea_d3m0k1d_ru_d3m0k1d_HellreigN_backend_internal_repository.ScriptUpdate'
|
||||||
|
produces:
|
||||||
|
- application/json
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: OK
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/gitea_d3m0k1d_ru_d3m0k1d_HellreigN_backend_internal_repository.Script'
|
||||||
|
"400":
|
||||||
|
description: Bad Request
|
||||||
|
schema:
|
||||||
|
additionalProperties:
|
||||||
|
type: string
|
||||||
|
type: object
|
||||||
|
"404":
|
||||||
|
description: Not Found
|
||||||
|
schema:
|
||||||
|
additionalProperties:
|
||||||
|
type: string
|
||||||
|
type: object
|
||||||
|
security:
|
||||||
|
- Bearer: []
|
||||||
|
summary: Update script
|
||||||
|
tags:
|
||||||
|
- scripts
|
||||||
|
/scripts/:id/run:
|
||||||
|
post:
|
||||||
|
consumes:
|
||||||
|
- application/json
|
||||||
|
description: Loads a script from storage, resolves interpreter command, and
|
||||||
|
executes on the specified agent
|
||||||
|
parameters:
|
||||||
|
- description: Script ID
|
||||||
|
in: path
|
||||||
|
name: id
|
||||||
|
required: true
|
||||||
|
type: integer
|
||||||
|
- description: Agent token and optional stdin
|
||||||
|
in: body
|
||||||
|
name: body
|
||||||
|
required: true
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/internal_handlers.RunStoredScriptIn'
|
||||||
|
produces:
|
||||||
|
- application/json
|
||||||
|
responses:
|
||||||
|
"201":
|
||||||
|
description: Created
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/internal_handlers.RunScriptOut'
|
||||||
|
"400":
|
||||||
|
description: Bad Request
|
||||||
|
schema:
|
||||||
|
additionalProperties:
|
||||||
|
type: string
|
||||||
|
type: object
|
||||||
|
"404":
|
||||||
|
description: Not Found
|
||||||
|
schema:
|
||||||
|
additionalProperties:
|
||||||
|
type: string
|
||||||
|
type: object
|
||||||
|
"500":
|
||||||
|
description: Internal Server Error
|
||||||
|
schema:
|
||||||
|
additionalProperties:
|
||||||
|
type: string
|
||||||
|
type: object
|
||||||
|
security:
|
||||||
|
- Bearer: []
|
||||||
|
summary: Run script by ID
|
||||||
|
tags:
|
||||||
|
- scripts
|
||||||
/scripts/interpreters:
|
/scripts/interpreters:
|
||||||
get:
|
get:
|
||||||
description: Returns all script interpreters available in the system
|
description: Returns all script interpreters available in the system
|
||||||
@@ -1357,6 +1597,23 @@ paths:
|
|||||||
summary: Run a script on an agent
|
summary: Run a script on an agent
|
||||||
tags:
|
tags:
|
||||||
- scripts
|
- scripts
|
||||||
|
/scripts/tree:
|
||||||
|
get:
|
||||||
|
description: Returns a hierarchical tree of all scripts organized by their paths
|
||||||
|
produces:
|
||||||
|
- application/json
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: OK
|
||||||
|
schema:
|
||||||
|
items:
|
||||||
|
$ref: '#/definitions/gitea_d3m0k1d_ru_d3m0k1d_HellreigN_backend_internal_repository.ScriptTreeNode'
|
||||||
|
type: array
|
||||||
|
security:
|
||||||
|
- Bearer: []
|
||||||
|
summary: Get script directory tree
|
||||||
|
tags:
|
||||||
|
- scripts
|
||||||
securityDefinitions:
|
securityDefinitions:
|
||||||
Bearer:
|
Bearer:
|
||||||
description: Type "Bearer" followed by a space and the JWT token.
|
description: Type "Bearer" followed by a space and the JWT token.
|
||||||
|
|||||||
@@ -50,6 +50,11 @@ func (e *Executor) WorkDir() string {
|
|||||||
return e.workDir
|
return e.workDir
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GRPCURL returns the gRPC server URL (host:port)
|
||||||
|
func (e *Executor) GRPCURL() string {
|
||||||
|
return e.grpcServerHost + ":" + e.grpcServerPort
|
||||||
|
}
|
||||||
|
|
||||||
// Deploy runs Ansible playbook for the given inventory
|
// Deploy runs Ansible playbook for the given inventory
|
||||||
func (e *Executor) Deploy(
|
func (e *Executor) Deploy(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
@@ -66,6 +71,7 @@ func (e *Executor) Deploy(
|
|||||||
cmd := exec.CommandContext(ctx, "ansible-playbook",
|
cmd := exec.CommandContext(ctx, "ansible-playbook",
|
||||||
"-i", inventoryPath,
|
"-i", inventoryPath,
|
||||||
"-e", fmt.Sprintf("backend_url=%s", e.backendURL),
|
"-e", fmt.Sprintf("backend_url=%s", e.backendURL),
|
||||||
|
"-e", fmt.Sprintf("grpc_url=%s", e.grpcServerHost+":"+e.grpcServerPort),
|
||||||
playbookPath,
|
playbookPath,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ type InventoryHost struct {
|
|||||||
Password string
|
Password string
|
||||||
DeployType string
|
DeployType string
|
||||||
Token string
|
Token string
|
||||||
|
GRPCURL string
|
||||||
}
|
}
|
||||||
|
|
||||||
// Inventory represents an Ansible inventory file
|
// Inventory represents an Ansible inventory file
|
||||||
@@ -32,6 +33,7 @@ const inventoryTemplateText = `{{ range .Hosts }}
|
|||||||
deploy_type={{ .DeployType }}
|
deploy_type={{ .DeployType }}
|
||||||
agent_token={{ .Token }}
|
agent_token={{ .Token }}
|
||||||
agent_label={{ .Name }}
|
agent_label={{ .Name }}
|
||||||
|
grpc_url={{ .GRPCURL }}
|
||||||
|
|
||||||
{{ end }}`
|
{{ end }}`
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
package ansible
|
package ansible
|
||||||
|
|
||||||
// BinaryDeployPlaybook returns the Ansible playbook for binary deployment
|
// BinaryDeployPlaybook returns the Ansible playbook for binary deployment.
|
||||||
|
// Downloads the agent binary, writes config, and starts it directly (no systemd).
|
||||||
|
// systemd unit is managed separately (e.g. via goreleaser .deb/.rpm packages).
|
||||||
const BinaryDeployPlaybook = `---
|
const BinaryDeployPlaybook = `---
|
||||||
- name: Deploy HellreigN Agent (Binary)
|
- name: Deploy HellreigN Agent (Binary)
|
||||||
hosts: all
|
hosts: all
|
||||||
@@ -11,7 +13,6 @@ const BinaryDeployPlaybook = `---
|
|||||||
backend_url: "{{ backend_url }}"
|
backend_url: "{{ backend_url }}"
|
||||||
install_dir: /opt/hellreign
|
install_dir: /opt/hellreign
|
||||||
bin_name: hellreign-agent
|
bin_name: hellreign-agent
|
||||||
service_name: hellreign-agent
|
|
||||||
cert_dir: "{{ install_dir }}/certs"
|
cert_dir: "{{ install_dir }}/certs"
|
||||||
|
|
||||||
tasks:
|
tasks:
|
||||||
@@ -37,45 +38,29 @@ const BinaryDeployPlaybook = `---
|
|||||||
copy:
|
copy:
|
||||||
content: |
|
content: |
|
||||||
backend_url: "{{ backend_url }}"
|
backend_url: "{{ backend_url }}"
|
||||||
|
grpc_url: "{{ grpc_url | default('localhost:9001') }}"
|
||||||
label: "{{ agent_label }}"
|
label: "{{ agent_label }}"
|
||||||
registration_token: "{{ agent_token }}"
|
registration_token: "{{ agent_token }}"
|
||||||
cert_dir: "{{ cert_dir }}"
|
cert_dir: "{{ cert_dir }}"
|
||||||
|
services:
|
||||||
|
- name: system
|
||||||
|
type: journald
|
||||||
dest: "{{ install_dir }}/config.yml"
|
dest: "{{ install_dir }}/config.yml"
|
||||||
mode: '0644'
|
mode: '0644'
|
||||||
|
|
||||||
- name: Create systemd service file
|
- name: Start HellreigN Agent
|
||||||
copy:
|
shell: |
|
||||||
content: |
|
nohup {{ install_dir }}/{{ bin_name }} > /dev/null 2>&1 &
|
||||||
[Unit]
|
echo $!
|
||||||
Description=HellreigN Agent
|
args:
|
||||||
After=network.target
|
executable: /bin/bash
|
||||||
|
environment:
|
||||||
[Service]
|
CONFIG_FILE: "{{ install_dir }}/config.yml"
|
||||||
Type=simple
|
register: agent_pid
|
||||||
ExecStart={{ install_dir }}/{{ bin_name }}
|
changed_when: true
|
||||||
Restart=always
|
|
||||||
RestartSec=5
|
|
||||||
Environment=CONFIG_FILE={{ install_dir }}/config.yml
|
|
||||||
StandardOutput=journal
|
|
||||||
StandardError=journal
|
|
||||||
|
|
||||||
[Install]
|
|
||||||
WantedBy=multi-user.target
|
|
||||||
dest: /etc/systemd/system/{{ service_name }}.service
|
|
||||||
mode: '0644'
|
|
||||||
|
|
||||||
- name: Reload systemd daemon
|
|
||||||
systemd:
|
|
||||||
daemon_reload: yes
|
|
||||||
|
|
||||||
- name: Enable and start HellreigN Agent service
|
|
||||||
systemd:
|
|
||||||
name: "{{ service_name }}"
|
|
||||||
enabled: yes
|
|
||||||
state: started
|
|
||||||
`
|
`
|
||||||
|
|
||||||
// DockerDeployPlaybook returns the Ansible playbook for Docker deployment
|
// DockerDeployPlaybook returns the Ansible playbook for Docker deployment.
|
||||||
const DockerDeployPlaybook = `---
|
const DockerDeployPlaybook = `---
|
||||||
- name: Deploy HellreigN Agent (Docker)
|
- name: Deploy HellreigN Agent (Docker)
|
||||||
hosts: all
|
hosts: all
|
||||||
@@ -84,6 +69,7 @@ const DockerDeployPlaybook = `---
|
|||||||
agent_label: "{{ agent_label }}"
|
agent_label: "{{ agent_label }}"
|
||||||
agent_token: "{{ agent_token }}"
|
agent_token: "{{ agent_token }}"
|
||||||
backend_url: "{{ backend_url }}"
|
backend_url: "{{ backend_url }}"
|
||||||
|
grpc_url: "{{ grpc_url | default('localhost:9001') }}"
|
||||||
container_name: hellreign-agent-{{ agent_label }}
|
container_name: hellreign-agent-{{ agent_label }}
|
||||||
image: "gitea.d3m0k1d.ru/d3m0k1d/hellreign-agent:latest"
|
image: "gitea.d3m0k1d.ru/d3m0k1d/hellreign-agent:latest"
|
||||||
cert_dir: /etc/hellreign-agent/certs
|
cert_dir: /etc/hellreign-agent/certs
|
||||||
@@ -117,9 +103,13 @@ const DockerDeployPlaybook = `---
|
|||||||
copy:
|
copy:
|
||||||
content: |
|
content: |
|
||||||
backend_url: "{{ backend_url }}"
|
backend_url: "{{ backend_url }}"
|
||||||
|
grpc_url: "{{ grpc_url }}"
|
||||||
label: "{{ agent_label }}"
|
label: "{{ agent_label }}"
|
||||||
registration_token: "{{ agent_token }}"
|
registration_token: "{{ agent_token }}"
|
||||||
cert_dir: "{{ cert_dir }}"
|
cert_dir: "{{ cert_dir }}"
|
||||||
|
services:
|
||||||
|
- name: system
|
||||||
|
type: journald
|
||||||
dest: "{{ cert_dir }}/config.yml"
|
dest: "{{ cert_dir }}/config.yml"
|
||||||
mode: '0644'
|
mode: '0644'
|
||||||
|
|
||||||
|
|||||||
@@ -186,45 +186,3 @@ func (c *Collector) Agents() []*Agent {
|
|||||||
}
|
}
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
// ServicesStream handles the ServicesUpdate client-streaming RPC.
|
|
||||||
// Agents send service status updates which are stored in the collector.
|
|
||||||
// Returns a single response when the agent closes the stream.
|
|
||||||
func (c *Collector) ServicesStream(stream proto.Collector_ServicesStreamServer) error {
|
|
||||||
md, ok := metadata.FromIncomingContext(stream.Context())
|
|
||||||
if !ok {
|
|
||||||
return fmt.Errorf("no metadata in context")
|
|
||||||
}
|
|
||||||
|
|
||||||
whoamiVals := md["whoami"]
|
|
||||||
if len(whoamiVals) == 0 {
|
|
||||||
return fmt.Errorf("whoami metadata missing")
|
|
||||||
}
|
|
||||||
agentName := whoamiVals[0]
|
|
||||||
|
|
||||||
log.Printf("Agent %s started services update stream", agentName)
|
|
||||||
|
|
||||||
for {
|
|
||||||
update, err := stream.Recv()
|
|
||||||
if err == io.EOF {
|
|
||||||
log.Printf("Agent %s finished services update stream", agentName)
|
|
||||||
return stream.SendAndClose(&proto.ServicesUpdateResp{})
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("failed to receive services update: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
c.mu.Lock()
|
|
||||||
if agent, ok := c.agents[agentName]; ok {
|
|
||||||
services := make([]string, 0, len(update.Services))
|
|
||||||
for _, s := range update.Services {
|
|
||||||
services = append(services, fmt.Sprintf("%s:%s", s.Name, s.Status))
|
|
||||||
}
|
|
||||||
agent.Services = services
|
|
||||||
log.Printf("Updated services for agent %s: %v", agentName, agent.Services)
|
|
||||||
} else {
|
|
||||||
log.Printf("Warning: received services update for unknown agent %s", agentName)
|
|
||||||
}
|
|
||||||
c.mu.Unlock()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -60,6 +60,18 @@ func (self *Commander) GetAgent(aid string) (agent Agent, ok bool) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetAgentByLabel searches for an agent by its human-readable label.
|
||||||
|
func (self *Commander) GetAgentByLabel(label string) (agent Agent, ok bool) {
|
||||||
|
self.mu.RLock()
|
||||||
|
defer self.mu.RUnlock()
|
||||||
|
for _, a := range self.agents {
|
||||||
|
if a.Label == label {
|
||||||
|
return a, true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
func (self *Commander) Agents() []Agent {
|
func (self *Commander) Agents() []Agent {
|
||||||
self.mu.RLock()
|
self.mu.RLock()
|
||||||
defer self.mu.RUnlock()
|
defer self.mu.RUnlock()
|
||||||
|
|||||||
@@ -117,6 +117,7 @@ func (adg *AgentDeployGroup) DeployAgents(c *gin.Context) {
|
|||||||
Password: server.Password,
|
Password: server.Password,
|
||||||
DeployType: string(server.DeployType),
|
DeployType: string(server.DeployType),
|
||||||
Token: token,
|
Token: token,
|
||||||
|
GRPCURL: adg.executor.GRPCURL(),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,274 @@
|
|||||||
|
package handlers
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
|
"gitea.d3m0k1d.ru/d3m0k1d/HellreigN/backend/internal/grpcsrv/commander"
|
||||||
|
"gitea.d3m0k1d.ru/d3m0k1d/HellreigN/backend/internal/models"
|
||||||
|
"gitea.d3m0k1d.ru/d3m0k1d/HellreigN/backend/internal/repository"
|
||||||
|
"gitea.d3m0k1d.ru/d3m0k1d/HellreigN/backend/internal/service"
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ScriptHandlersGroup handles script management routes.
|
||||||
|
type ScriptHandlersGroup struct {
|
||||||
|
svc *service.ScriptService
|
||||||
|
cmder *commander.Commander
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewScriptHandlersGroup creates a new ScriptHandlersGroup.
|
||||||
|
func NewScriptHandlersGroup(svc *service.ScriptService, cmder *commander.Commander) *ScriptHandlersGroup {
|
||||||
|
return &ScriptHandlersGroup{svc: svc, cmder: cmder}
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetTree returns the script directory tree.
|
||||||
|
// @Summary Get script directory tree
|
||||||
|
// @Description Returns a hierarchical tree of all scripts organized by their paths
|
||||||
|
// @Tags scripts
|
||||||
|
// @Produce json
|
||||||
|
// @Success 200 {array} repository.ScriptTreeNode
|
||||||
|
// @Security Bearer
|
||||||
|
// @Router /scripts/tree [get]
|
||||||
|
func (sh *ScriptHandlersGroup) GetTree(c *gin.Context) {
|
||||||
|
tree, err := sh.svc.BuildTree()
|
||||||
|
if err != nil {
|
||||||
|
c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to build script tree"})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if tree == nil {
|
||||||
|
tree = []repository.ScriptTreeNode{}
|
||||||
|
}
|
||||||
|
|
||||||
|
c.JSON(http.StatusOK, tree)
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateScript creates a new script.
|
||||||
|
// @Summary Create script
|
||||||
|
// @Description Creates a new script with path, content, and interpreter binding
|
||||||
|
// @Tags scripts
|
||||||
|
// @Accept json
|
||||||
|
// @Produce json
|
||||||
|
// @Param body body repository.ScriptCreate true "Script data"
|
||||||
|
// @Success 201 {object} repository.Script
|
||||||
|
// @Security Bearer
|
||||||
|
// @Router /scripts [post]
|
||||||
|
func (sh *ScriptHandlersGroup) CreateScript(c *gin.Context) {
|
||||||
|
var req repository.ScriptCreate
|
||||||
|
if err := c.ShouldBindJSON(&req); err != nil {
|
||||||
|
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid request body"})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
script, err := sh.svc.Repo.CreateScript(req)
|
||||||
|
if err != nil {
|
||||||
|
if isUniqueConstraint(err) {
|
||||||
|
c.JSON(http.StatusConflict, gin.H{"error": "script with this path already exists"})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to create script"})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.JSON(http.StatusCreated, script)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetScript returns a script by ID.
|
||||||
|
// @Summary Get script
|
||||||
|
// @Description Returns a script by its ID
|
||||||
|
// @Tags scripts
|
||||||
|
// @Produce json
|
||||||
|
// @Param id path int true "Script ID"
|
||||||
|
// @Success 200 {object} repository.Script
|
||||||
|
// @Failure 400 {object} map[string]string
|
||||||
|
// @Failure 404 {object} map[string]string
|
||||||
|
// @Security Bearer
|
||||||
|
// @Router /scripts/:id [get]
|
||||||
|
func (sh *ScriptHandlersGroup) GetScript(c *gin.Context) {
|
||||||
|
id, err := strconv.ParseInt(c.Param("id"), 10, 64)
|
||||||
|
if err != nil {
|
||||||
|
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid id"})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
script, err := sh.svc.Repo.GetScript(id)
|
||||||
|
if err != nil {
|
||||||
|
if errors.Is(err, repository.ErrNotFound) {
|
||||||
|
c.JSON(http.StatusNotFound, gin.H{"error": "script not found"})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to get script"})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.JSON(http.StatusOK, script)
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateScript updates a script.
|
||||||
|
// @Summary Update script
|
||||||
|
// @Description Updates a script's path, content, or interpreter
|
||||||
|
// @Tags scripts
|
||||||
|
// @Accept json
|
||||||
|
// @Produce json
|
||||||
|
// @Param id path int true "Script ID"
|
||||||
|
// @Param body body repository.ScriptUpdate true "Script data"
|
||||||
|
// @Success 200 {object} repository.Script
|
||||||
|
// @Failure 400 {object} map[string]string
|
||||||
|
// @Failure 404 {object} map[string]string
|
||||||
|
// @Security Bearer
|
||||||
|
// @Router /scripts/:id [put]
|
||||||
|
func (sh *ScriptHandlersGroup) UpdateScript(c *gin.Context) {
|
||||||
|
id, err := strconv.ParseInt(c.Param("id"), 10, 64)
|
||||||
|
if err != nil {
|
||||||
|
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid id"})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var req repository.ScriptUpdate
|
||||||
|
if err := c.ShouldBindJSON(&req); err != nil {
|
||||||
|
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid request body"})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
script, err := sh.svc.Repo.UpdateScript(id, req)
|
||||||
|
if err != nil {
|
||||||
|
if errors.Is(err, repository.ErrNotFound) {
|
||||||
|
c.JSON(http.StatusNotFound, gin.H{"error": "script not found"})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to update script"})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.JSON(http.StatusOK, script)
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteScript deletes a script.
|
||||||
|
// @Summary Delete script
|
||||||
|
// @Description Deletes a script by its ID
|
||||||
|
// @Tags scripts
|
||||||
|
// @Param id path int true "Script ID"
|
||||||
|
// @Success 200 {object} map[string]string
|
||||||
|
// @Failure 400 {object} map[string]string
|
||||||
|
// @Failure 404 {object} map[string]string
|
||||||
|
// @Security Bearer
|
||||||
|
// @Router /scripts/:id [delete]
|
||||||
|
func (sh *ScriptHandlersGroup) DeleteScript(c *gin.Context) {
|
||||||
|
id, err := strconv.ParseInt(c.Param("id"), 10, 64)
|
||||||
|
if err != nil {
|
||||||
|
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid id"})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := sh.svc.Repo.DeleteScript(id); err != nil {
|
||||||
|
if errors.Is(err, repository.ErrNotFound) {
|
||||||
|
c.JSON(http.StatusNotFound, gin.H{"error": "script not found"})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to delete script"})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.JSON(http.StatusOK, gin.H{"message": "script deleted"})
|
||||||
|
}
|
||||||
|
|
||||||
|
// RunScriptByID executes a stored script on a target agent.
|
||||||
|
// @Summary Run script by ID
|
||||||
|
// @Description Loads a script from storage, resolves interpreter command, and executes on the specified agent
|
||||||
|
// @Tags scripts
|
||||||
|
// @Accept json
|
||||||
|
// @Produce json
|
||||||
|
// @Param id path int true "Script ID"
|
||||||
|
// @Param body body RunStoredScriptIn true "Agent token and optional stdin"
|
||||||
|
// @Success 201 {object} RunScriptOut
|
||||||
|
// @Failure 400 {object} map[string]string
|
||||||
|
// @Failure 404 {object} map[string]string
|
||||||
|
// @Failure 500 {object} map[string]string
|
||||||
|
// @Security Bearer
|
||||||
|
// @Router /scripts/:id/run [post]
|
||||||
|
func (sh *ScriptHandlersGroup) RunScriptByID(c *gin.Context) {
|
||||||
|
id, err := strconv.ParseInt(c.Param("id"), 10, 64)
|
||||||
|
if err != nil {
|
||||||
|
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid id"})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var in RunStoredScriptIn
|
||||||
|
if err := c.ShouldBindJSON(&in); err != nil {
|
||||||
|
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid request body"})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
script, err := sh.svc.Repo.GetScript(id)
|
||||||
|
if err != nil {
|
||||||
|
if errors.Is(err, repository.ErrNotFound) {
|
||||||
|
c.JSON(http.StatusNotFound, gin.H{"error": "script not found"})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to get script"})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
command, err := sh.svc.ResolveCommand(c.Request.Context(), script.InterpreterID, script.Content)
|
||||||
|
if err != nil {
|
||||||
|
c.JSON(http.StatusInternalServerError, gin.H{"error": fmt.Sprintf("failed to resolve command: %v", err)})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
agent, ok := sh.cmder.GetAgent(in.Token)
|
||||||
|
if !ok {
|
||||||
|
c.JSON(http.StatusNotFound, gin.H{"error": "agent not found"})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
jid, err := agent.AddJob(models.JobForInsert{
|
||||||
|
Command: command,
|
||||||
|
Stdin: in.Stdin,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
c.JSON(http.StatusInternalServerError, gin.H{"error": fmt.Sprintf("failed to add job: %v", err)})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
job, err := agent.WaitJob(jid)
|
||||||
|
if err != nil {
|
||||||
|
c.JSON(http.StatusInternalServerError, gin.H{"error": fmt.Sprintf("job execution failed: %v", err)})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.JSON(http.StatusCreated, RunScriptOut{
|
||||||
|
ID: job.ID,
|
||||||
|
Command: job.Command,
|
||||||
|
Stdin: job.Stdin,
|
||||||
|
Stdout: job.Stdout,
|
||||||
|
Stderr: job.Stderr,
|
||||||
|
Status: job.Status,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// RunStoredScriptIn is the request body for running a stored script on an agent.
|
||||||
|
type RunStoredScriptIn struct {
|
||||||
|
Token string `json:"token" binding:"required"`
|
||||||
|
Stdin *string `json:"stdin"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// isUniqueConstraint checks if the error is a SQLite UNIQUE constraint violation.
|
||||||
|
func isUniqueConstraint(err error) bool {
|
||||||
|
return err != nil && (err.Error() != "" && contains(err.Error(), "UNIQUE constraint"))
|
||||||
|
}
|
||||||
|
|
||||||
|
func contains(s, substr string) bool {
|
||||||
|
return len(s) >= len(substr) && searchSubstring(s, substr)
|
||||||
|
}
|
||||||
|
|
||||||
|
func searchSubstring(s, substr string) bool {
|
||||||
|
for i := 0; i <= len(s)-len(substr); i++ {
|
||||||
|
if s[i:i+len(substr)] == substr {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
@@ -149,3 +149,37 @@ type DeployResult struct {
|
|||||||
Success bool `json:"success" example:"true" description:"Whether deployment succeeded"`
|
Success bool `json:"success" example:"true" description:"Whether deployment succeeded"`
|
||||||
Error string `json:"error,omitempty" example:"" description:"Error message if deployment failed"`
|
Error string `json:"error,omitempty" example:"" description:"Error message if deployment failed"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Script represents a stored script with path and interpreter binding.
|
||||||
|
type Script struct {
|
||||||
|
ID int64 `json:"id"`
|
||||||
|
Path string `json:"path"`
|
||||||
|
Content string `json:"content"`
|
||||||
|
InterpreterID int64 `json:"interpreter_id"`
|
||||||
|
CreatedAt *string `json:"created_at"`
|
||||||
|
UpdatedAt *string `json:"updated_at"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// ScriptCreate is the request body for creating a script.
|
||||||
|
type ScriptCreate struct {
|
||||||
|
Path string `json:"path" binding:"required"`
|
||||||
|
Content string `json:"content"`
|
||||||
|
InterpreterID int64 `json:"interpreter_id" binding:"required"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// ScriptUpdate is the request body for updating a script.
|
||||||
|
type ScriptUpdate struct {
|
||||||
|
Path *string `json:"path"`
|
||||||
|
Content *string `json:"content"`
|
||||||
|
InterpreterID *int64 `json:"interpreter_id"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// ScriptTreeNode represents a node in the script directory tree.
|
||||||
|
type ScriptTreeNode struct {
|
||||||
|
Name string `json:"name"`
|
||||||
|
Type string `json:"type"` // "folder" or "file"
|
||||||
|
Children []ScriptTreeNode `json:"children,omitempty"`
|
||||||
|
ID *int64 `json:"id,omitempty"`
|
||||||
|
Content *string `json:"content,omitempty"`
|
||||||
|
InterpreterID *int64 `json:"interpreter_id,omitempty"`
|
||||||
|
}
|
||||||
|
|||||||
@@ -507,3 +507,134 @@ func (r *Repository) UpdatePassword(login string, newPassword string) error {
|
|||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// CreateScript inserts a new script into the database.
|
||||||
|
func (r *Repository) CreateScript(sc ScriptCreate) (*Script, error) {
|
||||||
|
result, err := r.DB.Exec(
|
||||||
|
`INSERT INTO scripts (path, content, interpreter_id) VALUES (?, ?, ?)`,
|
||||||
|
sc.Path, sc.Content, sc.InterpreterID,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("insert script: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
id, err := result.LastInsertId()
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("get last insert id: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &Script{
|
||||||
|
ID: id,
|
||||||
|
Path: sc.Path,
|
||||||
|
Content: sc.Content,
|
||||||
|
InterpreterID: sc.InterpreterID,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetScript retrieves a script by ID.
|
||||||
|
func (r *Repository) GetScript(id int64) (*Script, error) {
|
||||||
|
var s Script
|
||||||
|
err := r.DB.QueryRow(
|
||||||
|
`SELECT id, path, content, interpreter_id, created_at, updated_at FROM scripts WHERE id = ?`,
|
||||||
|
id,
|
||||||
|
).Scan(&s.ID, &s.Path, &s.Content, &s.InterpreterID, &s.CreatedAt, &s.UpdatedAt)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
if errors.Is(err, sql.ErrNoRows) {
|
||||||
|
return nil, ErrNotFound
|
||||||
|
}
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &s, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetScriptByPath retrieves a script by its path.
|
||||||
|
func (r *Repository) GetScriptByPath(path string) (*Script, error) {
|
||||||
|
var s Script
|
||||||
|
err := r.DB.QueryRow(
|
||||||
|
`SELECT id, path, content, interpreter_id, created_at, updated_at FROM scripts WHERE path = ?`,
|
||||||
|
path,
|
||||||
|
).Scan(&s.ID, &s.Path, &s.Content, &s.InterpreterID, &s.CreatedAt, &s.UpdatedAt)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
if errors.Is(err, sql.ErrNoRows) {
|
||||||
|
return nil, ErrNotFound
|
||||||
|
}
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &s, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListScripts returns all scripts.
|
||||||
|
func (r *Repository) ListScripts() ([]Script, error) {
|
||||||
|
rows, err := r.DB.Query(
|
||||||
|
`SELECT id, path, content, interpreter_id, created_at, updated_at FROM scripts`,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer rows.Close()
|
||||||
|
|
||||||
|
var scripts []Script
|
||||||
|
for rows.Next() {
|
||||||
|
var s Script
|
||||||
|
if err := rows.Scan(&s.ID, &s.Path, &s.Content, &s.InterpreterID, &s.CreatedAt, &s.UpdatedAt); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
scripts = append(scripts, s)
|
||||||
|
}
|
||||||
|
return scripts, rows.Err()
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateScript updates a script by ID.
|
||||||
|
func (r *Repository) UpdateScript(id int64, update ScriptUpdate) (*Script, error) {
|
||||||
|
existing, err := r.GetScript(id)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
newPath := existing.Path
|
||||||
|
newContent := existing.Content
|
||||||
|
newInterpreterID := existing.InterpreterID
|
||||||
|
|
||||||
|
if update.Path != nil {
|
||||||
|
newPath = *update.Path
|
||||||
|
}
|
||||||
|
if update.Content != nil {
|
||||||
|
newContent = *update.Content
|
||||||
|
}
|
||||||
|
if update.InterpreterID != nil {
|
||||||
|
newInterpreterID = *update.InterpreterID
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = r.DB.Exec(
|
||||||
|
`UPDATE scripts SET path = ?, content = ?, interpreter_id = ?, updated_at = CURRENT_TIMESTAMP WHERE id = ?`,
|
||||||
|
newPath, newContent, newInterpreterID, id,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("update script: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &Script{
|
||||||
|
ID: id,
|
||||||
|
Path: newPath,
|
||||||
|
Content: newContent,
|
||||||
|
InterpreterID: newInterpreterID,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteScript deletes a script by ID.
|
||||||
|
func (r *Repository) DeleteScript(id int64) error {
|
||||||
|
result, err := r.DB.Exec(`DELETE FROM scripts WHERE id = ?`, id)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
affected, err := result.RowsAffected()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if affected == 0 {
|
||||||
|
return ErrNotFound
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|||||||
@@ -3,66 +3,170 @@ package service
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"sort"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"gitea.d3m0k1d.ru/d3m0k1d/HellreigN/backend/internal/repository"
|
"gitea.d3m0k1d.ru/d3m0k1d/HellreigN/backend/internal/repository"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// ScriptService handles script CRUD, tree building, and interpreter resolution.
|
||||||
type ScriptService struct {
|
type ScriptService struct {
|
||||||
repo *repository.ScriptInterpreterRepo
|
Repo *repository.Repository
|
||||||
|
InterpreterRepo *repository.ScriptInterpreterRepo
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewScriptService(repo *repository.ScriptInterpreterRepo) *ScriptService {
|
// NewScriptService creates a new ScriptService with both script and interpreter repos.
|
||||||
return &ScriptService{repo: repo}
|
func NewScriptService(repo *repository.Repository) *ScriptService {
|
||||||
|
return &ScriptService{Repo: repo}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ResolveCommand builds the full argv[] by prepending the interpreter's argv
|
// NewScriptServiceWithInterpreters creates a ScriptService with interpreter support.
|
||||||
// to the script text (as the last argument).
|
func NewScriptServiceWithInterpreters(repo *repository.Repository, interpRepo *repository.ScriptInterpreterRepo) *ScriptService {
|
||||||
func (self *ScriptService) ResolveCommand(
|
return &ScriptService{Repo: repo, InterpreterRepo: interpRepo}
|
||||||
ctx context.Context,
|
}
|
||||||
interpreterID int64,
|
|
||||||
scriptText string,
|
// treeNode is an internal representation for building the tree.
|
||||||
) ([]string, error) {
|
type treeNode struct {
|
||||||
interpreter, err := self.repo.GetByID(ctx, interpreterID)
|
name string
|
||||||
|
typ string // "folder" or "file"
|
||||||
|
children map[string]*treeNode
|
||||||
|
// File-specific fields
|
||||||
|
id *int64
|
||||||
|
content *string
|
||||||
|
interpreterID *int64
|
||||||
|
}
|
||||||
|
|
||||||
|
// BuildTree builds a directory tree from all scripts in the database.
|
||||||
|
// Each script path is treated as a file path (e.g. "deploy/nginx/restart.sh").
|
||||||
|
func (s *ScriptService) BuildTree() ([]repository.ScriptTreeNode, error) {
|
||||||
|
scripts, err := s.Repo.ListScripts()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(interpreter.Argv) == 0 {
|
root := make(map[string]*treeNode)
|
||||||
return nil, fmt.Errorf("interpreter %q has empty argv", interpreter.Name)
|
|
||||||
|
for _, sc := range scripts {
|
||||||
|
parts := strings.Split(sc.Path, "/")
|
||||||
|
|
||||||
|
// Walk through path parts, creating folders as needed
|
||||||
|
currentMap := root
|
||||||
|
for i, part := range parts {
|
||||||
|
isFile := i == len(parts)-1
|
||||||
|
if _, exists := currentMap[part]; !exists {
|
||||||
|
node := &treeNode{
|
||||||
|
name: part,
|
||||||
|
children: make(map[string]*treeNode),
|
||||||
|
}
|
||||||
|
if isFile {
|
||||||
|
node.typ = "file"
|
||||||
|
id := sc.ID
|
||||||
|
content := sc.Content
|
||||||
|
interpreterID := sc.InterpreterID
|
||||||
|
node.id = &id
|
||||||
|
node.content = &content
|
||||||
|
node.interpreterID = &interpreterID
|
||||||
|
} else {
|
||||||
|
node.typ = "folder"
|
||||||
|
}
|
||||||
|
currentMap[part] = node
|
||||||
|
}
|
||||||
|
currentMap = currentMap[part].children
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
argv := make([]string, len(interpreter.Argv)+1)
|
return buildTreeSlice(root), nil
|
||||||
copy(argv, interpreter.Argv)
|
|
||||||
argv[len(argv)-1] = scriptText
|
|
||||||
return argv, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (self *ScriptService) Create(
|
// buildTreeSlice converts a map of treeNodes to a sorted slice of ScriptTreeNode.
|
||||||
ctx context.Context,
|
func buildTreeSlice(m map[string]*treeNode) []repository.ScriptTreeNode {
|
||||||
in repository.ScriptInterpreterCreate,
|
result := make([]repository.ScriptTreeNode, 0, len(m))
|
||||||
) (*repository.ScriptInterpreter, error) {
|
for _, node := range m {
|
||||||
return self.repo.Create(ctx, in)
|
result = append(result, toScriptTreeNode(node))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sort: folders first, then files, alphabetically within each group
|
||||||
|
sort.Slice(result, func(i, j int) bool {
|
||||||
|
if result[i].Type != result[j].Type {
|
||||||
|
return result[i].Type == "folder"
|
||||||
|
}
|
||||||
|
return result[i].Name < result[j].Name
|
||||||
|
})
|
||||||
|
|
||||||
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
func (self *ScriptService) GetByID(
|
// toScriptTreeNode converts a treeNode to a ScriptTreeNode with recursively converted children.
|
||||||
ctx context.Context,
|
func toScriptTreeNode(node *treeNode) repository.ScriptTreeNode {
|
||||||
id int64,
|
result := repository.ScriptTreeNode{
|
||||||
) (*repository.ScriptInterpreter, error) {
|
Name: node.name,
|
||||||
return self.repo.GetByID(ctx, id)
|
Type: node.typ,
|
||||||
|
Children: []repository.ScriptTreeNode{},
|
||||||
|
}
|
||||||
|
|
||||||
|
if node.typ == "file" {
|
||||||
|
result.ID = node.id
|
||||||
|
result.Content = node.content
|
||||||
|
result.InterpreterID = node.interpreterID
|
||||||
|
} else {
|
||||||
|
result.Children = buildTreeSlice(node.children)
|
||||||
|
}
|
||||||
|
|
||||||
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
func (self *ScriptService) List(ctx context.Context) ([]repository.ScriptInterpreter, error) {
|
// ResolveCommand resolves the full command for a script using its interpreter.
|
||||||
return self.repo.List(ctx)
|
func (s *ScriptService) ResolveCommand(ctx context.Context, interpreterID int64, scriptText string) ([]string, error) {
|
||||||
|
if s.InterpreterRepo == nil {
|
||||||
|
return nil, fmt.Errorf("interpreter repo not configured")
|
||||||
|
}
|
||||||
|
|
||||||
|
interpreter, err := s.InterpreterRepo.GetByID(ctx, interpreterID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("get interpreter: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Build command: argv[0] argv[1] ... -c scriptText
|
||||||
|
cmd := append(interpreter.Argv, "-c", scriptText)
|
||||||
|
return cmd, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (self *ScriptService) Update(
|
// List returns all interpreters.
|
||||||
ctx context.Context,
|
func (s *ScriptService) List(ctx context.Context) ([]repository.ScriptInterpreter, error) {
|
||||||
id int64,
|
if s.InterpreterRepo == nil {
|
||||||
in repository.ScriptInterpreterUpdate,
|
return nil, fmt.Errorf("interpreter repo not configured")
|
||||||
) (*repository.ScriptInterpreter, error) {
|
}
|
||||||
return self.repo.Update(ctx, id, in)
|
return s.InterpreterRepo.List(ctx)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (self *ScriptService) Delete(ctx context.Context, id int64) error {
|
// Create creates a new interpreter.
|
||||||
return self.repo.Delete(ctx, id)
|
func (s *ScriptService) Create(ctx context.Context, in repository.ScriptInterpreterCreate) (*repository.ScriptInterpreter, error) {
|
||||||
|
if s.InterpreterRepo == nil {
|
||||||
|
return nil, fmt.Errorf("interpreter repo not configured")
|
||||||
|
}
|
||||||
|
return s.InterpreterRepo.Create(ctx, in)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetByID returns an interpreter by ID.
|
||||||
|
func (s *ScriptService) GetByID(ctx context.Context, id int64) (*repository.ScriptInterpreter, error) {
|
||||||
|
if s.InterpreterRepo == nil {
|
||||||
|
return nil, fmt.Errorf("interpreter repo not configured")
|
||||||
|
}
|
||||||
|
return s.InterpreterRepo.GetByID(ctx, id)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update updates an interpreter.
|
||||||
|
func (s *ScriptService) Update(ctx context.Context, id int64, in repository.ScriptInterpreterUpdate) (*repository.ScriptInterpreter, error) {
|
||||||
|
if s.InterpreterRepo == nil {
|
||||||
|
return nil, fmt.Errorf("interpreter repo not configured")
|
||||||
|
}
|
||||||
|
return s.InterpreterRepo.Update(ctx, id, in)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete deletes an interpreter.
|
||||||
|
func (s *ScriptService) Delete(ctx context.Context, id int64) error {
|
||||||
|
if s.InterpreterRepo == nil {
|
||||||
|
return fmt.Errorf("interpreter repo not configured")
|
||||||
|
}
|
||||||
|
return s.InterpreterRepo.Delete(ctx, id)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -57,6 +57,18 @@ CREATE TABLE IF NOT EXISTS script_interpreters (
|
|||||||
);
|
);
|
||||||
`
|
`
|
||||||
|
|
||||||
|
const CreateScriptsTable = `
|
||||||
|
CREATE TABLE IF NOT EXISTS scripts (
|
||||||
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
|
path TEXT NOT NULL UNIQUE,
|
||||||
|
content TEXT NOT NULL DEFAULT '',
|
||||||
|
interpreter_id INTEGER NOT NULL,
|
||||||
|
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
FOREIGN KEY (interpreter_id) REFERENCES script_interpreters(id)
|
||||||
|
);
|
||||||
|
`
|
||||||
|
|
||||||
const CreateLogsTable = `
|
const CreateLogsTable = `
|
||||||
CREATE TABLE IF NOT EXISTS logs (
|
CREATE TABLE IF NOT EXISTS logs (
|
||||||
timestamp DateTime64(3) DEFAULT now(),
|
timestamp DateTime64(3) DEFAULT now(),
|
||||||
|
|||||||
@@ -44,5 +44,10 @@ func Open(path string) (*sql.DB, error) {
|
|||||||
log.Println("[sqlite] is_active column migration applied")
|
log.Println("[sqlite] is_active column migration applied")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Create scripts table if not exists
|
||||||
|
if _, err := db.Exec(CreateScriptsTable); err != nil {
|
||||||
|
return nil, fmt.Errorf("migrate scripts: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
return db, nil
|
return db, nil
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user