diff --git a/.build/compile.sh b/.build/compile.sh new file mode 100644 index 00000000..85a06495 --- /dev/null +++ b/.build/compile.sh @@ -0,0 +1,19 @@ +#!/bin/bash +set -e + +# Create a Python virtual environment +python -m venv compile_env + +# Activate the virtual environment +source compile_env/bin/activate + +# Install the current package and pyinstaller +pip install .. pyinstaller + +# Run PyInstaller with the specified options +pyinstaller ../main.py --onedir --name coretex --copy-metadata readchar --copy-metadata coretex --add-data "../coretex/resources/_coretex.py:coretex/resources" --workpath coretex --noconfirm + +# Deactivate and remove the virtual environment +deactivate +rm -rf compile_env +rm -rf coretex.spec diff --git a/.build/coretex.pkg b/.build/coretex.pkg new file mode 100644 index 00000000..ae4397ef Binary files /dev/null and b/.build/coretex.pkg differ diff --git a/.build/deb-package.sh b/.build/deb-package.sh new file mode 100644 index 00000000..a2d0945d --- /dev/null +++ b/.build/deb-package.sh @@ -0,0 +1,38 @@ +#!/bin/bash +set -e + +# Define package name and directory structure +PACKAGE_NAME="coretex" +PACKAGE_DIR="${PACKAGE_NAME}" +DEBIAN_DIR="${PACKAGE_DIR}/DEBIAN" +BIN_DIR="${PACKAGE_DIR}/usr/local/bin" + +# Create the necessary directory structure +mkdir -p "${DEBIAN_DIR}" +mkdir -p "${BIN_DIR}" + +# Create a Python virtual environment +python -m venv compile_env + +# Activate the virtual environment +source compile_env/bin/activate + +# Install the toml package (for generate_control.py) +pip install toml + +# Move the entire dist/main directory contents directly to /usr/local/bin +mv dist/coretex/* "${BIN_DIR}/" +rm -rf dist/ + +# Generate the control file using Python (assuming you have the Python script ready) +python generate_control.py + +# Deactivate and remove the virtual environment +deactivate +rm -rf compile_env + +# Build the .deb package +dpkg-deb --build "${PACKAGE_DIR}" + +# Remove unnecessary dir's +rm -rf "${PACKAGE_DIR}" \ No newline at end of file diff --git a/.build/generate_control.py b/.build/generate_control.py new file mode 100644 index 00000000..5bc19e6f --- /dev/null +++ b/.build/generate_control.py @@ -0,0 +1,32 @@ +import toml + + +def main() -> None: + # Load data from pyproject.toml + with open("../pyproject.toml", "r") as f: + pyproject = toml.load(f) + + # Extract necessary information + packageName = pyproject["project"]["name"] + version = pyproject["project"]["version"] + description = pyproject["project"]["description"] + + # Create control file content + controlContent = ( + f"Package: {packageName}\n" + f"Version: {version}\n" + "Section: base\n" + "Priority: optional\n" + "Architecture: all\n" + "Depends: python3\n" + "Maintainer: Your Name < your.email@example.com >\n" + f"Description: {description}\n" + ) + + # Write to control file + with open(f"{packageName}/DEBIAN/control", "w") as controlFile: + controlFile.write(controlContent) + + +if __name__ == "__main__": + main() diff --git a/.build/macos-package.sh b/.build/macos-package.sh new file mode 100644 index 00000000..9df4c5d4 --- /dev/null +++ b/.build/macos-package.sh @@ -0,0 +1,16 @@ +#!/bin/bash +set -e + +# Extract version from pyproject.toml +VERSION=$(grep '^version = ' ../pyproject.toml | sed 's/version = "\(.*\)"/\1/') + +# Define package name and directory structure +PACKAGE_NAME="coretex" +PAYLOAD_DIR="dist/coretex/" + +# Build the .pkg package using pkgbuild +pkgbuild --root "${PAYLOAD_DIR}" --identifier ai.coretex --version "${VERSION}" --install-location "/usr/local/bin" "${PACKAGE_NAME}.pkg" + +# Clean up +rm -rf coretex/ +rm -rf dist/ \ No newline at end of file diff --git a/.build/windows-package.sh b/.build/windows-package.sh new file mode 100644 index 00000000..e69de29b diff --git a/coretex/cli/commands/node.py b/coretex/cli/commands/node.py index 98dfd63b..e05bc45a 100644 --- a/coretex/cli/commands/node.py +++ b/coretex/cli/commands/node.py @@ -24,8 +24,8 @@ from ..modules import node as node_module from ..modules.node import NodeStatus, getNodeStatus from ..modules.user import initializeUserSession -from ..modules.utils import onBeforeCommandExecute, checkEnvironment -from ..modules.update import activateAutoUpdate +from ..modules.update import activateAutoUpdate, getNodeStatus +from ..modules.utils import onBeforeCommandExecute from ...utils import docker from ...configuration import NodeConfiguration, InvalidConfiguration, ConfigurationNotFound @@ -208,8 +208,12 @@ def logs(tail: Optional[int], follow: bool, timestamps: bool) -> None: @click.group() @onBeforeCommandExecute(docker.isDockerAvailable, excludeSubcommands = ["status"]) @onBeforeCommandExecute(initializeUserSession) +<<<<<<< HEAD +@onBeforeCommandExecute(node_module.checkResourceLimitations) +======= @onBeforeCommandExecute(node_module.checkResourceLimitations, excludeSubcommands = ["status"]) @onBeforeCommandExecute(checkEnvironment) +>>>>>>> develop def node() -> None: pass diff --git a/coretex/cli/modules/cron.py b/coretex/cli/modules/cron.py index e504f335..48fb03c0 100644 --- a/coretex/cli/modules/cron.py +++ b/coretex/cli/modules/cron.py @@ -36,9 +36,9 @@ def jobExists(script: str) -> bool: return any(script in line for line in existingLines) -def scheduleJob(scriptName: str) -> None: +def scheduleJob(commandName: str) -> None: existingLines = getExisting() - cronJob = f"*/30 * * * * {CONFIG_DIR / scriptName} >> {CONFIG_DIR}/logs/ctx_autoupdate.log 2>&1\n" + cronJob = f"*/30 * * * * {commandName} >> {CONFIG_DIR}/logs/ctx_autoupdate.log 2>&1\n" existingLines.append(cronJob) tempCronFilePath = CONFIG_DIR / "temp.cron" diff --git a/coretex/cli/modules/node.py b/coretex/cli/modules/node.py index 80696556..ec473892 100644 --- a/coretex/cli/modules/node.py +++ b/coretex/cli/modules/node.py @@ -302,7 +302,7 @@ def promptRam(ramLimit: int) -> int: if nodeRam < config_defaults.MINIMUM_RAM: ui.errorEcho( f"ERROR: Configured RAM ({nodeRam}GB) is lower than " - "the minimum Node RAM requirement ({config_defaults.MINIMUM_RAM}GB)." + f"the minimum Node RAM requirement ({config_defaults.MINIMUM_RAM}GB)." ) return promptRam(ramLimit) diff --git a/coretex/cli/modules/update.py b/coretex/cli/modules/update.py index 1250ad84..11330174 100644 --- a/coretex/cli/modules/update.py +++ b/coretex/cli/modules/update.py @@ -16,15 +16,10 @@ # along with this program. If not, see . from enum import IntEnum -from pathlib import Path import requests -from .utils import getExecPath from .cron import jobExists, scheduleJob -from ..resources import RESOURCES_DIR -from ...utils import command -from ...configuration import DEFAULT_VENV_PATH UPDATE_SCRIPT_NAME = "update_node.sh" @@ -39,31 +34,17 @@ class NodeStatus(IntEnum): reconnecting = 5 -def generateUpdateScript() -> str: - dockerExecPath = getExecPath("docker") - gitExecPath = getExecPath("git") - bashScriptTemplatePath = RESOURCES_DIR / "update_script_template.sh" - - with bashScriptTemplatePath.open("r") as scriptFile: - bashScriptTemplate = scriptFile.read() - - return bashScriptTemplate.format( - dockerPath = dockerExecPath, - gitPath = gitExecPath, - venvPath = DEFAULT_VENV_PATH - ) - - -def dumpScript(updateScriptPath: Path) -> None: - with updateScriptPath.open("w") as scriptFile: - scriptFile.write(generateUpdateScript()) - - command(["chmod", "+x", str(updateScriptPath)], ignoreStdout = True) +def activateAutoUpdate() -> None: + commandName = "coretex node update -n" + if not jobExists(str(commandName)): + scheduleJob(commandName) -def activateAutoUpdate() -> None: - updateScriptPath = DEFAULT_VENV_PATH.parent / UPDATE_SCRIPT_NAME - dumpScript(updateScriptPath) - if not jobExists(str(updateScriptPath)): - scheduleJob(UPDATE_SCRIPT_NAME) +def getNodeStatus() -> NodeStatus: + try: + response = requests.get(f"http://localhost:21000/status", timeout = 1) + status = response.json()["status"] + return NodeStatus(status) + except: + return NodeStatus.inactive diff --git a/coretex/cli/modules/utils.py b/coretex/cli/modules/utils.py index ddf96258..52b99f8d 100644 --- a/coretex/cli/modules/utils.py +++ b/coretex/cli/modules/utils.py @@ -16,15 +16,10 @@ # along with this program. If not, see . from typing import List, Any, Tuple, Optional, Callable -from pathlib import Path from functools import wraps from importlib.metadata import version as getLibraryVersion -import sys -import venv -import shutil import logging -import platform from py3nvml import py3nvml @@ -32,7 +27,6 @@ import requests from . import ui -from ...configuration import DEFAULT_VENV_PATH from ...utils.process import command @@ -40,51 +34,6 @@ def formatCliVersion(version: Tuple[int, int, int]) -> str: return ".".join(map(str, version)) -def fetchCtxSource() -> Optional[str]: - _, output, _ = command([sys.executable, "-m", "pip", "freeze"], ignoreStdout = True, ignoreStderr = True) - packages = output.splitlines() - - for package in packages: - if "coretex" in package: - return package.replace(" ", "") - - return None - - -def createEnvironment(venvPython: Path) -> None: - if DEFAULT_VENV_PATH.exists(): - shutil.rmtree(DEFAULT_VENV_PATH) - - venv.create(DEFAULT_VENV_PATH, with_pip = True) - - if platform.system() == "Windows": - venvPython = DEFAULT_VENV_PATH / "Scripts" / "python.exe" - - ctxSource = fetchCtxSource() - if ctxSource is not None: - command([str(venvPython), "-m", "pip", "install", ctxSource], ignoreStdout = True, ignoreStderr = True) - - -def checkEnvironment() -> None: - venvPython = DEFAULT_VENV_PATH / "bin" / "python" - venvActivate = DEFAULT_VENV_PATH / "bin" / "activate" - venvCoretex = DEFAULT_VENV_PATH / "bin" / "coretex" - - if not venvActivate.exists() or not venvPython.exists(): - createEnvironment(venvPython) - return - - try: - command([str(venvCoretex), "version"], check = True, ignoreStderr = True, ignoreStdout = True) - except Exception: - createEnvironment(venvPython) - return - - -def updateLib() -> None: - command([sys.executable, "-m", "pip", "install", "--no-cache-dir", "--upgrade", "coretex"], ignoreStdout = True, ignoreStderr = True) - - def parseLibraryVersion(version: str) -> Optional[Tuple[int, int, int]]: parts = version.split(".") diff --git a/coretex/cli/resources/__init__.py b/coretex/cli/resources/__init__.py deleted file mode 100644 index 7bf608d8..00000000 --- a/coretex/cli/resources/__init__.py +++ /dev/null @@ -1,18 +0,0 @@ -# Copyright (C) 2023 Coretex LLC - -# This file is part of Coretex.ai - -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Affero General Public License as -# published by the Free Software Foundation, either version 3 of the -# License, or (at your option) any later version. - -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Affero General Public License for more details. - -# You should have received a copy of the GNU Affero General Public License -# along with this program. If not, see . - -from .resources import RESOURCES_DIR diff --git a/coretex/cli/resources/resources.py b/coretex/cli/resources/resources.py deleted file mode 100644 index a5101f6e..00000000 --- a/coretex/cli/resources/resources.py +++ /dev/null @@ -1,21 +0,0 @@ -# Copyright (C) 2023 Coretex LLC - -# This file is part of Coretex.ai - -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Affero General Public License as -# published by the Free Software Foundation, either version 3 of the -# License, or (at your option) any later version. - -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Affero General Public License for more details. - -# You should have received a copy of the GNU Affero General Public License -# along with this program. If not, see . - -from pathlib import Path - - -RESOURCES_DIR = Path(__file__).resolve().parent diff --git a/coretex/cli/resources/update_script_template.sh b/coretex/cli/resources/update_script_template.sh deleted file mode 100644 index 417da9ca..00000000 --- a/coretex/cli/resources/update_script_template.sh +++ /dev/null @@ -1,12 +0,0 @@ -#!/bin/bash - -# Variables -DOCKER_PATH={dockerPath} -GIT_PATH={gitPath} -VENV_PATH={venvPath} - -# Set PATH to include the directory where docker is located -export PATH=$DOCKER_PATH:$GIT_PATH - -# Execute the "coretex node update" command -. "$VENV_PATH/bin/activate" && which coretex && coretex node update -n diff --git a/coretex/cli/main.py b/main.py similarity index 58% rename from coretex/cli/main.py rename to main.py index 283cde2e..167eca4f 100644 --- a/coretex/cli/main.py +++ b/main.py @@ -17,17 +17,19 @@ from importlib.metadata import version as getLibraryVersion +import sys +import multiprocessing + import click -from .commands.login import login -from .commands.model import model -from .commands.node import node -from .commands.task import task -from .commands.project import project +from coretex.cli.commands.login import login +from coretex.cli.commands.model import model +from coretex.cli.commands.node import node +from coretex.cli.commands.task import task +from coretex.cli.commands.project import project -from .modules import ui, utils -from .modules.intercept import ClickExceptionInterceptor -from ..utils.process import CommandException +from coretex.cli.modules import ui, utils +from coretex.cli.modules.intercept import ClickExceptionInterceptor @click.command() @@ -38,24 +40,10 @@ def version() -> None: @click.command() def update() -> None: - currentVersion = utils.fetchCurrentVersion() - latestVersion = utils.fetchLatestVersion() - - if currentVersion is None or latestVersion is None: - return - - if latestVersion > currentVersion: - try: - ui.progressEcho("Updating coretex...") - utils.updateLib() - ui.successEcho( - f"Coretex successfully updated from {utils.formatCliVersion(currentVersion)} " - f"to {utils.formatCliVersion(latestVersion)}!" - ) - except CommandException: - ui.errorEcho("Failed to update coretex.") - else: - ui.stdEcho("Coretex version is up to date.") + ui.stdEcho( + "This command isn't implemented yet for compiled version but will be available soon." + "Thanks for your patience!" + ) @click.group(cls = ClickExceptionInterceptor) @@ -71,3 +59,14 @@ def cli() -> None: cli.add_command(task) cli.add_command(version) cli.add_command(update) + + +if __name__ == "__main__": + # Ensures compatibility with the multiprocessing module when running as a compiled executable. + # PyInstaller creates a 'frozen' executable, which might not handle multiprocessing properly without this. + multiprocessing.freeze_support() + + # If the script is running as a PyInstaller compiled executable ('frozen' state), + # invoke the command-line interface (CLI) with the provided arguments. + if getattr(sys, 'frozen', False): + cli(sys.argv[1:]) diff --git a/pyproject.toml b/pyproject.toml index 66bd80a0..52fb5202 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -56,6 +56,3 @@ exclude = ["tests*", "docs_images*", "doxygen-awesome-css*"] [tool.setuptools.package-data] "*" = ["py.typed", "resources/*"] - -[project.scripts] -coretex = "coretex.cli.main:cli"