Pyinstaller builds (#258)

* pyinstaller specs

* add udev rules

* remove ecdsa dep

* udev rules, pyinstaller spec update

* update udev readme

* Fix issue with Tor service creation

* remove pyinstaller artifacts for bip32

* main -> cli

* remove daemon and debug modes from binaries

* add aarch64 (ARM) build

* fix when no tor

* fix flake8

Co-authored-by: benk10 <ben.kaufman10@gmail.com>
This commit is contained in:
Stepan Snigirev 2020-07-26 11:20:13 +02:00 committed by GitHub
parent 6e0bb08744
commit 2575aed4ad
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
22 changed files with 458 additions and 244 deletions

3
.gitignore vendored
View file

@ -2,10 +2,11 @@ __pycache__
.pytest_cache
*.pyc
.env
.venv
.tor_service_key
build
dist
*.egg-info
.vscode
cert.pem
key.pem
key.pem

5
pyinstaller/README.md Normal file
View file

@ -0,0 +1,5 @@
# Pyinstaller build
To build a binary: `pip3 install pyinstaller`
Then from this folder run `pyinstaller --onefile specterd.spec`

View file

@ -0,0 +1,4 @@
from hwilib.devices import __all__
hiddenimports = []
for d in __all__:
hiddenimports.append('hwilib.devices.' + d)

30
pyinstaller/specterd.py Normal file
View file

@ -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()

60
pyinstaller/specterd.spec Normal file
View file

@ -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 )

Binary file not shown.

BIN
pyinstaller/windll/libusb-1.0.dll Executable file

Binary file not shown.

BIN
pyinstaller/windll/libusb-1.0.lib Executable file

Binary file not shown.

BIN
pyinstaller/windll/libusb-1.0.pdb Executable file

Binary file not shown.

View file

@ -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': {

View file

@ -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()

View file

@ -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)

View file

@ -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(')')]

View file

@ -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):

9
udev/20-hw1.rules Normal file
View file

@ -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"

View file

@ -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"

8
udev/51-coinkite.rules Normal file
View file

@ -0,0 +1,8 @@
# probably not needed:
SUBSYSTEMS=="usb", ATTRS{idVendor}=="d13e", ATTRS{idProduct}=="cc10", GROUP="plugdev", MODE="0666"
# required:
# from <https://github.com/signal11/hidapi/blob/master/udev/99-hid.rules>
KERNEL=="hidraw*", ATTRS{idVendor}=="d13e", ATTRS{idProduct}=="cc10", GROUP="plugdev", MODE="0666"

View file

@ -0,0 +1 @@
SUBSYSTEM=="usb", TAG+="uaccess", TAG+="udev-acl", SYMLINK+="dbb%n", ATTRS{idVendor}=="03eb", ATTRS{idProduct}=="2402"

17
udev/51-trezor.rules Normal file
View file

@ -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"

11
udev/51-usb-keepkey.rules Normal file
View file

@ -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"

View file

@ -0,0 +1 @@
KERNEL=="hidraw*", SUBSYSTEM=="hidraw", ATTRS{idVendor}=="03eb", ATTRS{idProduct}=="2402", TAG+="uaccess", TAG+="udev-acl", SYMLINK+="dbbf%n"

24
udev/README.md Normal file
View file

@ -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`
```