Brought up to date.

This commit is contained in:
Daan Selen
2025-05-27 13:11:47 +02:00
parent 78d127bd46
commit 0d16807432
9 changed files with 171 additions and 43 deletions

View File

@@ -1,33 +0,0 @@
# GhostRunner
This project aims to create a way to schedule commands to be run as soon as possible when they offline initialy.<br>
The way to accomplish this is to create a tracked task list, and keep track of it has been successfully done.
# Technical details.
Go(lang) backend server which exposes an HTTP API which can be used to add tasks to the process.<br>
Python executor/runner which actually executes the commands, Python was chosen because of the availability of: [LibMeshCtrl Python Library](https://pypi.org/project/libmeshctrl/).<br>
Create a python virtual environment inside the `runner` folder.
# JSON Templates:
TokenBody:
```json
{
"tokenname": "NewUsableToken",
"authtoken": "admintokengoeshereofcourse",
"selectedtoken": "thetokenyouwanttoremove"
}
```
TaskBody:
```json
{
"name": "DeployTask",
"authtoken": "abc123securetoken",
"data": {
"command": "deploy-app",
"nodeids": "node1,node2,node3"
}
}
```

128
docs/README.md Normal file
View File

@@ -0,0 +1,128 @@
# GhostRunner
This project aims to create a way to schedule commands to be run as soon as possible when they offline initialy.<br>
The way to accomplish this is to create a tracked task list, and keep track of it has been successfully done.
# Technical details.
Go(lang) backend server which exposes an HTTP API which can be used to add tasks to the process.<br>
Python executor/runner which actually executes the commands, Python was chosen because of the availability of: [LibMeshCtrl Python Library](https://pypi.org/project/libmeshctrl/).<br>
Create a python virtual environment inside the `runner` folder.
# JSON Templates:
Following is mock data.
### `InfoResponse`
```json
{
"status": 200,
"message": "Request successful",
"data": {
"example": "This is some mock data"
}
}
```
---
### `TokenCreateDetails`
```json
{
"name": "DeploymentToken123"
}
```
---
### `TokenCreateBody`
```json
{
"authtoken": "abc123securetoken",
"details": {
"name": "DeploymentToken123"
}
}
```
---
### `TokenListBody`
```json
{
"authtoken": "abc123securetoken"
}
```
---
### `TaskData`
```json
{
"name": "UpdateScript",
"command": "sudo apt update && sudo apt upgrade -y",
"nodeids": ["node-001", "node-002", "node-003"],
"creation": "2025-05-27T10:15:30Z",
"status": "pending"
}
```
---
### `TaskBody`
```json
{
"authtoken": "abc123securetoken",
"details": {
"name": "UpdateScript",
"command": "sudo apt update && sudo apt upgrade -y",
"nodeids": ["node-001", "node-002", "node-003"],
"creation": "2025-05-27T10:15:30Z",
"status": "pending"
}
}
```
---
### `Device`
```json
{
"name": "RaspberryPi-01",
"nodeid": "node-001"
}
```
---
### `PyOnlineDevices`
```json
{
"online_devices": [
{
"name": "RaspberryPi-01",
"nodeid": "node-001"
},
{
"name": "Server-02",
"nodeid": "node-005"
}
],
"offline_devices": [
{
"name": "IoT-Gateway",
"nodeid": "node-003"
}
],
"total_devices": 3
}
```

1
docs/RUNNER.md Normal file
View File

@@ -0,0 +1 @@
# GhostRunner Python Runner module.

View File

@@ -2,6 +2,10 @@ import meshctrl
from json import dumps
class connect:
@staticmethod
async def quit(session: meshctrl.Session) -> None: # Function to use when quitting, but gracefully.
await session.close()
@staticmethod
async def connect(hostname: str, username: str, password: str) -> meshctrl.Session:
session = meshctrl.Session(
@@ -13,8 +17,21 @@ class connect:
return session
@staticmethod
async def list_online(session: meshctrl.Session,
mode: str = "online") -> dict: # Default is return online devices, but function can also return the offline devices if specified.
async def run(session: meshctrl.Session, command: str, nodeids: list[str]) -> None:
try:
response = await session.run_command(nodeids=nodeids,
command=command,
ignore_output=False,
timeout=900)
except Exception as error:
print("Run Command failed.", error)
return
for device in response:
print(dumps(response[device]["result"]))
@staticmethod
async def list_online(session: meshctrl.Session) -> dict: # Default is return online devices, but function can also return the offline devices if specified.
raw_device_list = await session.list_devices()

View File

@@ -1,6 +1,7 @@
#!/bin/python3
import argparse
from ast import literal_eval
import asyncio
from json import dumps
@@ -13,18 +14,25 @@ def cmd_flags() -> argparse.Namespace:
parser.add_argument("-lo", "--list-online", action='store_true', help="Specify if the program needs to list online devices.")
parser.add_argument("-rc", "--run", action='store_true', help="Make the program run a command.")
parser.add_argument("--command", type=str, help="Specify the actual command that is going to run.")
parser.add_argument("--nodeids", type=str, help="Specify which nodes the command is going to be run on.")
parser.add_argument('--nodeids', nargs='+', help='List of node IDs')
parser.add_argument("-i", "--indent", action='store_true', help="Specify whether the output needs to be indented.")
return parser.parse_args()
async def main():
async def prepare_command(command: str, nodeids: list[str]) -> list[str]: # Have some checks so it happens correctly.
if len(nodeids) < 1 or len(command) < 1:
print("No nodeids or command passed... quiting.")
return []
return nodeids
async def main() -> None:
args = cmd_flags()
credentials = utilities.load_config()
session = await connect.connect(credentials["hostname"],
credentials["username"],
credentials["password"])
credentials["username"],
credentials["password"])
if args.list_online:
online_devices = await connect.list_online(session)
@@ -32,12 +40,18 @@ async def main():
print(dumps(online_devices,indent=4))
else:
print(dumps(online_devices))
return await connect.quit(session) # Exit gracefully. Because python.
if args.run:
if not args.command or not args.nodeids:
return
print("When using run, also use --comand and --nodeids")
return await connect.quit(session) # Exit gracefully. Because python.
print(args.nodeids)
command = args.command
nodeids = args.nodeids
nodeids = await prepare_command(command, nodeids)
await connect.run(session, command, nodeids)
await session.close()

Binary file not shown.

View File

@@ -1,3 +1,3 @@
#!/bin/bash
./../runner/venv/bin/python3 ./../runner/runner.py
./../runner/venv/bin/python3 ./../runner/runner.py "$@"

View File

@@ -46,6 +46,7 @@ func ReadConf(configPath string) ConfigStruct {
section = inidata.Section(runnerSection)
// MeshCentral
config.MeshHostname = section.Key("hostname").String()
config.MeshUsername = section.Key("username").String()
config.MeshPassword = section.Key("password").String()