#!/usr/bin/env python3 """WalletPress CLI — run the backend without Docker. Usage: walletpress serve # Start the API server walletpress init # Initialize data directory walletpress generate # Generate a wallet from CLI walletpress validate # Validate an address walletpress version # Show version No Docker required. Just pip install walletpress && walletpress serve. """ from __future__ import annotations import argparse import json import logging import os import sys logging.basicConfig(level=logging.INFO, format="%(asctime)s [%(levelname)s] %(name)s: %(message)s") logger = logging.getLogger("walletpress") def cmd_serve(args): """Start the WalletPress API server.""" from core.config import cfg import uvicorn port = args.port or cfg.port host = args.host or cfg.host os.makedirs(cfg.data_dir, exist_ok=True) logger.info(f"WalletPress v{cfg.version} starting on {host}:{port}") logger.info(f"Data directory: {cfg.data_dir}") logger.info(f"API docs: http://{host}:{port}/docs") logger.info(f"Health: http://{host}:{port}/health") if cfg.vault_password: logger.info("Vault encryption: AES-256-GCM + Argon2id") else: logger.warning("Vault encryption DISABLED. Set WP_VAULT_PASSWORD for production.") uvicorn.run( "main:app", host=host, port=port, reload=args.reload, log_level="info", ) def cmd_deploy(args): """Production one-command setup (requires Pro license key). Detects OS, installs deps, configures everything, starts service. Setup methods: --docker Deploy with Docker Compose (production stack) --method auto Auto-detect best method (default) --method cli Direct installation with systemd/launchd --domain DOMAIN Set domain for SSL auto-config --email EMAIL Email for Let's Encrypt notifications """ from core.config import cfg as _cfg from core.license import get_license lic = get_license() if not lic.is_paid: print(" walletpress deploy requires a Pro license.") print(" Buy: https://walletpress.cc/buy") print(" Or use hosted: https://walletpress.cc/pricing") return 1 method = args.method or "auto" domain = args.domain or "" print(f" WalletPress Pro Deploy — v{_cfg.version}") print(f" {'='*50}") print(f" License: {lic.tier.title()}") print(f" Method: {method}") if domain: print(f" Domain: {domain}") print() if method == "docker": print(" Setting up Docker Compose production stack...") compose_path = os.path.join(os.path.dirname(__file__), "..", "installers", "docker-compose.prod.yml") if os.path.exists(compose_path): print(f" Compose file: {compose_path}") print(f" Run: docker compose -f {compose_path} up -d") else: print(" Docker compose file not found. Re-download from walletpress.cc.") return # CLI method — direct installation import platform import subprocess system = platform.system().lower() print(f" Detected OS: {system}") if system == "linux": print(" Installing system dependencies...") try: subprocess.run(["sudo", "apt-get", "update", "-qq"], check=False) subprocess.run(["sudo", "apt-get", "install", "-y", "-qq", "python3", "python3-venv", "python3-pip", "openssl", "sqlite3", "curl"], check=False) except Exception: pass print(" Setting up virtual environment...") venv_path = "/opt/walletpress/venv" subprocess.run(["sudo", "python3", "-m", "venv", venv_path], check=False) print(" Installing Python dependencies...") req_path = os.path.join(os.path.dirname(__file__), "requirements.txt") subprocess.run(["sudo", f"{venv_path}/bin/pip", "install", "-q", "-r", req_path], check=False) print(" Generating credentials...") admin_key = subprocess.run(["openssl", "rand", "-hex", 32], capture_output=True, text=True).stdout.strip() vault_pass = subprocess.run(["openssl", "rand", "-hex", 32], capture_output=True, text=True).stdout.strip() subprocess.run(["openssl", "rand", "-hex", 16], capture_output=True, text=True).stdout.strip() print(" Creating configuration...") subprocess.run(["sudo", "mkdir", "-p", "/etc/walletpress"], check=False) subprocess.run(["sudo", "mkdir", "-p", "/var/lib/walletpress"], check=False) env_content = f"""WP_ADMIN_KEY={admin_key} WP_VAULT_PASSWORD={vault_pass} WP_LICENSE_KEY={os.environ.get('WP_LICENSE_KEY', '')} WP_DOMAIN={domain} WP_CORS_ORIGINS={f'https://{domain}' if domain else '*'} WP_SMTP_HOST= WP_SMTP_PORT=587 WP_SMTP_USER= WP_SMTP_PASS= WP_FROM_EMAIL=walletpress@{domain if domain else 'localhost'} WP_NOTIFY_EMAIL= WP_ALLOWED_IPS= WP_RATE_LIMIT=120 """ subprocess.run(["sudo", "tee", "/etc/walletpress/env"], input=env_content, text=True, check=False) subprocess.run(["sudo", "chmod", "600", "/etc/walletpress/env"], check=False) print(" Installing systemd service...") service_content = f"""[Unit] Description=WalletPress Pro After=network.target [Service] Type=simple User=walletpress Group=walletpress EnvironmentFile=/etc/walletpress/env WorkingDirectory=/opt/walletpress ExecStart={venv_path}/bin/uvicorn main:app --host 127.0.0.1 --port 8010 Restart=always RestartSec=5 [Install] WantedBy=multi-user.target """ subprocess.run(["sudo", "tee", "/etc/systemd/system/walletpress.service"], input=service_content, text=True, check=False) subprocess.run(["sudo", "systemctl", "daemon-reload"], check=False) subprocess.run(["sudo", "systemctl", "enable", "walletpress"], check=False) subprocess.run(["sudo", "systemctl", "start", "walletpress"], check=False) print() print(f" {'='*50}") print(" WALLETPRESS PRO DEPLOYED") print(f" {'='*50}") print(" URL: http://localhost:8010") print(" Admin: /api/v1/license") print(" Health: /health") print(" Docs: /docs") print("") print(f" Admin key: {admin_key}") print(f" Vault pass: {vault_pass}") print(f" License: {lic.tier.title()}") print("") print(" Save these credentials. They won't be shown again.") print(" Production: set up nginx reverse proxy + Let's Encrypt SSL.") elif system == "darwin": print(" macOS detected. Set up with launchd:") print(" brew install python@3.12 openssl sqlite") print(" python3 -m venv venv") print(" source venv/bin/activate") print(" pip install -r requirements.txt") print(" uvicorn main:app --port 8010") else: print(f" Unsupported OS: {system}") print(" Use Docker: docker compose -f installers/docker-compose.prod.yml up -d") return 0 def cmd_init(args): """Initialize the WalletPress data directory.""" from core.config import cfg from core.auth import get_key_store from core.vault import get_vault os.makedirs(cfg.data_dir, exist_ok=True) get_vault() get_key_store() if not cfg.admin_key: logger.warning("WP_ADMIN_KEY not set. API authentication will be disabled.") logger.warning("Set WP_ADMIN_KEY environment variable for production.") if not cfg.vault_password: logger.warning("WP_VAULT_PASSWORD not set. Private keys will NOT be encrypted at rest.") admin_key = cfg.admin_key logger.info(f"WalletPress initialized at {cfg.data_dir}") if admin_key: logger.info(f"Admin key: {admin_key[:16]}...") config_path = cfg.data_dir / ".env.example" if not config_path.exists(): config_path.write_text("""# WalletPress Configuration # Copy to .env and customize WP_HOST=0.0.0.0 WP_PORT=8010 WP_DATA_DIR=/data WP_ADMIN_KEY=change-me-to-a-random-string WP_VAULT_PASSWORD=change-me-to-a-strong-password WP_CORS_ORIGINS=* WP_RATE_LIMIT=60 WP_MAX_BATCH=100 """) logger.info(f"Example config created at {config_path}") def cmd_generate(args): """Generate a wallet from the CLI.""" from wallet_engine.generator import get_generator from wallet_engine.chains import CHAINS generator = get_generator() chain = args.chain or "eth" if chain not in CHAINS: print(f"Unsupported chain: {chain}") print(f"Supported: {', '.join(list(CHAINS.keys())[:20])}...") sys.exit(1) wallet = generator.generate(chain, label=args.label or f"cli_{chain}") print(json.dumps(wallet.to_safe_dict(), indent=2)) print(f"\n{'!'*60}") print("! WARNING: Private key is shown above. Keep it SECRET.") print(f"{'!'*60}") def cmd_validate(args): """Validate a wallet address.""" from wallet_engine.chains import CHAINS, validate_address chain = args.chain or "eth" address = args.address chain_info = CHAINS.get(chain.lower()) if not chain_info: print(f"Unsupported chain: {chain}") sys.exit(1) valid = validate_address(chain, address) print(f"Chain: {chain_info.name} ({chain})") print(f"Address: {address}") print(f"Valid: {'YES ✓' if valid else 'NO ✗'}") print(f"Expected pattern: {chain_info.address_pattern}") if not valid: sys.exit(1) def cmd_doctor(args): """Run system diagnostics and show setup status.""" from core.config import cfg as _cfg from core.onboarding import onboarding_status status = onboarding_status() from core.license import get_license lic = get_license() print(f"\n WalletPress Doctor — v{_cfg.version}") print(f" {'='*50}") print(f" License: {lic.tier.title()} ({'permanent key' if lic.is_paid else 'free — 3 chains only'})") print(f" Vault encryption: {'✅ AES-256-GCM' if _cfg.vault_password else '❌ Not configured'}") print(f" Admin API key: {'✅ Set' if _cfg.admin_key else '❌ Not set'}") print(f" RPC endpoints: {status['rpc_health']['rpc_configured']}/{status['rpc_health']['total_chains']} configured") print(f" SSL: {'✅ Configured' if os.path.exists('/etc/letsencrypt') or os.getenv('WP_SSL_ENABLED') else '❌ Not configured'}") print(f" SMTP: {'✅ Configured' if os.getenv('WP_SMTP_HOST') else '❌ Not configured'}") print() print(f" Issues: {status['items_missing']} of {status['items_total']} setup items incomplete") missing = status['missing_items'][:5] for item in missing: print(f" ⚠ {item['item']}") print() if lic.is_paid: print(" Visit walletpress.cc/app for the hosted dashboard.") else: print(" → Buy Pro ($199) for deploy script and setup support: walletpress.cc/buy") print() def cmd_version(args): """Show WalletPress version.""" from core.config import cfg print(f"WalletPress v{cfg.version}") print("License: MIT") print("Repository: https://github.com/cryptorugmuncher/walletpress") def main(): parser = argparse.ArgumentParser(description="WalletPress — self-hosted multi-chain wallet management") parser.add_argument("--version", action="store_true", help="Show version") sub = parser.add_subparsers(dest="command", help="Commands") serve_parser = sub.add_parser("serve", help="Start the API server") serve_parser.add_argument("--host", default="", help="Bind host") serve_parser.add_argument("--port", type=int, default=0, help="Bind port") serve_parser.add_argument("--reload", action="store_true", help="Auto-reload on code changes") deploy_parser = sub.add_parser("deploy", help="Production one-liner setup (requires Pro license)") deploy_parser.add_argument("--docker", action="store_true", help="Deploy with Docker Compose") deploy_parser.add_argument("--method", default="auto", choices=["auto", "cli", "docker"], help="Deployment method") deploy_parser.add_argument("--domain", default="", help="Domain for SSL configuration") deploy_parser.add_argument("--email", default="", help="Email for Let's Encrypt") sub.add_parser("init", help="Initialize data directory") gen_parser = sub.add_parser("generate", help="Generate a wallet") gen_parser.add_argument("--chain", default="eth", help="Chain key (eth, sol, btc, etc.)") gen_parser.add_argument("--label", default="", help="Wallet label") val_parser = sub.add_parser("validate", help="Validate an address") val_parser.add_argument("--chain", default="eth", help="Chain key") val_parser.add_argument("address", help="Wallet address to validate") sub.add_parser("doctor", help="Run system diagnostics") backup_parser = sub.add_parser("backup", help="Export vault + proofs to encrypted archive") backup_parser.add_argument("--output", default="walletpress_backup.json", help="Output file path") restore_parser = sub.add_parser("restore", help="Restore vault from backup archive") restore_parser.add_argument("input", help="Backup file to restore from") sub.add_parser("version", help="Show version") args = parser.parse_args() if args.version: cmd_version(args) return if args.command == "serve": cmd_serve(args) elif args.command == "deploy": cmd_deploy(args) elif args.command == "init": cmd_init(args) elif args.command == "generate": cmd_generate(args) elif args.command == "validate": cmd_validate(args) elif args.command == "doctor": cmd_doctor(args) elif args.command == "backup": cmd_backup(args) elif args.command == "restore": cmd_restore(args) elif args.command == "version": cmd_version(args) else: parser.print_help() def cmd_backup(args): """Export vault, proofs, and audit log to a portable JSON archive.""" import json from core.vault import get_vault print(f" Exporting WalletPress data to {args.output}...") vault = get_vault() wallets = vault.list(limit=100000) data = { "version": "1.1.0", "exported_at": __import__("time").time(), "wallets": [{ "id": w.id, "chain": w.chain, "address": w.address, "label": w.label, "tags": w.tags, "created_at": w.created_at, "encrypted_key": w.encrypted_key, "public_key": w.public_key, "derivation_path": w.derivation_path, "encrypted": w.encrypted, "balance_usd": w.balance_usd, "notes": w.notes, } for w in wallets], } with open(args.output, "w") as f: json.dump(data, f, indent=2, default=str) print(f" Exported {len(wallets)} wallets to {args.output}") print(f" Restore: walletpress restore {args.output}") def cmd_restore(args): """Restore vault from a backup archive.""" import json from core.vault import WalletEntry, get_vault print(f" Restoring from {args.input}...") with open(args.input) as f: data = json.load(f) vault = get_vault() count = 0 for w_data in data.get("wallets", []): entry = WalletEntry( id=w_data["id"], chain=w_data["chain"], address=w_data["address"], label=w_data.get("label", ""), tags=w_data.get("tags", []), created_at=w_data.get("created_at", 0), encrypted_key=w_data.get("encrypted_key", ""), public_key=w_data.get("public_key", ""), derivation_path=w_data.get("derivation_path", ""), encrypted=w_data.get("encrypted", False), balance_usd=w_data.get("balance_usd", 0), notes=w_data.get("notes", ""), ) if vault.put(entry): count += 1 print(f" Restored {count} of {len(data.get('wallets', []))} wallets") if __name__ == "__main__": main()