mirror of
https://github.com/PR0M3TH3AN/VoxVera.git
synced 2026-10-28 02:00:40 +00:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a017bf876f |
No files matched your search
@@ -38,6 +38,12 @@ jobs:
|
||||
pip install -r requirements.txt
|
||||
pip install pyinstaller
|
||||
|
||||
- name: Download Tor Binaries
|
||||
shell: bash
|
||||
run: |
|
||||
chmod +x scripts/download_tor.sh
|
||||
./scripts/download_tor.sh
|
||||
|
||||
- name: Build with PyInstaller
|
||||
shell: bash
|
||||
run: |
|
||||
|
||||
+3
-2
@@ -14,7 +14,7 @@ voxvera = ["templates/**", "src/**", "resources/**", "locales/**", "resources/bi
|
||||
|
||||
[project]
|
||||
name = "voxvera"
|
||||
version = "0.1.5"
|
||||
version = "0.1.6"
|
||||
description = "VoxVera CLI utilities"
|
||||
readme = "README.md"
|
||||
authors = [{name = "VoxVera"}]
|
||||
@@ -26,9 +26,10 @@ dependencies = [
|
||||
"Pillow",
|
||||
"jsmin",
|
||||
"htmlmin2",
|
||||
"pypdf",
|
||||
"wcwidth",
|
||||
"onionshare-cli",
|
||||
]
|
||||
|
||||
[project.optional-dependencies]
|
||||
test = [
|
||||
"pytest",
|
||||
|
||||
@@ -6,3 +6,5 @@ jsmin
|
||||
htmlmin2
|
||||
pypdf
|
||||
wcwidth
|
||||
onionshare-cli
|
||||
|
||||
@@ -19,6 +19,9 @@ mkdir -p voxvera/resources/bin
|
||||
|
||||
echo "Building VoxVera binary for $(uname)..."
|
||||
|
||||
echo "Ensuring Tor binaries are downloaded..."
|
||||
bash scripts/download_tor.sh
|
||||
|
||||
# Use python module to call pyinstaller
|
||||
PY_CMD="python3 -m PyInstaller"
|
||||
|
||||
|
||||
+8
-3
@@ -112,10 +112,15 @@ def test_check_all_present(capsys, monkeypatch):
|
||||
|
||||
|
||||
def test_check_missing(capsys, monkeypatch):
|
||||
def fake_which(cmd):
|
||||
return None if cmd == "onionshare-cli" else "/usr/bin/" + cmd
|
||||
import builtins
|
||||
original_import = builtins.__import__
|
||||
|
||||
monkeypatch.setattr(shutil, "which", fake_which)
|
||||
def fake_import(name, *args, **kwargs):
|
||||
if name == "onionshare_cli":
|
||||
raise ImportError("Mocked missing")
|
||||
return original_import(name, *args, **kwargs)
|
||||
|
||||
monkeypatch.setattr(builtins, "__import__", fake_import)
|
||||
cli.main(["check"])
|
||||
captured = capsys.readouterr()
|
||||
assert "onionshare-cli" in captured.out
|
||||
|
||||
+9
-3
@@ -46,7 +46,11 @@ def check_tor_running():
|
||||
|
||||
def check_onionshare_cli():
|
||||
"""Check if onionshare-cli is installed."""
|
||||
return shutil.which("onionshare-cli") is not None
|
||||
try:
|
||||
import onionshare_cli # noqa: F401
|
||||
return True
|
||||
except ImportError:
|
||||
return False
|
||||
|
||||
|
||||
@pytest.fixture(scope="module")
|
||||
@@ -222,12 +226,14 @@ class TestFullWorkflow:
|
||||
host_dir = tmp_path / "host" / "reachtest"
|
||||
assert host_dir.is_dir(), f"Build did not create {host_dir}"
|
||||
|
||||
# Launch onionshare-cli directly against the built host directory
|
||||
# Launch onionshare-cli directly against the built host directory using our internal wrapper
|
||||
logfile = host_dir / "onionshare.log"
|
||||
log_fh = open(logfile, "w")
|
||||
|
||||
cmd = [
|
||||
"onionshare-cli",
|
||||
sys.executable,
|
||||
"-m", "voxvera.cli",
|
||||
"_internal_onionshare",
|
||||
"--website",
|
||||
"--public",
|
||||
"--persistent",
|
||||
|
||||
@@ -115,9 +115,12 @@ def test_portable_bundle_integrity(tmp_path, monkeypatch):
|
||||
shutil.copytree(REPO_ROOT / "voxvera" / "locales", voxvera_dir / "locales")
|
||||
os.makedirs(voxvera_dir / "vendor", exist_ok=True)
|
||||
(voxvera_dir / "vendor" / "anchor.txt").write_text("anchor")
|
||||
(project_root / "voxvera-run.sh").write_text("echo run")
|
||||
(project_root / "uninstall.sh").write_text("echo uninstall")
|
||||
(project_root / "uninstall.ps1").write_text("echo uninstall")
|
||||
|
||||
# Copy real scripts from repo
|
||||
for script in ["voxvera-run.sh", "uninstall.sh", "uninstall.ps1", "install.sh", "install.ps1", "setup.sh"]:
|
||||
if (REPO_ROOT / script).exists():
|
||||
shutil.copy(REPO_ROOT / script, project_root / script)
|
||||
|
||||
# ROOT in cli.py points to the voxvera/ directory
|
||||
monkeypatch.setattr(cli, "ROOT", voxvera_dir)
|
||||
|
||||
|
||||
+1
-1
@@ -1,7 +1,7 @@
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
__version__ = "0.1.5"
|
||||
__version__ = "0.1.6"
|
||||
|
||||
# Add the vendor directory to sys.path as a fallback.
|
||||
# For standard installs (pip/pipx), the installed dependencies in the venv
|
||||
|
||||
+59
-6
@@ -64,6 +64,14 @@ def t(path: str, **kwargs) -> str:
|
||||
|
||||
|
||||
def require_cmd(cmd: str):
|
||||
if cmd == "onionshare-cli":
|
||||
try:
|
||||
import onionshare_cli # noqa: F401
|
||||
return True
|
||||
except ImportError:
|
||||
print("Required tool 'onionshare-cli' not bundled or installed.", file=sys.stderr)
|
||||
return False
|
||||
|
||||
if shutil.which(cmd) is None:
|
||||
print(
|
||||
f"Required command '{cmd}' not found. Please install it.", file=sys.stderr
|
||||
@@ -75,10 +83,11 @@ def require_cmd(cmd: str):
|
||||
def check_deps():
|
||||
console = Console()
|
||||
|
||||
# External CLI tools still required at runtime
|
||||
cli_tools = ["onionshare-cli"]
|
||||
# External CLI tools still required at runtime (none anymore if bundled)
|
||||
cli_tools = []
|
||||
# Python packages used by the build pipeline
|
||||
py_packages = {
|
||||
"onionshare-cli": "onionshare_cli",
|
||||
"qrcode": "qrcode",
|
||||
"PIL (Pillow)": "PIL",
|
||||
"jsmin": "jsmin",
|
||||
@@ -445,6 +454,11 @@ def build_assets(
|
||||
"landing": l_data.get("landing", {})
|
||||
}
|
||||
|
||||
# Override landing data with user custom config data so JS doesn't overwrite it
|
||||
for field in ["title", "subtitle", "headline", "content", "url_message"]:
|
||||
if field in data and data[field]:
|
||||
all_locales[code]["landing"][field] = data[field]
|
||||
|
||||
html = html.replace("{{locales}}", json.dumps(all_locales))
|
||||
|
||||
# 1. Statically replace localization tokens {{t_web_...}}
|
||||
@@ -554,10 +568,29 @@ def get_tor_ports():
|
||||
return socks_port, ctl_port
|
||||
|
||||
|
||||
def serve(config_path: str) -> str | None:
|
||||
if not require_cmd("onionshare-cli"):
|
||||
def _internal_onionshare():
|
||||
"""Hidden command to run the bundled onionshare-cli."""
|
||||
import platform
|
||||
|
||||
# Try to add bundled Tor to PATH so OnionShare can find it
|
||||
system = platform.system().lower()
|
||||
platform_dir = "win" if "windows" in system else "mac" if "darwin" in system else "linux"
|
||||
tor_dir = ROOT / "resources" / "tor" / platform_dir
|
||||
if tor_dir.exists():
|
||||
os.environ["PATH"] = str(tor_dir) + os.pathsep + os.environ.get("PATH", "")
|
||||
|
||||
try:
|
||||
from onionshare_cli import main
|
||||
# sys.argv is ['voxvera', '_internal_onionshare', '--website', ...]
|
||||
# We need to make it look like ['onionshare-cli', '--website', ...]
|
||||
sys.argv = ["onionshare-cli"] + sys.argv[2:]
|
||||
sys.exit(main())
|
||||
except ImportError:
|
||||
print("Error: onionshare-cli not bundled or installed.", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
def serve(config_path: str) -> str | None:
|
||||
socks, ctl = get_tor_ports()
|
||||
|
||||
folder_name = load_config(config_path)["folder_name"]
|
||||
@@ -572,8 +605,11 @@ def serve(config_path: str) -> str | None:
|
||||
env["TOR_SOCKS_PORT"] = socks
|
||||
env["TOR_CONTROL_PORT"] = ctl
|
||||
|
||||
# Run the bundled onionshare-cli via our hidden command
|
||||
# sys.argv[0] is the current executable (python script or PyInstaller binary)
|
||||
cmd = [
|
||||
"onionshare-cli",
|
||||
sys.argv[0],
|
||||
"_internal_onionshare",
|
||||
"--website",
|
||||
"--public",
|
||||
"--persistent",
|
||||
@@ -584,6 +620,12 @@ def serve(config_path: str) -> str | None:
|
||||
"-v",
|
||||
str(dir_path),
|
||||
]
|
||||
|
||||
# If running via 'python -m voxvera.cli', sys.argv[0] might be the script path,
|
||||
# so we should ensure it's executed correctly if not a PyInstaller binary.
|
||||
if not getattr(sys, 'frozen', False) and sys.argv[0].endswith('.py'):
|
||||
cmd = [sys.executable, sys.argv[0]] + cmd[1:]
|
||||
|
||||
log_fh = open(logfile, "w")
|
||||
proc = subprocess.Popen(cmd, stdout=log_fh, stderr=subprocess.STDOUT, env=env)
|
||||
|
||||
@@ -848,6 +890,11 @@ def build_site():
|
||||
"landing": l_data.get("landing", {})
|
||||
}
|
||||
|
||||
# Override landing data with user custom config data so JS doesn't overwrite it
|
||||
for field in ["title", "subtitle", "headline", "content", "url_message"]:
|
||||
if field in data and data[field]:
|
||||
all_locales[code]["landing"][field] = data[field]
|
||||
|
||||
html = html.replace("{{locales}}", json.dumps(all_locales))
|
||||
|
||||
# 1. Statically replace localization tokens (web tokens)
|
||||
@@ -1228,8 +1275,14 @@ def main(argv=None):
|
||||
sub.add_parser("build-docs", help="Generate localized documentation from templates")
|
||||
sub.add_parser("build-site", help="Refresh the main site/ directory assets and bundle")
|
||||
sub.add_parser("manage", help="Manage VoxVera servers interactively")
|
||||
sub.add_parser("_internal_onionshare", help=argparse.SUPPRESS)
|
||||
|
||||
args, unknown_args = parser.parse_known_args(argv)
|
||||
|
||||
if args.command == "_internal_onionshare":
|
||||
_internal_onionshare()
|
||||
return
|
||||
|
||||
args = parser.parse_args(argv)
|
||||
config_path = Path(args.config).resolve()
|
||||
|
||||
# Determine language
|
||||
|
||||
Reference in new issue
Block a user