Bugfix/UIUX: Fix Tor configuration issues and improve UX of built-in Tor (#2304)

* fix: tor control port input was not saved
* fix built-in tor issues and improve ux
* change copy in tor settings
* display btc price without decimals + simplify the filter
* fix wrong default value in config manager
This commit is contained in:
Manolis Mandrapilias 2023-03-20 19:00:23 +01:00 committed by GitHub
parent e9d6dd89d5
commit f3bb281ac4
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
10 changed files with 667 additions and 562 deletions

View file

@ -185,7 +185,7 @@ class ConfigManager(GenericDataManager):
def update_tor_type(self, tor_type, user):
"""update the Tor type to use"""
if self.data.get("tor_type", "builtin") != tor_type:
if self.data.get("tor_type") != tor_type:
self.data["tor_type"] = tor_type
self._save()
@ -205,7 +205,6 @@ class ConfigManager(GenericDataManager):
if self.data["tor_control_port"] != tor_control_port:
self.data["tor_control_port"] = tor_control_port
self._save()
self.update_tor_controller()
def generate_torrc_password(self, overwrite=False):
if "torrc_password" not in self.data or overwrite:

View file

@ -137,11 +137,7 @@ def altunit(context, value):
if value < 0:
return "-"
if app.specter.price_check and (app.specter.alt_rate and app.specter.alt_symbol):
rate = (
"{:,.2f}".format(float(value) * float(app.specter.alt_rate))
.rstrip("0")
.rstrip(".")
)
rate = "{:,.0f}".format(float(value) * float(app.specter.alt_rate))
if app.specter.alt_symbol in ["$", "£"]:
return app.specter.alt_symbol + rate
else:

View file

@ -222,7 +222,7 @@ def general():
@login_required
def tor():
"""
controls the tor related settings
Controls the Tor related settings
GET for displaying the page, POST for updates
param action might be "save", "test_tor" or "toggle_hidden_service"
param proxy_url the Tor deamon url, usually something like socks5h://localhost:9050
@ -235,6 +235,7 @@ def tor():
current_version = notify_upgrade(app, flash)
proxy_url = app.specter.proxy_url
only_tor = app.specter.only_tor
unselectOnlyTor = False
tor_control_port = app.specter.tor_control_port
tor_type = app.specter.tor_type
# This is true if the Python interpreter has been bundled with the application into a single executable, so basically it is true for the apps but not for pip-installations
@ -248,17 +249,64 @@ def tor():
hidden_service = request.form.get("hidden_service") == "on"
if action == "save":
logger.info("Updating Tor settings...")
app.specter.update_tor_type(tor_type, current_user)
# Remove the built-in Tor setup if the user has it and is checking the "Disabled" radio button
if tor_type == "disabled" and app.specter.tor_type == "builtin":
if app.specter.is_tor_dameon_running():
app.specter.tor_daemon.stop_tor_daemon()
shutil.rmtree(os.path.join(app.specter.data_folder, "tor-binaries"))
os.remove(os.path.join(app.specter.data_folder, "torrc"))
flash("Tor disabled. Built-in Tor setup uninstalled")
# Also reset Tor only mode if it was used
if app.specter.only_tor:
unselectOnlyTor = True
app.specter.update_only_tor(False, current_user)
# Reset Tor only mode if it was used if user chooses the disable option
if tor_type == "disabled" and app.specter.tor_type == "custom":
if app.specter.only_tor:
unselectOnlyTor = True
app.specter.update_only_tor(False, current_user)
if tor_type == "custom":
app.specter.update_proxy_url(proxy_url, current_user)
app.specter.update_tor_control_port(tor_control_port, current_user)
# Only save the custom setup if we can connect
try:
requests_session = requests.Session()
requests_session.proxies["http"] = proxy_url
requests_session.proxies["https"] = proxy_url
res = requests_session.get(
"http://2gzyxa5ihm7nsggfxnu52rck2vv4rvmdlkiu3zzui5du4xyclen53wid.onion/", # Tor Project onion v3 website
timeout=30,
)
if res.status_code == 200:
app.specter.update_proxy_url(proxy_url, current_user)
app.specter.update_tor_control_port(
tor_control_port, current_user
)
flash(_("Custom Tor setup is working and saved!"), "info")
except Exception:
flash(
_("Custom Tor setup is not working and couldn't be saved!"),
"error",
)
return redirect(request.referrer or "/")
else:
proxy_url = "socks5h://localhost:9050"
tor_control_port = ""
app.specter.update_only_tor(only_tor, current_user)
logger.info("Updating Tor settings...")
app.specter.update_tor_type(tor_type, current_user)
# Updates "only_tor" to True only if tor_type is not "disabled"; setting to False without check for existing Tor config
if only_tor == True:
if tor_type == "disabled":
flash(
"You can't enforce to use Tor with Tor being disabled.", "error"
)
unselectOnlyTor = True
else:
app.specter.update_only_tor(only_tor, current_user)
else:
app.specter.update_only_tor(only_tor, current_user)
if hidden_service != app.specter.config["tor_status"]:
if not app.config["DEBUG"]:
if app.specter.config["auth"].get("method", "none") == "none":
@ -302,19 +350,25 @@ def tor():
logger.info("Starting Tor...")
try:
app.specter.tor_daemon.start_tor_daemon()
if only_tor:
app.specter.update_only_tor(True, current_user)
flash(_("Specter has started Tor"))
except Exception as e:
flash(_("Failed to start Tor, error: {}").format(e), "error")
logger.error(f"Failed to start Tor, error: {e}")
elif action == "stoptor":
logger.info("Stopping Tor...")
try:
app.specter.tor_daemon.stop_tor_daemon()
time.sleep(1)
time.sleep(2)
if app.specter.only_tor:
unselectOnlyTor = True
app.specter.update_only_tor(False, current_user)
flash(_("Specter stopped Tor successfully"))
except Exception as e:
flash(_("Failed to stop Tor, error: {}").format(e), "error")
logger.exception(f"Failed to start Tor, error: {e}", e)
logger.exception(f"Failed to stop Tor, error: {e}", e)
elif action == "uninstalltor":
logger.info("Uninstalling Tor...")
try:
@ -322,6 +376,8 @@ def tor():
app.specter.tor_daemon.stop_tor_daemon()
shutil.rmtree(os.path.join(app.specter.data_folder, "tor-binaries"))
os.remove(os.path.join(app.specter.data_folder, "torrc"))
tor_type = "disabled"
app.specter.update_tor_type(tor_type, current_user)
flash(_("Tor uninstalled successfully"))
except Exception as e:
flash(_("Failed to uninstall Tor, error: {}").format(e), "error")
@ -377,6 +433,7 @@ def tor():
tor_builtin_possible=tor_builtin_possible,
proxy_url=proxy_url,
only_tor=only_tor,
unselectOnlyTor=unselectOnlyTor,
tor_control_port=tor_control_port,
tor_service_id=app.tor_service_id,
torbrowser_installed=os.path.isfile(app.specter.torbrowser_path),

View file

@ -168,7 +168,7 @@ class Specter:
if self.tor_type == "builtin" and os.path.isfile(self.torbrowser_path):
self.tor_daemon.start_tor_daemon()
if self.tor_type != "none":
if self.tor_type != "disabled":
self.update_tor_controller()
self.checker = Checker(lambda: self.check(check_all=True), desc="health")
@ -402,13 +402,13 @@ class Specter:
# mark
def update_tor_control_port(self, tor_control_port, user):
"""set the control port of the tor daemon"""
if self.config_manager.update_tor_control_port:
self.update_tor_controller()
self.config_manager.update_tor_control_port(tor_control_port, user)
# mark
def generate_torrc_password(self, overwrite=False):
self.config_manager.generate_torrc_password(overwrite)
# This is only used for the custom Tor setup, the built-in setup uses TorDaemonController
def update_tor_controller(self):
if "torrc_password" not in self.config:
# Will be missing if the user did not go through the built-in Tor setup
@ -614,7 +614,7 @@ class Specter:
@property
def tor_type(self):
return self.user_config.get("tor_type", "none")
return self.user_config.get("tor_type", "disabled")
@property
def proxy_url(self):

File diff suppressed because it is too large Load diff

View file

@ -15,15 +15,15 @@
<svg class="flex-shrink-0 inline w-6 h-6 mr-3" version="1.1" viewBox="0 0 64 64" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><!--Generated by IJSVG (https://github.com/iconjar/IJSVG)--><path d="M32.0022,55.9844l-1.04907e-06,-2.84217e-14c-13.2548,-5.79387e-07 -24,-10.7452 -24,-24c5.79387e-07,-13.2548 10.7452,-24 24,-24c13.2548,5.79387e-07 24,10.7452 24,24l2.13163e-14,-1.04907e-06c0,13.2548 -10.7452,24 -24,24Zm1.5,-32l3.82016e-08,-2.71086e-09c-1.79133,0.127116 -3.35165,-1.21031 -3.5,-3l6.50646e-08,-7.8524e-07c0.148296,-1.78972 1.70866,-3.12717 3.5,-3l-5.85322e-09,4.15529e-10c1.79134,-0.127171 3.3517,1.21028 3.5,3l-5.57104e-08,6.72365e-07c-0.148295,1.78976 -1.70872,3.12723 -3.5001,3Zm1.4206,6.7635l-2.7853,10.5093l-8.37179e-08,3.15667e-07c-0.270677,1.02062 0.262417,2.08281 1.2425,2.4757l-5.91015e-08,-2.36708e-08c0.379505,0.151996 0.628277,0.519688 0.6282,0.9285v0.323l8.52651e-14,4.05e-07c0,0.551933 -0.447167,0.999503 -0.9991,1h-2.9891l-4.04477e-08,1.96948e-10c-1.65938,0.00807986 -3.01111,-1.33056 -3.01919,-2.98993c-0.00127082,-0.260991 0.0314695,-0.521035 0.0973943,-0.773566l2.7852,-10.5094l2.79671e-08,-1.05468e-07c0.270652,-1.02067 -0.262537,-2.08289 -1.2427,-2.4757l-2.20542e-08,-8.83296e-09c-0.379505,-0.151996 -0.628276,-0.519688 -0.6282,-0.9285v-0.3229l-1.77636e-14,-9.40042e-08c-8.33514e-08,-0.552011 0.447289,-0.999614 0.9993,-1h2.9887l1.06497e-07,-5.28143e-10c1.65943,-0.00822943 3.01133,1.33033 3.01956,2.98976c0.00129458,0.261047 -0.0314356,0.52115 -0.0973639,0.773738Z" fill="currentColor" fill-rule="evenodd"></path></svg>
<span class="sr-only">Info</span>
<div>
{{ _("This anonymizes connections to block explorers, price providers and Electrum servers if Tor is activated here.") }}
{{ _("Activating Tor here automatically anonymizes connections to Electrum servers and to Spotbit price providers. If you want to enforce Tor for more, check out the 'Force calls over Tor' option below.") }}
</div>
</div>
<label for="tor_type_none" class="flex bg-dark-800 p-4 mb-5 border-2 border-dark-700 rounded-xl align-start hover:bg-dark-700 cursor-pointer">
<input class="mr-4 h-8" id="tor_type_none" type="radio" name="tor_type" value="none" data-style="width: 20px; min-width: 20px; margin-top: 20px;" onchange="toggletor_type('none')" {% if tor_type == 'none' %} checked {% endif %}/>
<label for="tor_type_disabled" class="flex bg-dark-800 p-4 mb-5 border-2 border-dark-700 rounded-xl align-start hover:bg-dark-700 cursor-pointer">
<input class="mr-4 h-8" id="tor_type_disabled" type="radio" name="tor_type" value="disabled" data-style="width: 20px; min-width: 20px; margin-top: 20px;" onchange="toggletor_type('disabled')" {% if tor_type == 'disabled' %} checked {% endif %}/>
<div>
<h3>Disabled</h3>
<p>{{ _("No Tor setup yet") }}</p>
<p>{{ _("No Tor setup") }}</p>
</div>
</label>
@ -31,21 +31,24 @@
<label for="tor_type_builtin" class="flex bg-dark-800 p-4 mb-5 border-2 border-dark-700 rounded-xl align-start hover:bg-dark-700 cursor-pointer">
<input class="mr-4 h-8" id="tor_type_builtin" type="radio" name="tor_type" value="builtin" data-style="width: 20px; min-width: 20px; margin-top: 20px;" onchange="toggletor_type('builtin')" {% if tor_type == 'builtin' %} checked {% endif %}/>
<div>
<h3>Built in</h3>
<h3>Built-in</h3>
<p>{{ _("Quick and automatic setup") }}</p>
{% if torbrowser_installed %}
{% if torbrowser_running %}
<span data-style="display: inline; float: right;margin-top: 10px; margin-right: 10px;">
<span class="note">Running&nbsp;&nbsp;<img src="{{ url_for('static', filename='img/check.svg') }}" class="svg-check-green" data-style="vertical-align: middle;"/>&nbsp;&nbsp;</span>
<button data-style="display: inline; width: 80px;" type="submit" class="btn" name="action" value="stoptor">{{ _("Stop") }}</button>&nbsp;
<button data-style="display: inline; width: 80px;" type="submit" class="btn" name="action" value="test_tor">{{ _("Test") }}</button>
</span>
<div class="flex items-center gap-3">
<svg class="w-6 h-6 text-accent cursor-auto" version="1.1" viewBox="0 0 64 64" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><!--Generated by IJSVG (https://github.com/iconjar/IJSVG)--><path d="M32.01,56.0208l-1.04907e-06,-2.13163e-14c-13.2548,-5.79387e-07 -24,-10.7452 -24,-24c5.79387e-07,-13.2548 10.7452,-24 24,-24c13.2548,5.79387e-07 24,10.7452 24,24l2.84217e-14,-1.04907e-06c0,13.2548 -10.7452,24 -24,24Zm13.4,-35.4186l-3.49088e-08,-3.45428e-08c-0.785624,-0.777389 -2.0527,-0.770712 -2.83008,0.0149119c-0.0803372,0.0811883 -0.153576,0.169105 -0.218915,0.262788l-12.8438,18.3901l-5.8744,-8.4112l5.28764e-08,7.58143e-08c-0.632255,-0.906528 -1.87968,-1.12887 -2.78621,-0.496615c-0.0936828,0.0653387 -0.1816,0.138578 -0.262788,0.218915l2.67369e-08,-2.81027e-08c-0.695734,0.731275 -0.767842,1.85586 -0.1712,2.67l6.6244,9.485l8.22893e-08,1.17716e-07c0.953738,1.36434 2.83291,1.69719 4.19725,0.743454c0.289461,-0.202347 0.541107,-0.453993 0.743454,-0.743454l13.5937,-19.4644l2.1684e-09,-2.9606e-09c0.596265,-0.814103 0.524082,-1.93834 -0.1714,-2.6695Z" fill="currentColor" fill-rule="evenodd"></path></svg>
<div>Tor is running</div>
<button type="submit" class="btn bg-dark-900 border-dark-700 hover:border-dark-600" name="action" value="stoptor">{{ _("Stop") }}</button>
<button id="uninstall-btn" type="submit" class="btn bg-dark-900 border-dark-700 hover:border-dark-600" name="action" value="uninstalltor">{{ _("Uninstall") }}</button>
</div>
{% else %}
<span data-style="display: inline; float: right; margin-top: 10px; margin-right: 10px;">
<span class="note">{{ _("Stopped") }}&nbsp;&nbsp;</span>
<button data-style="display: inline; width: 80px;" type="submit" class="btn" name="action" value="uninstalltor">{{ _("Uninstall") }}</button>&nbsp;
<button data-style="display: inline; width: 80px;" type="submit" class="btn" name="action" value="starttor">{{ _("Start") }}</button>
</span>
<div class="flex items-center gap-3">
<svg class="w-6 h-6 cursor-auto" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"></path><path d="M9 4h-2a2 2 0 0 0 -2 2v12a2 2 0 0 0 2 2h2a2 2 0 0 0 2 -2v-12a2 2 0 0 0 -2 -2z" stroke-width="0" fill="currentColor"></path><path d="M17 4h-2a2 2 0 0 0 -2 2v12a2 2 0 0 0 2 2h2a2 2 0 0 0 2 -2v-12a2 2 0 0 0 -2 -2z" stroke-width="0" fill="currentColor"></path></svg>
<div>Tor is stopped</div>
<button type="submit" class="btn bg-dark-900 border-dark-700 hover:border-dark-600" name="action" value="starttor">{{ _("Start") }}</button>
<button type="submit" class="btn bg-dark-900 border-dark-700 hover:border-dark-600" name="action" value="test_tor">{{ _("Test") }}</button>
<button id="uninstall-btn" type="submit" class="btn bg-dark-900 border-dark-700 hover:border-dark-600" name="action" value="uninstalltor">{{ _("Uninstall") }}</button>
</div>
{% endif %}
{% else %}
<a href="{{ url_for('setup_endpoint.tor_from_settings' ) }}" class="button mt-4 mb-2 text-white bg-accent" id="setup-tor-button">{{ _("Set Up") }}</a>
@ -72,8 +75,8 @@
</p>
<div class="floating-wrapper">
<input class="floating-input peer" name="tor_control_port" value="{{ tor_control_port }}" type="text" placeholder=" " />
<label for="tor_control_port" class="floating-label">{{ _("Tor Control Port") }}</label>
<input id="tor-control-port-input" class="floating-input peer" name="tor_control_port" value="" type="text" placeholder=" "/>
<label id="tor-control-port-label" for="tor_control_port" class="floating-label">{{ _("Tor Control Port (Leave blank to use default)") }}</label>
</div>
<p> {{ _("Restart Specter for change of the control port to take effect.") }} </p>
@ -116,16 +119,13 @@
{% endif %}
<label class="flex items-center mt-4">
<input type="checkbox" id="only-tor" name="only_tor" {% if only_tor %}checked{% endif %}>
{{ _("Force external calls over Tor?") }}
<input type="checkbox" id="only-tor" name="only_tor" {% if only_tor and not unselectOnlyTor %}checked{% endif %}>
{{ _("Force calls over Tor") }}
<div id="tooltip-container" class="tooltip-little-grow tooltip-width">
<tool-tip id="tooltips-tor">
<h4 slot="title">{{ _("Tor Only mode") }}</h4>
<span slot="paragraph">
{{ _("Some optional Specter functionality, like rescanning UTXO on a pruned node or getting the Bitcoin price, might make calls to external APIs and services.") }}<br><br>
{{ _("Toggle this on to ensure Specter routes all these external calls over Tor proxy.") }}<br><br>
{{ _("Note: Some external sources may stop working if they block Tor calls.") }}
{{ _("This will have a significant performance impact. Specter will be much slower!") }}
{{ _("This ensures that Specter routes all external calls over Tor if Tor is enabled.") }}<br><br>
{{ _("Some functionality may stop working and this will have a significant performance impact especially since calls to your Bitcoin Core node will be routed through Tor, too.") }}
</span>
</tool-tip>
</div>
@ -153,6 +153,35 @@
}
}
const setupTorBtn = document.getElementById('setup-tor-button')
const builtinRadioBtn = document.getElementById('tor_type_builtin')
// Button disappears after successful setup
if (setupTorBtn) {
setupTorBtn.addEventListener('click', () => {
builtinRadioBtn.checked = true
})
}
// We use localStorage only temporarily when coming back from the setup route to show the success message
if (localStorage.getItem('torSetupCompleted') === 'true') {
showNotification(`{{ _("Built-in Tor setup was saved and Tor is running.") }}`, 5000)
localStorage.removeItem('torSetupCompleted')
}
const torControlPortInput = document.getElementById('tor-control-port-input')
const torControlPortLabel = document.getElementById('tor-control-port-label')
// Changes the label text upon user input
torControlPortInput.addEventListener('input', () => {
if (torControlPortInput.value) {
torControlPortLabel.textContent = 'Tor Control Port'
}
else {
torControlPortLabel.textContent = 'Tor Control Port (Leave blank to use default)'
}
})
function checkAuthEabled() {
{% if specter.config.auth.method == "none" %}
document.getElementById('hidden_service').checked = false;

View file

@ -1,35 +1,9 @@
{% extends "base.jinja" %}
{% block main %}
<div style="display: none;"><!--<style>-->
.wizard-btn {
width: 200px;
min-width: 200px;
max-width: 200px;
height: 36px;
margin: auto;
}
.helper-btn {
text-decoration: underline;
margin: 10px 50px;
}
</div><!--</style>-->
<div class="card center">
{% block setup %}
{% endblock %}
</div>
<br><br>
<div class="row">
<form method="POST" action="{{ url_for('welcome_endpoint.about') }}">
<input type="hidden" class="csrf-token" name="csrf_token" value="{{ csrf_token() }}"/>
<button type="submit" name="action" value="cancelsetup" class="btn hidden danger" id="reset-setup-process" data-style="max-height: 36px;" onclick="">{{ _("Cancel process") }}</button>
</form>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<button type="button" class="btn hidden action" id="show-progress-details" data-style="max-height: 36px;" onclick="showPageOverlay('progress-details');">{{ _("View Progress") }}</button>
</div>
<h1 id="progress-details" class="hidden" data-style="margin: auto;"></h1>
{% block setup %}
{% endblock %}
{% endblock %}
{% block scripts %}
<script>
let currentStep = parseInt('{{step}}');
@ -87,24 +61,15 @@
let stage = result.stage;
let progress = parseFloat(result.stage_progress);
if (progress == -1) {
localStorage.setItem("torSetupCompleted", true)
window.location.href = nextURL;
}
document.getElementById('progress-details').innerHTML = `${stage}<br>`;
if (progress > 0 && progress < 100) {
document.getElementById('progress-details').innerHTML += `<br><br><b data-style="font-size:0.8em;">{{ _("Download Progress:") }}" ${progress}%</b>`;
}
setTimeout(fetchProgress, 1000);
} catch(e) {
console.log('Caught error:', e);
return { success: false, error: e };
}
}
document.getElementById('progress-details').classList.remove('hidden');
document.getElementById('show-progress-details').classList.remove('hidden');
document.getElementById('reset-setup-process').classList.remove('hidden');
showPageOverlay('progress-details');
document.getElementById('progress-details').innerHTML = `{{ _("Starting up...") }}`;
setTimeout(fetchProgress, 1000);
}
</script>

View file

@ -2,13 +2,11 @@
{% block setup %}
<h1>{{ _("Setup Tor daemon") }}</h1>
<div class="flex">
<img src="{{ url_for('static', filename='img/ghost_3d.png') }}" style="width: 32px;"/>
<div class="flex mt-5">
<img src="{{ url_for('static', filename='img/favicon-dark-mode.png') }}" style="width: 32px;"/>
<img src="{{ url_for('static', filename='img/arrow-right.svg') }}" style="width: 32px;" class="svg-white"/>
<img style="width: 32px;" src="{{ url_for('static', filename='img/tor.svg') }}"/>
</div>
<div class="grid grid-cols-2 gap-3 mt-8">
{% if nextURL == 'setup_endpoint.node_type'%}
<a href="{{ url_for('setup_endpoint.node_type') }}" class="button">{{ _("Skip") }}</a>
@ -37,8 +35,8 @@
}
const jsonResponse = await response.json();
if ("success" in jsonResponse) {
showNotification(jsonResponse.success);
document.getElementById('setup-tor-button').classList.add('hidden');
showPacman()
startProgressCheck('torbrowser', "{{ url_for(nextURL) }}");
return;
} if (jsonResponse.error == "Tor is already installed") {
@ -46,7 +44,7 @@
}
showError(jsonResponse.error, 4000);
} catch(e) {
showError(`{{ _("Failed to download Tor daemon...") }}`);
showError(`{{ _("Failed to install Tor daemon...") }}`);
showError(e);
}
}

View file

@ -14,7 +14,7 @@ logger = logging.getLogger(__name__)
class TorDaemonController:
"""A class controlling the tor-daemon process directly on the machine"""
"""A class controlling the Tor daemon process directly on the machine"""
def __init__(
self,
@ -24,7 +24,7 @@ class TorDaemonController:
self.tor_daemon_path = tor_daemon_path
self.tor_config_path = tor_config_path
self.tor_daemon_proc = None
self.is_running() # throws an exception if port 9050 is open (which looks like another tor-process is running)
self.is_running()
def start_tor_daemon(self, cleanup_at_exit=True):
if self.is_running():
@ -41,7 +41,7 @@ class TorDaemonController:
stderr=subprocess.STDOUT,
)
logger.debug(
"Running tor-daemon process with pid {}".format(self.tor_daemon_proc.pid)
"Running Tor daemon process with pid {}".format(self.tor_daemon_proc.pid)
)
def get_logs(self):
@ -65,13 +65,22 @@ class TorDaemonController:
return hashed_pw
def is_running(self):
# This fails the cypress-tests, unfortunately
# if not self.tor_daemon_proc and self.is_port_open():
# raise SpecterError(
# "Port 9050 is open but tor_daemon_proc is not existing. Probably another Tor-Daemon is running?!"
# )
return self.tor_daemon_proc and self.tor_daemon_proc.poll() is None
"""Checks whether the Tor process is still running.
Note: poll() from the subprocess module does not work reliably. For example,
if the process has been terminated but its exit code has not yet been collected, it would still indicate
that the process is still running.
"""
if self.tor_daemon_proc is None:
return False
try:
# Note: This pid here is usually not the same as the pid given by the OS to the Tor process
process = psutil.Process(self.tor_daemon_proc.pid)
logger.debug(f"Is the built-in Tor daemon running? {process.is_running()}")
return process.is_running()
except psutil.NoSuchProcess:
return False
# Currently not used
def is_port_open(self):
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
location = ("127.0.0.1", 9050)
@ -84,15 +93,16 @@ class TorDaemonController:
return False
def stop_tor_daemon(self):
timeout = 50 # in secs
# It is possible for the Tor process to terminate unexpectedly without updating self.tor_daemon_proc
if not self.is_running():
return
if self.tor_daemon_proc:
# This double approach ensures that the Tor child process is terminated on all levels (within Specter and on the OS level).
# This seems to be unnecessary for Linux, but it is necessary for MacOS and Windows.
if platform.system() == "Windows":
subprocess.run("Taskkill /IM tor.exe /F")
else:
cmdline_args = f"{self.tor_daemon_path} --defaults-torrc {self.tor_config_path}" # Sth. like: ~/.specter/tor-binaries/tor --defaults-torrc ~/.specter/torrc
subprocess.run(["pkill", "-f", cmdline_args])
self.tor_daemon_proc.terminate()
procs = psutil.Process().children()
for p in procs:
p.terminate()
_, alive = psutil.wait_procs(procs, timeout=timeout)
for p in alive:
logger.info("tor daemon did not terminated in time, killing!")
p.kill()
self.tor_daemon_proc = None

View file

@ -1,5 +1,6 @@
import os, time, requests, platform, tarfile, zipfile, sys, subprocess, shutil, stat, zipfile, logging
import pgpy
from flask_login import current_user
from pathlib import Path
from .sha256sum import sha256sum
from .file_download import download_file
@ -19,9 +20,9 @@ def copytree(src, dst, symlinks=False, ignore=None):
def setup_tor_thread(specter=None):
"""This will extracted the tor-binary out of the tar.xz packaged with specterd and copy
"""This will extract the Tor binary out of the tar.xz packaged with specterd and copy
it over to the ~/.specter/tor-binaries folder
Then it will create a torrc file and start the tor-demon
It will then create a torrc file and start the Tor daemon
"""
try:
specter.update_setup_status("torbrowser", "STARTING_SETUP")
@ -59,10 +60,9 @@ def setup_tor_thread(specter=None):
file.write(
f"\nHashedControlPassword {specter.tor_daemon.get_hashed_password(specter.config['torrc_password'])}"
)
specter.tor_daemon.start_tor_daemon()
specter.update_tor_controller()
specter.reset_setup("torbrowser")
specter.update_tor_type("builtin", current_user)
except Exception as e:
logger.exception(f"Failed to install Tor.")
specter.update_setup_error("torbrowser", str(e))