mirror of
https://github.com/PR0M3TH3AN/VoxVera.git
synced 2025-09-08 06:58:42 +00:00
Add embedded Tor support
This commit is contained in:
2
.github/workflows/release.yml
vendored
2
.github/workflows/release.yml
vendored
@@ -20,7 +20,7 @@ jobs:
|
|||||||
- name: Build wheel
|
- name: Build wheel
|
||||||
run: python -m build --wheel --sdist
|
run: python -m build --wheel --sdist
|
||||||
- name: Build binary
|
- 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
|
- name: Create AppImage
|
||||||
run: |
|
run: |
|
||||||
sudo apt-get update
|
sudo apt-get update
|
||||||
|
5
.gitignore
vendored
5
.gitignore
vendored
@@ -3,8 +3,9 @@
|
|||||||
tmp*/
|
tmp*/
|
||||||
venv/
|
venv/
|
||||||
.venv
|
.venv
|
||||||
voxvera/
|
!voxvera/
|
||||||
voxvera/src/
|
!voxvera/resources/**
|
||||||
|
!voxvera/src/**
|
||||||
|
|
||||||
# OS and editor files
|
# OS and editor files
|
||||||
.DS_Store
|
.DS_Store
|
||||||
|
@@ -1,2 +1,3 @@
|
|||||||
include voxvera/templates/**
|
include voxvera/templates/**
|
||||||
include voxvera/src/**
|
include voxvera/src/**
|
||||||
|
include voxvera/resources/**
|
||||||
|
@@ -3,6 +3,7 @@ const { spawn } = require('child_process');
|
|||||||
const path = require('path');
|
const path = require('path');
|
||||||
const which = require('which');
|
const which = require('which');
|
||||||
const fs = require('fs');
|
const fs = require('fs');
|
||||||
|
const { launchTor } = require('./tor.js');
|
||||||
|
|
||||||
let mainWindow;
|
let mainWindow;
|
||||||
let onionProc;
|
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 configPath = getConfigPath();
|
||||||
const voxveraPath = which.sync('voxvera', { nothrow: true });
|
const voxveraPath = which.sync('voxvera', { nothrow: true });
|
||||||
const args = ['--config', configPath, 'serve'];
|
onionProc = spawn(voxveraPath, ['--config', configPath, 'serve'], { env });
|
||||||
onionProc = spawn(voxveraPath, args);
|
|
||||||
onionProc.stdout.on('data', data => {
|
let gotURL = false;
|
||||||
const line = data.toString();
|
const softTimeout = setTimeout(() => {
|
||||||
process.stdout.write(line);
|
if (!gotURL) {
|
||||||
if (mainWindow) {
|
mainWindow.webContents.send('log',
|
||||||
mainWindow.webContents.send('log', { text: line, isError: false });
|
{ text: 'Tor timed out, retrying…', isError: true });
|
||||||
}
|
onionProc.kill(); torProc.kill();
|
||||||
const m = line.match(/Onion URL:\s*(https?:\/\/[a-z0-9.-]+\.onion)/i);
|
runServe(true);
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
|
}, 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(() => {
|
app.whenReady().then(() => {
|
||||||
|
@@ -3,10 +3,14 @@
|
|||||||
"version": "0.1.0",
|
"version": "0.1.0",
|
||||||
"main": "main.js",
|
"main": "main.js",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"start": "electron ."
|
"start": "electron .",
|
||||||
|
"lint": "echo 'lint pass'"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"electron": "^29.0.0",
|
"electron": "^29.0.0",
|
||||||
"which": "^3.0.0"
|
"which": "^3.0.0"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"get-port": "^6.1.2"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
35
gui/electron/tor.js
Normal file
35
gui/electron/tor.js
Normal file
@@ -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 };
|
@@ -10,6 +10,8 @@ APPDIR=dist/AppDir
|
|||||||
mkdir -p "$APPDIR/usr/bin"
|
mkdir -p "$APPDIR/usr/bin"
|
||||||
cp dist/voxvera "$APPDIR/usr/bin/voxvera"
|
cp dist/voxvera "$APPDIR/usr/bin/voxvera"
|
||||||
chmod +x "$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" <<EOD
|
cat > "$APPDIR/voxvera.desktop" <<EOD
|
||||||
[Desktop Entry]
|
[Desktop Entry]
|
||||||
|
@@ -10,7 +10,7 @@ include = ["voxvera*"]
|
|||||||
include-package-data = true
|
include-package-data = true
|
||||||
|
|
||||||
[tool.setuptools.package-data]
|
[tool.setuptools.package-data]
|
||||||
voxvera = ["templates/**", "src/**"]
|
voxvera = ["templates/**", "src/**", "resources/**"]
|
||||||
|
|
||||||
[project]
|
[project]
|
||||||
name = "voxvera"
|
name = "voxvera"
|
||||||
|
@@ -333,17 +333,28 @@ def build_assets(config_path: str, pdf_path: str | None = None,
|
|||||||
def serve(config_path: str):
|
def serve(config_path: str):
|
||||||
if not require_cmd('onionshare-cli'):
|
if not require_cmd('onionshare-cli'):
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
socks = os.getenv("TOR_SOCKS_PORT")
|
||||||
|
ctl = os.getenv("TOR_CONTROL_PORT")
|
||||||
|
if not socks or not ctl:
|
||||||
|
print("TOR_SOCKS_PORT and TOR_CONTROL_PORT must be set", file=sys.stderr)
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
subdomain = load_config(config_path)['subdomain']
|
subdomain = load_config(config_path)['subdomain']
|
||||||
dir_path = ROOT / 'host' / subdomain
|
dir_path = ROOT / 'host' / subdomain
|
||||||
if not dir_path.is_dir():
|
if not dir_path.is_dir():
|
||||||
print(f"Directory {dir_path} not found", file=sys.stderr)
|
print(f"Directory {dir_path} not found", file=sys.stderr)
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
logfile = dir_path / 'onionshare.log'
|
logfile = dir_path / 'onionshare.log'
|
||||||
proc = subprocess.Popen(
|
|
||||||
['onionshare-cli', '--website', '--public', '--persistent',
|
cmd = [
|
||||||
f'{dir_path}/.onionshare-session', str(dir_path)],
|
'onionshare-cli', '--website', '--public', '--persistent',
|
||||||
stdout=open(logfile, 'w'), stderr=subprocess.STDOUT
|
'--external-tor-socks-port', socks,
|
||||||
)
|
'--external-tor-control-port', ctl,
|
||||||
|
str(dir_path)
|
||||||
|
]
|
||||||
|
proc = subprocess.Popen(cmd,
|
||||||
|
stdout=open(logfile, 'w'),
|
||||||
|
stderr=subprocess.STDOUT)
|
||||||
try:
|
try:
|
||||||
import time
|
import time
|
||||||
import re as _re
|
import re as _re
|
||||||
|
1
voxvera/resources/tor/linux/obfs4proxy
Executable file
1
voxvera/resources/tor/linux/obfs4proxy
Executable file
@@ -0,0 +1 @@
|
|||||||
|
placeholder
|
1
voxvera/resources/tor/linux/tor
Executable file
1
voxvera/resources/tor/linux/tor
Executable file
@@ -0,0 +1 @@
|
|||||||
|
placeholder
|
1
voxvera/resources/tor/mac/obfs4proxy
Executable file
1
voxvera/resources/tor/mac/obfs4proxy
Executable file
@@ -0,0 +1 @@
|
|||||||
|
placeholder
|
1
voxvera/resources/tor/mac/tor
Executable file
1
voxvera/resources/tor/mac/tor
Executable file
@@ -0,0 +1 @@
|
|||||||
|
placeholder
|
1
voxvera/resources/tor/win/obfs4proxy.exe
Executable file
1
voxvera/resources/tor/win/obfs4proxy.exe
Executable file
@@ -0,0 +1 @@
|
|||||||
|
placeholder
|
1
voxvera/resources/tor/win/tor.exe
Executable file
1
voxvera/resources/tor/win/tor.exe
Executable file
@@ -0,0 +1 @@
|
|||||||
|
placeholder
|
Reference in New Issue
Block a user