Compare commits

...
12 Commits
25 changed files with 188 additions and 93 deletions

No files matched your search

+30
View File
@@ -10,7 +10,10 @@ jobs:
build:
name: Build for ${{ matrix.os }}
runs-on: ${{ matrix.os }}
permissions:
contents: write
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, windows-latest, macos-latest]
include:
@@ -38,6 +41,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: |
@@ -57,6 +66,22 @@ jobs:
chmod +x packaging/build_deb.sh
./packaging/build_deb.sh dist/${{ matrix.artifact_name }}
- name: Build AppImage (Linux)
if: matrix.os == 'ubuntu-latest'
run: |
mkdir -p voxvera/resources/bin
cp dist/${{ matrix.artifact_name }} voxvera/resources/bin/voxvera-linux-x86_64
chmod +x packaging/build_appimage.sh
./packaging/build_appimage.sh
- name: Build Flatpak (Linux)
if: matrix.os == 'ubuntu-latest'
run: |
sudo apt-get update
sudo apt-get install -y flatpak flatpak-builder
chmod +x packaging/build_flatpak.sh
./packaging/build_flatpak.sh || echo "Flatpak build failed"
- name: Build .dmg (macOS)
if: matrix.os == 'macos-latest'
run: |
@@ -71,6 +96,8 @@ jobs:
dist/${{ matrix.artifact_name }}
voxvera/resources/bin/*.deb
voxvera/resources/bin/*.dmg
voxvera/resources/bin/*.AppImage
voxvera/resources/bin/*.flatpak
- name: Release
uses: softprops/action-gh-release@v2
@@ -80,5 +107,8 @@ jobs:
dist/${{ matrix.artifact_name }}
voxvera/resources/bin/*.deb
voxvera/resources/bin/*.dmg
voxvera/resources/bin/*.AppImage
voxvera/resources/bin/*.flatpak
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
+8 -2
View File
@@ -46,6 +46,12 @@ voxvera/src/qrcode-tear-offs.png
# PyInstaller
*.spec
dist_bin/
voxvera/resources/bin/voxvera-*
site/download/voxvera-portable.zip
voxvera/resources/bin/*
!voxvera/resources/bin/.gitkeep
site/download/*.zip
site/download/*.exe
site/download/*.deb
site/download/*.dmg
site/download/*.AppImage
site/download/*.flatpak
site/download/voxvera-linux
+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
View File
Whitespace-only changes.
+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.
+5
View File
@@ -17,6 +17,10 @@ mkdir -p "$APPDIR/usr/bin"
cp "$BINARY" "$APPDIR/usr/bin/voxvera"
chmod +x "$APPDIR/usr/bin/voxvera"
# AppRun symlink (Crucial for AppImage to launch)
ln -sf usr/bin/voxvera "$APPDIR/AppRun"
chmod +x "$APPDIR/AppRun"
# Desktop file
cat > "$APPDIR/voxvera.desktop" <<EOD
[Desktop Entry]
@@ -41,6 +45,7 @@ chmod +x appimagetool
# Run appimagetool
echo "Running appimagetool..."
export ARCH
mkdir -p voxvera/resources/bin
./appimagetool --appimage-extract-and-run "$APPDIR" voxvera/resources/bin/VoxVera-x86_64.AppImage
rm appimagetool
+1
View File
@@ -39,6 +39,7 @@ sed -i "s/^Version: .*/Version: $VERSION/" "$PKG_DIR/DEBIAN/control"
sed -i "s/^Architecture: .*/Architecture: $DEB_ARCH/" "$PKG_DIR/DEBIAN/control"
# Build the package
mkdir -p voxvera/resources/bin
dpkg-deb --build "$PKG_DIR" "voxvera/resources/bin/voxvera_${VERSION}_${DEB_ARCH}.deb"
echo "Debian package created at voxvera/resources/bin/voxvera_${VERSION}_${DEB_ARCH}.deb"
+1
View File
@@ -33,6 +33,7 @@ cp "$BINARY" "$STAGING_DIR/voxvera"
chmod +x "$STAGING_DIR/voxvera"
# Create the DMG
mkdir -p voxvera/resources/bin
hdiutil create -volname "VoxVera $VERSION" -srcfolder "$STAGING_DIR" -ov -format UDZO "voxvera/resources/bin/$DMG_NAME"
echo "DMG created at voxvera/resources/bin/$DMG_NAME"
+1
View File
@@ -30,6 +30,7 @@ rm -rf "$BUILD_DIR" "$REPO_DIR"
flatpak-builder --force-clean --repo="$REPO_DIR" "$BUILD_DIR" "$MANIFEST"
# Generate a standalone .flatpak file
mkdir -p voxvera/resources/bin
flatpak build-bundle "$REPO_DIR" voxvera/resources/bin/VoxVera-x86_64.flatpak org.voxvera.VoxVera
echo "Flatpak bundle created at voxvera/resources/bin/VoxVera-x86_64.flatpak"
+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.8"
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.8"
# 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
Binary file not shown.
Binary file not shown.