diff --git a/cli/onionshare_cli/__init__.py b/cli/onionshare_cli/__init__.py index 7361ac9e..34627deb 100644 --- a/cli/onionshare_cli/__init__.py +++ b/cli/onionshare_cli/__init__.py @@ -22,7 +22,7 @@ import os, sys, time, argparse, threading from datetime import datetime from datetime import timedelta -from .common import Common +from .common import Common, CannotFindTor from .web import Web from .onion import * from .onionshare import OnionShare @@ -320,7 +320,15 @@ def main(cwd=None): web = Web(common, False, mode_settings, mode) # Start the Onion object - onion = Onion(common, use_tmp_dir=True) + try: + onion = Onion(common, use_tmp_dir=True) + except CannotFindTor: + print("You must install tor to use OnionShare from the command line") + if common.platform == "Darwin": + print("In macOS, you can do this with Homebrew (https://brew.sh):") + print(" brew install tor") + sys.exit() + try: onion.connect( custom_settings=False, diff --git a/cli/onionshare_cli/common.py b/cli/onionshare_cli/common.py index a1213387..e8b174b6 100644 --- a/cli/onionshare_cli/common.py +++ b/cli/onionshare_cli/common.py @@ -34,6 +34,12 @@ from pkg_resources import resource_filename from .settings import Settings +class CannotFindTor(Exception): + """ + OnionShare can't find a tor binary + """ + + class Common: """ The Common object is shared amongst all parts of OnionShare. @@ -82,6 +88,8 @@ class Common: def get_tor_paths(self): if self.platform == "Linux": tor_path = shutil.which("tor") + if not tor_path: + raise CannotFindTor() obfs4proxy_file_path = shutil.which("obfs4proxy") prefix = os.path.dirname(os.path.dirname(tor_path)) tor_geo_ip_file_path = os.path.join(prefix, "share/tor/geoip") @@ -94,6 +102,8 @@ class Common: tor_geo_ipv6_file_path = os.path.join(base_path, "Data", "Tor", "geoip6") elif self.platform == "Darwin": tor_path = shutil.which("tor") + if not tor_path: + raise CannotFindTor() obfs4proxy_file_path = shutil.which("obfs4proxy") prefix = os.path.dirname(os.path.dirname(tor_path)) tor_geo_ip_file_path = os.path.join(prefix, "share/tor/geoip") diff --git a/cli/onionshare_cli/onion.py b/cli/onionshare_cli/onion.py index 1b025bf8..d6f59a59 100644 --- a/cli/onionshare_cli/onion.py +++ b/cli/onionshare_cli/onion.py @@ -32,7 +32,6 @@ import getpass import psutil from distutils.version import LooseVersion as Version -from . import common from .settings import Settings # TODO: Figure out how to localize this for the GUI @@ -44,40 +43,30 @@ class TorErrorAutomatic(Exception): using automatic settings that should work with Tor Browser. """ - pass - class TorErrorInvalidSetting(Exception): """ This exception is raised if the settings just don't make sense. """ - pass - class TorErrorSocketPort(Exception): """ OnionShare can't connect to the Tor controller using the supplied address and port. """ - pass - class TorErrorSocketFile(Exception): """ OnionShare can't connect to the Tor controller using the supplied socket file. """ - pass - class TorErrorMissingPassword(Exception): """ OnionShare connected to the Tor controller, but it requires a password. """ - pass - class TorErrorUnreadableCookieFile(Exception): """ @@ -85,8 +74,6 @@ class TorErrorUnreadableCookieFile(Exception): to access the cookie file. """ - pass - class TorErrorAuthError(Exception): """ @@ -94,8 +81,6 @@ class TorErrorAuthError(Exception): that a Tor controller isn't listening on this port. """ - pass - class TorErrorProtocolError(Exception): """ @@ -103,8 +88,6 @@ class TorErrorProtocolError(Exception): isn't acting like a Tor controller (such as in Whonix). """ - pass - class TorTooOld(Exception): """ @@ -113,8 +96,6 @@ class TorTooOld(Exception): is too old. """ - pass - class BundledTorTimeout(Exception): """ diff --git a/desktop/README.md b/desktop/README.md index 053bf4ea..97d0fd30 100644 --- a/desktop/README.md +++ b/desktop/README.md @@ -51,7 +51,7 @@ Download Tor Browser and extract the binaries: python scripts\get-tor-windows.py ``` -### Prepare the code +### Prepare the virtual environment OnionShare uses [Briefcase](https://briefcase.readthedocs.io/en/latest/). @@ -79,15 +79,27 @@ pip install briefcase In order to work with the desktop app, you'll need to build a wheel of the CLI package first, and copy it into the `desktop` folder. You'll need to re-run this script each time you change the CLI code. ```sh -./scripts/rebuild-cli.sh +python scripts/rebuild-cli.py ``` -Run OnionShare from the source tree like this: +### Running OnionShare from the source code tree + +Inside the virtual environment, run OnionShare like this to install all of the dependencies: ``` briefcase dev -d ``` +Once you have the dependencies installed, you can run it using the `dev.sh` script, which lets you use command line arguments, such as to `--verbose` or `--local-only`: + +``` +./scripts/dev.sh --help +./scripts/dev.sh -v +./scripts/dev.sh -v --local-only +``` + +Windows uses `scripts\dev.bat` instead. + ## Running tests Install these packages inside your virtual environment: diff --git a/desktop/scripts/dev.bat b/desktop/scripts/dev.bat new file mode 100644 index 00000000..9b537a90 --- /dev/null +++ b/desktop/scripts/dev.bat @@ -0,0 +1,3 @@ +cd src +python -c "import onionshare; onionshare.main()" %* +cd .. \ No newline at end of file diff --git a/desktop/scripts/dev.sh b/desktop/scripts/dev.sh new file mode 100755 index 00000000..6ce5e796 --- /dev/null +++ b/desktop/scripts/dev.sh @@ -0,0 +1,9 @@ +#!/bin/bash + +# Run OnionShare desktop, allowing you to use command-line arguments + +SCRIPTS_DIR="$( cd "$(dirname "${BASH_SOURCE[0]}")" >/dev/null 2>&1 && pwd )" +cd $SCRIPTS_DIR + +cd ../src +python -c "import onionshare; onionshare.main()" $@ \ No newline at end of file diff --git a/desktop/scripts/rebuild-cli.py b/desktop/scripts/rebuild-cli.py new file mode 100755 index 00000000..c13461bc --- /dev/null +++ b/desktop/scripts/rebuild-cli.py @@ -0,0 +1,45 @@ +#!/usr/bin/env python3 +""" +This script builds the CLI python wheel, copies it to the desktop folder, +and installs it in the virtual environment. +""" + +import inspect +import os +import sys +import glob +import subprocess +import shutil + + +def main(): + # Build paths + root_path = os.path.dirname( + os.path.dirname( + os.path.dirname(os.path.abspath(inspect.getfile(inspect.currentframe()))) + ) + ) + cli_path = os.path.join(root_path, "cli") + desktop_path = os.path.join(root_path, "desktop") + + # Delete old wheels + for filename in glob.glob(os.path.join(cli_path, "dist", "*.whl")): + os.remove(filename) + + # Build new wheel + subprocess.call(["poetry", "install"], cwd=cli_path) + subprocess.call(["poetry", "build"], cwd=cli_path) + wheel_filename = glob.glob(os.path.join(cli_path, "dist", "*.whl"))[0] + wheel_basename = os.path.basename(wheel_filename) + shutil.copyfile( + wheel_filename, + os.path.join(desktop_path, wheel_basename), + ) + + # Reinstall the new wheel + subprocess.call(["pip", "uninstall", "onionshare-cli", "-y"]) + subprocess.call(["pip", "install", os.path.join(desktop_path, wheel_basename)]) + + +if __name__ == "__main__": + main() diff --git a/desktop/scripts/rebuild-cli.sh b/desktop/scripts/rebuild-cli.sh deleted file mode 100755 index f8191955..00000000 --- a/desktop/scripts/rebuild-cli.sh +++ /dev/null @@ -1,9 +0,0 @@ -#!/bin/bash - -# Build the CLI python wheel and copy it to the desktop folder - -SCRIPTS_DIR="$( cd "$(dirname "${BASH_SOURCE[0]}")" >/dev/null 2>&1 && pwd )" -cd $SCRIPTS_DIR -cd ../../cli -poetry build -cp dist/*.whl ../desktop \ No newline at end of file diff --git a/desktop/src/onionshare/settings_dialog.py b/desktop/src/onionshare/settings_dialog.py index 5f37bda1..40de8648 100644 --- a/desktop/src/onionshare/settings_dialog.py +++ b/desktop/src/onionshare/settings_dialog.py @@ -142,7 +142,7 @@ class SettingsDialog(QtWidgets.QDialog): self.tor_geo_ip_file_path, self.tor_geo_ipv6_file_path, self.obfs4proxy_file_path, - ) = self.common.get_tor_paths() + ) = self.common.gui.get_tor_paths() if not self.obfs4proxy_file_path or not os.path.isfile( self.obfs4proxy_file_path ): @@ -165,7 +165,7 @@ class SettingsDialog(QtWidgets.QDialog): self.tor_geo_ip_file_path, self.tor_geo_ipv6_file_path, self.obfs4proxy_file_path, - ) = self.common.get_tor_paths() + ) = self.common.gui.get_tor_paths() if not self.obfs4proxy_file_path or not os.path.isfile( self.obfs4proxy_file_path ):