Compare commits

...
9 Commits
18 changed files with 165 additions and 91 deletions

No files matched your search

+9
View File
@@ -10,6 +10,8 @@ jobs:
build:
name: Build for ${{ matrix.os }}
runs-on: ${{ matrix.os }}
permissions:
contents: write
strategy:
matrix:
os: [ubuntu-latest, windows-latest, macos-latest]
@@ -38,6 +40,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: |
@@ -80,5 +88,6 @@ jobs:
dist/${{ matrix.artifact_name }}
voxvera/resources/bin/*.deb
voxvera/resources/bin/*.dmg
fail_on_unmatched_files: false
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
-62
View File
@@ -1,62 +0,0 @@
name: Release
on:
push:
tags:
- 'v*.*.*'
jobs:
build-release:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: '3.12'
- name: Install build tools
run: |
python -m pip install --upgrade pip
pip install build pyinstaller
- name: Build wheel
run: python -m build --wheel --sdist
- name: Build binary
run: |
pyinstaller --onefile \
--add-data "voxvera/locales:voxvera/locales" \
--add-data "voxvera/src:voxvera/src" \
--add-data "voxvera/templates:voxvera/templates" \
--add-data "voxvera/resources:voxvera/resources" \
--add-data "requirements.txt:." \
--name voxvera-linux \
voxvera/cli.py
- name: Create AppImage
run: |
sudo apt-get update
sudo apt-get install -y wget
bash packaging/build_appimage.sh
- name: Build Docker image
run: |
OWNER_LC=$(echo "${{ github.repository_owner }}" | tr '[:upper:]' '[:lower:]')
docker build -t ghcr.io/${OWNER_LC}/voxvera:${{ github.ref_name }} .
- name: Login to GHCR
uses: docker/login-action@v2
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Push Docker image
run: |
OWNER_LC=$(echo "${{ github.repository_owner }}" | tr '[:upper:]' '[:lower:]')
docker push ghcr.io/${OWNER_LC}/voxvera:${{ github.ref_name }}
- name: Publish to PyPI
uses: pypa/gh-action-pypi-publish@release/v1
with:
password: ${{ secrets.PYPI_API_TOKEN }}
- name: Upload release assets
uses: softprops/action-gh-release@v1
with:
files: |
dist/*.whl
dist/*.tar.gz
dist/voxvera-linux
dist/VoxVera.AppImage
+10 -4
View File
@@ -1,17 +1,21 @@
# VoxVera Release Process
**⚠️ ATTENTION AI AGENTS (CRITICAL WORKFLOW):**
Do **NOT** automatically package, tag, or push a new release after completing a set of changes. Releases should **ONLY** be triggered when the user explicitly requests one (e.g., "Package a release now" or "Let's release this").
Instead of releasing immediately, you must append a summary of your completed work to the `UPCOMING_RELEASE.md` file. This allows development to span multiple context sessions and ensures that new releases contain a substantial amount of value. When a release is finally requested, use the contents of `UPCOMING_RELEASE.md` to format the GitHub Release notes, and then clear the document for the next cycle.
This document outlines the mandatory steps to perform before every release to ensure code quality, cross-language integrity, and professional distribution assets.
## 1. Pre-Flight Checks (Quality Assurance)
Before any release, the codebase must pass all internal quality checks.
Before any release, the codebase must pass all internal quality checks **without taking any shortcuts**. All tests must be rigorous, use real data where appropriate, and passing them should be a true mark of quality.
### Run Tests
Ensure all core CLI, E2E, and Integrity tests pass:
```bash
pytest tests/
```
*Note: The `test_integrity.py` check is critical as it verifies that all 14+ language locales have synchronized keys.*
*Note: The `test_integrity.py` check is critical as it verifies that all 14+ language locales have synchronized keys and all binary bundles are 100% self-contained.*
### Run Linting
Ensure the code adheres to style guidelines (no trailing whitespace, proper spacing, etc.):
@@ -20,12 +24,14 @@ Ensure the code adheres to style guidelines (no trailing whitespace, proper spac
flake8 voxvera/ --exclude=voxvera/vendor/
```
## 2. Generate Localized Assets
## 2. Update & Generate Localized Assets
Before releasing, you must ensure that **all documentation accurately reflects the new features and functions** introduced in the release.
All documentation and site templates must be synchronized with the translation system. This ensures that any changes to documentation are reflected across all 14+ supported languages.
### Synchronize Documentation
If you have modified any documentation templates in `docs/templates/`, you **must** use the translation system to regenerate the localized documentation before release:
If you have modified any documentation templates in `docs/templates/` (which you should, to cover new features), you **must** use the translation system to regenerate the localized documentation before release:
```bash
# Regenerate docs/ folders for all languages from templates and locales
python3 -m voxvera.cli build-docs
+14
View File
@@ -0,0 +1,14 @@
# Upcoming Release Notes
*This document serves as a running changelog for the NEXT version of VoxVera. AI agents must append notable changes here as they are completed. Once a release is explicitly requested by the user, these notes will be used to format the GitHub Release description, and this file should be cleared/reset for the next cycle.*
### Version: Unreleased
#### 🚀 New Features
- *(Add new features here)*
#### 🛠 Improvements
- *(Add improvements here)*
#### 🐛 Bug Fixes
- *(Add bug fixes here)*
+9
View File
@@ -28,6 +28,15 @@ If you have downloaded the source code ZIP:
- **Linux/macOS:** `./setup.sh`
- **Windows:** `.\voxvera-install.sh` (or run `python -m pip install .`)
## Uninstallation
If you wish to remove VoxVera from your system, you can use the provided uninstall scripts from the source directory. This will safely remove the CLI tool and any downloaded dependencies.
- **Linux / macOS:** Run `./uninstall.sh`
- **Windows (PowerShell):** Run `.\uninstall.ps1`
*Note: Uninstallation will not delete your generated flyers or your `.onion` keys stored in the `~/voxvera-exports` or local `host/` folders.*
## Step-by-Step
1. **Initialize:** Run `voxvera init` and follow the prompts. You will be asked to select your language first.
+9
View File
@@ -28,6 +28,15 @@ If you have downloaded the source code ZIP:
- **Linux/macOS:** `./setup.sh`
- **Windows:** `.\voxvera-install.sh` (or run `python -m pip install .`)
## Uninstallation
If you wish to remove VoxVera from your system, you can use the provided uninstall scripts from the source directory. This will safely remove the CLI tool and any downloaded dependencies.
- **Linux / macOS:** Run `./uninstall.sh`
- **Windows (PowerShell):** Run `.\uninstall.ps1`
*Note: Uninstallation will not delete your generated flyers or your `.onion` keys stored in the `~/voxvera-exports` or local `host/` folders.*
## Step-by-Step
1. **Initialize:** Run `voxvera init` and follow the prompts. You will be asked to select your language first.
+9
View File
@@ -28,6 +28,15 @@ If you have downloaded the source code ZIP:
- **Linux/macOS:** `./setup.sh`
- **Windows:** `.\voxvera-install.sh` (or run `python -m pip install .`)
## Uninstallation
If you wish to remove VoxVera from your system, you can use the provided uninstall scripts from the source directory. This will safely remove the CLI tool and any downloaded dependencies.
- **Linux / macOS:** Run `./uninstall.sh`
- **Windows (PowerShell):** Run `.\uninstall.ps1`
*Note: Uninstallation will not delete your generated flyers or your `.onion` keys stored in the `~/voxvera-exports` or local `host/` folders.*
## Step-by-Step
1. **Initialize:** Run `voxvera init` and follow the prompts. You will be asked to select your language first.
+3 -2
View File
@@ -14,7 +14,7 @@ voxvera = ["templates/**", "src/**", "resources/**", "locales/**", "resources/bi
[project]
name = "voxvera"
version = "0.1.5"
version = "0.1.7"
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",
+2
View File
@@ -6,3 +6,5 @@ jsmin
htmlmin2
pypdf
wcwidth
onionshare-cli
+3
View File
@@ -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"
+13 -6
View File
@@ -1,12 +1,19 @@
#!/usr/bin/env bash
set -euo pipefail
VERSION="${VERSION:-13.5.18}"
BASE_URL="https://www.torproject.org/dist/torbrowser"
VERSION="${VERSION:-15.0.7}"
BASE_URL="https://dist.torproject.org/torbrowser"
ARCH=$(uname -m)
if [ "$ARCH" == "aarch64" ] || [ "$ARCH" == "arm64" ]; then
TOR_ARCH="aarch64"
else
TOR_ARCH="x86_64"
fi
case "$(uname -s)" in
Linux*) PLATFORM=linux; ARCHIVE="tor-expert-bundle-linux-x86_64-${VERSION}.tar.gz"; EXE=tor;;
Darwin*) PLATFORM=mac; ARCHIVE="tor-expert-bundle-macos-x86_64-${VERSION}.tar.gz"; EXE=tor;;
Linux*) PLATFORM=linux; ARCHIVE="tor-expert-bundle-linux-${TOR_ARCH}-${VERSION}.tar.gz"; EXE=tor;;
Darwin*) PLATFORM=mac; ARCHIVE="tor-expert-bundle-macos-${TOR_ARCH}-${VERSION}.tar.gz"; EXE=tor;;
MINGW*|MSYS*|CYGWIN*) PLATFORM=win; ARCHIVE="tor-expert-bundle-windows-x86_64-${VERSION}.tar.gz"; EXE=tor.exe;;
*) echo "Unsupported OS" >&2; exit 1;;
esac
@@ -26,10 +33,10 @@ DEST="$(dirname "$0")/../voxvera/resources/tor/$PLATFORM"
mkdir -p "$DEST"
TOR_BIN=$(find "$TMPDIR" -type f -name "$EXE" | head -n 1)
OBFS_BIN=$(find "$TMPDIR" -type f -name "obfs4proxy*" | head -n 1)
OBFS_BIN=$(find "$TMPDIR" -type f \( -name "lyrebird*" -o -name "obfs4proxy*" \) | head -n 1)
if [[ -z "$TOR_BIN" || -z "$OBFS_BIN" ]]; then
echo "Failed to locate tor or obfs4proxy in archive" >&2
echo "Failed to locate tor or lyrebird/obfs4proxy in archive" >&2
exit 1
fi
Binary file not shown.
+1 -1
View File
@@ -378,7 +378,7 @@
</style>
<script>
// VoxVera Multi-Language Engine
const locales = {"ar": {"meta": {"language_name": "\u0627\u0644\u0639\u0631\u0628\u064a\u0629", "flag": "\ud83c\uddf8\ud83c\udde6", "direction": "rtl"}, "web": {"tear_off_tor_browser": "\u0627\u0633\u062a\u062e\u062f\u0645 \u0645\u062a\u0635\u0641\u062d Tor", "tear_off_desc": "\u0644\u0644\u0645\u0633\u0627\u0639\u062f\u0629 \u0641\u064a \u0646\u0634\u0631 \u0647\u0630\u0647 \u0627\u0644\u0631\u0633\u0627\u0644\u0629\u060c \u0627\u0646\u062a\u0642\u0644 \u0625\u0644\u0649", "tear_off_footer": "\u0627\u0646\u0642\u0631 \u0641\u0648\u0642 \u0627\u0644\u0632\u0631 \u0623\u062f\u0646\u0627\u0647 \u0644\u0644\u0637\u0628\u0627\u0639\u0629", "built_with": "\u0628\u0646\u064a \u0628\u0648\u0627\u0633\u0637\u0629", "print_button": "\u0627\u0637\u0628\u0639 \u0627\u0644\u0645\u0646\u0634\u0648\u0631", "download_button": "\u062a\u062d\u0645\u064a\u0644 \u0627\u0644\u0623\u062f\u0627\u0629 \u0648\u0627\u0644\u0643\u0648\u062f"}, "landing": {"title": "\u0633\u0631\u064a \u0644\u0644\u063a\u0627\u064a\u0629", "subtitle": "~~\u0644\u0627~~ \u0644\u0644\u0646\u0634\u0631", "headline": "\u0639\u0645\u0644\u064a\u0629 VOX VERA", "content": "\u062a\u062d\u0631\u0631 \u0645\u0646 \u0627\u0644\u0631\u0642\u0627\u0628\u0629 \u0645\u0639 VoxVera. \u0623\u062f\u0627\u0629 \u062a\u0633\u0648\u064a\u0642 \u0639\u0635\u0627\u0628\u0627\u062a \u0645\u062c\u0647\u0648\u0644\u0629 \u0648\u0646\u0634\u0631 \u0631\u0633\u0627\u0626\u0644. \u0633\u0648\u0627\u0621 \u0639\u0628\u0631 \u0627\u0644\u0625\u0646\u062a\u0631\u0646\u062a \u0623\u0648 \u0641\u064a \u0627\u0644\u0639\u0627\u0644\u0645 \u0627\u0644\u0645\u0627\u062f\u064a\u060c \u064a\u0645\u0643\u0646\u0647\u0627 \u062a\u0645\u0643\u064a\u0646\u0643 \u0645\u0646 \u0646\u0634\u0631 \u0623\u0641\u0643\u0627\u0631\u0643 \u0628\u062c\u0631\u0623\u0629\u060c \u0645\u062d\u0645\u064a\u0629 \u0628\u0625\u062e\u0641\u0627\u0621 \u0627\u0644\u0647\u0648\u064a\u0629 \u0627\u0644\u0643\u0627\u0645\u0644 (\u0625\u0630\u0627 \u0643\u0646\u062a \u062a\u0633\u062a\u0636\u064a\u0641 \u0639\u0628\u0631 Tor). \u0642\u0645 \u0628\u062a\u062d\u0645\u064a\u0644 \u0627\u0644\u0643\u0648\u062f\u060c \u0648\u0635\u0645\u0645 \u0645\u0648\u0642\u0639 \u0645\u0646\u0634\u0648\u0631\u060c \u0648\u0627\u0633\u062a\u0636\u064a\u0641\u0647 \u0639\u0628\u0631 \u0627\u0644\u0625\u0646\u062a\u0631\u0646\u062a \u0648\u0642\u0645 \u0628\u062a\u0636\u062e\u064a\u0645 \u0631\u0633\u0627\u0644\u062a\u0643.\n\n\u0627\u0633\u062a\u062e\u062f\u0645 \u0627\u0644\u0642\u0648\u0629 \u0627\u0644\u0645\u064a\u0645\u064a\u0629 \u0644\u0645\u0634\u0627\u0631\u0643\u0629 \u0623\u0641\u0643\u0627\u0631\u0643 \u0641\u064a \u0645\u062f\u0631\u0633\u062a\u0643\u060c \u0623\u0648 \u0645\u0643\u0627\u0646 \u0639\u0645\u0644\u0643\u060c \u0623\u0648 \u0645\u062c\u062a\u0645\u0639\u0627\u062a\u0643 \u0639\u0628\u0631 \u0627\u0644\u0625\u0646\u062a\u0631\u0646\u062a\u060c \u0623\u0648 \u062d\u062a\u0649 \u0639\u0644\u0649 \u0645\u0633\u062a\u0648\u0649 \u0627\u0644\u0639\u0627\u0644\u0645. \u062a\u0636\u0645\u0646 VoxVera \u062a\u0631\u062f\u062f \u0635\u062f\u0649 \u0631\u0633\u0627\u0644\u062a\u0643 \u0628\u0639\u064a\u062f\u0627\u064b \u0648\u0639\u0644\u0649 \u0646\u0637\u0627\u0642 \u0648\u0627\u0633\u0639\u060c \u0645\u0639 \u0623\u0642\u0633\u0627\u0645 \u0642\u0627\u0628\u0644\u0629 \u0644\u0644\u0646\u0632\u0639 \u062a\u062a\u0645\u064a\u0632 \u0628\u0631\u0648\u0627\u0628\u0637 \u0641\u0631\u064a\u062f\u0629 \u0648\u0623\u0643\u0648\u0627\u062f QR \u0644\u0633\u0647\u0648\u0644\u0629 \u0625\u0639\u0627\u062f\u0629 \u0627\u0644\u0637\u0628\u0627\u0639\u0629.\n\n\u064a\u0645\u0643\u0646 \u0627\u0644\u062d\u0641\u0627\u0638 \u0639\u0644\u0649 \u0627\u0644\u062e\u0635\u0648\u0635\u064a\u0629. \u064a\u0645\u0643\u0646 \u0645\u0634\u0627\u0631\u0643\u0629 \u0627\u0644\u0645\u0646\u0634\u0648\u0631\u0627\u062a \u0639\u0628\u0631 \u0634\u0628\u0643\u0629 Tor\u060c \u0645\u0645\u0627 \u064a\u062d\u0645\u064a \u0627\u0644\u0645\u0636\u064a\u0641 \u0648\u0627\u0644\u0645\u0633\u062a\u062e\u062f\u0645\u064a\u0646 \u0645\u0646 \u0627\u0644\u0631\u0642\u0627\u0628\u0629.\n\n\u0627\u0646\u0636\u0645 \u0625\u0644\u064a\u0646\u0627 \u0641\u064a \u062b\u0648\u0631\u0629 \u062a\u0642\u062f\u0631 \u0627\u0644\u062d\u0642\u064a\u0642\u0629 \u0648\u0627\u0644\u0634\u0641\u0627\u0641\u064a\u0629. \u0645\u0639\u0627\u064b\u060c \u064a\u0645\u0643\u0646\u0646\u0627 \u0628\u0646\u0627\u0621 \u0634\u0628\u0643\u0629 \u0645\u0646 \u0627\u0644\u0645\u0648\u0627\u0637\u0646\u064a\u0646 \u0627\u0644\u0645\u0633\u062a\u0646\u064a\u0631\u064a\u0646 \u0627\u0644\u0630\u064a\u0646 \u0644\u0627 \u064a\u062e\u0634\u0648\u0646 \u0627\u0644\u062a\u062d\u062f\u062b.", "url_message": "\u0627\u062d\u0635\u0644 \u0639\u0644\u0649 VoxVera \u0648\u0627\u0628\u062f\u0623 \u0628\u0646\u0634\u0631 \u0631\u0633\u0627\u0644\u062a\u0643.", "name": "Vox Vera Printable Flyers"}}, "de": {"meta": {"language_name": "Deutsch", "flag": "\ud83c\udde9\ud83c\uddea"}, "web": {"teLine truncated
const locales = {"ar": {"meta": {"language_name": "\u0627\u0644\u0639\u0631\u0628\u064a\u0629", "flag": "\ud83c\uddf8\ud83c\udde6", "direction": "rtl"}, "web": {"tear_off_tor_browser": "\u0627\u0633\u062a\u062e\u062f\u0645 \u0645\u062a\u0635\u0641\u062d Tor", "tear_off_desc": "\u0644\u0644\u0645\u0633\u0627\u0639\u062f\u0629 \u0641\u064a \u0646\u0634\u0631 \u0647\u0630\u0647 \u0627\u0644\u0631\u0633\u0627\u0644\u0629\u060c \u0627\u0646\u062a\u0642\u0644 \u0625\u0644\u0649", "tear_off_footer": "\u0627\u0646\u0642\u0631 \u0641\u0648\u0642 \u0627\u0644\u0632\u0631 \u0623\u062f\u0646\u0627\u0647 \u0644\u0644\u0637\u0628\u0627\u0639\u0629", "built_with": "\u0628\u0646\u064a \u0628\u0648\u0627\u0633\u0637\u0629", "print_button": "\u0627\u0637\u0628\u0639 \u0627\u0644\u0645\u0646\u0634\u0648\u0631", "download_button": "\u062a\u062d\u0645\u064a\u0644 \u0627\u0644\u0623\u062f\u0627\u0629 \u0648\u0627\u0644\u0643\u0648\u062f"}, "landing": {"title": "TOP SECRET", "subtitle": "DO ~~NOT~~ DISTRIBUTE", "headline": "OPERATION VOX VERA", "content": "Break free from censorship with VoxVera. An anonymous guerrilla marketing and message-spreading tool. Whether online or in the physical world, it can empower you to spread your ideas boldly, shielded by complete anonymity (if you host over Tor). Download the code, design a flyer site, host it online and amplify your message. \n\nUse memetic power to share your ideas in your school, workplace, online communities, or even globally. VoxVera ensures your message resonates far and wide, with tear-off sections featuring unique URLs and QR codes for easy reprinting. \n\nPrivacy can be maintained. Flyers can be shared via the Tor network, protecting hoster and users from censorship.\n\nJoin us in a revolution that values truth and transparency. Together, we can build a network of informed citizens who are unafraid to speak out.\n", "url_message": "Get VoxVera and start spreading your message.", "name": "Vox Vera Printable Flyers"}}, "de": {"meta": {"language_name": "Deutsch", "flag": "\ud83c\udde9\ud83c\uddea"}, "web": {"tear_off_tor_browser": "Tor-Browser verwenden", "tear_off_desc": "um diese Nachricht zu verbreiten, gehen Sie zu", "tear_off_footer": "Klicken Sie auf die Schaltfl\u00e4che unten zum Drucken", "built_with": "erstellt mit", "print_button": "Flyer drucken", "download_button": "Tool & Quellcode herunterladen"}, "landing": {"title": "TOP SECRET", "subtitle": "DO ~~NOT~~ DISTRIBUTE", "headline": "OPERATION VOX VERA", "content": "Break free from censorship with VoxVera. An anonymous guerrilla marketing and message-spreading tool. Whether online or in the physical world, it can empower you to spread your ideas boldly, shielded by complete anonymity (if you host over Tor). Download the code, design a flyer site, host it online and amplify your message. \n\nUse memetic power to share your ideas in your school, workplace, online communities, or even globally. VoxVera ensures your message resonates far and wide, with tear-off sections featuring unique URLs and QR codes for easy reprinting. \n\nPrivacy can be maintained. Flyers can be shared via the Tor network, protecting hoster and users from censorship.\n\nJoin us in a revolution that values truth and transparency. Together, we can build a network of informed citizens who are unafraid to speak out.\n", "url_message": "Get VoxVera and start spreading your message.", "name": "Vox Vera Printable Flyers"}}, "en": {"meta": {"language_name": "English", "flag": "\ud83c\uddfa\ud83c\uddf8"}, "web": {"tear_off_tor_browser": "Use Tor Browser", "tear_off_desc": "to help spread this message go to", "tear_off_footer": "click the button below to print", "built_with": "built with", "print_button": "Print this page", "download_button": "Download Source Code"}, "landing": {"title": "TOP SECRET", "subtitle": "DO ~~NOT~~ DISTRIBUTE", "headline": "OPERATION VOX VERA", "content": "Break free from censorship with VoxVera. An anonymous guerrilla marketing and message-spreading tool. Whether online or in the physical world, it can empower you to spread your ideas boldly, shielded by complete anonymity (if you host over Tor). Download the code, design a flyer site, host it online and amplify your message. \n\nUse memetic power to share your ideas in your school, workplace, online communities, or even globally. VoxVera ensures your message resonates far and wide, with tear-off sections featuring unique URLs and QR codes for easy reprinting. \n\nPrivacy can be maintained. Flyers can be shared via the Tor network, protecting hoster and users from censorship.\n\nJoin us in a revolution that values truth and transparency. Together, we can build a network of informed citizens who are unafraid to speak out.\n", "url_message": "Get VoxVera and start spreading your message.", "name": "Vox Vera Printable Flyers"}}, "es": {"meta": {"language_name": "Espa\u00f1ol", "flag": "\ud83c\uddea\ud83c\uddf8"}, "web": {"tear_off_tor_browser": "Use el Navegador Tor",Line truncated
function t(path, lang) {
const parts = path.split('.');
+8 -3
View File
@@ -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
View File
@@ -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",
+6 -3
View File
@@ -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
View File
@@ -1,7 +1,7 @@
import sys
from pathlib import Path
__version__ = "0.1.5"
__version__ = "0.1.7"
# Add the vendor directory to sys.path as a fallback.
# For standard installs (pip/pipx), the installed dependencies in the venv
+59 -6
View File
@@ -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