Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Pyinstaller builds #258

Merged
merged 14 commits into from
Jul 26, 2020
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -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 changes: 5 additions & 0 deletions pyinstaller/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# Pyinstaller build

To build a binary: `pip3 install pyinstaller`

Then from this folder run `pyinstaller --onefile specterd.spec`
4 changes: 4 additions & 0 deletions pyinstaller/hooks/hook-hwilib.devices.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
from hwilib.devices import __all__
hiddenimports = []
for d in __all__:
hiddenimports.append('hwilib.devices.' + d)
30 changes: 30 additions & 0 deletions pyinstaller/specterd.py
Original file line number Diff line number Diff line change
@@ -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 changes: 60 additions & 0 deletions pyinstaller/specterd.spec
Original file line number Diff line number Diff line change
@@ -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 added pyinstaller/windll/libsecp256k1.dll
Binary file not shown.
Binary file added pyinstaller/windll/libusb-1.0.dll
Binary file not shown.
Binary file added pyinstaller/windll/libusb-1.0.lib
Binary file not shown.
Binary file added pyinstaller/windll/libusb-1.0.pdb
Binary file not shown.
241 changes: 3 additions & 238 deletions src/cryptoadvance/specter/__main__.py
Original file line number Diff line number Diff line change
@@ -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': {
Expand Down
Loading