Initial commit

This commit is contained in:
ciomek committed 2026-01-25 10:23:02 +01:00
commit 55c3caa766
49 files changed
+1602

No files matched your search

+180
View File
@@ -0,0 +1,180 @@
.stfolder
logs
storage
# Created by https://www.toptal.com/developers/gitignore/api/python
# Edit at https://www.toptal.com/developers/gitignore?templates=python
### Python ###
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
*$py.class
# C extensions
*.so
# Distribution / packaging
.Python
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
share/python-wheels/
*.egg-info/
.installed.cfg
*.egg
MANIFEST
# PyInstaller
# Usually these files are written by a python script from a template
# before PyInstaller builds the exe, so as to inject date/other infos into it.
*.manifest
*.spec
# Installer logs
pip-log.txt
pip-delete-this-directory.txt
# Unit test / coverage reports
htmlcov/
.tox/
.nox/
.coverage
.coverage.*
.cache
nosetests.xml
coverage.xml
*.cover
*.py,cover
.hypothesis/
.pytest_cache/
cover/
# Translations
*.mo
*.pot
# Django stuff:
*.log
local_settings.py
db.sqlite3
db.sqlite3-journal
# Flask stuff:
instance/
.webassets-cache
# Scrapy stuff:
.scrapy
# Sphinx documentation
docs/_build/
# PyBuilder
.pybuilder/
target/
# Jupyter Notebook
.ipynb_checkpoints
# IPython
profile_default/
ipython_config.py
# pyenv
# For a library or package, you might want to ignore these files since the code is
# intended to run in multiple environments; otherwise, check them in:
# .python-version
# pipenv
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
# However, in case of collaboration, if having platform-specific dependencies or dependencies
# having no cross-platform support, pipenv may install dependencies that don't work, or not
# install all needed dependencies.
#Pipfile.lock
# poetry
# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
# This is especially recommended for binary packages to ensure reproducibility, and is more
# commonly ignored for libraries.
# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
#poetry.lock
# pdm
# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
#pdm.lock
# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
# in version control.
# https://pdm.fming.dev/#use-with-ide
.pdm.toml
# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
__pypackages__/
# Celery stuff
celerybeat-schedule
celerybeat.pid
# SageMath parsed files
*.sage.py
# Environments
.env
.venv
env/
venv/
ENV/
env.bak/
venv.bak/
# Spyder project settings
.spyderproject
.spyproject
# Rope project settings
.ropeproject
# mkdocs documentation
/site
# mypy
.mypy_cache/
.dmypy.json
dmypy.json
# Pyre type checker
.pyre/
# pytype static type analyzer
.pytype/
# Cython debug symbols
cython_debug/
# PyCharm
# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
# and can be added to the global gitignore or merged into this file. For a more nuclear
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
#.idea/
### Python Patch ###
# Poetry local configuration file - https://python-poetry.org/docs/configuration/#local-configuration
poetry.toml
# ruff
.ruff_cache/
# LSP config files
pyrightconfig.json
# End of https://www.toptal.com/developers/gitignore/api/python
+5
View File
@@ -0,0 +1,5 @@
# Default ignored files
/shelf/
/workspace.xml
# Editor-based HTTP Client requests
/httpRequests/
+11
View File
@@ -0,0 +1,11 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="PYTHON_MODULE" version="4">
<component name="NewModuleRootManager">
<content url="file://$MODULE_DIR$">
<sourceFolder url="file://$MODULE_DIR$" isTestSource="false" />
<excludeFolder url="file://$MODULE_DIR$/.venv" />
</content>
<orderEntry type="jdk" jdkName="Python 3.13 (colony)" jdkType="Python SDK" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
</module>
+6
View File
@@ -0,0 +1,6 @@
<component name="InspectionProjectProfileManager">
<settings>
<option name="USE_PROJECT_PROFILE" value="false" />
<version value="1.0" />
</settings>
</component>
+7
View File
@@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="Black">
<option name="sdkName" value="Python 3.13 (colony)" />
</component>
<component name="ProjectRootManager" version="2" project-jdk-name="Python 3.13 (colony)" project-jdk-type="Python SDK" />
</project>
+8
View File
@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/.idea/colony.iml" filepath="$PROJECT_DIR$/.idea/colony.iml" />
</modules>
</component>
</project>
@@ -0,0 +1,90 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ChangeListManager">
<list default="true" id="a5f8e4e7-da52-40b9-ac09-6eedaaa69b69" name="Changes" comment="" />
<option name="SHOW_DIALOG" value="false" />
<option name="HIGHLIGHT_CONFLICTS" value="true" />
<option name="HIGHLIGHT_NON_ACTIVE_CHANGELIST" value="false" />
<option name="LAST_RESOLUTION" value="IGNORE" />
</component>
<component name="FileTemplateManagerImpl">
<option name="RECENT_TEMPLATES">
<list>
<option value="Python Script" />
</list>
</option>
</component>
<component name="ProjectColorInfo">{
&quot;associatedIndex&quot;: 3
}</component>
<component name="ProjectId" id="37rDRQ5zG8SbPZaUPDbrYGJVv81" />
<component name="ProjectViewState">
<option name="hideEmptyMiddlePackages" value="true" />
<option name="showLibraryContents" value="true" />
</component>
<component name="PropertiesComponent"><![CDATA[{
"keyToString": {
"ModuleVcsDetector.initialDetectionPerformed": "true",
"Python.main.executor": "Debug",
"RunOnceActivity.ShowReadmeOnStart": "true",
"RunOnceActivity.TerminalTabsStorage.copyFrom.TerminalArrangementManager.252": "true",
"last_opened_file_path": "/home/ciomek/Repositories/colony"
}
}]]></component>
<component name="RecentsManager">
<key name="MoveFile.RECENT_KEYS">
<recent name="$PROJECT_DIR$/colony/core" />
<recent name="$PROJECT_DIR$/colony/core/storage" />
<recent name="$PROJECT_DIR$/storage" />
<recent name="$PROJECT_DIR$/src/domain" />
<recent name="$PROJECT_DIR$/src/core" />
</key>
</component>
<component name="RunManager">
<configuration name="main" type="PythonConfigurationType" factoryName="Python" temporary="true" nameIsGenerated="true">
<module name="colony" />
<option name="ENV_FILES" value="" />
<option name="INTERPRETER_OPTIONS" value="" />
<option name="PARENT_ENVS" value="true" />
<envs>
<env name="PYTHONUNBUFFERED" value="1" />
</envs>
<option name="SDK_HOME" value="" />
<option name="WORKING_DIRECTORY" value="$PROJECT_DIR$" />
<option name="IS_MODULE_SDK" value="true" />
<option name="ADD_CONTENT_ROOTS" value="true" />
<option name="ADD_SOURCE_ROOTS" value="true" />
<option name="RUN_TOOL" value="" />
<option name="SCRIPT_NAME" value="$PROJECT_DIR$/main.py" />
<option name="PARAMETERS" value="--role Explorer --listen --discover" />
<option name="SHOW_COMMAND_LINE" value="false" />
<option name="EMULATE_TERMINAL" value="false" />
<option name="MODULE_MODE" value="false" />
<option name="REDIRECT_INPUT" value="false" />
<option name="INPUT_FILE" value="" />
<method v="2" />
</configuration>
<recent_temporary>
<list>
<item itemvalue="Python.main" />
</list>
</recent_temporary>
</component>
<component name="SharedIndexes">
<attachedChunks>
<set>
<option value="bundled-python-sdk-f2b7a9f6281b-6e1f45a539f7-com.jetbrains.pycharm.pro.sharedIndexes.bundled-PY-253.29346.142" />
</set>
</attachedChunks>
</component>
<component name="TaskManager">
<task active="true" id="Default" summary="Default task">
<changelist id="a5f8e4e7-da52-40b9-ac09-6eedaaa69b69" name="Changes" comment="" />
<created>1767652287935</created>
<option name="number" value="Default" />
<option name="presentableId" value="Default" />
<updated>1767652287935</updated>
</task>
<servers />
</component>
</project>
@@ -0,0 +1,90 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ChangeListManager">
<list default="true" id="a5f8e4e7-da52-40b9-ac09-6eedaaa69b69" name="Changes" comment="" />
<option name="SHOW_DIALOG" value="false" />
<option name="HIGHLIGHT_CONFLICTS" value="true" />
<option name="HIGHLIGHT_NON_ACTIVE_CHANGELIST" value="false" />
<option name="LAST_RESOLUTION" value="IGNORE" />
</component>
<component name="FileTemplateManagerImpl">
<option name="RECENT_TEMPLATES">
<list>
<option value="Python Script" />
</list>
</option>
</component>
<component name="ProjectColorInfo">{
&quot;associatedIndex&quot;: 3
}</component>
<component name="ProjectId" id="37rDRQ5zG8SbPZaUPDbrYGJVv81" />
<component name="ProjectViewState">
<option name="hideEmptyMiddlePackages" value="true" />
<option name="showLibraryContents" value="true" />
</component>
<component name="PropertiesComponent"><![CDATA[{
"keyToString": {
"ModuleVcsDetector.initialDetectionPerformed": "true",
"Python.main.executor": "Debug",
"RunOnceActivity.ShowReadmeOnStart": "true",
"RunOnceActivity.TerminalTabsStorage.copyFrom.TerminalArrangementManager.252": "true",
"last_opened_file_path": "/home/ciomek/Repositories/colony"
}
}]]></component>
<component name="RecentsManager">
<key name="MoveFile.RECENT_KEYS">
<recent name="$PROJECT_DIR$/colony/core" />
<recent name="$PROJECT_DIR$/colony/core/storage" />
<recent name="$PROJECT_DIR$/storage" />
<recent name="$PROJECT_DIR$/src/domain" />
<recent name="$PROJECT_DIR$/src/core" />
</key>
</component>
<component name="RunManager">
<configuration name="main" type="PythonConfigurationType" factoryName="Python" temporary="true" nameIsGenerated="true">
<module name="colony" />
<option name="ENV_FILES" value="" />
<option name="INTERPRETER_OPTIONS" value="" />
<option name="PARENT_ENVS" value="true" />
<envs>
<env name="PYTHONUNBUFFERED" value="1" />
</envs>
<option name="SDK_HOME" value="" />
<option name="WORKING_DIRECTORY" value="$PROJECT_DIR$" />
<option name="IS_MODULE_SDK" value="true" />
<option name="ADD_CONTENT_ROOTS" value="true" />
<option name="ADD_SOURCE_ROOTS" value="true" />
<option name="RUN_TOOL" value="" />
<option name="SCRIPT_NAME" value="$PROJECT_DIR$/main.py" />
<option name="PARAMETERS" value="--role Explorer --listen --discover" />
<option name="SHOW_COMMAND_LINE" value="false" />
<option name="EMULATE_TERMINAL" value="false" />
<option name="MODULE_MODE" value="false" />
<option name="REDIRECT_INPUT" value="false" />
<option name="INPUT_FILE" value="" />
<method v="2" />
</configuration>
<recent_temporary>
<list>
<item itemvalue="Python.main" />
</list>
</recent_temporary>
</component>
<component name="SharedIndexes">
<attachedChunks>
<set>
<option value="bundled-python-sdk-f2b7a9f6281b-6e1f45a539f7-com.jetbrains.pycharm.pro.sharedIndexes.bundled-PY-253.29346.142" />
</set>
</attachedChunks>
</component>
<component name="TaskManager">
<task active="true" id="Default" summary="Default task">
<changelist id="a5f8e4e7-da52-40b9-ac09-6eedaaa69b69" name="Changes" comment="" />
<created>1767652287935</created>
<option name="number" value="Default" />
<option name="presentableId" value="Default" />
<updated>1767652287935</updated>
</task>
<servers />
</component>
</project>
+31
View File
@@ -0,0 +1,31 @@
-----BEGIN CERTIFICATE-----
MIIFazCCA1OgAwIBAgIUHQX+QFDRuraAYQngpD7Bc+JGrpwwDQYJKoZIhvcNAQEL
BQAwRTELMAkGA1UEBhMCUEwxEzARBgNVBAgMClNvbWUtU3RhdGUxITAfBgNVBAoM
GEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDAeFw0yNjAxMDUyMzA2NTVaFw0yNzAx
MDUyMzA2NTVaMEUxCzAJBgNVBAYTAlBMMRMwEQYDVQQIDApTb21lLVN0YXRlMSEw
HwYDVQQKDBhJbnRlcm5ldCBXaWRnaXRzIFB0eSBMdGQwggIiMA0GCSqGSIb3DQEB
AQUAA4ICDwAwggIKAoICAQDOhVAjaUf7KLly/+uE4Fh0iYD3ovmEiO/01VUpzK7C
2mYaMDQrf9woupxBZ/CAIfhtL7ixvVXxtzVOeLsg2Xe+S6Ns80oaY1rPRStG16C5
Fa0IWP4sArUJwhE+NlM6eGKz2Xr8RS6jpPa3YTmkG21JH79vqS+nyuRLJjbF7253
LTPdPdmoRQJF8WZ8pOsu0w2c5zqvBePozTohG6E/v6ci1CoBHgVpbQmqLlzQOVu4
IoJEx11M+15bJWAtewwhtwpl+oJxL6n853C6Gdl6TNypclHdbksy0tYs5HDYlwyb
YIwyegPosCrVlc/XZEvAkoZtSsWYaK9fEOwviDA4aoQ6wqrGUF0WSEESm0jghMJD
hsWaek3H9JNtgkYOxOa6Xss1sNxlc+po/NPmlrajKw0EgcWIJfWYexOhs6EvJLFp
dp7gyy12BT7uBm+vSKy5NU1o2+o33lt+UD/iycfNKZN389lcEFeXWHi8/ym9zcyo
UhLelLWYtVyamyh98EL6wSib6zHHmCWWCP91irDJW1mM4e6TxIAVwkySUZT4zQmV
iywG/eFqyaALRpwdzRsDgcTm/ppB1j3q/o4zeXcqp3HIE0ynidavt0vIv6CtLoLh
/U3Ciyq3P/icxoKEkhf/js3E5FDdG0crXJpEAGKaJTekyVxjjcm0U4n2bkuiDXg+
CQIDAQABo1MwUTAdBgNVHQ4EFgQU+oGAMQk9nBWdSwAngFGMbWiw/vEwHwYDVR0j
BBgwFoAU+oGAMQk9nBWdSwAngFGMbWiw/vEwDwYDVR0TAQH/BAUwAwEB/zANBgkq
hkiG9w0BAQsFAAOCAgEARXkQyGGhEZX2dKUpjZhlW0RtH6t5ragjOXSbcNqb+XD7
JSjsJvvVcSXFp6wU1kS3CKJ75SVzhMPdtDU9idg0DX/T9UAXkWb+rnQp3JieWrGz
1VO6UfRjYLSbzF5ZM8aSAHQtyihidqy8dGlrZdv0Wgfqakw/oF/FPWNDBLrEyWKw
KRo0Qa9isqnpDmxJcqhu8nmtAOdpHKhwBsCnpBPLEtLKEAlkkgAirbzULl4gm3yx
JuA8ckoyUOyBgR898SmxY2RlkouRKyD7WW/FX5NnZJBZVujgBY3/Q9R+vD1Gca0t
eu4autzbcDFZqLcTXf75zkqNpKWx6htAY8KNBTz5IczTVbTBap8Zsb/ziMBCkfNA
DS54WFo4zyYYF3K7zs+8idB/vysP2Kldsuxe/8CR0183jCQlFn2fkgSs1IhaKoOz
e6bHxAH89BT6E0E2C3gwQVgXm75v64PBnZCakHxP4ilmbDRZqEIIrE8FvDxi/dBT
zk3CmbXk9BZ0oVP51IOjVpqCgo6DhuewBg+HMe2ayypNaKmf0bWqj8607dzlpYjI
wdW8aXzMMME8XF5IOK14aJyNnd3iycDB2t7YPEPEb4LtAGYnnXHEwTPUqkzs9XjJ
A4PnziWystBqJKe51pOxy9HeL3VLBvNsPpmQvBwRHRCHsGohRcg6WS0px7trPaw=
-----END CERTIFICATE-----
+52
View File
@@ -0,0 +1,52 @@
-----BEGIN PRIVATE KEY-----
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
sYiPdVsPJw3i9f/r29R7w5RLUiCJ6Q==
-----END PRIVATE KEY-----
View File
Whitespace-only changes.
View File
Whitespace-only changes.
+19
View File
@@ -0,0 +1,19 @@
from colony.core.network.nodes import LocalNode
class RoleContext:
def __init__(self, network, role_manager):
self._network = network
self._role_manager = role_manager
def execute(self, command, *args, **kwargs):
return self._network.execute(command, *args, **kwargs)
def peers(self):
return self._network.get_all_nodes()
def self(self) -> LocalNode:
return self._network.get_self()
def set_self_node_roles(self, roles):
self._role_manager.set_roles(roles)
+34
View File
@@ -0,0 +1,34 @@
from dependency_injector.wiring import inject, Provide
from colony.core.logger.logging import get_logger
class Dispatcher:
def __init__(self, network, role_registry):
self.logger = get_logger(self.__class__.__name__)
self.network = network
self.role_registry = role_registry
def dispatch(self, payload):
self.logger.debug(f"Dispatching payload for command {payload['command']['role']}.{payload["command"]['name']}")
c = payload["command"]
role_name = c["role"]
command_name = c["name"]
args = tuple(c.get("args", []))
kwargs = dict(c.get("kwargs", {}))
role_entry = self.role_registry.get_role_class_by_name(role_name)
if role_entry is None:
raise ValueError(f"Unknown role {role_name}")
commands_enum = role_entry.COMMANDS
command = commands_enum(f"{role_name}.{command_name}")
node = self.network.get_self()
self.logger.info(f"Executing {role_name}.{command_name} on local node {node.id}")
result = node.execute_local(command, *args, **kwargs)
self.logger.debug(f"Passing the results from {role_name}.{command_name}")
return result
+5
View File
@@ -0,0 +1,5 @@
class NetworkException(Exception):
pass
class NoRoleException(Exception):
pass
View File
Whitespace-only changes.
+25
View File
@@ -0,0 +1,25 @@
import logging
class ColorFormatter(logging.Formatter):
COLORS = {
'DEBUG': '\033[37m',
'INFO': '\033[36m',
'WARNING': '\033[33m',
'ERROR': '\033[31m',
'CRITICAL': '\033[41m',
}
RESET = '\033[0m'
BOLD = '\033[1m'
def format(self, record):
color = self.COLORS.get(record.levelname, self.RESET)
message = super().format(record)
if record.levelname in ('WARNING', 'ERROR', 'CRITICAL'):
message = f"{self.BOLD}{color}{message}{self.RESET}"
else:
message = f"{color}{message}{self.RESET}"
return message
+42
View File
@@ -0,0 +1,42 @@
import logging
import threading
from pathlib import Path
from colony.core.logger.colors import ColorFormatter
LOG_DIR = Path("logs")
LOG_DIR.mkdir(exist_ok=True)
_loggers = {}
_logger_lock = threading.Lock()
FIXED_NAME_WIDTH = 15
def get_logger(name: str) -> logging.Logger:
with _logger_lock:
if name in _loggers:
return _loggers[name]
logger = logging.getLogger(name)
logger.setLevel(logging.DEBUG)
ch = logging.StreamHandler()
ch.setLevel(logging.DEBUG)
ch_formatter = ColorFormatter(
f'[%(asctime)s] %(levelname)-7s %(name)-{FIXED_NAME_WIDTH}s: %(message)s',
datefmt='%Y-%m-%d %H:%M:%S'
)
ch.setFormatter(ch_formatter)
logger.addHandler(ch)
fh = logging.FileHandler(LOG_DIR / f"{name}.log", encoding='utf-8')
fh.setLevel(logging.DEBUG)
fh_formatter = logging.Formatter(
f'[%(asctime)s] [%(threadName)s] %(levelname)-8s %(name)-{FIXED_NAME_WIDTH}s: %(message)s',
datefmt='%Y-%m-%d %H:%M:%S'
)
fh.setFormatter(fh_formatter)
logger.addHandler(fh)
logger.propagate = False
_loggers[name] = logger
return logger
View File
Whitespace-only changes.
+95
View File
@@ -0,0 +1,95 @@
import json
from enum import Enum
from typing import Optional
from colony.core.network.nodes import LocalNode, Node
from colony.core.network.requests import CommandRequest
from colony.core.logger.logging import get_logger
from colony.settings import configuration
from colony.utils.network import get_local_ip, get_hostname
class Network:
def __init__(self):
self.logger = get_logger(self.__class__.__name__)
self.nodes = []
self.self_node = LocalNode(
hostname=get_hostname(),
ip=get_local_ip(),
roles=[],
port=configuration.listener.port
)
self.logger.info(f"Self node created: {self.self_node.id}")
if not self.nodes:
self.nodes.append(self.self_node)
# @staticmethod
# def initialize():
# network = Network()
# network.logger.info("Network.initialize() called")
#
# network.self_node = LocalNode(
# hostname=get_hostname(),
# ip=get_local_ip(),
# roles=[],
# port=configuration.listener.port
# )
# network.logger.info(f"Self node created: {network.self_node.id}")
#
# if not network.nodes:
# network.nodes.append(network.self_node)
#
# return network
def get_self(self):
return self.self_node
def get_all_nodes(self):
return self.nodes
def get_node_of_role(self, role_name: str) -> Optional[Node]:
for node in self.nodes:
for role in node.roles:
if role == role_name:
return node
self.logger.warning(f"No node found with role: {role_name}")
return None
def execute(self, command: Enum, *args, **kwargs):
request = CommandRequest.form(command, *args, **kwargs)
self.logger.info(f"Executing command {request.id} on role {request.role}")
node = self.get_node_of_role(request.role)
if node is None:
raise ValueError(f"No node found with role {request.role}")
result = node.execute(command, *args, **kwargs)
self.logger.debug(f"Command {request.id} executed")
return result
def load_network_from_snapshot(self, file_path: str = "storage/colony_snapshot.json"):
self.logger.info(f"Loading network snapshot from {file_path}")
nodes = []
try:
with open(file_path, "r", encoding="utf-8") as f:
snapshot = json.load(f)
except FileNotFoundError:
self.logger.warning(f"Snapshot file {file_path} not found. Starting with empty network")
snapshot = {"nodes": []}
for node_data in snapshot.get("nodes", []):
node = Node(
hostname=node_data["hostname"],
ip=node_data["ip"],
roles=node_data.get("roles", []),
port=node_data["port"]
)
self.logger.info(f"Loaded node from snapshot: {node.id}")
nodes.append(node)
return nodes
+100
View File
@@ -0,0 +1,100 @@
import datetime
import platform
import json
import GPUtil
import psutil
class HardwareStatistics:
def __init__(self):
self.cpu = None
self.mem = None
self.gpu = None
self.disk = None
self.os_info = None
self.boot_time = None
@staticmethod
def collect():
self = HardwareStatistics()
self.cpu = self._get_cpu_stats()
self.mem = self._get_memory_stats()
self.gpu = self._get_gpu_stats()
self.disk = self._get_disk_stats()
self.os_info = self._get_os_info()
self.boot_time = datetime.datetime.fromtimestamp(
psutil.boot_time()
).isoformat()
return self
def to_dict(self):
return {
"cpu": self.cpu,
"memory": self.mem,
"gpu": self.gpu,
"disk": self.disk,
"os": self.os_info,
"boot_time": self.boot_time
}
def to_json(self):
return json.dumps(self.to_dict(), indent=2)
def _get_cpu_stats(self):
return {
"cores": psutil.cpu_count(logical=False),
"threads": psutil.cpu_count(logical=True),
"usage_percent": psutil.cpu_percent(interval=1)
}
def _get_memory_stats(self):
mem = psutil.virtual_memory()
return {
"total": mem.total,
"available": mem.available,
"used": mem.used,
"percent": mem.percent
}
def _get_gpu_stats(self):
gpus = GPUtil.getGPUs()
if not gpus:
return []
return [
{
"id": gpu.id,
"name": gpu.name,
"load": gpu.load * 100,
"memory_total": gpu.memoryTotal,
"memory_used": gpu.memoryUsed,
"memory_free": gpu.memoryFree,
"temperature": gpu.temperature
}
for gpu in gpus
]
def _get_disk_stats(self):
disk_info = []
for d in psutil.disk_partitions():
usage = psutil.disk_usage(d.mountpoint)
disk_info.append({
"device": d.device,
"mountpoint": d.mountpoint,
"fstype": d.fstype,
"total": usage.total,
"used": usage.used,
"free": usage.free,
"percent": usage.percent
})
return disk_info
def _get_os_info(self):
return {
"system": platform.system(),
"node": platform.node(),
"release": platform.release(),
"version": platform.version(),
"machine": platform.machine(),
"processor": platform.processor()
}
+64
View File
@@ -0,0 +1,64 @@
from enum import Enum
from typing import List
from colony.core.network.hardware import HardwareStatistics
from colony.core.network.requests import CommandRequest
from colony.transport.whisperer import Whisperer
class Node:
def __init__(self, hostname: str, ip: str, roles: List[str], port: int):
self.id = f"{hostname}@{ip}"
self.hostname = hostname
self.ip = ip
self.roles = roles
self.port = port
def execute(self, command: Enum, *args, **kwargs):
request = CommandRequest.form(command, *args, **kwargs)
return Whisperer(self.ip, self.port).send({
"command": {
"role": request.role,
"name": request.name,
"args": request.args,
"kwargs": request.kwargs
}
})
def to_dict(self):
return {
"id": self.id,
"hostname": self.hostname,
"ip": self.ip,
"port": self.port,
"roles": self.roles,
"hardware": HardwareStatistics.collect().to_dict()
}
class LocalNode(Node):
def __init__(self, hostname: str, ip: str, roles: List[str], port: int):
super().__init__(hostname, ip, roles, port)
self.__roles = dict()
def get_role_instances(self):
return self.__roles
def set_role_instances(self, roles):
for role_name, role_instance in roles.items():
self.__roles[role_name] = role_instance
def execute_local(self, command: Enum, *args, **kwargs):
request = CommandRequest.form(command, *args, **kwargs)
role = self.__roles.get(request.role)
if role is None:
raise ValueError(f"Role {request.role} not bound to local node")
return role.execute_local(
request.id,
*request.args,
**request.kwargs
)
+33
View File
@@ -0,0 +1,33 @@
import dataclasses
from enum import Enum
from typing import Any, Tuple, Dict, Optional
@dataclasses.dataclass(frozen=True)
class CommandRequest:
_id: Enum
args: Optional[Tuple] = None
kwargs: Optional[Dict[str, Any]] = None
@property
def id(self):
return self._id.value
@property
def name(self):
try:
return self.id.split('.')[1]
except IndexError:
return None
@property
def role(self):
try:
return self.id.split('.')[0]
except IndexError:
return None
@classmethod
def form(cls, id: Enum, *args: Any, **kwargs: Any):
return cls(id, args, kwargs)
+37
View File
@@ -0,0 +1,37 @@
import json
from enum import Enum
from pathlib import Path
from typing import Any
class StateManager:
class Data(Enum):
Roles = "roles"
STATE_FILE = Path("state.json")
def __init__(self):
self._data = {}
self._load()
def _load(self):
if self.STATE_FILE.exists():
with open(self.STATE_FILE, "r", encoding="utf-8") as f:
self._data = json.load(f)
else:
self._data = {}
self._save()
for key in self.Data:
self._data.setdefault(key.value, {})
def _save(self):
with open(self.STATE_FILE, "w", encoding="utf-8") as f:
json.dump(self._data, f, indent=4)
def get(self, key: Data) -> Any:
return self._data[key.value]
def set(self, key: Data, value: Any):
self._data[key.value] = value
self._save()
+60
View File
@@ -0,0 +1,60 @@
import os
import json
from enum import Enum
STORAGE_DIR = "storage"
class Storage:
def __init__(self, storage_dir):
self.storage_dir = storage_dir
os.makedirs(self.storage_dir, exist_ok=True)
def get_path(self, filename):
return os.path.join(self.storage_dir, filename)
def ls(self, subdir):
path = os.path.join(self.storage_dir, subdir)
if not os.path.exists(path):
return []
return os.listdir(path)
def save(self, filename, data):
path = self.get_path(filename)
with open(path, 'w', encoding='utf-8') as f:
json.dump(data, f, indent=2)
return path
def open(self, filename):
path = self.get_path(filename)
if not os.path.exists(path):
return None
with open(path, 'rb') as f:
return json.loads(f.read())
def exists(self, filename):
return os.path.exists(self.get_path(filename))
def delete(self, filename):
path = self.get_path(filename)
if os.path.exists(path):
os.remove(path)
return True
return False
class DefaultStorage(Storage):
def __init__(self):
super().__init__(STORAGE_DIR)
class DEFAULT_STORAGE_FILES(Enum):
COLONY_SNAPSHOT = 'colony_snapshot.json'
+50
View File
@@ -0,0 +1,50 @@
from dataclasses import dataclass
from enum import Enum
class RolePriority(Enum):
GENERAL = 0 # should be on each and every node
CRITICAL = 1 # network can't work properly without it
HIGH = 2 # network can manager without it but with some difficulty
LOW = 3 # it's just nice to have
class HardwareType(Enum):
DISK_SPACE = 1
CPU = 2
GPU = 3
RAM = 4
LONGEVITY = 5
class HardwarePriority:
hardware: HardwareType
priority: int
@dataclass
class HardwarePreferences:
priorities: dict[HardwareType, int]
def priority_of(self, hardware: HardwareType) -> int:
return self.priorities.get(hardware, 0)
class RoleStats:
def __init__(self, cls, priority: RolePriority, hardware_preferences: HardwarePreferences = None):
self.name = cls.__name__
self.cls = cls
self.priority = priority
self.hardware_preferences = hardware_preferences
class RoleRegistry:
def __init__(self, role_list):
roles = {}
for role_stat in role_list:
roles[role_stat.name] = role_stat
self.roles = roles
def get_role_class_by_name(self, name):
return self.roles.get(name).cls
View File
Whitespace-only changes.
View File
Whitespace-only changes.
+82
View File
@@ -0,0 +1,82 @@
import datetime
from enum import Enum
from colony.core.storage import DEFAULT_STORAGE_FILES
from colony.roles.role import Role
from colony.roles.unit.role import Unit
from colony.roles.vaultwarden.role import VaultWarden
from colony.transport.discover import Discover
from colony.transport.whisperer import Whisperer
class Explorer(Role):
CONTINUOUS = True
LOOP_SECONDS_WAIT = 60
class COMMANDS(Enum):
pass
def __init__(self, context):
commands = {
}
super().__init__(commands, context)
def loop(self):
self.logger.info("Getting network snapshot")
snapshot = self.get_snapshot()['result']
self.logger.debug(f"Loaded snapshot")
discovered_peers = self.discover_peers()
self.logger.info(f"Discovered peers: {list(discovered_peers.keys())}")
for ip, port in discovered_peers.items():
self.logger.info(f"Sending SHOW_STATS command to {ip}:{port}")
stats = self.fetch_stats_from_peer(ip, port)
self.logger.debug(f"Received stats from {ip}")
snapshot[ip] = stats | {'last_update': datetime.datetime.now().isoformat(), 'last_seen': datetime.datetime.now().isoformat(), 'status': 'reachable'}
missing_ips = set(snapshot.keys()) - set(discovered_peers.keys())
for ip in missing_ips:
self.logger.warning(f"Peer {ip} was in snapshot but not reachable. Marking as unreachable")
snapshot[ip]['last_update'] = datetime.datetime.now().isoformat()
snapshot[ip]['status'] = 'unreachable'
self.set_snapshot(snapshot)
self.logger.info("Snapshot updated successfully")
def get_snapshot(self):
return self.context.execute(VaultWarden.COMMANDS.OPEN, DEFAULT_STORAGE_FILES.COLONY_SNAPSHOT.value)
def set_snapshot(self, snapshot):
return self.context.execute(VaultWarden.COMMANDS.OVERWRITE, DEFAULT_STORAGE_FILES.COLONY_SNAPSHOT.value, snapshot)
def discover_peers(self):
peers = {}
# for ip in get_local_subnet().hosts():
for ip in ['192.168.65.101', '192.168.65.102']:
try:
port = Discover.request(str(ip))
peers[str(ip)] = port
except (TimeoutError, ConnectionRefusedError):
continue
return peers
def fetch_stats_from_peer(self, ip, port):
command = Unit.COMMANDS.SHOW_STATS
role_name, command_name = command.value.split('.')
request = {
"command": {
"role": role_name,
"name": command_name,
"args": [],
"kwargs": {}
}
}
return Whisperer(ip, port).send(request).get('result', {})
View File
Whitespace-only changes.
+21
View File
@@ -0,0 +1,21 @@
from enum import Enum
from colony.roles.role import Role, Command
from colony.roles.unit.role import Unit
class Foreman(Role):
CONTINUOUS = True
LOOP_SECONDS_WAIT = 60
class COMMANDS(Enum):
pass
def __init__(self, context):
commands = {
}
super().__init__(commands, context)
def loop(self):
self.context.self().execute(Unit.COMMANDS.SET_ROLES, ['Unit', 'Foreman', 'Explorer', 'VaultWarden'])
+73
View File
@@ -0,0 +1,73 @@
import threading
from dependency_injector.wiring import inject, Container, Provide
from colony.core.context import RoleContext
from colony.core.state_manager import StateManager
from colony.core.logger.logging import get_logger
class RoleManager:
def __init__(self, network, role_registry):
self.network = network
self.role_registry = role_registry
self.logger = get_logger(self.__class__.__name__)
def initialize_roles(self):
self.logger.info("Initializing roles...")
context = RoleContext(self.network, self)
roles = {}
for role_name in StateManager().get(StateManager.Data.Roles):
role_cls = self.role_registry.get_role_class_by_name(role_name)
self.logger.debug(f"Creating instance of role {role_name}. ")
role_instance = role_cls(context=context)
roles[role_name] = role_instance
self.network.self_node.roles = [role for role in roles.keys()]
self.network.self_node.set_role_instances(roles)
self.logger.info(f"Roles initialized: {list(roles.keys())}. ")
def get_role_instances(self):
return self.network.self_node.get_role_instances()
def start_all(self):
for role_name, role in self.get_role_instances().items():
self.logger.info(f"Starting role {role_name}")
t = threading.Thread(target=role.start, daemon=True, name=role_name)
t.start()
self.logger.debug(f"Thread started for role {role_name}: {t.name}")
def set_roles(self, new_roles):
self.logger.info("Updating roles...")
current_roles = self.get_role_instances()
roles_to_remove = set(current_roles) - set(new_roles)
roles_to_add = set(new_roles) - set(current_roles)
for role_name in roles_to_remove:
self.logger.debug(f"Stopping and removing role {role_name}")
current_roles[role_name].stop()
del current_roles[role_name]
context = RoleContext(self.network, self)
for role_name in roles_to_add:
self.logger.debug(f"Creating and adding new role {role_name}")
role_cls = available_roles[role_name]
current_roles[role_name] = role_cls(context=context)
StateManager().set(StateManager.Data.Roles, list(current_roles.keys()))
self.network.self_node.roles = list(current_roles.keys())
self.network.self_node.set_role_instances(current_roles)
for role_name in roles_to_add:
role = current_roles[role_name]
self.logger.info(f"Starting role {role_name}")
t = threading.Thread(target=role.start, daemon=True, name=role_name)
t.start()
self.logger.debug(f"Thread started for role {role_name}: {t.name}")
self.logger.info(f"Roles updated to: {list(current_roles.keys())}")
+58
View File
@@ -0,0 +1,58 @@
import threading
from colony.core.context import RoleContext
from colony.core.logger.logging import get_logger
class Command:
def __init__(self, callback):
self.callback = callback
class Role:
CONTINUOUS = None
LOOP_SECONDS_WAIT = None
def __init__(self, commands, context: RoleContext):
self.commands = commands
self.context = context
self._stop_event = threading.Event()
self._thread = None
if self.CONTINUOUS and self.LOOP_SECONDS_WAIT is None:
raise NotImplementedError("You made this role to run continuously but have not set seconds to wait. Please set constant variable LOOP_SECONDS_WAIT. ")
self.logger = get_logger(self.__class__.__name__)
self.logger.info(f'{self.__class__.__name__} initialized and ready')
def __str__(self):
return self.__class__.__name__
def start(self):
if not self.CONTINUOUS:
return
self._thread = threading.Thread(target=self._run_loop, daemon=True, name=str(self))
self._thread.start()
def _run_loop(self):
while not self._stop_event.is_set():
self.loop()
self._stop_event.wait(self.LOOP_SECONDS_WAIT)
def loop(self):
if self.CONTINUOUS:
raise NotImplementedError("Continuous roles must implement loop()")
def stop(self):
self._stop_event.set()
def execute_local(self, name, *args, **kwargs):
command = self.commands.get(name)
if not command:
raise ValueError(f"No command {name}")
return command.callback(*args, **kwargs)
View File
Whitespace-only changes.
+29
View File
@@ -0,0 +1,29 @@
from enum import Enum
from colony.core.network.hardware import HardwareStatistics
from colony.roles.role import Role, Command
class Unit(Role):
class COMMANDS(Enum):
SHOW_STATS = 'Unit.ShowStats'
SHOW_HARDWARE = 'Unit.ShowHardware'
SET_ROLES = 'Unit.SetRoles'
def __init__(self, context):
commands = {
self.COMMANDS.SHOW_STATS.value: Command(self.show_stats),
self.COMMANDS.SHOW_HARDWARE.value: Command(self.show_hardware),
self.COMMANDS.SET_ROLES.value: Command(self.set_roles),
}
super().__init__(commands, context)
def show_hardware(self):
return HardwareStatistics.collect().to_dict()
def show_stats(self):
self_node = self.context.self()
return self_node.to_dict()
def set_roles(self, roles):
self.context.set_self_node_roles(roles)
Whitespace-only changes.
+31
View File
@@ -0,0 +1,31 @@
from enum import Enum
from colony.core.storage import DefaultStorage
from colony.roles.role import Role, Command
class VaultWarden(Role):
class COMMANDS(Enum):
LS = 'VaultWarden.ls'
OPEN = 'VaultWarden.open'
OVERWRITE = 'VaultWarden.overwrite'
def __init__(self, context):
commands = {
self.COMMANDS.LS.value: Command(self.ls),
self.COMMANDS.OPEN.value: Command(self.open),
self.COMMANDS.OVERWRITE.value: Command(self.overwrite)
}
self.storage = DefaultStorage()
super().__init__(commands, context)
def ls(self, dir):
return self.storage.ls(dir)
def open(self, file):
return self.storage.open(file)
def overwrite(self, filename, content):
return self.storage.save(filename, content)
+7
View File
@@ -0,0 +1,7 @@
from dynaconf import Dynaconf
configuration = Dynaconf(
envvar_prefix="COLONY",
settings_files=['config.json'],
load_dotenv=False,
)
View File
Whitespace-only changes.
+32
View File
@@ -0,0 +1,32 @@
import socket
import struct
class Discover:
DISCOVER_PORT = 28149
MAGIC = b"OK"
@staticmethod
def listen(real_port, host="0.0.0.0"):
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
s.bind((host, Discover.DISCOVER_PORT))
s.listen()
while True:
conn, _ = s.accept()
try:
data = conn.recv(2)
if data == Discover.MAGIC:
conn.sendall(struct.pack("!H", real_port))
finally:
conn.close()
@staticmethod
def request(target_host, timeout=3):
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.settimeout(timeout)
s.connect((target_host, Discover.DISCOVER_PORT))
s.sendall(Discover.MAGIC)
data = s.recv(2)
s.close()
return struct.unpack("!H", data)[0]
+64
View File
@@ -0,0 +1,64 @@
import json
import socket
import ssl
import threading
from asyncio import Queue
from colony.settings import configuration
class Listener:
def __init__(self, dispatcher, host=None, port=None, cert_file=None, keyfile=None):
self.dispatcher = dispatcher
self.host = host or getattr(configuration.listener, "host", "0.0.0.0")
self.port = port or getattr(configuration.listener, "port", 9000)
self.cert_file = cert_file or getattr(configuration.listener, "cert_file", "certs/server.crt")
self.keyfile = keyfile or getattr(configuration.listener, "keyfile", "certs/server.key")
self.queue = Queue()
self.running = False
def handle_client(self, conn):
context = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER)
context.load_cert_chain(certfile=self.cert_file, keyfile=self.keyfile)
tls_conn = context.wrap_socket(conn, server_side=True)
try:
data = tls_conn.recv(65536)
payload = json.loads(data.decode())
result = self.dispatcher.dispatch(payload)
response = json.dumps({
"status": "ok",
"result": result
}).encode()
tls_conn.sendall(response)
except Exception as e:
error = json.dumps({
"status": "error",
"error": str(e)
}).encode()
tls_conn.sendall(error)
raise
finally:
tls_conn.close()
def start(self):
self.running = True
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
sock.bind((self.host, self.port))
sock.listen(5)
while self.running:
try:
conn, addr = sock.accept()
t = threading.Thread(target=self.handle_client, args=(conn,), name='HandleClient')
t.daemon = True
t.start()
except KeyboardInterrupt:
break
sock.close()
+38
View File
@@ -0,0 +1,38 @@
import json
import socket
import ssl
from colony.settings import configuration
class Whisperer:
def __init__(self, host=None, port=None, cert_file=None, keyfile=None):
self.host = host or getattr(configuration.whisperer, "host", "127.0.0.1")
self.port = port or getattr(configuration.whisperer, "port", 9000)
self.cert_file = cert_file or getattr(configuration.whisperer, "cert_file", None)
self.keyfile = keyfile or getattr(configuration.whisperer, "keyfile", None)
def send(self, payload, timeout=500):
data = json.dumps(payload).encode()
context = ssl.create_default_context()
context.check_hostname = False
context.verify_mode = ssl.CERT_NONE
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.settimeout(timeout)
tls_sock = context.wrap_socket(sock)
try:
tls_sock.connect((self.host, self.port))
tls_sock.sendall(data)
response = tls_sock.recv(65536)
if not response:
raise RuntimeError("Connection closed without response. ")
return json.loads(response.decode())
except socket.timeout:
raise TimeoutError("No response before timeout. ")
finally:
tls_sock.close()
View File
Whitespace-only changes.
+17
View File
@@ -0,0 +1,17 @@
import ipaddress
import socket
def get_local_subnet():
hostname = socket.gethostname()
local_ip = socket.gethostbyname(hostname)
subnet = ipaddress.ip_network(f"{local_ip}/24", strict=False)
return subnet
def get_local_ip():
return socket.gethostbyname(socket.gethostname())
def get_hostname():
return socket.gethostname()
+12
View File
@@ -0,0 +1,12 @@
{
"whisperer": {
"host": "0.0.0.0",
"port": 65334
},
"listener": {
"host": "0.0.0.0",
"port": 65334,
"cert_file": "certs/server.crt",
"keyfile": "certs/server.key"
}
}
+51
View File
@@ -0,0 +1,51 @@
import threading
from colony.core.network.core import Network
from colony.core.dispatcher import Dispatcher
from colony.core.state_manager import StateManager
from colony.core.storage import DefaultStorage
from colony.role_registry import RoleRegistry
from colony.roles.manager import RoleManager
from colony.transport.discover import Discover
from colony.transport.listener import Listener
from role_settings import available_roles
from colony.settings import configuration
def main():
storage = DefaultStorage()
state_manager = StateManager()
network = Network()
role_registry = RoleRegistry(role_list=available_roles)
dispatcher = Dispatcher(network=network, role_registry=role_registry)
role_manager = RoleManager(network=network, role_registry=role_registry)
threads = []
listener = Listener(dispatcher=dispatcher)
t = threading.Thread(
target=listener.start,
daemon=True,
name="Listener"
)
t.start()
threads.append(t)
t = threading.Thread(
target=Discover.listen,
args=(configuration.listener.port,),
daemon=True,
name="Discover"
)
t.start()
threads.append(t)
role_manager.initialize_roles()
role_manager.start_all()
for t in threads:
t.join()
if __name__ == "__main__":
main()
+5
View File
@@ -0,0 +1,5 @@
dependency-injector==4.48.3
dynaconf==3.2.12
GPUtil==1.4.0
psutil==7.2.1
setuptools==80.9.0
+30
View File
@@ -0,0 +1,30 @@
from colony.role_registry import RoleStats, HardwarePreferences, RolePriority, HardwareType
from colony.roles.explorer.role import Explorer
from colony.roles.foreman.role import Foreman
from colony.roles.unit.role import Unit
from colony.roles.vaultwarden.role import VaultWarden
available_roles = [
RoleStats(
cls=Unit,
priority=RolePriority.GENERAL,
),
RoleStats(
cls=Foreman,
priority=RolePriority.CRITICAL,
),
RoleStats(
cls=VaultWarden,
priority=RolePriority.CRITICAL,
hardware_preferences=HardwarePreferences(
{
HardwareType.DISK_SPACE: 1,
HardwareType.LONGEVITY: 3,
}
)
),
RoleStats(
cls=Explorer,
priority=RolePriority.HIGH,
)
]
+8
View File
@@ -0,0 +1,8 @@
{
"roles": [
"Unit",
"Foreman",
"Explorer",
"VaultWarden"
]
}