From 74ab1657f7d48b93835ab30b619e138f73219f6b Mon Sep 17 00:00:00 2001 From: aitbc1 Date: Fri, 20 Mar 2026 09:39:52 +0100 Subject: [PATCH] refactor: consolidate environment configuration and add production genesis tooling - Replace verbose .env.example with concise production-ready template - Add blockchain core, coordinator API, and marketplace web sections - Remove development/production split in favor of single config file - Add create-keystore command to genesis CLI for encrypted key generation - Add init-production command to initialize production chain DB from genesis - Add create_keystore helper function in scripts/keystore.py - --- .env.example | 113 ++++++++++++++--------------- cli/aitbc_cli/commands/genesis.py | 45 ++++++++++++ cli/aitbc_cli/commands/keystore.py | 67 +++++++++++++++++ cli/aitbc_cli/main.py | 2 + scripts/keystore.py | 22 ++++++ 5 files changed, 190 insertions(+), 59 deletions(-) create mode 100644 cli/aitbc_cli/commands/keystore.py diff --git a/.env.example b/.env.example index 9073d7a8..39de6a4a 100644 --- a/.env.example +++ b/.env.example @@ -1,63 +1,58 @@ -# AITBC Environment Configuration -# SECURITY NOTICE: Use service-specific environment files -# -# For development, copy from: -# config/environments/development/coordinator.env -# config/environments/development/wallet-daemon.env -# -# For production, use AWS Secrets Manager and Kubernetes secrets -# Templates available in config/environments/production/ +# AITBC Central Environment Example +# SECURITY NOTICE: Use a secrets manager for production. Do not commit real secrets. +# Run: python config/security/environment-audit.py --format text -# ============================================================================= -# BASIC CONFIGURATION ONLY -# ============================================================================= -# Application Environment -APP_ENV=development -DEBUG=false -LOG_LEVEL=INFO +# ========================= +# Blockchain core +# ========================= +chain_id=ait-mainnet +supported_chains=ait-mainnet +rpc_bind_host=0.0.0.0 +rpc_bind_port=8006 +p2p_bind_host=0.0.0.0 +p2p_bind_port=8005 +proposer_id=aitbc1genesis +proposer_key=changeme_hex_private_key +keystore_path=/opt/aitbc/keystore +keystore_password_file=/opt/aitbc/keystore/.password +gossip_backend=broadcast +gossip_broadcast_url=redis://127.0.0.1:6379 +db_path=/opt/aitbc/apps/blockchain-node/data/ait-mainnet/chain.db +mint_per_unit=0 +coordinator_ratio=0.05 +block_time_seconds=60 +enable_block_production=true -# ============================================================================= -# SECURITY REQUIREMENTS -# ============================================================================= -# IMPORTANT: Do NOT store actual secrets in this file -# Use AWS Secrets Manager for production -# Generate secure keys with: openssl rand -hex 32 +# ========================= +# Coordinator API +# ========================= +APP_ENV=production +APP_HOST=127.0.0.1 +APP_PORT=8011 +DATABASE__URL=sqlite:///./data/coordinator.db +BLOCKCHAIN_RPC_URL=http://127.0.0.1:8026 +ALLOW_ORIGINS=["http://localhost:8011","http://localhost:8000","http://8026"] +JOB_TTL_SECONDS=900 +HEARTBEAT_INTERVAL_SECONDS=10 +HEARTBEAT_TIMEOUT_SECONDS=30 +RATE_LIMIT_REQUESTS=60 +RATE_LIMIT_WINDOW_SECONDS=60 +CLIENT_API_KEYS=["client_prod_key_use_real_value"] +MINER_API_KEYS=["miner_prod_key_use_real_value"] +ADMIN_API_KEYS=["admin_prod_key_use_real_value"] +HMAC_SECRET=change_this_to_a_32_byte_random_secret +JWT_SECRET=change_this_to_another_32_byte_random_secret -# ============================================================================= -# SERVICE CONFIGURATION -# ============================================================================= -# Choose your service configuration: -# 1. Copy service-specific .env file from config/environments/ -# 2. Fill in actual values (NEVER commit secrets) -# 3. Run: python config/security/environment-audit.py +# ========================= +# Marketplace Web +# ========================= +VITE_MARKETPLACE_DATA_MODE=live +VITE_MARKETPLACE_API=/api +VITE_MARKETPLACE_ENABLE_BIDS=true +VITE_MARKETPLACE_REQUIRE_AUTH=false -# ============================================================================= -# DEVELOPMENT QUICK START -# ============================================================================= -# For quick development setup: -# cp config/environments/development/coordinator.env .env -# cp config/environments/development/wallet-daemon.env .env.wallet -# -# Then edit the copied files with your values - -# ============================================================================= -# PRODUCTION DEPLOYMENT -# ============================================================================= -# For production deployment: -# 1. Use AWS Secrets Manager for all sensitive values -# 2. Reference secrets as: secretRef:secret-name:key -# 3. Run security audit before deployment -# 4. Use templates in config/environments/production/ - -# ============================================================================= -# SECURITY VALIDATION -# ============================================================================= -# Validate your configuration: -# python config/security/environment-audit.py --format text - -# ============================================================================= -# FOR MORE INFORMATION -# ============================================================================= -# See: config/security/secret-validation.yaml -# See: config/security/environment-audit.py -# See: config/environments/ directory +# ========================= +# Notes +# ========================= +# For production: move secrets to a secrets manager and reference via secretRef +# Validate config: python config/security/environment-audit.py --format text diff --git a/cli/aitbc_cli/commands/genesis.py b/cli/aitbc_cli/commands/genesis.py index 016aec64..d373040e 100755 --- a/cli/aitbc_cli/commands/genesis.py +++ b/cli/aitbc_cli/commands/genesis.py @@ -9,12 +9,57 @@ from ..core.genesis_generator import GenesisGenerator, GenesisValidationError from ..core.config import MultiChainConfig, load_multichain_config from ..models.chain import GenesisConfig from ..utils import output, error, success +from .keystore import create_keystore_via_script +import subprocess +import sys @click.group() def genesis(): """Genesis block generation and management commands""" pass + +@genesis.command() +@click.option('--address', required=True, help='Wallet address (id) to create') +@click.option('--password-file', default='/opt/aitbc/data/keystore/.password', show_default=True, type=click.Path(exists=True, dir_okay=False), help='Path to password file') +@click.option('--output-dir', default='/opt/aitbc/data/keystore', show_default=True, help='Directory to write keystore file') +@click.option('--force', is_flag=True, help='Overwrite existing keystore file if present') +@click.pass_context +def create_keystore(ctx, address, password_file, output_dir, force): + """Create an encrypted keystore for a genesis/treasury address.""" + try: + create_keystore_via_script(address=address, password_file=password_file, output_dir=output_dir, force=force) + success(f"Created keystore for {address} at {output_dir}") + except Exception as e: + error(f"Error creating keystore: {e}") + raise click.Abort() + + +@genesis.command(name="init-production") +@click.option('--chain-id', default='ait-mainnet', show_default=True, help='Chain ID to initialize') +@click.option('--genesis-file', default='data/genesis_prod.yaml', show_default=True, help='Path to genesis YAML (copy to /opt/aitbc/genesis_prod.yaml if needed)') +@click.option('--db', default='/opt/aitbc/data/ait-mainnet/chain.db', show_default=True, help='SQLite DB path') +@click.option('--force', is_flag=True, help='Overwrite existing DB (removes file if present)') +@click.pass_context +def init_production(ctx, chain_id, genesis_file, db, force): + """Initialize production chain DB using genesis allocations.""" + db_path = Path(db) + if db_path.exists() and force: + db_path.unlink() + python_bin = Path(__file__).resolve().parents[3] / 'apps' / 'blockchain-node' / '.venv' / 'bin' / 'python3' + cmd = [ + str(python_bin), + str(Path(__file__).resolve().parents[3] / 'scripts' / 'init_production_genesis.py'), + '--chain-id', chain_id, + '--db', db, + ] + try: + subprocess.run(cmd, check=True) + success(f"Initialized production genesis for {chain_id} at {db}") + except subprocess.CalledProcessError as e: + error(f"Genesis init failed: {e}") + raise click.Abort() + @genesis.command() @click.argument('config_file', type=click.Path(exists=True)) @click.option('--output', '-o', 'output_file', help='Output file path') diff --git a/cli/aitbc_cli/commands/keystore.py b/cli/aitbc_cli/commands/keystore.py new file mode 100644 index 00000000..7ecf2d07 --- /dev/null +++ b/cli/aitbc_cli/commands/keystore.py @@ -0,0 +1,67 @@ +import click +import importlib.util +from pathlib import Path + + +def _load_keystore_script(): + """Dynamically load the top-level scripts/keystore.py module.""" + root = Path(__file__).resolve().parents[3] # /opt/aitbc + ks_path = root / "scripts" / "keystore.py" + spec = importlib.util.spec_from_file_location("aitbc_scripts_keystore", ks_path) + if spec is None or spec.loader is None: + raise ImportError(f"Unable to load keystore script from {ks_path}") + module = importlib.util.module_from_spec(spec) + spec.loader.exec_module(module) + return module + +@click.group() +def keystore(): + """Keystore operations (create wallets/keystores).""" + pass + +@keystore.command() +@click.option("--address", required=True, help="Wallet address (id) to create") +@click.option( + "--password-file", + default="/opt/aitbc/data/keystore/.password", + show_default=True, + type=click.Path(exists=True, dir_okay=False), + help="Path to password file", +) +@click.option( + "--output", + default="/opt/aitbc/data/keystore", + show_default=True, + help="Directory to write keystore files", +) +@click.option( + "--force", + is_flag=True, + help="Overwrite existing keystore file if present", +) +@click.pass_context +def create(ctx, address: str, password_file: str, output: str, force: bool): + """Create an encrypted keystore for the given address. + + Examples: + aitbc keystore create --address aitbc1genesis + aitbc keystore create --address aitbc1treasury --password-file keystore/.password --output keystore + """ + pwd_path = Path(password_file) + with open(pwd_path, "r", encoding="utf-8") as f: + password = f.read().strip() + out_dir = Path(output) if output else Path("/opt/aitbc/data/keystore") + out_dir.mkdir(parents=True, exist_ok=True) + + ks_module = _load_keystore_script() + ks_module.create_keystore(address=address, password=password, keystore_dir=out_dir, force=force) + click.echo(f"Created keystore for {address} at {out_dir}") + + +# Helper so other commands (genesis) can reuse the same logic +def create_keystore_via_script(address: str, password_file: str = "/opt/aitbc/data/keystore/.password", output_dir: str = "/opt/aitbc/data/keystore", force: bool = False): + pwd = Path(password_file).read_text(encoding="utf-8").strip() + out_dir = Path(output_dir) + out_dir.mkdir(parents=True, exist_ok=True) + ks_module = _load_keystore_script() + ks_module.create_keystore(address=address, password=pwd, keystore_dir=out_dir, force=force) diff --git a/cli/aitbc_cli/main.py b/cli/aitbc_cli/main.py index c41a8e4f..4ecd81df 100755 --- a/cli/aitbc_cli/main.py +++ b/cli/aitbc_cli/main.py @@ -46,6 +46,7 @@ from .commands.marketplace_advanced import advanced # Re-enabled after fixing r from .commands.swarm import swarm from .commands.chain import chain from .commands.genesis import genesis +from .commands.keystore import keystore from .commands.test_cli import test from .commands.node import node from .commands.analytics import analytics @@ -256,6 +257,7 @@ cli.add_command(ai_group) cli.add_command(swarm) cli.add_command(chain) cli.add_command(genesis) +cli.add_command(keystore) cli.add_command(test) cli.add_command(node) cli.add_command(analytics) diff --git a/scripts/keystore.py b/scripts/keystore.py index b37d622a..0f8d55ed 100644 --- a/scripts/keystore.py +++ b/scripts/keystore.py @@ -41,6 +41,28 @@ def encrypt_private_key(private_key_hex: str, password: str) -> dict: } +def create_keystore(address: str, password: str, keystore_dir: Path | str = "/opt/aitbc/keystore", force: bool = False) -> Path: + """Create encrypted keystore file and return its path.""" + keystore_dir = Path(keystore_dir) + keystore_dir.mkdir(parents=True, exist_ok=True) + out_file = keystore_dir / f"{address}.json" + + if out_file.exists() and not force: + raise FileExistsError(f"Keystore file {out_file} exists. Use force=True to overwrite.") + + private_key = secrets.token_hex(32) + encrypted = encrypt_private_key(private_key, password) + keystore = { + "address": address, + "crypto": encrypted, + "created_at": datetime.utcnow().isoformat() + "Z", + } + + out_file.write_text(json.dumps(keystore, indent=2)) + os.chmod(out_file, 0o600) + return out_file + + def main() -> None: parser = argparse.ArgumentParser(description="Generate encrypted keystore for an account") parser.add_argument("address", help="Account address (e.g., aitbc1treasury)")