diff --git a/README.md b/README.md
deleted file mode 100644
index 8cdff24..0000000
--- a/README.md
+++ /dev/null
@@ -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.
-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.
-Python executor/runner which actually executes the commands, Python was chosen because of the availability of: [LibMeshCtrl Python Library](https://pypi.org/project/libmeshctrl/).
-
-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"
- }
-}
-```
\ No newline at end of file
diff --git a/docs/README.md b/docs/README.md
new file mode 100644
index 0000000..11beb19
--- /dev/null
+++ b/docs/README.md
@@ -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.
+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.
+Python executor/runner which actually executes the commands, Python was chosen because of the availability of: [LibMeshCtrl Python Library](https://pypi.org/project/libmeshctrl/).
+
+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
+}
+```
\ No newline at end of file
diff --git a/docs/RUNNER.md b/docs/RUNNER.md
new file mode 100644
index 0000000..098e908
--- /dev/null
+++ b/docs/RUNNER.md
@@ -0,0 +1 @@
+# GhostRunner Python Runner module.
\ No newline at end of file
diff --git a/server/SERVER.md b/docs/SERVER.md
similarity index 100%
rename from server/SERVER.md
rename to docs/SERVER.md
diff --git a/runner/modules/connect.py b/runner/modules/connect.py
index 22fcf52..d6c37b4 100644
--- a/runner/modules/connect.py
+++ b/runner/modules/connect.py
@@ -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()
diff --git a/runner/runner.py b/runner/runner.py
index 9039e50..ef441e3 100644
--- a/runner/runner.py
+++ b/runner/runner.py
@@ -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(args.nodeids)
+ print("When using run, also use --comand and --nodeids")
+ return await connect.quit(session) # Exit gracefully. Because python.
+
+ command = args.command
+ nodeids = args.nodeids
+ nodeids = await prepare_command(command, nodeids)
+
+ await connect.run(session, command, nodeids)
await session.close()
diff --git a/server/ghostrunner-server b/server/ghostrunner-server
index c035592..8bdd4de 100755
Binary files a/server/ghostrunner-server and b/server/ghostrunner-server differ
diff --git a/server/python.sh b/server/python.sh
index 414809c..9c3c88b 100755
--- a/server/python.sh
+++ b/server/python.sh
@@ -1,3 +1,3 @@
#!/bin/bash
-./../runner/venv/bin/python3 ./../runner/runner.py
+./../runner/venv/bin/python3 ./../runner/runner.py "$@"
\ No newline at end of file
diff --git a/server/src/modules/utilities/confread.go b/server/src/modules/utilities/confread.go
index 0c30eea..8d43dbc 100644
--- a/server/src/modules/utilities/confread.go
+++ b/server/src/modules/utilities/confread.go
@@ -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()