Automatic Tor Hidden Service Support (#257)

* Add Tor url to sidebar

* Dynamic start/ stop Tor

* Start Tor hidden service by default (if possible)

* Updates

* fixes

* Update tor_address.jinja

* Update tor_address.jinja

* Allow starting Tor after running specter

* Delete .env_example
This commit is contained in:
benk10 2020-07-25 00:06:34 +03:00 committed by GitHub
parent 779f8c8ac9
commit 92ebff592c
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
10 changed files with 339 additions and 122 deletions

View file

@ -1,3 +0,0 @@
# The cleartext password that was entered into:
# $ tor --hash-password "your-tor-passphrase"
TOR_PASSWORD=your-tor-passphrase

View file

@ -14,23 +14,12 @@ Install Tor on the same server that you'll be running Specter Desktop:
* [Debian / Ubuntu](https://2019.www.torproject.org/docs/debian.html.en)
* [macOS](https://2019.www.torproject.org/docs/tor-doc-osx.html.en)
### Configure Tor authentication
```sh
$ tor --hash-password "your-tor-passphrase"
```
That returns a password hash such as:
```sh
16:CE9058DA89498A4160373C70FF7FFF70CC2E20B6788FC48F5C35B2E85B
```
Update your `torrc` config file (usually `/etc/tor/torrc` or `/usr/local/etc/tor/torrc` on macOS Homebrew installs). Uncomment the `ControlPort` line as well as the `HashedControlPassword` line. Remember to paste in your own hashed password result from above.
### Configure Tor port
Update your `torrc` config file (usually `/etc/tor/torrc` or `/usr/local/etc/tor/torrc` on macOS Homebrew installs) and uncomment the `ControlPort` line.
```sh
## The port on which Tor will listen for local connections from Tor
## controller applications, as documented in control-spec.txt.
ControlPort 9051
## If you enable the controlport, be sure to enable one of these
## authentication methods, to prevent attackers from accessing it.
HashedControlPassword 16:CE9058DA89498A4160373C70FF7FFF70CC2E20B6788FC48F5C35B2E85B
#CookieAuthentication 1
```
Restart the Tor service:
@ -39,10 +28,10 @@ Restart the Tor service:
### Running with Tor using command line
You can start the server and provide your tor password using `--tor=` flag:
You can start the server using `--tor` flag:
```sh
$ python3 -m cryptoadvance.specter server --tor=your-tor-passphrase
$ python3 -m cryptoadvance.specter server --tor
```
### Configure environment variables
@ -59,22 +48,6 @@ FLASK_ENV=production
#FLASK_ENV=development
```
### Specify Tor secrets
The Tor password that we hashed above will need to be shared with Specter Desktop.
Copy the example `.env_example` file:
```sh
$ cp .env_example .env
```
And then edit `.env` and specify `TOR_PASSWORD`:
```sh
# The cleartext password that was entered into:
# $ tor --hash-password "your-tor-passphrase"
TOR_PASSWORD=your-tor-passphrase
```
### Launch with Tor
Now just start Specter Desktop as usual:

View file

@ -3,7 +3,6 @@ certifi==2019.9.11
chardet==3.0.4
Click==7.0
daemonize==2.5.0
ecdsa>=0.13.3
Flask==1.1.2
Flask-Cors==3.0.8
Flask-Login==0.5.0
@ -12,7 +11,7 @@ pyserial==3.4
python-dotenv==0.13.0
requests==2.23.0
six==1.12.0
stem==1.7.1
stem==1.8.0
# only for testing currently
docker==4.1.0
pytest==5.2.2

View file

@ -1,17 +1,16 @@
import atexit
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 .helpers import which
from .server import DATA_FOLDER, create_app, init_app
from os import path
@ -30,15 +29,15 @@ def cli():
@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
@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")
# provide tor password here
@click.option("--tor")
@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, tor, hwibridge):
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"))
@ -53,7 +52,7 @@ def server(daemon, stop, restart, force, port, host, cert, key, tor, hwibridge):
time.sleep(0.3)
try:
os.remove(pid_file)
except Exception as e:
except Exception:
pass
elif daemon:
if not force:
@ -82,7 +81,7 @@ def server(daemon, stop, restart, force, port, host, cert, key, tor, hwibridge):
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))
@ -108,53 +107,74 @@ def server(daemon, stop, restart, force, port, host, cert, key, tor, hwibridge):
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))
# if tor password is not provided but env variable is set
if tor is None and os.getenv('CONNECT_TOR') == 'True':
from dotenv import load_dotenv
load_dotenv() # Load the secrets from .env
tor = os.getenv('TOR_PASSWORD')
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=False):
if tor is not None:
from . import tor_util
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
tor_util.run_on_hidden_service(app,
debug=False,
tor_password=tor,
tor_port=tor_port,
save_address_to=toraddr_file,
**kwargs)
else:
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))
if tor is not None:
print("* For onion address check the file %s" % toraddr_file)
# 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+")
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
run(app.config['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):
def bitcoind(debug, mining, docker_tag):
mining_every_x_seconds = 15
if debug:
logging.getLogger().setLevel(logging.DEBUG)
@ -164,20 +184,33 @@ def bitcoind(debug,mining, docker_tag):
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))
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 not docker_tag in tags_of_image:
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))
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 ")
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)
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():
@ -188,22 +221,21 @@ def bitcoind(debug,mining, docker_tag):
# make them spendable
my_bitcoind.mine(block_count=100)
click.echo(" --> ",nl=False)
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)
click.echo("%i" % (i % 10), nl=False)
if i % 10 == 9:
click.echo(" ", nl=False)
i += 1
if i >= 50:
i=0
i = 0
click.echo(" ")
click.echo(" --> ",nl=False)
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
@ -222,4 +254,4 @@ if __name__ == "__main__":
'handlers': ['wsgi']
}
})
cli()
cli()

View file

@ -30,6 +30,8 @@ from io import BytesIO
import traceback
from .devices.electrum import b43_decode
from binascii import b2a_base64
from .tor_util import start_hidden_service, stop_hidden_services
from stem.control import Controller
from pathlib import Path
env_path = Path('.') / '.flaskenv'
@ -55,12 +57,31 @@ def selfcheck():
if app.config.get('LOGIN_DISABLED'):
app.login('admin')
########## template injections #############
@app.context_processor
def inject_debug():
''' Can be used in all jinja2 templates '''
return dict(debug=app.config['DEBUG'])
@app.context_processor
def inject_tor():
if app.config['DEBUG']:
return dict(tor_service_id='', tor_enabled=False)
if request.args.get('action', '') == 'stoptor' or request.args.get('action', '') == 'starttor':
if hasattr(current_user, 'is_admin') and current_user.is_admin:
try:
current_hidden_services = app.controller.list_ephemeral_hidden_services()
except Exception:
current_hidden_services = []
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)
return dict(tor_service_id=app.tor_service_id, tor_enabled=app.tor_enabled)
################ routes ####################
@app.route('/wallets/<wallet_alias>/combine/', methods=['GET', 'POST'])
@login_required
@ -282,10 +303,10 @@ def general_settings():
hwi_bridge_url = request.form['hwi_bridge_url']
if current_user.is_admin:
loglevel = request.form['loglevel']
if action == "save":
if current_user.is_admin:
set_loglevel(app,loglevel)
set_loglevel(app, loglevel)
app.specter.update_explorer(explorer, current_user)
app.specter.update_hwi_bridge_url(hwi_bridge_url, current_user)
@ -335,7 +356,7 @@ def bitcoin_core_settings():
passwd = request.form['password']
port = request.form['port']
host = request.form['host']
# protocol://host
if "://" in host:
arr = host.split("://")
@ -706,7 +727,7 @@ def new_wallet(wallet_type):
err = "%r" % e
wallet.getdata()
return redirect("/wallets/%s/" % wallet.alias)
return render_template(
"wallet/new_wallet/new_wallet.jinja",
wallet_type=wallet_type,

View file

@ -0,0 +1,31 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="512px" height="512px" viewBox="0 0 512 512" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<defs>
<linearGradient x1="50%" y1="100%" x2="50%" y2="0%" id="linearGradient-1">
<stop stop-color="#420C5D" offset="0%"></stop>
<stop stop-color="#951AD1" offset="100%"></stop>
</linearGradient>
<path d="M25,29 C152.577777,29 256,131.974508 256,259 C256,386.025492 152.577777,489 25,489 L25,29 Z" id="path-2"></path>
<filter x="-18.2%" y="-7.4%" width="129.4%" height="114.8%" filterUnits="objectBoundingBox" id="filter-3">
<feOffset dx="-8" dy="0" in="SourceAlpha" result="shadowOffsetOuter1"></feOffset>
<feGaussianBlur stdDeviation="10" in="shadowOffsetOuter1" result="shadowBlurOuter1"></feGaussianBlur>
<feColorMatrix values="0 0 0 0 0.250980392 0 0 0 0 0.250980392 0 0 0 0 0.250980392 0 0 0 0.2 0" type="matrix" in="shadowBlurOuter1"></feColorMatrix>
</filter>
</defs>
<g id="tor-browser-icon" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<g id="icon_512x512">
<g id="Group">
<g id="tb_icon/Stable">
<g id="Stable">
<circle id="background" fill="#F2E4FF" fill-rule="nonzero" cx="256" cy="256" r="246"></circle>
<path d="M256.525143,465.439707 L256.525143,434.406609 C354.826191,434.122748 434.420802,354.364917 434.420802,255.992903 C434.420802,157.627987 354.826191,77.8701558 256.525143,77.5862948 L256.525143,46.5531962 C371.964296,46.8441537 465.446804,140.489882 465.446804,255.992903 C465.446804,371.503022 371.964296,465.155846 256.525143,465.439707 Z M256.525143,356.820314 C311.970283,356.529356 356.8487,311.516106 356.8487,255.992903 C356.8487,200.476798 311.970283,155.463547 256.525143,155.17259 L256.525143,124.146588 C329.115485,124.430449 387.881799,183.338693 387.881799,255.992903 C387.881799,328.654211 329.115485,387.562455 256.525143,387.846316 L256.525143,356.820314 Z M256.525143,201.718689 C286.266674,202.00255 310.3026,226.180407 310.3026,255.992903 C310.3026,285.812497 286.266674,309.990353 256.525143,310.274214 L256.525143,201.718689 Z M0,255.992903 C0,397.384044 114.60886,512 256,512 C397.384044,512 512,397.384044 512,255.992903 C512,114.60886 397.384044,0 256,0 C114.60886,0 0,114.60886 0,255.992903 Z" id="center" fill="url(#linearGradient-1)"></path>
<g id="half" transform="translate(140.500000, 259.000000) scale(-1, 1) translate(-140.500000, -259.000000) ">
<use fill="black" fill-opacity="1" filter="url(#filter-3)" xlink:href="#path-2"></use>
<use fill="url(#linearGradient-1)" fill-rule="evenodd" xlink:href="#path-2"></use>
</g>
</g>
</g>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.9 KiB

View file

@ -0,0 +1,144 @@
<style>
#onion-address-container {
max-width: 220px;
padding: 10px;
margin: 10px;
background: #7D4698;
border-radius: 10px;
}
#tor-open-btn {
position: absolute;
cursor: pointer;
margin-left: 210px;
width: 24px;
}
#tor-close-btn {
cursor: pointer;
color: #fff;
font-size: 0.8em;
float: right;
margin-bottom: 5px;
}
.onion-address {
word-wrap: break-word;
color: #fff;
font-size: 0.7em;
text-decoration: none;
}
.onion-address:hover {
text-decoration: underline;
cursor: pointer;
}
.dot {
cursor: pointer;
height: 8px;
width: 8px;
position: absolute;
border-radius: 50%;
display: inline-block;
}
.dot-on {
{% if tor_enabled %}
background-color: #0f0;
{% else %}
background-color: #ccc;
{% endif %}
}
.dot-off {
{% if tor_enabled %}
background-color: #f00;
{% else %}
background-color: #ccc;
{% endif %}
}
.tor-btn {
margin-top: 10px;
width: 90px;
border: none;
font-size: 0.8em;
color: #fff;
padding: 5px;
border-radius: 5px;
border: 1px solid #59316B;
background: #7D4698;
}
.tor-btn:hover {
cursor: pointer;
background: #59316B;
}
</style>
<div style="position: relative;">
<img id="tor-open-btn" src="/static/img/tor.svg"/>
<span class="dot {% if tor_service_id %}dot-on{% else %}dot-off{% endif %}" id="tor-symbol-status-dot" style="margin-left: 227px;"></span>
</div>
<div id="onion-address-container" class="hidden">
<div style="position: relative;">
<img id="tor-close-btn" src="/static/img/tor.svg" style="width: 24px; position: absolute; left: 177px;"/>
<span class="dot {% if tor_service_id %}dot-on{% else %}dot-off{% endif %}" id="tor-popup-status-dot" style="left: 193px;"></span>
</div>
{% if tor_service_id %}
<span style="font-size: 0.92em; color: #fff;">Specter Running on Tor</span><br>
<div style="margin-top: 5px;"> <span style="font-size: 0.75em; color: #fff;">Running at:</span></div>
<div class="center">
<span title="Copy Tor address" class="onion-address centered" onclick="copyText('{{ tor_service_id }}.onion', 'Copied Tor hidden service address: {{ tor_service_id }}.onion')">
{{ tor_service_id }}.onion
</span>
</div>
{% if current_user.is_admin and (tor_service_id and tor_service_id + '.onion' not in request.url) %}
<form action="?" class="center">
<button class="tor-btn" type="submit" name="action" value="stoptor">Stop Tor</button>
</form>
{% endif %}
{% else %}
{% if debug %}
<span style="font-size: 0.92em; color: #fff;">Tor is unavailable...</span><br>
<div style="margin-top: 5px;">
<span style="font-size: 0.75em; color: #fff;">You are running Specter in debug mode, but Tor hidden serices are only available in production mode.</span>
</div>
{% else %}
<span style="font-size: 0.92em; color: #fff;">Tor service is down...</span><br>
{% if current_user.is_admin and ('.onion' not in request.url) %}
<form action="?" class="center">
<button class="tor-btn" type="submit" name="action" value="starttor">Start Tor</button>
</form>
{% endif %}
{% endif %}
{% endif %}
</div>
<script>
document.addEventListener("DOMContentLoaded", function() {
let torOpenBtn = document.getElementById('tor-open-btn');
let torCloseBtn = document.getElementById('tor-close-btn');
let torDataContainer = document.getElementById('onion-address-container');
let torPopupStatusDot = document.getElementById('tor-popup-status-dot');
let torSymbolStatusDot = document.getElementById('tor-symbol-status-dot');
function openTorPopup() {
torOpenBtn.style.display = 'none';
torDataContainer.style.display = 'block';
torPopupStatusDot.style.display = 'inline-block';
torSymbolStatusDot.style.display = 'none';
}
function closeTorPopup() {
torOpenBtn.style.display = 'block';
torDataContainer.style.display = 'none';
torPopupStatusDot.style.display = 'none';
torSymbolStatusDot.style.display = 'inline-block';
}
torSymbolStatusDot.addEventListener("click", openTorPopup);
torOpenBtn.addEventListener("click", openTorPopup);
torPopupStatusDot.addEventListener("click", closeTorPopup);
torCloseBtn.addEventListener("click", closeTorPopup);
});
</script>

View file

@ -1,6 +1,7 @@
{% from 'includes/sidebar/components/sidebar_btn.jinja' import sidebar_btn %}
<div id="side-content">
<nav class="side">
{% include "includes/sidebar/components/tor_address.jinja" %}
{% if specter.info["initialblockdownload"] %}
<p class="warning">⚠️<br>Blockchain is still syncing...<br>Data may be shown incorrectly.</p>
{% endif %}

View file

@ -1,45 +1,62 @@
import os
import stem
from stem.control import Controller
from .server import DATA_FOLDER
def run_on_hidden_service(app, tor_password=None, tor_port=80, save_address_to=None, **kwargs):
port = 5000 # default flask port
if "port" in kwargs:
port = kwargs["port"]
def start_hidden_service(app):
app.controller.reconnect()
key_path = os.path.abspath(
os.path.expanduser(os.path.join(DATA_FOLDER, '.tor_service_key'))
)
app.tor_service_id = None
if not os.path.exists(key_path):
service = app.controller.create_ephemeral_hidden_service(
{app.tor_port: app.port}, await_publication=True
)
app.tor_service_id = service.service_id
print(
'* Started a new hidden service with the address of %s.onion'
% app.tor_service_id
)
with open(key_path, 'w') as key_file:
key_file.write(
'%s:%s' % (service.private_key_type, service.private_key)
)
else:
kwargs["port"] = port
with open(key_path) as key_file:
key_type, key_content = key_file.read().split(':', 1)
with Controller.from_port() as controller:
print(' * Connecting to tor')
controller.authenticate(tor_password)
service = app.controller.create_ephemeral_hidden_service(
{app.tor_port: app.port},
key_type=key_type,
key_content=key_content,
await_publication=True,
)
app.tor_service_id = service.service_id
print('* Resumed %s.onion' % app.tor_service_id)
key_path = os.path.abspath(os.path.expanduser(os.path.join(DATA_FOLDER,'.tor_service_key')))
tor_service_id = None
# save address to file
if app.save_tor_address_to is not None:
with open(app.save_tor_address_to, 'w') as f:
f.write('%s.onion' % app.tor_service_id)
app.tor_service_id = app.tor_service_id
app.tor_enabled = True
if not os.path.exists(key_path):
service = controller.create_ephemeral_hidden_service({tor_port: port}, await_publication = True)
tor_service_id = service.service_id
print("* Started a new hidden service with the address of %s.onion" % tor_service_id)
with open(key_path, 'w') as key_file:
key_file.write('%s:%s' % (service.private_key_type, service.private_key))
def stop_hidden_services(app):
try:
app.controller.reconnect()
hidden_services = app.controller.list_ephemeral_hidden_services()
print(' * Shutting down our hidden service')
for tor_service_id in hidden_services:
app.controller.remove_ephemeral_hidden_service(tor_service_id)
# Sanity
if (len(app.controller.list_ephemeral_hidden_services()) != 0):
print(' * Failed to shut down our hidden services...')
else:
with open(key_path) as key_file:
key_type, key_content = key_file.read().split(':', 1)
service = controller.create_ephemeral_hidden_service({tor_port: port}, key_type = key_type, key_content = key_content, await_publication = True)
tor_service_id = service.service_id
print("* Resumed %s.onion" % tor_service_id)
# save address to file
if save_address_to is not None:
with open(save_address_to, "w") as f:
f.write("%s.onion" % tor_service_id)
try:
app.run(**kwargs)
finally:
if tor_service_id:
print(" * Shutting down our hidden service")
controller.remove_ephemeral_hidden_service(tor_service_id)
print(' * Hidden services were shut down successfully')
app.tor_service_id = None
except Exception:
pass # we tried...

View file

@ -241,6 +241,8 @@ def app(specter_regtest_configured):
app.app_context().push()
app.config["TESTING"]=True
app.testing = True
app.tor_service_id = None
app.tor_enabled = False
init_app(app, specter=specter_regtest_configured)
return app