diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md index a5c8ebdf..5df55bfe 100644 --- a/.github/copilot-instructions.md +++ b/.github/copilot-instructions.md @@ -11,7 +11,10 @@ - no redundant code - move repeated logic into helper functions - use type hints to specify the expected types of function arguments and return values -- check `ruff.toml` for formatting rules -- always lint changes using `ruff check` +- check `pyproject.toml` for formatting rules +- always lint changes using `uv run ruff check` - tests should be placed in `tests/` directory, follow the existing structure and code style -- to run a test always use `bash scripts/run_tests.sh tests/path_to_test.py -k [TEST_NAME]` command \ No newline at end of file +- always use `uv` to run all commands in the repo (e.g., `uv run ruff`, `uv run pytest`, etc.) +- for running tests, export environment variables in the terminal before running the tests: `. ./scripts/export_env.sh` + +- additional external context is located in context directory \ No newline at end of file diff --git a/.gitignore b/.gitignore index 87a0d8f8..65232943 100644 --- a/.gitignore +++ b/.gitignore @@ -125,3 +125,4 @@ tests/.skale/node_data/node_options.json tests/.skale/config/nginx.conf.j2 .zed +uv.lock \ No newline at end of file diff --git a/node_cli/cli/node.py b/node_cli/cli/node.py index 55304356..f13825d9 100644 --- a/node_cli/cli/node.py +++ b/node_cli/cli/node.py @@ -17,8 +17,11 @@ # You should have received a copy of the GNU Affero General Public License # along with this program. If not, see . +from typing import get_args + import click +from skale.core.types import EnvType from node_cli.cli.info import TYPE from node_cli.core.node import ( cleanup as cleanup_skale, @@ -38,7 +41,6 @@ run_checks, ) from node_cli.configs import DEFAULT_NODE_BASE_PORT -from node_cli.configs.user import ALLOWED_ENV_TYPES from node_cli.core.node_options import upsert_node_mode from node_cli.utils.decorators import check_inited from node_cli.utils.helper import abort_if_false, streamed_cmd, IP_TYPE @@ -85,10 +87,10 @@ def register_node(name, ip, port, domain): @node.command('init', help='Initialize SKALE node') -@click.argument('env_file') +@click.argument('config_file') @streamed_cmd -def init_node(env_file): - init(env_filepath=env_file, node_type=TYPE) +def init_node(config_file): + init(config_file=config_file, node_type=TYPE) @node.command('update', help='Update node from .env file') @@ -101,12 +103,12 @@ def init_node(env_file): ) @click.option('--pull-config', 'pull_config_for_schain', hidden=True, type=str) @click.option('--unsafe', 'unsafe_ok', help='Allow unsafe update', hidden=True, is_flag=True) -@click.argument('env_file') +@click.argument('config_file') @streamed_cmd -def update_node(env_file, pull_config_for_schain, unsafe_ok): +def update_node(config_file, pull_config_for_schain, unsafe_ok): update( node_mode=NodeMode.ACTIVE, - env_filepath=env_file, + env_filepath=config_file, pull_config_for_schain=pull_config_for_schain, node_type=TYPE, unsafe_ok=unsafe_ok, @@ -227,7 +229,7 @@ def _set_domain_name(domain): @click.option( '--network', '-n', - type=click.Choice(ALLOWED_ENV_TYPES), + type=click.Choice(get_args(EnvType)), default='mainnet', help='Network to check', ) diff --git a/node_cli/configs/__init__.py b/node_cli/configs/__init__.py index 35d776d8..f9fc0aa5 100644 --- a/node_cli/configs/__init__.py +++ b/node_cli/configs/__init__.py @@ -19,6 +19,7 @@ import os import sys +from pathlib import Path from node_cli.utils.global_config import read_g_config @@ -43,6 +44,11 @@ NODE_DATA_PATH = os.path.join(SKALE_DIR, 'node_data') SCHAIN_NODE_DATA_PATH = os.path.join(NODE_DATA_PATH, 'schains') NODE_CLI_STATUS_FILENAME = 'node_cli.status' + +SETTINGS_DIR = Path(NODE_DATA_PATH) / 'settings' +NODE_SETTINGS_PATH = SETTINGS_DIR / 'node.toml' +INTERNAL_SETTINGS_PATH = SETTINGS_DIR / 'internal.toml' + NODE_CONFIG_PATH = os.path.join(NODE_DATA_PATH, 'node_config.json') CONTAINER_CONFIG_PATH = os.path.join(SKALE_DIR, 'config') CONTAINER_CONFIG_TMP_PATH = os.path.join(SKALE_TMP_DIR, 'config') @@ -52,8 +58,6 @@ INIT_ENV_FILEPATH = os.path.join(SKALE_DIR, '.env') SKALE_RUN_DIR = '/var/run/skale' -SGX_CERTIFICATES_DIR_NAME = 'sgx_certs' - COMPOSE_PATH = os.path.join(CONTAINER_CONFIG_PATH, 'docker-compose.yml') FAIR_COMPOSE_PATH = os.path.join(CONTAINER_CONFIG_PATH, 'docker-compose-fair.yml') STATIC_PARAMS_FILEPATH = os.path.join(CONTAINER_CONFIG_PATH, 'static_params.yaml') @@ -95,9 +99,6 @@ IPTABLES_RULES_STATE_FILEPATH = os.path.join(IPTABLES_DIR, 'rules.v4') DEFAULT_SSH_PORT = 22 -FLASK_SECRET_KEY_FILENAME = 'flask_db_key.txt' -FLASK_SECRET_KEY_FILE = os.path.join(NODE_DATA_PATH, FLASK_SECRET_KEY_FILENAME) - DOCKER_CONFIG_FILEPATH = '/etc/docker/daemon.json' HIDE_STREAM_LOG = os.getenv('HIDE_STREAM_LOG') diff --git a/node_cli/configs/user.py b/node_cli/configs/_user.py similarity index 98% rename from node_cli/configs/user.py rename to node_cli/configs/_user.py index a4f52c2c..6ab7b6f5 100644 --- a/node_cli/configs/user.py +++ b/node_cli/configs/_user.py @@ -52,6 +52,7 @@ class ValidationResult(NamedTuple): class BaseUserConfig(ABC): node_version: str env_type: str + endpoint: str filebeat_host: str block_device: str @@ -89,7 +90,6 @@ def validate_params(cls, params: Dict) -> ValidationResult: @dataclass class FairUserConfig(BaseUserConfig): fair_contracts: str - boot_endpoint: str sgx_server_url: str enforce_btrfs: str = '' telegraf: str = '' @@ -99,13 +99,11 @@ class FairUserConfig(BaseUserConfig): @dataclass class PassiveFairUserConfig(BaseUserConfig): fair_contracts: str - boot_endpoint: str enforce_btrfs: str = '' @dataclass class FairBootUserConfig(BaseUserConfig): - endpoint: str manager_contracts: str ima_contracts: str sgx_server_url: str @@ -114,7 +112,6 @@ class FairBootUserConfig(BaseUserConfig): @dataclass class SkaleUserConfig(BaseUserConfig): - endpoint: str manager_contracts: str ima_contracts: str docker_lvmpy_version: str @@ -132,7 +129,6 @@ class SkaleUserConfig(BaseUserConfig): @dataclass class PassiveSkaleUserConfig(BaseUserConfig): - endpoint: str manager_contracts: str schain_name: str = '' ima_contracts: str = '' diff --git a/node_cli/core/host.py b/node_cli/core/host.py index da4be640..920b651e 100644 --- a/node_cli/core/host.py +++ b/node_cli/core/host.py @@ -39,6 +39,7 @@ SGX_CERTS_PATH, REPORTS_PATH, REDIS_DATA_PATH, + SETTINGS_DIR, SCHAINS_DATA_PATH, LOG_PATH, REMOVED_CONTAINERS_FOLDER_PATH, @@ -50,7 +51,6 @@ NGINX_CONFIG_FILEPATH, ) from node_cli.configs.cli_logger import LOG_DATA_PATH -from node_cli.configs.user import SKALE_DIR_ENV_FILEPATH, CONFIGS_ENV_FILEPATH from node_cli.core.nftables import NFTablesManager from node_cli.utils.helper import safe_mkdir @@ -73,20 +73,6 @@ def fix_url(url): return False -def get_flask_secret_key() -> str: - secret_key_filepath = os.path.join(NODE_DATA_PATH, 'flask_db_key.txt') - - if not os.path.exists(secret_key_filepath): - error_exit(f'Flask secret key file not found at {secret_key_filepath}') - - try: - with open(secret_key_filepath, 'r') as key_file: - secret_key = key_file.read().strip() - return secret_key - except (IOError, OSError) as e: - error_exit(f'Failed to read Flask secret key: {e}') - - def prepare_host(env_filepath: str, env_type: str, allocation: bool = False) -> None: if not env_filepath or not env_type: error_exit('Missing required parameters for host initialization') @@ -121,6 +107,7 @@ def make_dirs(): LOG_PATH, REPORTS_PATH, REDIS_DATA_PATH, + SETTINGS_DIR, SKALE_RUN_DIR, SKALE_STATE_DIR, SKALE_TMP_DIR, @@ -128,16 +115,6 @@ def make_dirs(): safe_mkdir(dir_path) -def save_env_params(env_filepath: str) -> None: - copyfile(env_filepath, SKALE_DIR_ENV_FILEPATH) - - -def link_env_file(): - if not (os.path.islink(CONFIGS_ENV_FILEPATH) or os.path.isfile(CONFIGS_ENV_FILEPATH)): - logger.info('Creating symlink %s → %s', SKALE_DIR_ENV_FILEPATH, CONFIGS_ENV_FILEPATH) - os.symlink(SKALE_DIR_ENV_FILEPATH, CONFIGS_ENV_FILEPATH) - - def init_logs_dir(): safe_mkdir(LOG_DATA_PATH) safe_mkdir(REMOVED_CONTAINERS_FOLDER_PATH) diff --git a/node_cli/core/node.py b/node_cli/core/node.py index 2128d43f..191d2077 100644 --- a/node_cli/core/node.py +++ b/node_cli/core/node.py @@ -43,9 +43,8 @@ TM_INIT_TIMEOUT, ) from node_cli.configs.cli_logger import LOG_DATA_PATH as CLI_LOG_DATA_PATH -from node_cli.configs.user import SKALE_DIR_ENV_FILEPATH, get_validated_user_config from node_cli.core.checks import run_checks as run_host_checks -from node_cli.core.host import get_flask_secret_key, is_node_inited, save_env_params +from node_cli.core.host import is_node_inited from node_cli.core.resources import update_resource_allocation from node_cli.core.node_options import ( active_fair, @@ -152,11 +151,11 @@ def register_node(name, p2p_ip, public_ip, port, domain_name): @check_not_inited -def init(env_filepath: str, node_type: NodeType) -> None: +def init(config_file: str, node_type: NodeType) -> None: node_mode = NodeMode.ACTIVE - env = compose_node_env(env_filepath=env_filepath, node_type=node_type, node_mode=node_mode) + env = compose_node_env(node_type=node_type, node_mode=node_mode) - init_op(env_filepath=env_filepath, env=env, node_mode=node_mode) + init_op(env_filepath=config_file, env=env, node_mode=node_mode) logger.info('Waiting for containers initialization') time.sleep(TM_INIT_TIMEOUT) if not is_base_containers_alive(node_type=node_type, node_mode=node_mode): @@ -169,7 +168,7 @@ def init(env_filepath: str, node_type: NodeType) -> None: @check_not_inited def restore(backup_path, env_filepath, node_type: NodeType, no_snapshot=False, config_only=False): node_mode = NodeMode.ACTIVE - env = compose_node_env(env_filepath=env_filepath, node_type=node_type, node_mode=node_mode) + env = compose_node_env(node_type=node_type, node_mode=node_mode) if env is None: return save_env_params(env_filepath) @@ -211,7 +210,7 @@ def update_passive(env_filepath: str) -> None: prev_version = CliMetaManager().get_meta_info().version if (__version__ == 'test' or __version__.startswith('2.6')) and prev_version == '2.5.0': migrate_2_6() - env = compose_node_env(env_filepath, node_type=NodeType.SKALE, node_mode=NodeMode.PASSIVE) + env = compose_node_env(node_type=NodeType.SKALE, node_mode=NodeMode.PASSIVE) update_ok = update_passive_op(env_filepath, env) if update_ok: logger.info('Waiting for containers initialization') @@ -227,69 +226,22 @@ def update_passive(env_filepath: str) -> None: @check_user def cleanup(node_mode: NodeMode, prune: bool = False) -> None: node_mode = upsert_node_mode(node_mode=node_mode) - env = compose_node_env( - SKALE_DIR_ENV_FILEPATH, - save=False, - node_type=NodeType.SKALE, - node_mode=node_mode, - skip_user_conf_validation=True, - ) + env = compose_node_env(NodeType.SKALE, node_mode) cleanup_skale_op(node_mode=node_mode, env=env, prune=prune) logger.info('SKALE node was cleaned up, all containers and data removed') -def compose_node_env( - env_filepath: str, - node_type: NodeType, - node_mode: NodeMode, - inited_node: bool = False, - sync_schains: Optional[bool] = None, - pull_config_for_schain: Optional[str] = None, - save: bool = True, - is_fair_boot: bool = False, - skip_user_conf_validation: bool = False, -) -> dict[str, str]: - if env_filepath is not None: - user_config = get_validated_user_config( - node_type=node_type, - node_mode=node_mode, - env_filepath=env_filepath, - is_fair_boot=is_fair_boot, - skip_user_conf_validation=skip_user_conf_validation, - ) - if save: - save_env_params(env_filepath) - else: - user_config = get_validated_user_config( - node_type=node_type, - node_mode=node_mode, - env_filepath=INIT_ENV_FILEPATH, - is_fair_boot=is_fair_boot, - skip_user_conf_validation=skip_user_conf_validation, - ) - +def compose_node_env(node_type: NodeType, node_mode: NodeMode) -> dict[str, str]: if node_mode == NodeMode.PASSIVE or node_type == NodeType.FAIR: mnt_dir = SCHAINS_MNT_DIR_SINGLE_CHAIN else: mnt_dir = SCHAINS_MNT_DIR_REGULAR - env = { 'SKALE_DIR': SKALE_DIR, 'SCHAINS_MNT_DIR': mnt_dir, 'FILESTORAGE_MAPPING': FILESTORAGE_MAPPING, 'SKALE_LIB_PATH': SKALE_STATE_DIR, - **user_config.to_env(), } - - if inited_node and not node_mode == NodeMode.PASSIVE: - env['FLASK_SECRET_KEY'] = get_flask_secret_key() - - if sync_schains and not node_mode == NodeMode.PASSIVE: - env['BACKUP_RUN'] = 'True' - - if pull_config_for_schain: - env['PULL_CONFIG_FOR_SCHAIN'] = pull_config_for_schain - return {k: v for k, v in env.items() if v != ''} @@ -313,10 +265,10 @@ def update( migrate_2_6() logger.info('Node update started') env = compose_node_env( - env_filepath, - inited_node=True, - sync_schains=False, - pull_config_for_schain=pull_config_for_schain, + # env_filepath, + # inited_node=True, + # sync_schains=False, + # pull_config_for_schain=pull_config_for_schain, node_type=node_type, node_mode=node_mode, ) diff --git a/node_cli/operations/base.py b/node_cli/operations/base.py index 1a64d585..713a6775 100644 --- a/node_cli/operations/base.py +++ b/node_cli/operations/base.py @@ -53,7 +53,7 @@ cleanup_no_lvm_datadir, update_node_cli_schain_status, ) -from node_cli.operations.common import configure_filebeat, configure_flask, unpack_backup_archive +from node_cli.operations.common import configure_filebeat, unpack_backup_archive from node_cli.operations.config_repo import ( download_skale_node, sync_skale_node, @@ -76,6 +76,7 @@ from node_cli.utils.meta import CliMetaManager, FairCliMetaManager from node_cli.utils.node_type import NodeMode, NodeType from node_cli.utils.print_formatters import print_failed_requirements_checks +from node_cli.utils.settings import save_settings logger = logging.getLogger(__name__) @@ -134,6 +135,7 @@ def update(env_filepath: str, env: Dict, node_mode: NodeMode) -> bool: generate_nginx_config() prepare_host(env_filepath, env['ENV_TYPE'], allocation=True) + save_settings(node_type=NodeType.SKALE, node_mode=node_mode) init_shared_space_volume(env['ENV_TYPE']) meta_manager = CliMetaManager() @@ -170,12 +172,12 @@ def init(env_filepath: str, env: dict, node_mode: NodeMode) -> None: configure_nftables(enable_monitoring=enable_monitoring) prepare_host(env_filepath, env_type=env['ENV_TYPE']) + save_settings(node_type=NodeType.SKALE, node_mode=node_mode) link_env_file() mark_active_node() configure_filebeat() - configure_flask() generate_nginx_config() lvmpy_install(env) @@ -222,7 +224,7 @@ def init_passive( NodeMode.PASSIVE, env['ENV_TYPE'], CONTAINER_CONFIG_PATH, - check_type=CheckType.PREINSTALL + check_type=CheckType.PREINSTALL, ) if failed_checks: print_failed_requirements_checks(failed_checks) @@ -280,7 +282,7 @@ def update_passive(env_filepath: str, env: Dict) -> bool: NodeMode.PASSIVE, env['ENV_TYPE'], CONTAINER_CONFIG_PATH, - check_type=CheckType.PREINSTALL + check_type=CheckType.PREINSTALL, ) if failed_checks: print_failed_requirements_checks(failed_checks) diff --git a/node_cli/operations/common.py b/node_cli/operations/common.py index 7c876fa8..c595a3b8 100644 --- a/node_cli/operations/common.py +++ b/node_cli/operations/common.py @@ -19,7 +19,6 @@ import logging import os -import secrets import shutil import stat import tarfile @@ -27,7 +26,6 @@ from node_cli.configs import ( FILEBEAT_CONFIG_PATH, - FLASK_SECRET_KEY_FILE, G_CONF_HOME, SRC_FILEBEAT_CONFIG_PATH, ) @@ -43,17 +41,6 @@ def configure_filebeat(): logger.info('Filebeat configured') -def configure_flask(): - if os.path.isfile(FLASK_SECRET_KEY_FILE): - logger.info('Flask secret key already exists') - else: - logger.info('Generating Flask secret key...') - flask_secret_key = secrets.token_urlsafe(16) - with open(FLASK_SECRET_KEY_FILE, 'w') as f: - f.write(flask_secret_key) - logger.info('Flask secret key generated and saved') - - def unpack_backup_archive(backup_path: str) -> None: logger.info('Unpacking backup archive...') with tarfile.open(backup_path) as tar: diff --git a/node_cli/operations/fair.py b/node_cli/operations/fair.py index f1e62305..096d1623 100644 --- a/node_cli/operations/fair.py +++ b/node_cli/operations/fair.py @@ -46,7 +46,7 @@ ) from node_cli.migrations.fair.from_boot import migrate_nftables_from_boot from node_cli.operations.base import checked_host, turn_off -from node_cli.operations.common import configure_filebeat, configure_flask, unpack_backup_archive +from node_cli.operations.common import configure_filebeat, unpack_backup_archive from node_cli.operations.config_repo import ( sync_skale_node, update_images, @@ -70,6 +70,7 @@ from node_cli.utils.meta import FairCliMetaManager from node_cli.utils.print_formatters import print_failed_requirements_checks from node_cli.utils.node_type import NodeMode, NodeType +from node_cli.utils.settings import save_settings logger = logging.getLogger(__name__) @@ -93,11 +94,11 @@ def init_fair_boot(env_filepath: str, env: dict) -> None: configure_nftables(enable_monitoring=enable_monitoring) prepare_host(env_filepath, env_type=env['ENV_TYPE']) + save_settings(node_type=NodeType.FAIR, node_mode=NodeMode.ACTIVE) link_env_file() mark_active_node() configure_filebeat() - configure_flask() generate_nginx_config() prepare_block_device(env['BLOCK_DEVICE'], force=env['ENFORCE_BTRFS'] == 'True') @@ -131,10 +132,10 @@ def init( configure_nftables() configure_filebeat() - configure_flask() generate_nginx_config() prepare_host(env_filepath, env_type=env['ENV_TYPE']) + save_settings(node_type=NodeType.FAIR, node_mode=node_mode) link_env_file() prepare_block_device(env['BLOCK_DEVICE'], force=env['ENFORCE_BTRFS'] == 'True') @@ -186,6 +187,7 @@ def update_fair_boot(env_filepath: str, env: dict, node_mode: NodeMode = NodeMod prepare_block_device(env['BLOCK_DEVICE'], force=env['ENFORCE_BTRFS'] == 'True') prepare_host(env_filepath, env['ENV_TYPE']) + save_settings(node_type=NodeType.FAIR, node_mode=NodeMode.ACTIVE) meta_manager = FairCliMetaManager() current_stream = meta_manager.get_meta_info().config_stream @@ -231,6 +233,7 @@ def update( generate_nginx_config() prepare_host(env_filepath, env['ENV_TYPE'], allocation=True) + save_settings(node_type=NodeType.FAIR, node_mode=node_mode) meta_manager = FairCliMetaManager() current_stream = meta_manager.get_meta_info().config_stream skip_cleanup = env.get('SKIP_DOCKER_CLEANUP') == 'True' diff --git a/node_cli/utils/docker_utils.py b/node_cli/utils/docker_utils.py index 695a1253..37e3fab9 100644 --- a/node_cli/utils/docker_utils.py +++ b/node_cli/utils/docker_utils.py @@ -338,9 +338,6 @@ def compose_up( run_cmd(cmd=get_up_compose_cmd(node_type=node_type, node_mode=node_mode), env=env) return - if 'SGX_CERTIFICATES_DIR_NAME' not in env: - env['SGX_CERTIFICATES_DIR_NAME'] = SGX_CERTIFICATES_DIR_NAME - if active_fair(node_type, node_mode): logger.info('Running fair base set of containers') if is_fair_boot: diff --git a/node_cli/utils/helper.py b/node_cli/utils/helper.py index dd1a7151..9d96a3ca 100644 --- a/node_cli/utils/helper.py +++ b/node_cli/utils/helper.py @@ -30,6 +30,7 @@ import urllib.parse import urllib.request import uuid +from pathlib import Path from functools import wraps from logging import Formatter, StreamHandler from typing import Any, NoReturn, Optional @@ -85,13 +86,13 @@ def write_json(path: str, content: dict) -> None: json.dump(content, outfile, indent=4) -def save_json(path: str, content: dict) -> None: +def save_json(path: str | Path, content: dict) -> None: tmp_path = get_tmp_path(path) write_json(tmp_path, content) shutil.move(tmp_path, path) -def init_file(path, content=None): +def init_file(path: str | Path, content=None): if not os.path.exists(path): write_json(path, content) @@ -339,7 +340,7 @@ def cleanup_dir_content(folder: str) -> None: shutil.rmtree(file_path) -def safe_mkdir(path: str, print_res: bool = False) -> None: +def safe_mkdir(path: str | Path, print_res: bool = False) -> None: if os.path.exists(path): logger.debug(f'Directory {path} already exists') return @@ -411,8 +412,8 @@ def convert(self, value, param, ctx): IP_TYPE = IpType() -def get_tmp_path(path: str) -> str: - base, ext = os.path.splitext(path) +def get_tmp_path(path: str | Path) -> str: + base, ext = os.path.splitext(str(path)) salt = uuid.uuid4().hex[:5] return base + salt + '.tmp' + ext diff --git a/node_cli/utils/node_type.py b/node_cli/utils/node_type.py index bf3f6d4f..754a1d69 100644 --- a/node_cli/utils/node_type.py +++ b/node_cli/utils/node_type.py @@ -20,9 +20,9 @@ from enum import Enum -class NodeType(Enum): - SKALE = 0 - FAIR = 1 +class NodeType(str, Enum): + SKALE = 'skale' + FAIR = 'fair' class NodeMode(str, Enum): diff --git a/node_cli/utils/print_formatters.py b/node_cli/utils/print_formatters.py index 7edec113..1da07d51 100644 --- a/node_cli/utils/print_formatters.py +++ b/node_cli/utils/print_formatters.py @@ -339,9 +339,6 @@ def format_timestamp(value): return str(value) -1 - - def print_chain_record(record): print( inspect.cleandoc(f""" diff --git a/node_cli/utils/settings.py b/node_cli/utils/settings.py new file mode 100644 index 00000000..c0b3aa76 --- /dev/null +++ b/node_cli/utils/settings.py @@ -0,0 +1,47 @@ +# -*- coding: utf-8 -*- +# +# This file is part of node-cli +# +# Copyright (C) 2026 SKALE Labs +# +# 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 skale.core.settings import ( + SETTINGS_MAP, + write_node_settings_file, + write_internal_settings_file, + InternalSettings, + SkaleSettings, + SkalePassiveSettings, + FairSettings, + FairBaseSettings, +) + +from node_cli.configs import NODE_SETTINGS_PATH, INTERNAL_SETTINGS_PATH + +from node_cli.utils.node_type import NodeMode, NodeType + +InternalSettings.model_config['toml_file'] = INTERNAL_SETTINGS_PATH +SkaleSettings.model_config['toml_file'] = NODE_SETTINGS_PATH +SkalePassiveSettings.model_config['toml_file'] = NODE_SETTINGS_PATH +FairSettings.model_config['toml_file'] = NODE_SETTINGS_PATH +FairBaseSettings.model_config['toml_file'] = NODE_SETTINGS_PATH + + +def save_settings(node_type: NodeType, node_mode: NodeMode) -> None: + write_internal_settings_file(path=INTERNAL_SETTINGS_PATH, data={}) # todof: fix + settings_type = SETTINGS_MAP[(node_type.value, node_mode.value)] + write_node_settings_file( + path=NODE_SETTINGS_PATH, settings_type=settings_type, data={} + ) # todof: fix diff --git a/pyproject.toml b/pyproject.toml index cf813bfa..3f2e25d1 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -10,15 +10,13 @@ readme = "README.md" requires-python = ">=3.13" license = { file = "LICENSE" } keywords = ["skale", "cli"] -authors = [ - { name = "SKALE Labs", email = "support@skalelabs.com" } -] +authors = [{ name = "SKALE Labs", email = "support@skalelabs.com" }] classifiers = [ - "Development Status :: 5 - Production/Stable", - "Intended Audience :: Developers", - "License :: OSI Approved :: GNU Affero General Public License v3", - "Natural Language :: English", - "Programming Language :: Python :: 3.13", + "Development Status :: 5 - Production/Stable", + "Intended Audience :: Developers", + "License :: OSI Approved :: GNU Affero General Public License v3", + "Natural Language :: English", + "Programming Language :: Python :: 3.13", ] dependencies = [ @@ -40,13 +38,14 @@ dependencies = [ "MarkupSafe==3.0.3", "Flask==3.1.2", "itsdangerous==2.2.0", - "cryptography==46.0.3", + "cryptography==46.0.5", "filelock==3.20.0", "sh==2.2.2", "python-crontab==3.3.0", "requests-mock==1.12.1", - "redis==7.1.0", - "PyInstaller==6.16.0", + "redis==7.1.1", + "PyInstaller==6.18.0", + "skale.py==7.12dev2", ] [project.urls] @@ -78,11 +77,13 @@ target-version = "py313" [tool.ruff.format] quote-style = "single" +[tool.uv] +prerelease = "allow" + + [tool.pytest.ini_options] log_cli = false log_cli_level = "INFO" log_cli_format = "%(asctime)s [%(levelname)8s] %(message)s (%(filename)s:%(lineno)s)" log_cli_date_format = "%Y-%m-%d %H:%M:%S" -filterwarnings = [ - "ignore::DeprecationWarning", -] +filterwarnings = ["ignore::DeprecationWarning"] diff --git a/scripts/export_env.sh b/scripts/export_env.sh new file mode 100644 index 00000000..af30b4ab --- /dev/null +++ b/scripts/export_env.sh @@ -0,0 +1,8 @@ +DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )" +PROJECT_DIR=$(dirname $DIR) + +export LVMPY_LOG_DIR="$PROJECT_DIR/tests/" +export HIDE_STREAM_LOG=true +export TEST_HOME_DIR="$PROJECT_DIR/tests/" +export GLOBAL_SKALE_DIR="$PROJECT_DIR/tests/etc/skale" +export DOTENV_FILEPATH='tests/test-env' \ No newline at end of file diff --git a/scripts/run_tests.sh b/scripts/run_tests.sh index efc72c6c..23592396 100755 --- a/scripts/run_tests.sh +++ b/scripts/run_tests.sh @@ -1,11 +1,3 @@ #!/usr/bin/env bash -DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )" -PROJECT_DIR=$(dirname $DIR) - -LVMPY_LOG_DIR="$PROJECT_DIR/tests/" \ - HIDE_STREAM_LOG=true \ - TEST_HOME_DIR="$PROJECT_DIR/tests/" \ - GLOBAL_SKALE_DIR="$PROJECT_DIR/tests/etc/skale" \ - DOTENV_FILEPATH='tests/test-env' \ - py.test --cov=$PROJECT_DIR/ --ignore=tests/core/nftables_test.py --ignore=tests/core/migration_test.py tests/ $@ +py.test --cov=$PROJECT_DIR/ --ignore=tests/core/nftables_test.py --ignore=tests/core/migration_test.py tests/ $@ diff --git a/tests/cli/node_test.py b/tests/cli/node_test.py index b31cddc0..bb5aa6d0 100644 --- a/tests/cli/node_test.py +++ b/tests/cli/node_test.py @@ -422,7 +422,6 @@ def test_turn_on_maintenance_off(mocked_g_config, regular_user_conf, active_node resp_mock = response_mock(requests.codes.ok, {'status': 'ok', 'payload': None}) with ( mock.patch('subprocess.run', new=subprocess_run_mock), - mock.patch('node_cli.core.node.get_flask_secret_key'), mock.patch('node_cli.core.node.turn_on_op'), mock.patch('node_cli.core.node.is_base_containers_alive'), mock.patch('node_cli.utils.decorators.is_node_inited', return_value=True), @@ -475,6 +474,7 @@ def test_node_version(meta_file_v2): == "{'version': '0.1.1', 'config_stream': 'develop', 'docker_lvmpy_version': '1.1.2'}\n" ) + def test_cleanup_node(mocked_g_config): pathlib.Path(SKALE_DIR).mkdir(parents=True, exist_ok=True) @@ -493,4 +493,4 @@ def test_cleanup_node(mocked_g_config): ): result = run_command(cleanup_node, ['--yes']) assert result.exit_code == 0 - cleanup_mock.assert_called_once_with(node_mode=NodeMode.ACTIVE, prune=False, env={}) \ No newline at end of file + cleanup_mock.assert_called_once_with(node_mode=NodeMode.ACTIVE, prune=False, env={}) diff --git a/tests/conftest.py b/tests/conftest.py index 5434e697..cc5a5fa3 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -380,7 +380,7 @@ def fair_user_conf(tmp_path): test_env_path = pathlib.Path(tmp_path / 'test-env') try: test_env = """ - BOOT_ENDPOINT=http://localhost:8545 + ENDPOINT=http://localhost:8545 NODE_VERSION='main' FILEBEAT_HOST=127.0.0.1:3010 SGX_SERVER_URL=http://127.0.0.1 diff --git a/tests/core/core_node_test.py b/tests/core/core_node_test.py index 953e7ca5..0fe113b2 100644 --- a/tests/core/core_node_test.py +++ b/tests/core/core_node_test.py @@ -243,7 +243,6 @@ def test_compose_node_env( inited_node, sync_schains, expected_mnt_dir, - expect_flask_key, expect_backup_run, ): user_config_path = request.getfixturevalue(test_user_conf) @@ -251,7 +250,6 @@ def test_compose_node_env( with ( mock.patch('node_cli.configs.user.validate_alias_or_address'), mock.patch('node_cli.core.node.save_env_params'), - mock.patch('node_cli.core.node.get_flask_secret_key', return_value='mock_secret'), ): result_env = compose_node_env( env_filepath=user_config_path.as_posix(), @@ -264,11 +262,6 @@ def test_compose_node_env( ) assert result_env['SCHAINS_MNT_DIR'] == expected_mnt_dir - assert ( - 'FLASK_SECRET_KEY' in result_env and result_env['FLASK_SECRET_KEY'] is not None - ) == expect_flask_key - if expect_flask_key: - assert result_env['FLASK_SECRET_KEY'] == 'mock_secret' should_have_backup = sync_schains and node_mode != NodeMode.PASSIVE assert ('BACKUP_RUN' in result_env and result_env['BACKUP_RUN'] == 'True') == should_have_backup @@ -358,7 +351,6 @@ def test_update_node(regular_user_conf, mocked_g_config, resource_file, inited_n with ( mock.patch('subprocess.run', new=subprocess_run_mock), mock.patch('node_cli.core.node.update_op'), - mock.patch('node_cli.core.node.get_flask_secret_key'), mock.patch('node_cli.core.node.save_env_params'), mock.patch('node_cli.operations.base.configure_nftables'), mock.patch('node_cli.core.host.prepare_host'), @@ -500,4 +492,5 @@ def test_cleanup_success( skip_user_conf_validation=True, ) mock_cleanup_skale_op.assert_called_once_with( - node_mode=NodeMode.ACTIVE, env=mock_env, prune=False) \ No newline at end of file + node_mode=NodeMode.ACTIVE, env=mock_env, prune=False + ) diff --git a/tests/utils/settings_test.py b/tests/utils/settings_test.py new file mode 100644 index 00000000..1d9deeb1 --- /dev/null +++ b/tests/utils/settings_test.py @@ -0,0 +1,20 @@ +# -*- coding: utf-8 -*- +# +# This file is part of node-cli +# +# Copyright (C) 2026 SKALE Labs +# +# 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 node_cli.utils.node_type import NodeMode, NodeType