diff --git a/contrib/linux/Dockerfile b/contrib/linux/Dockerfile index 22b7725..2446181 100644 --- a/contrib/linux/Dockerfile +++ b/contrib/linux/Dockerfile @@ -17,7 +17,30 @@ RUN python3 -m venv venv && \ # Compile Python CLI into a standalone binary RUN venv/bin/pip install --no-cache-dir pyinstaller && \ - venv/bin/pyinstaller --onefile --name cli src/cli.py && \ + venv/bin/pyinstaller \ + --onefile --name cli \ + --hidden-import _cffi_backend \ + --hidden-import coincurve._cffi_backend \ + --hidden-import src.crypto \ + --hidden-import src.hd_wallet \ + --hidden-import src.p2pk \ + --hidden-import src.p2pkh \ + --hidden-import src.p2sh \ + --hidden-import src.p2wpkh \ + --hidden-import src.p2tr \ + --hidden-import src.single_wallet \ + --collect-data bip_utils \ + --collect-submodules src \ + --collect-submodules coincurve \ + --collect-binaries coincurve \ + --collect-data coincurve \ + src/cli.py && \ + dist/cli hd_generate '{}' > /tmp/cli-hd-generate.json && \ + cat /tmp/cli-hd-generate.json && \ + grep -Eq '"ok"[[:space:]]*:[[:space:]]*true' /tmp/cli-hd-generate.json && \ + dist/cli p2pkh '{}' > /tmp/cli-p2pkh.json && \ + cat /tmp/cli-p2pkh.json && \ + grep -Eq '"ok"[[:space:]]*:[[:space:]]*true' /tmp/cli-p2pkh.json && \ mkdir -p frontend/resources && \ cp dist/cli frontend/resources/cli diff --git a/contrib/windows/Dockerfile b/contrib/windows/Dockerfile index b4d66aa..d4888f2 100644 --- a/contrib/windows/Dockerfile +++ b/contrib/windows/Dockerfile @@ -78,11 +78,26 @@ RUN xvfb-run -a wine "C:\\Python311\\python.exe" -m PyInstaller \ --onefile --name cli \ --hidden-import _cffi_backend \ --hidden-import coincurve._cffi_backend \ + --hidden-import src.crypto \ + --hidden-import src.hd_wallet \ + --hidden-import src.p2pk \ + --hidden-import src.p2pkh \ + --hidden-import src.p2sh \ + --hidden-import src.p2wpkh \ + --hidden-import src.p2tr \ + --hidden-import src.single_wallet \ --collect-data bip_utils \ + --collect-submodules src \ --collect-submodules coincurve \ --collect-binaries coincurve \ --collect-data coincurve \ src/cli.py && \ + script -qec 'xvfb-run -a wine cmd /c "Z:\\build\\dist\\cli.exe hd_generate {}"' /tmp/cli-hd-generate.log && \ + cat /tmp/cli-hd-generate.log && \ + grep -Eq '"ok"[[:space:]]*:[[:space:]]*true' /tmp/cli-hd-generate.log && \ + script -qec 'xvfb-run -a wine cmd /c "Z:\\build\\dist\\cli.exe p2pkh {}"' /tmp/cli-p2pkh.log && \ + cat /tmp/cli-p2pkh.log && \ + grep -Eq '"ok"[[:space:]]*:[[:space:]]*true' /tmp/cli-p2pkh.log && \ mkdir -p frontend/resources && \ cp dist/cli.exe frontend/resources/cli.exe diff --git a/src/cli.py b/src/cli.py index be11715..282f17d 100644 --- a/src/cli.py +++ b/src/cli.py @@ -4,44 +4,84 @@ Usage: python src/cli.py Prints a single JSON line to stdout. """ +import importlib import json import sys -try: - from src.hd_wallet import generate_hd_wallet, encrypt_wallet, decrypt_wallet - from src.p2pk import generate_p2pk - from src.p2pkh import generate_legacy_address - from src.p2sh import generate_p2sh_multisig - from src.p2wpkh import generate_segwit_address - from src.p2tr import generate_p2tr_address - from src.single_wallet import encrypt_single_wallet, decrypt_single_wallet -except ImportError: - from hd_wallet import generate_hd_wallet, encrypt_wallet, decrypt_wallet - from p2pk import generate_p2pk - from p2pkh import generate_legacy_address - from p2sh import generate_p2sh_multisig - from p2wpkh import generate_segwit_address - from p2tr import generate_p2tr_address - from single_wallet import encrypt_single_wallet, decrypt_single_wallet +def _load_function(module_name, function_name): + """ + Import only what is needed for the selected command. + This avoids hard-failing all commands when an optional dependency is missing. + """ + try: + module = importlib.import_module(f"src.{module_name}") + except ModuleNotFoundError as err: + if err.name not in {f"src.{module_name}", "src"}: + raise + module = importlib.import_module(module_name) + return getattr(module, function_name) + + +def _run_hd_generate(args): + return _load_function("hd_wallet", "generate_hd_wallet")(**args) + + +def _run_hd_encrypt(args): + return _load_function("hd_wallet", "encrypt_wallet")(args["wallet"], args["password"]) + + +def _run_hd_decrypt(args): + return _load_function("hd_wallet", "decrypt_wallet")(args["wallet"], args["password"]) + + +def _run_p2pk(args): + return _load_function("p2pk", "generate_p2pk")(**args) + + +def _run_p2pkh(args): + return _load_function("p2pkh", "generate_legacy_address")(**args) + + +def _run_p2sh(args): + return _load_function("p2sh", "generate_p2sh_multisig")(**args) + + +def _run_p2wpkh(args): + return _load_function("p2wpkh", "generate_segwit_address")(**args) + + +def _run_p2tr(args): + return _load_function("p2tr", "generate_p2tr_address")(**args) + + +def _run_single_encrypt(args): + return _load_function("single_wallet", "encrypt_single_wallet")(args["wallet"], args["password"]) + + +def _run_single_decrypt(args): + return _load_function("single_wallet", "decrypt_single_wallet")(args["wallet"], args["password"]) + COMMANDS = { - 'hd_generate': lambda a: generate_hd_wallet(**a), - 'hd_encrypt': lambda a: encrypt_wallet(a['wallet'], a['password']), - 'hd_decrypt': lambda a: decrypt_wallet(a['wallet'], a['password']), - 'p2pk': lambda a: generate_p2pk(**a), - 'p2pkh': lambda a: generate_legacy_address(**a), - 'p2sh': lambda a: generate_p2sh_multisig(**a), - 'p2wpkh': lambda a: generate_segwit_address(**a), - 'p2tr': lambda a: generate_p2tr_address(**a), - 'single_encrypt': lambda a: encrypt_single_wallet(a['wallet'], a['password']), - 'single_decrypt': lambda a: decrypt_single_wallet(a['wallet'], a['password']), + "hd_generate": _run_hd_generate, + "hd_encrypt": _run_hd_encrypt, + "hd_decrypt": _run_hd_decrypt, + "p2pk": _run_p2pk, + "p2pkh": _run_p2pkh, + "p2sh": _run_p2sh, + "p2wpkh": _run_p2wpkh, + "p2tr": _run_p2tr, + "single_encrypt": _run_single_encrypt, + "single_decrypt": _run_single_decrypt, } if __name__ == '__main__': command = sys.argv[1] args = json.loads(sys.argv[2]) if len(sys.argv) > 2 else {} try: + if command not in COMMANDS: + raise ValueError(f"Unsupported command: {command}") result = COMMANDS[command](args) - print(json.dumps({'ok': True, 'data': result})) + print(json.dumps({"ok": True, "data": result})) except Exception as e: - print(json.dumps({'ok': False, 'error': str(e)})) + print(json.dumps({"ok": False, "error": str(e)}))