diff --git a/.gitignore b/.gitignore index ab40a5b552..0d9de7d9dd 100644 --- a/.gitignore +++ b/.gitignore @@ -2,10 +2,11 @@ __pycache__ .pytest_cache *.pyc .env +.venv .tor_service_key build dist *.egg-info .vscode cert.pem -key.pem \ No newline at end of file +key.pem diff --git a/pyinstaller/README.md b/pyinstaller/README.md new file mode 100644 index 0000000000..9c0bf3a2f4 --- /dev/null +++ b/pyinstaller/README.md @@ -0,0 +1,5 @@ +# Pyinstaller build + +To build a binary: `pip3 install pyinstaller` + +Then from this folder run `pyinstaller --onefile specterd.spec` diff --git a/pyinstaller/hooks/hook-hwilib.devices.py b/pyinstaller/hooks/hook-hwilib.devices.py new file mode 100644 index 0000000000..be7bcec558 --- /dev/null +++ b/pyinstaller/hooks/hook-hwilib.devices.py @@ -0,0 +1,4 @@ +from hwilib.devices import __all__ +hiddenimports = [] +for d in __all__: + hiddenimports.append('hwilib.devices.' + d) \ No newline at end of file diff --git a/pyinstaller/specterd.py b/pyinstaller/specterd.py new file mode 100644 index 0000000000..902c872de9 --- /dev/null +++ b/pyinstaller/specterd.py @@ -0,0 +1,30 @@ +from logging.config import dictConfig +from cryptoadvance.specter.cli import server +import sys + +if __name__ == "__main__": + # central and early configuring of logging see + # https://flask.palletsprojects.com/en/1.1.x/logging/#basic-configuration + dictConfig({ + 'version': 1, + 'formatters': {'default': { + 'format': '[%(asctime)s] %(levelname)s in %(module)s: %(message)s', + }}, + 'handlers': {'wsgi': { + 'class': 'logging.StreamHandler', + 'stream': 'ext://flask.logging.wsgi_errors_stream', + 'formatter': 'default' + }}, + 'root': { + 'level': 'INFO', + 'handlers': ['wsgi'] + } + }) + if "--daemon" in sys.argv: + print("Daemon mode is not supported in binaries yet") + sys.exit(1) + if "--debug" in sys.argv: + print("Debug mode is useless in binary mode, don't use it") + sys.exit(1) + print("Starting Specter server. It may take a while, please be patient") + server() diff --git a/pyinstaller/specterd.spec b/pyinstaller/specterd.spec new file mode 100644 index 0000000000..ad51e33221 --- /dev/null +++ b/pyinstaller/specterd.spec @@ -0,0 +1,60 @@ +# -*- mode: python ; coding: utf-8 -*- +import platform +import subprocess +import mnemonic, os + +mnemonic_path = os.path.join(mnemonic.__path__[0], "wordlist") + +block_cipher = None + +binaries = [] +if platform.system() == 'Windows': + binaries = [("./windll/libusb-1.0.dll", ".")] +elif platform.system() == 'Linux': + if platform.processor() == 'aarch64': #ARM 64 bit + binaries = [("/lib/aarch64-linux-gnu/libusb-1.0.so.0", ".")] + else: + binaries = [("/lib/x86_64-linux-gnu/libusb-1.0.so.0", ".")] +elif platform.system() == 'Darwin': + find_brew_libusb_proc = subprocess.Popen(['brew', '--prefix', 'libusb'], stdout=subprocess.PIPE) + libusb_path = find_brew_libusb_proc.communicate()[0] + binaries = [(libusb_path.rstrip().decode() + "/lib/libusb-1.0.dylib", ".")] + +a = Analysis(['specterd.py'], + binaries=binaries, + datas=[('../src/cryptoadvance/specter/templates', 'templates'), + ('../src/cryptoadvance/specter/static', 'static'), + (mnemonic_path, 'mnemonic/wordlist'), + ], + hiddenimports=[ + 'pkg_resources.py2_warn', + 'cryptoadvance.specter.config' + ], + hookspath=['hooks/'], + runtime_hooks=[], + excludes=[], + win_no_prefer_redirects=False, + win_private_assemblies=False, + cipher=block_cipher, + noarchive=False) + +if platform.system() == 'Linux': + import hwilib + a.datas += Tree('../udev', prefix='hwilib/udev') + +pyz = PYZ(a.pure, a.zipped_data, + cipher=block_cipher) +exe = EXE(pyz, + a.scripts, + a.binaries, + a.zipfiles, + a.datas, + [], + name='specterd', + debug=False, + bootloader_ignore_signals=False, + strip=False, + upx=True, + upx_exclude=[], + runtime_tmpdir=None, + console=True ) diff --git a/pyinstaller/windll/libsecp256k1.dll b/pyinstaller/windll/libsecp256k1.dll new file mode 100755 index 0000000000..e719755863 Binary files /dev/null and b/pyinstaller/windll/libsecp256k1.dll differ diff --git a/pyinstaller/windll/libusb-1.0.dll b/pyinstaller/windll/libusb-1.0.dll new file mode 100755 index 0000000000..b2092a853c Binary files /dev/null and b/pyinstaller/windll/libusb-1.0.dll differ diff --git a/pyinstaller/windll/libusb-1.0.lib b/pyinstaller/windll/libusb-1.0.lib new file mode 100755 index 0000000000..10af16a23c Binary files /dev/null and b/pyinstaller/windll/libusb-1.0.lib differ diff --git a/pyinstaller/windll/libusb-1.0.pdb b/pyinstaller/windll/libusb-1.0.pdb new file mode 100755 index 0000000000..9faa3eb97f Binary files /dev/null and b/pyinstaller/windll/libusb-1.0.pdb differ diff --git a/src/cryptoadvance/specter/__main__.py b/src/cryptoadvance/specter/__main__.py index 4dd87db5dd..7be201a62f 100644 --- a/src/cryptoadvance/specter/__main__.py +++ b/src/cryptoadvance/specter/__main__.py @@ -1,244 +1,9 @@ -import logging from logging.config import dictConfig -import os -import sys -import time -from stem.control import Controller -from . import tor_util -import click - -import docker - -from .bitcoind import (BitcoindDockerController, - fetch_wallet_addresses_for_mining) -from .server import DATA_FOLDER, create_app, init_app - -from os import path -import signal - - -@click.group() -def cli(): - pass - - -@cli.command() -@click.option("--daemon", is_flag=True) -@click.option("--stop", is_flag=True) -@click.option("--restart", is_flag=True) -@click.option("--force", is_flag=True) -# options below can help to run it on a remote server, -# but better use nginx -@click.option("--port") # default - 25441 set to 80 for http, 443 for https -@click.option("--host", default="127.0.0.1") # set to 0.0.0.0 to make it available outside -# for https: -@click.option("--cert") -@click.option("--key") -@click.option('--debug/--no-debug', default=None) -@click.option('--tor', is_flag=True) -@click.option("--hwibridge", is_flag=True) -def server(daemon, stop, restart, force, port, host, cert, key, debug, tor, hwibridge): - # we will store our daemon PID here - pid_file = path.expanduser(path.join(DATA_FOLDER, "daemon.pid")) - toraddr_file = path.expanduser(path.join(DATA_FOLDER, "onion.txt")) - # check if pid file exists - if path.isfile(pid_file): - # if we need to stop daemon - if stop or restart: - print("Stopping the Specter server...") - with open(pid_file) as f: - pid = int(f.read()) - os.kill(pid, signal.SIGTERM) - time.sleep(0.3) - try: - os.remove(pid_file) - except Exception: - pass - elif daemon: - if not force: - print(f"PID file \"{pid_file}\" already exists. Use --force to overwrite") - return - else: - os.remove(pid_file) - if stop: - return - else: - if stop or restart: - print(f"Can't find PID file \"{pid_file}\"") - if stop: - return - - app = create_app() - app.app_context().push() - init_app(app, hwibridge=hwibridge) - - # watch templates folder to reload when something changes - extra_dirs = ['templates'] - extra_files = extra_dirs[:] - for extra_dir in extra_dirs: - for dirname, dirs, files in os.walk(extra_dir): - for filename in files: - filename = os.path.join(dirname, filename) - if os.path.isfile(filename): - extra_files.append(filename) - - # if port is not defined - get it from environment - if port is None: - port = int(os.getenv('PORT', 25441)) - else: - port = int(port) - - # certificates - if cert is None: - cert = os.getenv('CERT', None) - if key is None: - key = os.getenv('KEY', None) - - protocol = "http" - kwargs = { - "host": host, - "port": port, - "extra_files": extra_files, - } - if cert is not None and key is not None: - cert = os.path.abspath(cert) - key = os.path.abspath(key) - kwargs["ssl_context"] = (cert, key) - protocol = "https" - - if hwibridge: - app.logger.info( - "Running HWI Bridge mode, you can configure access \ - to the API at: %s://%s:%d/hwi/settings" - % (protocol, host, port) - ) - - # debug is false by default - def run(debug=debug): - with Controller.from_port() as controller: - app.controller = controller - port = 5000 # default flask port - if 'port' in kwargs: - port = kwargs['port'] - else: - kwargs['port'] = port - # if we have certificates - if "ssl_context" in kwargs: - tor_port = 443 - else: - tor_port = 80 - app.port = port - app.tor_port = tor_port - app.save_tor_address_to = toraddr_file - if debug and (tor or os.getenv('CONNECT_TOR') == 'True'): - print( - '* Warning: Cannot use Tor in debug mode. Starting in production mode instead.' - ) - if tor or os.getenv('CONNECT_TOR') == 'True': - try: - app.tor_enabled = True - tor_util.start_hidden_service(app) - except Exception as e: - print('* Failed to start Tor hidden service: {}'.format(e)) - print('* Continuing process with Tor disabled') - app.tor_service_id = None - app.tor_enabled = False - else: - app.tor_service_id = None - app.tor_enabled = False - app.run(debug=debug, **kwargs) - tor_util.stop_hidden_services(app) - - # check if we should run a daemon or not - if daemon or restart: - print("Starting server in background...") - print("* Hopefully running on %s://%s:%d/" % (protocol, host, port)) - # macOS + python3.7 is buggy - if sys.platform == "darwin" and \ - (sys.version_info.major == 3 and sys.version_info.minor < 8): - print( - "* WARNING: --daemon mode might not work properly in python 3.7 \ - and lower on MacOS. Upgrade to python 3.8+" - ) - from daemonize import Daemonize - d = Daemonize(app="specter", pid=pid_file, action=run) - d.start() - else: - # if not a daemon we can use DEBUG - if debug is None: - debug = app.config['DEBUG'] - run(debug=debug) - - -@cli.command() -@click.option('--debug/--no-debug', default=False) -@click.option('--mining/--no-mining', default=True) -@click.option('--docker-tag', "docker_tag", default="latest") -def bitcoind(debug, mining, docker_tag): - mining_every_x_seconds = 15 - if debug: - logging.getLogger().setLevel(logging.DEBUG) - click.echo(" --> starting or detecting container") - my_bitcoind = BitcoindDockerController(docker_tag=docker_tag) - try: - my_bitcoind.start_bitcoind() - except docker.errors.ImageNotFound: - click.echo(" --> Image with tag {} does not exist!".format(docker_tag)) - click.echo( - " --> Try to download first with docker pull \ - registry.gitlab.com/cryptoadvance/specter-desktop/python-bitcoind:{}" - .format(docker_tag) - ) - sys.exit(1) - tags_of_image = [image.split(":")[-1] for image in my_bitcoind.btcd_container.image.tags] - if docker_tag not in tags_of_image: - click.echo(" --> The running docker container is not the tag you requested!") - click.echo( - " --> please stop first with docker stop {}" - .format(my_bitcoind.btcd_container.id) - ) - sys.exit(1) - click.echo(" --> containerImage: %s" % my_bitcoind.btcd_container.image.tags) - click.echo(" --> url: %s" % my_bitcoind.rpcconn.render_url()) - click.echo(" --> user, password: bitcoin, secret") - click.echo(" --> host, port: localhost, 18443") - click.echo( - " --> bitcoin-cli: bitcoin-cli -regtest -rpcuser=bitcoin \ - -rpcpassword=secret getblockchaininfo " - ) - if mining: - click.echo( - " --> Now, mining a block every %i seconds. Avoid it via --no-mining" % - mining_every_x_seconds - ) - # Get each address some coins - try: - for address in fetch_wallet_addresses_for_mining(): - my_bitcoind.mine(address=address) - except FileNotFoundError: - # might happen if there no ~/.specter folder yet - pass - - # make them spendable - my_bitcoind.mine(block_count=100) - click.echo(" --> ", nl=False) - i = 0 - while True: - my_bitcoind.mine() - click.echo("%i" % (i % 10), nl=False) - if i % 10 == 9: - click.echo(" ", nl=False) - i += 1 - if i >= 50: - i = 0 - click.echo(" ") - click.echo(" --> ", nl=False) - time.sleep(mining_every_x_seconds) - +from .cli import cli if __name__ == "__main__": - # central and early configuring of logging - # see https://flask.palletsprojects.com/en/1.1.x/logging/#basic-configuration + # central and early configuring of logging see + # https://flask.palletsprojects.com/en/1.1.x/logging/#basic-configuration dictConfig({ 'version': 1, 'formatters': {'default': { diff --git a/src/cryptoadvance/specter/cli.py b/src/cryptoadvance/specter/cli.py new file mode 100644 index 0000000000..f900baa5fc --- /dev/null +++ b/src/cryptoadvance/specter/cli.py @@ -0,0 +1,268 @@ +import logging +from logging.config import dictConfig +import os +import sys +import time +from stem.control import Controller +from . import tor_util +import click + +import docker + +from .bitcoind import (BitcoindDockerController, + fetch_wallet_addresses_for_mining) +from .server import DATA_FOLDER, create_app, init_app + +from os import path +import signal + + +@click.group() +def cli(): + pass + + +@cli.command() +@click.option("--daemon", is_flag=True) +@click.option("--stop", is_flag=True) +@click.option("--restart", is_flag=True) +@click.option("--force", is_flag=True) +# options below can help to run it on a remote server, +# but better use nginx +@click.option("--port") # default - 25441 set to 80 for http, 443 for https +# set to 0.0.0.0 to make it available outside +@click.option("--host", default="127.0.0.1") +# for https: +@click.option("--cert") +@click.option("--key") +@click.option('--debug/--no-debug', default=None) +@click.option('--tor', is_flag=True) +@click.option("--hwibridge", is_flag=True) +def server(daemon, stop, restart, force, + port, host, cert, key, + debug, tor, hwibridge): + # we will store our daemon PID here + pid_file = path.expanduser(path.join(DATA_FOLDER, "daemon.pid")) + toraddr_file = path.expanduser(path.join(DATA_FOLDER, "onion.txt")) + # check if pid file exists + if path.isfile(pid_file): + # if we need to stop daemon + if stop or restart: + print("Stopping the Specter server...") + with open(pid_file) as f: + pid = int(f.read()) + os.kill(pid, signal.SIGTERM) + time.sleep(0.3) + try: + os.remove(pid_file) + except Exception: + pass + elif daemon: + if not force: + print(f"PID file \"{pid_file}\" already exists. \ + Use --force to overwrite") + return + else: + os.remove(pid_file) + if stop: + return + else: + if stop or restart: + print(f"Can't find PID file \"{pid_file}\"") + if stop: + return + + app = create_app() + app.app_context().push() + init_app(app, hwibridge=hwibridge) + + # watch templates folder to reload when something changes + extra_dirs = ['templates'] + extra_files = extra_dirs[:] + for extra_dir in extra_dirs: + for dirname, dirs, files in os.walk(extra_dir): + for filename in files: + filename = os.path.join(dirname, filename) + if os.path.isfile(filename): + extra_files.append(filename) + + # if port is not defined - get it from environment + if port is None: + port = int(os.getenv('PORT', 25441)) + else: + port = int(port) + + # certificates + if cert is None: + cert = os.getenv('CERT', None) + if key is None: + key = os.getenv('KEY', None) + + protocol = "http" + kwargs = { + "host": host, + "port": port, + "extra_files": extra_files, + } + if cert is not None and key is not None: + cert = os.path.abspath(cert) + key = os.path.abspath(key) + kwargs["ssl_context"] = (cert, key) + protocol = "https" + + if hwibridge: + app.logger.info( + "Running HWI Bridge mode, you can configure access \ + to the API at: %s://%s:%d/hwi/settings" + % (protocol, host, port) + ) + + # debug is false by default + def run(debug=debug): + try: + app.controller = Controller.from_port() + except Exception: + app.controller = None + try: + port = 5000 # default flask port + if 'port' in kwargs: + port = kwargs['port'] + else: + kwargs['port'] = port + # if we have certificates + if "ssl_context" in kwargs: + tor_port = 443 + else: + tor_port = 80 + app.port = port + app.tor_port = tor_port + app.save_tor_address_to = toraddr_file + if debug and (tor or os.getenv('CONNECT_TOR') == 'True'): + print(" * Warning: Cannot use Tor in debug mode. \ + Starting in production mode instead.") + debug = False + if tor or os.getenv('CONNECT_TOR') == 'True': + try: + app.tor_enabled = True + tor_util.start_hidden_service(app) + except Exception as e: + print(f' * Failed to start Tor hidden service: {e}') + print(' * Continuing process with Tor disabled') + app.tor_service_id = None + app.tor_enabled = False + else: + app.tor_service_id = None + app.tor_enabled = False + app.run(debug=debug, **kwargs) + tor_util.stop_hidden_services(app) + finally: + if app.controller is not None: + app.controller.close() + + # check if we should run a daemon or not + if daemon or restart: + print("Starting server in background...") + print(" * Hopefully running on %s://%s:%d/" % (protocol, host, port)) + # macOS + python3.7 is buggy + if sys.platform == "darwin" and \ + (sys.version_info.major == 3 and sys.version_info.minor < 8): + print(" * WARNING: --daemon mode might not \ + work properly in python 3.7 and lower \ + on MacOS. Upgrade to python 3.8+") + from daemonize import Daemonize + d = Daemonize(app="specter", pid=pid_file, action=run) + d.start() + else: + # if not a daemon we can use DEBUG + if debug is None: + debug = app.config['DEBUG'] + run(debug=debug) + + +@cli.command() +@click.option('--debug/--no-debug', default=False) +@click.option('--mining/--no-mining', default=True) +@click.option('--docker-tag', "docker_tag", default="latest") +def bitcoind(debug, mining, docker_tag): + mining_every_x_seconds = 15 + if debug: + logging.getLogger().setLevel(logging.DEBUG) + click.echo(" --> starting or detecting container") + my_bitcoind = BitcoindDockerController(docker_tag=docker_tag) + try: + my_bitcoind.start_bitcoind() + except docker.errors.ImageNotFound: + click.echo(f" --> Image with tag {docker_tag} does not exist!") + click.echo(f" --> Try to download first with docker pull \ + registry.gitlab.com/cryptoadvance/specter-desktop\ + /python-bitcoind:{docker_tag}") + sys.exit(1) + tags_of_image = [image.split(":")[-1] + for image in my_bitcoind.btcd_container.image.tags] + if docker_tag not in tags_of_image: + click.echo(" --> The running docker container is not \ + the tag you requested!") + click.echo( + " --> please stop first with docker stop {}" + .format(my_bitcoind.btcd_container.id) + ) + sys.exit(1) + click.echo(" --> containerImage: %s" % + my_bitcoind.btcd_container.image.tags) + click.echo(" --> url: %s" % my_bitcoind.rpcconn.render_url()) + click.echo(" --> user, password: bitcoin, secret") + click.echo(" --> host, port: localhost, 18443") + click.echo( + " --> bitcoin-cli: bitcoin-cli -regtest -rpcuser=bitcoin \ + -rpcpassword=secret getblockchaininfo " + ) + if mining: + click.echo( + " --> Now, mining a block every %i seconds. \ + Avoid it via --no-mining" % + mining_every_x_seconds + ) + # Get each address some coins + try: + for address in fetch_wallet_addresses_for_mining(): + my_bitcoind.mine(address=address) + except FileNotFoundError: + # might happen if there no ~/.specter folder yet + pass + + # make them spendable + my_bitcoind.mine(block_count=100) + click.echo(" --> ", nl=False) + i = 0 + while True: + my_bitcoind.mine() + click.echo("%i" % (i % 10), nl=False) + if i % 10 == 9: + click.echo(" ", nl=False) + i += 1 + if i >= 50: + i = 0 + click.echo(" ") + click.echo(" --> ", nl=False) + time.sleep(mining_every_x_seconds) + + +if __name__ == "__main__": + # central and early configuring of logging see + # https://flask.palletsprojects.com/en/1.1.x/logging/#basic-configuration + dictConfig({ + 'version': 1, + 'formatters': {'default': { + 'format': '[%(asctime)s] %(levelname)s in %(module)s: %(message)s', + }}, + 'handlers': {'wsgi': { + 'class': 'logging.StreamHandler', + 'stream': 'ext://flask.logging.wsgi_errors_stream', + 'formatter': 'default' + }}, + 'root': { + 'level': 'INFO', + 'handlers': ['wsgi'] + } + }) + cli() diff --git a/src/cryptoadvance/specter/controller.py b/src/cryptoadvance/specter/controller.py index 274e76dd79..c11b5fcffb 100644 --- a/src/cryptoadvance/specter/controller.py +++ b/src/cryptoadvance/specter/controller.py @@ -78,7 +78,13 @@ def inject_tor(): if request.args.get('action', '') == 'stoptor' and len(current_hidden_services) != 0: stop_hidden_services(app) if request.args.get('action', '') == 'starttor' and len(current_hidden_services) == 0: - start_hidden_service(app) + try: + start_hidden_service(app) + except Exception as e: + flash('Failed to start Tor hidden service.\ +Make sure you have Tor running with ControlPort configured and try again.\ +Error returned: {}'.format(e), 'error') + return dict(tor_service_id='', tor_enabled=False) return dict(tor_service_id=app.tor_service_id, tor_enabled=app.tor_enabled) diff --git a/src/cryptoadvance/specter/helpers.py b/src/cryptoadvance/specter/helpers.py index cbe0eaaf06..6eb9ca1286 100644 --- a/src/cryptoadvance/specter/helpers.py +++ b/src/cryptoadvance/specter/helpers.py @@ -194,6 +194,9 @@ def get_version_info(): ''' name="cryptoadvance.specter" try: + # fail right away if it's a binary + if getattr(sys, 'frozen', False): + raise RuntimeError("Using frozen binary, verision unavailable") latest_version = str(subprocess.run([sys.executable, '-m', 'pip', 'install', '{}==random'.format(name)], capture_output=True, text=True)) latest_version = latest_version[latest_version.find('(from versions:')+15:] latest_version = latest_version[:latest_version.find(')')] diff --git a/src/cryptoadvance/specter/rpc.py b/src/cryptoadvance/specter/rpc.py index e44607830e..daa010d9b7 100644 --- a/src/cryptoadvance/specter/rpc.py +++ b/src/cryptoadvance/specter/rpc.py @@ -118,7 +118,6 @@ def autodetect_cli_confs(port=None): conf_arr = detect_cli_confs() available_conf_arr = [] if len(conf_arr) > 0: - print("trying %d different configs" % len(conf_arr)) for conf in conf_arr: cli = BitcoinCLI(**conf) if port is not None: @@ -131,9 +130,6 @@ def autodetect_cli_confs(port=None): pass except Exception as e: pass - else: - print("Bitcoin-cli not found :(") - print("Detected %d bitcoin daemons" % len(available_conf_arr)) return available_conf_arr class RpcError(Exception): diff --git a/udev/20-hw1.rules b/udev/20-hw1.rules new file mode 100644 index 0000000000..1fd2c66b93 --- /dev/null +++ b/udev/20-hw1.rules @@ -0,0 +1,9 @@ +SUBSYSTEMS=="usb", ATTRS{idVendor}=="2581", ATTRS{idProduct}=="1b7c", MODE="0660", GROUP="plugdev" +SUBSYSTEMS=="usb", ATTRS{idVendor}=="2581", ATTRS{idProduct}=="2b7c", MODE="0660", GROUP="plugdev" +SUBSYSTEMS=="usb", ATTRS{idVendor}=="2581", ATTRS{idProduct}=="3b7c", MODE="0660", GROUP="plugdev" +SUBSYSTEMS=="usb", ATTRS{idVendor}=="2581", ATTRS{idProduct}=="4b7c", MODE="0660", GROUP="plugdev" +SUBSYSTEMS=="usb", ATTRS{idVendor}=="2581", ATTRS{idProduct}=="1807", MODE="0660", GROUP="plugdev" +SUBSYSTEMS=="usb", ATTRS{idVendor}=="2581", ATTRS{idProduct}=="1808", MODE="0660", GROUP="plugdev" +SUBSYSTEMS=="usb", ATTRS{idVendor}=="2c97", ATTRS{idProduct}=="0000", MODE="0660", GROUP="plugdev" +SUBSYSTEMS=="usb", ATTRS{idVendor}=="2c97", ATTRS{idProduct}=="0001", MODE="0660", GROUP="plugdev" +SUBSYSTEMS=="usb", ATTRS{idVendor}=="2c97", ATTRS{idProduct}=="0004", MODE="0660", GROUP="plugdev" \ No newline at end of file diff --git a/udev/49-micropython.rules b/udev/49-micropython.rules new file mode 100644 index 0000000000..f2b6f0681d --- /dev/null +++ b/udev/49-micropython.rules @@ -0,0 +1,5 @@ +# f055:9800 - MicroPython board +ATTRS{idVendor}=="f055", ATTRS{idProduct}=="9800", ENV{ID_MM_DEVICE_IGNORE}="1" +ATTRS{idVendor}=="f055", ATTRS{idProduct}=="9800", ENV{MTP_NO_PROBE}="1" +SUBSYSTEMS=="usb", ATTRS{idVendor}=="f055", ATTRS{idProduct}=="9800", MODE:="0666" +KERNEL=="ttyACM*", ATTRS{idVendor}=="f055", ATTRS{idProduct}=="9800", MODE:="0666" diff --git a/udev/51-coinkite.rules b/udev/51-coinkite.rules new file mode 100644 index 0000000000..27527c872e --- /dev/null +++ b/udev/51-coinkite.rules @@ -0,0 +1,8 @@ + +# probably not needed: +SUBSYSTEMS=="usb", ATTRS{idVendor}=="d13e", ATTRS{idProduct}=="cc10", GROUP="plugdev", MODE="0666" + +# required: +# from +KERNEL=="hidraw*", ATTRS{idVendor}=="d13e", ATTRS{idProduct}=="cc10", GROUP="plugdev", MODE="0666" + diff --git a/udev/51-hid-digitalbitbox.rules b/udev/51-hid-digitalbitbox.rules new file mode 100644 index 0000000000..94c86203af --- /dev/null +++ b/udev/51-hid-digitalbitbox.rules @@ -0,0 +1 @@ +SUBSYSTEM=="usb", TAG+="uaccess", TAG+="udev-acl", SYMLINK+="dbb%n", ATTRS{idVendor}=="03eb", ATTRS{idProduct}=="2402" diff --git a/udev/51-trezor.rules b/udev/51-trezor.rules new file mode 100644 index 0000000000..50472b9522 --- /dev/null +++ b/udev/51-trezor.rules @@ -0,0 +1,17 @@ +# TREZOR: The Original Hardware Wallet +# https://trezor.io/ +# +# Put this file into /etc/udev/rules.d +# +# If you are creating a distribution package, +# put this into /usr/lib/udev/rules.d or /lib/udev/rules.d +# depending on your distribution + +# TREZOR +SUBSYSTEM=="usb", ATTR{idVendor}=="534c", ATTR{idProduct}=="0001", MODE="0660", GROUP="plugdev", TAG+="uaccess", TAG+="udev-acl", SYMLINK+="trezor%n" +KERNEL=="hidraw*", ATTRS{idVendor}=="534c", ATTRS{idProduct}=="0001", MODE="0660", GROUP="plugdev", TAG+="uaccess", TAG+="udev-acl" + +# TREZOR v2 +SUBSYSTEM=="usb", ATTR{idVendor}=="1209", ATTR{idProduct}=="53c0", MODE="0660", GROUP="plugdev", TAG+="uaccess", TAG+="udev-acl", SYMLINK+="trezor%n" +SUBSYSTEM=="usb", ATTR{idVendor}=="1209", ATTR{idProduct}=="53c1", MODE="0660", GROUP="plugdev", TAG+="uaccess", TAG+="udev-acl", SYMLINK+="trezor%n" +KERNEL=="hidraw*", ATTRS{idVendor}=="1209", ATTRS{idProduct}=="53c1", MODE="0660", GROUP="plugdev", TAG+="uaccess", TAG+="udev-acl" diff --git a/udev/51-usb-keepkey.rules b/udev/51-usb-keepkey.rules new file mode 100644 index 0000000000..6e38213d38 --- /dev/null +++ b/udev/51-usb-keepkey.rules @@ -0,0 +1,11 @@ +# KeepKey: Your Private Bitcoin Vault +# http://www.keepkey.com/ +# Put this file into /usr/lib/udev/rules.d or /etc/udev/rules.d + +# KeepKey HID Firmware/Bootloader +SUBSYSTEM=="usb", ATTR{idVendor}=="2b24", ATTR{idProduct}=="0001", MODE="0666", GROUP="plugdev", TAG+="uaccess", TAG+="udev-acl", SYMLINK+="keepkey%n" +KERNEL=="hidraw*", ATTRS{idVendor}=="2b24", ATTRS{idProduct}=="0001", MODE="0666", GROUP="plugdev", TAG+="uaccess", TAG+="udev-acl" + +# KeepKey WebUSB Firmware/Bootloader +SUBSYSTEM=="usb", ATTR{idVendor}=="2b24", ATTR{idProduct}=="0002", MODE="0666", GROUP="plugdev", TAG+="uaccess", TAG+="udev-acl", SYMLINK+="keepkey%n" +KERNEL=="hidraw*", ATTRS{idVendor}=="2b24", ATTRS{idProduct}=="0002", MODE="0666", GROUP="plugdev", TAG+="uaccess", TAG+="udev-acl" diff --git a/udev/52-hid-digitalbitbox.rules b/udev/52-hid-digitalbitbox.rules new file mode 100644 index 0000000000..84fe717211 --- /dev/null +++ b/udev/52-hid-digitalbitbox.rules @@ -0,0 +1 @@ +KERNEL=="hidraw*", SUBSYSTEM=="hidraw", ATTRS{idVendor}=="03eb", ATTRS{idProduct}=="2402", TAG+="uaccess", TAG+="udev-acl", SYMLINK+="dbbf%n" diff --git a/udev/README.md b/udev/README.md new file mode 100644 index 0000000000..34918d3522 --- /dev/null +++ b/udev/README.md @@ -0,0 +1,24 @@ +# udev rules + +This directory contains all of the udev rules for the supported devices as retrieved from vendor websites and repositories. +These are necessary for the devices to be reachable on linux environments. + + - `20-hw1.rules` (Ledger): https://github.com/LedgerHQ/udev-rules/blob/master/20-hw1.rules + - `51-coinkite.rules` (Coldcard): https://github.com/Coldcard/ckcc-protocol/blob/master/51-coinkite.rules + - `51-hid-digitalbitbox.rules`, `52-hid-digitalbitbox.rules` (Digital Bitbox): https://shiftcrypto.ch/start_linux + - `51-trezor.rules` (Trezor): https://github.com/trezor/trezor-common/blob/master/udev/51-trezor.rules + - `51-usb-keepkey.rules` (Keepkey): https://github.com/keepkey/udev-rules/blob/master/51-usb-keepkey.rules + - `49-micropython.rules` (Specter-DIY): http://wiki.micropython.org/Installation#USB-Permissioning-on-Linux + +# Usage + +Apply these rules by copying them to `/etc/udev/rules.d/` and notifying `udevadm`. +Your user will need to be added to the `plugdev` group, which needs to be created if it does not already exist. + +``` +$ sudo cp udev/*.rules /etc/udev/rules.d/ +$ sudo udevadm trigger +$ sudo udevadm control --reload-rules +$ sudo groupadd plugdev +$ sudo usermod -aG plugdev `whoami` +``` \ No newline at end of file