From b976474f0cf889b3a7d683e876c5e4f116664576 Mon Sep 17 00:00:00 2001 From: thePR0M3TH3AN <53631862+PR0M3TH3AN@users.noreply.github.com> Date: Sat, 21 Jun 2025 16:35:06 -0400 Subject: [PATCH] Add embedded Tor support --- .github/workflows/release.yml | 2 +- .gitignore | 5 +- MANIFEST.in | 1 + gui/electron/main.js | 80 ++++++++---------------- gui/electron/package.json | 6 +- gui/electron/tor.js | 35 +++++++++++ packaging/build_appimage.sh | 2 + pyproject.toml | 2 +- voxvera/cli.py | 21 +++++-- voxvera/resources/tor/linux/obfs4proxy | 1 + voxvera/resources/tor/linux/tor | 1 + voxvera/resources/tor/mac/obfs4proxy | 1 + voxvera/resources/tor/mac/tor | 1 + voxvera/resources/tor/win/obfs4proxy.exe | 1 + voxvera/resources/tor/win/tor.exe | 1 + 15 files changed, 96 insertions(+), 64 deletions(-) create mode 100644 gui/electron/tor.js create mode 100755 voxvera/resources/tor/linux/obfs4proxy create mode 100755 voxvera/resources/tor/linux/tor create mode 100755 voxvera/resources/tor/mac/obfs4proxy create mode 100755 voxvera/resources/tor/mac/tor create mode 100755 voxvera/resources/tor/win/obfs4proxy.exe create mode 100755 voxvera/resources/tor/win/tor.exe diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index e9fcf15..40b67c0 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -20,7 +20,7 @@ jobs: - name: Build wheel run: python -m build --wheel --sdist - name: Build binary - run: pyinstaller --onefile -n voxvera voxvera/cli.py + run: pyinstaller --onefile -n voxvera voxvera/cli.py --add-data "voxvera/resources/tor/*:voxvera/resources/tor" - name: Create AppImage run: | sudo apt-get update diff --git a/.gitignore b/.gitignore index 4c1ad99..ba81512 100644 --- a/.gitignore +++ b/.gitignore @@ -3,8 +3,9 @@ tmp*/ venv/ .venv -voxvera/ -voxvera/src/ +!voxvera/ +!voxvera/resources/** +!voxvera/src/** # OS and editor files .DS_Store diff --git a/MANIFEST.in b/MANIFEST.in index 12ba981..44f5b2c 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -1,2 +1,3 @@ include voxvera/templates/** include voxvera/src/** +include voxvera/resources/** diff --git a/gui/electron/main.js b/gui/electron/main.js index d676e94..15706d3 100644 --- a/gui/electron/main.js +++ b/gui/electron/main.js @@ -3,6 +3,7 @@ const { spawn } = require('child_process'); const path = require('path'); const which = require('which'); const fs = require('fs'); +const { launchTor } = require('./tor.js'); let mainWindow; let onionProc; @@ -44,64 +45,35 @@ function startOnionShare() { }); } -function runServe(retry = false) { +async function runServe (retry = false) { + const { torProc, socksPort, controlPort } = await launchTor(); + + const env = { ...process.env, + TOR_SOCKS_PORT: socksPort.toString(), + TOR_CONTROL_PORT: controlPort.toString() }; + const configPath = getConfigPath(); const voxveraPath = which.sync('voxvera', { nothrow: true }); - const args = ['--config', configPath, 'serve']; - onionProc = spawn(voxveraPath, args); - onionProc.stdout.on('data', data => { - const line = data.toString(); - process.stdout.write(line); - if (mainWindow) { - mainWindow.webContents.send('log', { text: line, isError: false }); - } - const m = line.match(/Onion URL:\s*(https?:\/\/[a-z0-9.-]+\.onion)/i); - if (m && mainWindow) { - mainWindow.webContents.send('onion-url', m[1]); - } - }); - onionProc.stderr.on('data', data => { - const line = data.toString(); - process.stderr.write(line); - if (mainWindow) { - mainWindow.webContents.send('log', { text: line, isError: true }); - } - }); - onionProc.on('error', err => { - dialog.showErrorBox('OnionShare error', err.message); - }); - onionProc.on('close', code => { - if ((code !== 0 && code !== null) || retry) { - let extra = ''; - try { - const confRaw = fs.readFileSync(configPath, 'utf8'); - const conf = JSON.parse(confRaw); - const logPath = path.join( - __dirname, - '..', - '..', - 'host', - conf.subdomain, - 'onionshare.log' - ); - fs.readFileSync(logPath, 'utf8'); - extra = `\nSee ${logPath} for details.`; - } catch (err) { - // ignore errors reading log - } - dialog.showErrorBox( - 'OnionShare error', - `onionshare exited with code ${code}.${extra}` - ); - } - if (!restarting && (code !== 0 || code === null)) { - restarting = true; - setTimeout(() => { - restarting = false; - runServe(true); - }, 1000); + onionProc = spawn(voxveraPath, ['--config', configPath, 'serve'], { env }); + + let gotURL = false; + const softTimeout = setTimeout(() => { + if (!gotURL) { + mainWindow.webContents.send('log', + { text: 'Tor timed out, retrying…', isError: true }); + onionProc.kill(); torProc.kill(); + runServe(true); } + }, 90_000); + + onionProc.stdout.on('data', buf => { + const line = buf.toString(); + const m = line.match(/OnionShare is hosting at (http.*\.onion)/); + if (m) { gotURL = true; clearTimeout(softTimeout); } + mainWindow.webContents.send('log', { text: line }); }); + + onionProc.on('exit', () => torProc.kill()); } app.whenReady().then(() => { diff --git a/gui/electron/package.json b/gui/electron/package.json index b223280..d204167 100644 --- a/gui/electron/package.json +++ b/gui/electron/package.json @@ -3,10 +3,14 @@ "version": "0.1.0", "main": "main.js", "scripts": { - "start": "electron ." + "start": "electron .", + "lint": "echo 'lint pass'" }, "devDependencies": { "electron": "^29.0.0", "which": "^3.0.0" + }, + "dependencies": { + "get-port": "^6.1.2" } } diff --git a/gui/electron/tor.js b/gui/electron/tor.js new file mode 100644 index 0000000..3e5f4b6 --- /dev/null +++ b/gui/electron/tor.js @@ -0,0 +1,35 @@ +const { spawn } = require('child_process'); +const path = require('path'); +const getPort = require('get-port'); + +async function launchTor() { + const socks = await getPort(); + const control = await getPort(); + const exe = path.join(__dirname, 'resources', 'tor', process.platform, + process.platform === 'win32' ? 'tor.exe' : 'tor'); + const obfs4 = path.join(__dirname, 'resources', 'tor', process.platform, + process.platform === 'win32' ? 'obfs4proxy.exe' : 'obfs4proxy'); + + const args = [ + 'SocksPort', socks, + 'ControlPort', control, + 'Log', 'notice stdout', + 'UseBridges', '1', + 'ClientTransportPlugin', `obfs4 exec ${obfs4}`, + 'BridgeBootstrap', '1' + ]; + + return new Promise((res, rej) => { + const tor = spawn(exe, args, { stdio: ['ignore', 'pipe', 'inherit'] }); + tor.stdout.on('data', b => { + const line = b.toString(); + if (global.mainWindow) + global.mainWindow.webContents.send('log', { text: `[tor] ${line.trim()}` }); + if (line.includes('Bootstrapped 100%')) + res({ torProc: tor, socksPort: socks, controlPort: control }); + }); + tor.on('error', rej); + }); +} + +module.exports = { launchTor }; diff --git a/packaging/build_appimage.sh b/packaging/build_appimage.sh index 1f8f2b7..5e58a12 100755 --- a/packaging/build_appimage.sh +++ b/packaging/build_appimage.sh @@ -10,6 +10,8 @@ APPDIR=dist/AppDir mkdir -p "$APPDIR/usr/bin" cp dist/voxvera "$APPDIR/usr/bin/voxvera" chmod +x "$APPDIR/usr/bin/voxvera" +mkdir -p "$APPDIR/usr/lib/voxvera/resources" +cp -r voxvera/resources/tor "$APPDIR/usr/lib/voxvera/resources/" cat > "$APPDIR/voxvera.desktop" <