Compare commits

...
19 Commits
Author SHA1 Message Date
thePR0M3TH3AN 004cce2a4f chore: release v0.3.11 2026-03-19 22:05:16 -04:00
thePR0M3TH3AN ec0abd856c chore: release v0.3.10 2026-03-19 21:45:02 -04:00
thePR0M3TH3AN 7724ac78a7 chore: release v0.3.9 2026-03-19 21:36:04 -04:00
thePR0M3TH3AN c1a5e608de chore: release v0.3.8 2026-03-19 16:53:04 -04:00
thePR0M3TH3AN 44cf452f67 chore: release v0.3.7 2026-03-19 16:36:46 -04:00
thePR0M3TH3AN 4e49762a85 docs: add linux hosting validation runbook 2026-03-19 16:24:22 -04:00
thePR0M3TH3AN 2de7ea073c feat: add autostart lifecycle controls 2026-03-19 16:15:51 -04:00
thePR0M3TH3AN 2dd0cf4ed8 fix: install smoke test dependencies in ci 2026-03-19 16:03:55 -04:00
thePR0M3TH3AN 6c4d6d6aef feat: formalize platform runtime contracts 2026-03-19 15:57:50 -04:00
thePR0M3TH3AN e6ff21c069 chore: release v0.3.6 2026-03-17 16:16:17 -04:00
thePR0M3TH3AN 1fcb86ac8b chore: release v0.3.5 2026-03-17 15:35:17 -04:00
thePR0M3TH3AN 2b29d71ac5 chore: release v0.3.4 2026-03-17 14:18:42 -04:00
thePR0M3TH3ANandClaude Opus 4.6 46360ca6ea fix(install): clean stale generated files from site dirs before rebuild
Install script now removes index.html, QR codes, and download bundles
from existing site directories before rebuild-all regenerates them.
Preserves config.json, .onionshare-session, server.pid, and logs.
Also auto-rebuilds on serve if index.html is missing.

Co-Authored-By: Claude Opus 4.6 <[email protected]>
2026-03-16 21:46:14 -04:00
thePR0M3TH3ANandClaude Opus 4.6 03b1c51e27 feat(rebuild): add rebuild-all command and auto-rebuild on install
Existing sites kept stale index.html from old templates after upgrades.
New `voxvera rebuild-all` regenerates HTML for all sites from the
current master template while preserving keys, config, and content.
The install script now calls rebuild-all automatically after install.

Co-Authored-By: Claude Opus 4.6 <[email protected]>
2026-03-16 21:39:34 -04:00
thePR0M3TH3ANandClaude Opus 4.6 fc628fb9a0 fix(templates): sync bundled index.html copies with master template
These files are not read by the build pipeline (which always generates
from index-master.html), but keeping them in sync avoids confusion.

Co-Authored-By: Claude Opus 4.6 <[email protected]>
2026-03-16 21:33:47 -04:00
thePR0M3TH3ANandClaude Opus 4.6 91391b1dd7 fix(lint): add missing Console() instances in export/import key functions
Co-Authored-By: Claude Opus 4.6 <[email protected]>
2026-03-16 21:12:35 -04:00
thePR0M3TH3ANandClaude Opus 4.6 d61a7b4c92 feat(keys): add Export Keys / Import Keys to per-site menu with 14-language support
Allows users to export onion service session keys as base64 and import
them on another instance, enabling domain migration without re-hosting.
Bump version to 0.3.3.

Co-Authored-By: Claude Opus 4.6 <[email protected]>
2026-03-16 21:04:34 -04:00
thePR0M3TH3ANandClaude Opus 4.6 2dabd93809 bump(v0.3.2): version bump for new release
Co-Authored-By: Claude Opus 4.6 <[email protected]>
2026-03-16 19:51:37 -04:00
thePR0M3TH3ANandClaude Opus 4.6 ee5024678d fix(ui): tighten header spacing, preserve body edits on retry, add over-limit warning
- Switch content panel from space-between to flex-start so header
  elements (title, subtitle, headline) pack tightly instead of
  spreading across the full height
- Message area uses flex:1 to fill remaining space and keep footer
  at the bottom
- Body editor now preserves user text when re-editing after exceeding
  the 1000 character limit (previously reset to default)
- Show a yellow warning when user proceeds with over-limit body text
- Add body_over_limit_warning locale key across all 14 languages

Co-Authored-By: Claude Opus 4.6 <[email protected]>
2026-03-16 19:41:40 -04:00
138 changed files with 4417 additions and 621 deletions

No files matched your search

+25 -1
View File
@@ -69,11 +69,28 @@ jobs:
--add-data "voxvera/locales${SEP}voxvera/locales" \
--add-data "voxvera/src${SEP}voxvera/src" \
--add-data "voxvera/templates${SEP}voxvera/templates" \
--add-data "voxvera/resources${SEP}voxvera/resources" \
--add-data "voxvera/resources/tor${SEP}voxvera/resources/tor" \
--add-data "support-matrix.json${SEP}." \
--add-data "requirements.txt${SEP}." \
--name "${{ matrix.artifact_name }}" \
voxvera/cli.py
- name: Validate built binary contract surface (Linux/macOS)
if: matrix.os != 'windows-latest'
shell: bash
run: |
./dist/${{ matrix.artifact_name }} platform-status --json
./dist/${{ matrix.artifact_name }} doctor --json
./dist/${{ matrix.artifact_name }} autostart status --json
- name: Validate built binary contract surface (Windows)
if: matrix.os == 'windows-latest'
shell: pwsh
run: |
& ".\dist\${{ matrix.artifact_name }}" platform-status --json
& ".\dist\${{ matrix.artifact_name }}" doctor --json
& ".\dist\${{ matrix.artifact_name }}" autostart status --json
- name: Build .deb (Linux)
if: matrix.os == 'ubuntu-22.04'
run: |
@@ -90,6 +107,13 @@ jobs:
chmod +x packaging/build_appimage.sh
./packaging/build_appimage.sh dist/${{ matrix.artifact_name }}
- name: Validate Linux release artifacts
if: matrix.os == 'ubuntu-22.04'
shell: bash
run: |
chmod +x scripts/validate-release-artifacts.sh
./scripts/validate-release-artifacts.sh
- name: Build Flatpak (Linux)
if: matrix.os == 'ubuntu-22.04'
run: |
+39
View File
@@ -16,10 +16,49 @@ jobs:
python-version: '3.12'
- name: Install dependencies
run: |
sudo apt-get update
sudo apt-get install -y tor
python -m pip install --upgrade pip
pip install flake8
pip install -e ".[test]"
pip install git+https://github.com/onionshare/onionshare.git#subdirectory=cli
- name: Lint
run: flake8 voxvera tests --exclude=voxvera/vendor --ignore=E501,W503,W293,E402,E306,E704,E261,F841,E302,F401,E401,F541
- name: Test
run: pytest tests/ -v
- name: Platform smoke
run: bash scripts/platform-smoke.sh linux-cli
platform-contract:
runs-on: ${{ matrix.os }}
timeout-minutes: 15
strategy:
fail-fast: false
matrix:
include:
- os: macos-latest
smoke_target: macos-cli
- os: windows-latest
smoke_target: windows-cli
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: '3.12'
- name: Install dependencies
shell: bash
run: |
python -m pip install --upgrade pip
pip install -e ".[test]"
- name: Verify smoke target listing
shell: bash
run: bash scripts/platform-smoke.sh list
- name: Validate platform-status JSON
shell: bash
run: python -m voxvera.cli platform-status --json
- name: Validate doctor JSON
shell: bash
run: python -m voxvera.cli doctor --json
- name: Validate autostart JSON
shell: bash
run: python -m voxvera.cli autostart status --json
+7 -4
View File
@@ -16,9 +16,10 @@ Create a new JSON file in `voxvera/locales/{lang_code}.json`.
- **Formatting**: You can use Markdown-style strike-throughs (`~~redacted text~~`) in any of these strings; they will be automatically converted to the stylized "redacted" effect on the flyers.
### 2. The Landing Page
The main landing page (`site/index.html`) must be synchronized to include your new language.
- Open `site/index.html` and add your language to the `const locales` object in the `<script>` tag.
- Run `voxvera build-site` to refresh the public assets and ensure the pre-rendered HTML matches your new locale.
The main landing page is generated from the shared template and locale files.
- Add your language in `voxvera/locales/{lang_code}.json`.
- Do **not** edit `site/index.html` directly; it is generated output.
- Run `voxvera build-site` to refresh the public assets and inject your new locale into the generated HTML.
### 3. Documentation
VoxVera uses the **Doc-Sync Engine** to keep manuals in sync.
@@ -31,12 +32,14 @@ Run the following commands to verify your changes:
- `voxvera --lang {lang_code} check` (Verify CLI translation)
- `voxvera build-docs` (Generate localized manuals)
- `voxvera build-site` (Synchronize the public website and generate the latest portable bundle)
- Open `site/index.html` in a browser and test the language switcher.
- Open the generated `site/index.html` in a browser and test the language switcher.
## Code Contributions
- **Dependencies**: if you add a new Python library to `requirements.txt`, you MUST run `voxvera vendorize` to include it in the portable distribution.
- **Layout Integrity**: VoxVera uses visual width validation. If you add new UI elements, ensure they respect the physical boundaries of an 8.5"x11" flyer.
- **Tests**: Ensure all tests pass: `pytest tests/`. Run the integrity suite specifically when modifying locales: `pytest tests/test_integrity.py`.
- **Recommended local check**: Run `bash scripts/check.sh` before opening a PR. It mirrors the main lint/test/build checks in one command.
- **Generated artifacts**: Treat `voxvera/resources/bin/` as local build output, not source files. Release binaries belong in GitHub Releases, not hand-edited repo state.
- **Style**: Follow the existing coding style (PEP 8 for Python).
- **Documentation**: If adding a new feature, update the master templates in `docs/templates/` and run `voxvera build-docs`.
+7 -5
View File
@@ -1,5 +1,8 @@
FROM python:3.11-slim
# Experimental container path: Linux systemd remains the supported
# persistent-host deployment target.
# Install system dependencies (only runtime)
RUN apt-get update && apt-get install -y \
tor onionshare-cli \
@@ -13,10 +16,9 @@ WORKDIR /opt/voxvera
RUN pip install .
# Prepare flyers volume
RUN mkdir /flyers && ln -s /flyers /opt/voxvera/host
RUN mkdir /flyers
ENV VOXVERA_DIR=/flyers
VOLUME /flyers
# VoxVera expects to run in /opt/voxvera for local assets
WORKDIR /opt/voxvera
CMD ["voxvera", "quickstart"]
# Seed a default config when the volume is empty, then retry startup periodically.
CMD ["sh", "-lc", "mkdir -p /flyers && if [ ! -f /flyers/config.json ]; then cp /opt/voxvera/voxvera/src/config.json /flyers/config.json; fi && voxvera quickstart --non-interactive && while true; do sleep 300; voxvera start-all || true; done"]
+17 -3
View File
@@ -10,11 +10,17 @@ Generate printable flyers with QR codes linking to Tor (.onion) hidden services.
VoxVera is designed for maximum portability and censorship resistance.
Linux with systemd is the supported persistent-host deployment target.
Windows, macOS, Docker, Flatpak, AppImage, Homebrew, and Chocolatey remain experimental until their background Tor hosting and auto-recovery paths are validated end-to-end.
See [docs/platform-support-matrix.md](docs/platform-support-matrix.md) for the canonical platform status table and [docs/roadmap-platform-parity.md](docs/roadmap-platform-parity.md) for the parity plan.
For the supported Linux hosting path, use [docs/linux-hosting-validation.md](docs/linux-hosting-validation.md) as the operational validation runbook.
### 1. Standalone Binaries (Recommended)
Download the zero-dependency executable for your OS:
Download the executable for your OS:
- 🐧 [**Linux**](https://github.com/PR0M3TH3AN/VoxVera/releases/latest/download/voxvera-linux)
- 🪟 [**Windows**](https://github.com/PR0M3TH3AN/VoxVera/releases/latest/download/voxvera-windows.exe)
- 🍏 [**macOS**](https://github.com/PR0M3TH3AN/VoxVera/releases/latest/download/voxvera-macos)
- 🪟 [**Windows (Experimental)**](https://github.com/PR0M3TH3AN/VoxVera/releases/latest/download/voxvera-windows.exe)
- 🍏 [**macOS (Experimental)**](https://github.com/PR0M3TH3AN/VoxVera/releases/latest/download/voxvera-macos)
### 2. Universal Mirroring (The "Viral" Method)
If you found a physical VoxVera flyer, scan it and click **"Download"**. You will receive a `voxvera-portable.zip` containing the full tool, all dependencies, and all 14 languages—ready to run offline.
@@ -23,6 +29,7 @@ If you found a physical VoxVera flyer, scan it and click **"Download"**. You wil
```bash
curl -fsSL https://raw.githubusercontent.com/PR0M3TH3AN/VoxVera/main/install.sh | bash
```
On Linux, this now installs the recurring `systemd --user` recovery timer used to retry hidden-service startup after boot or offline periods. On macOS, the installer path is still experimental.
### 4. Developer / Python Install
```bash
@@ -30,6 +37,13 @@ pip install .
voxvera vendorize # Optional: pull dependencies locally for offline use
```
### 5. Local Validation
Run the full local validation sweep with:
```bash
bash scripts/check.sh
```
This runs linting, Python tests, Electron smoke tests, dependency checks, and the docs/site rebuild steps used during development.
---
## Key Features
+8 -1
View File
@@ -46,37 +46,44 @@ python3 -m voxvera.cli build-site
## 3. Build Distribution Binaries
Standardized binaries and AppImages must be generated for the current platform.
Linux CLI delivery is the supported deployment target today. Other package formats should be treated as experimental release artifacts until their Tor/OnionShare background-hosting behavior is validated end-to-end.
Capture platform evidence before promoting any support tier: run `bash scripts/platform-smoke.sh linux-cli` locally for the supported Linux path, and keep CI/workflow evidence aligned with the same command.
As other runtime targets mature, use `bash scripts/platform-smoke.sh macos-cli`, `bash scripts/platform-smoke.sh windows-cli`, or `bash scripts/platform-smoke.sh docker-cli` on their native environments before changing their support labels.
### Standard Binary
Generates an architecture-specific binary (e.g., `voxvera-linux-x86_64`) in `voxvera/resources/bin/`.
```bash
./scripts/build_binaries.sh
```
This directory is local build output only. Do not treat old files in `voxvera/resources/bin/` as canonical release history; the GitHub Release artifacts are the publish surface.
### AppImage (Linux)
Wraps the standard binary into a self-contained AppImage.
```bash
./packaging/build_appimage.sh
```
Experimental package surface.
### Flatpak (Linux)
Generates a standalone `.flatpak` bundle. Requires `flatpak-builder`.
```bash
./packaging/build_flatpak.sh
```
Experimental package surface.
### Debian Package (Linux)
Generates a `.deb` package for Debian/Ubuntu systems.
```bash
./packaging/build_deb.sh
```
Experimental package surface unless its runtime dependencies and recovery behavior are explicitly validated for the release.
### DMG (macOS)
Generates a `.dmg` installer for macOS (must be run on macOS).
```bash
./packaging/build_dmg.sh
```
Experimental package surface.
## 4. Release Execution
+1 -1
View File
@@ -1 +1 @@
# Upcoming Release Notes
+19 -4
View File
@@ -15,14 +15,20 @@ echo "Starting E2E verification at $(date)"
if [ -n "${VOXVERA_BIN:-}" ] && [ -f "$VOXVERA_BIN" ]; then
voxvera_cmd="$VOXVERA_BIN"
echo "Using provided VoxVera binary: $voxvera_cmd" >&2
elif command -v voxvera >/dev/null 2>&1; then
voxvera_cmd="$(command -v voxvera)"
echo "Using existing VoxVera on PATH: $voxvera_cmd" >&2
else
echo "Installing VoxVera in editable mode..."
python3 -m pip install --break-system-packages -e ".[e2e]"
python3 -m pip install --break-system-packages --no-build-isolation -e ".[e2e]"
voxvera_cmd="$(command -v voxvera || echo "$HOME/.local/bin/voxvera")"
fi
echo "VoxVera command: $voxvera_cmd"
echo "Checking platform contract before workflow..."
bash scripts/platform-smoke.sh linux-cli
# Generate demo flyer
echo "Initializing demo flyer..."
"$voxvera_cmd" --lang en init --template voxvera <<EOI
@@ -30,16 +36,22 @@ DemoUser
voxvera
EOI
config_path="$HOME/host/voxvera/config.json"
if [ ! -f "$config_path" ]; then
echo "CRITICAL: Expected config file not found after init: $config_path" >&2
exit 1
fi
echo "Building demo flyer..."
"$voxvera_cmd" --lang en build
"$voxvera_cmd" --lang en --config "$config_path" build
# Verify build output exists in host/
folder_name="voxvera"
host_dir="voxvera/host/$folder_name"
host_dir="$HOME/host/$folder_name"
if [ ! -d "$host_dir" ]; then
echo "CRITICAL: Host directory $host_dir not found!" >&2
ls -R voxvera/host/ || true
ls -R "$HOME/host/" || true
exit 1
fi
@@ -95,6 +107,9 @@ if [ "${VOXVERA_E2E_OFFLINE:-}" != "1" ]; then
exit 1
fi
echo "Checking platform contract with live Tor..."
VOXVERA_REQUIRE_TOR=1 bash scripts/platform-smoke.sh linux-cli
# Start OnionShare
echo "Starting OnionShare..."
# Explicitly set environment variables that OnionShare-cli respects
+6 -4
View File
@@ -16,9 +16,10 @@ Create a new JSON file in `voxvera/locales/{lang_code}.json`.
- **Formatting**: You can use Markdown-style strike-throughs (`~~redacted text~~`) in any of these strings; they will be automatically converted to the stylized "redacted" effect on the flyers.
### 2. The Landing Page
The main landing page (`site/index.html`) must be synchronized to include your new language.
- Open `site/index.html` and add your language to the `const locales` object in the `<script>` tag.
- Run `voxvera build-site` to refresh the public assets and ensure the pre-rendered HTML matches your new locale.
The main landing page is generated from the shared template and locale files.
- Add your language in `voxvera/locales/{lang_code}.json`.
- Do **not** edit `site/index.html` directly; it is generated output.
- Run `voxvera build-site` to refresh the public assets and inject your new locale into the generated HTML.
### 3. Documentation
VoxVera uses the **Doc-Sync Engine** to keep manuals in sync.
@@ -31,12 +32,13 @@ Run the following commands to verify your changes:
- `voxvera --lang {lang_code} check` (Verify CLI translation)
- `voxvera build-docs` (Generate localized manuals)
- `voxvera build-site` (Synchronize the public website and generate the latest portable bundle)
- Open `site/index.html` in a browser and test the language switcher.
- Open the generated `site/index.html` in a browser and test the language switcher.
## Code Contributions
- **Dependencies**: if you add a new Python library to `requirements.txt`, you MUST run `voxvera vendorize` to include it in the portable distribution.
- **Layout Integrity**: VoxVera uses visual width validation. If you add new UI elements, ensure they respect the physical boundaries of an 8.5"x11" flyer.
- **Tests**: Ensure all tests pass: `pytest tests/`. Run the integrity suite specifically when modifying locales: `pytest tests/test_integrity.py`.
- **Generated artifacts**: Treat `voxvera/resources/bin/` as local build output, not source files. Release binaries belong in GitHub Releases, not hand-edited repo state.
- **Style**: Follow the existing coding style (PEP 8 for Python).
- **Documentation**: If adding a new feature, update the master templates in `docs/templates/` and run `voxvera build-docs`.
+6 -3
View File
@@ -1,5 +1,7 @@
# Docker Usage
Experimental: the container path is not yet a production-grade hidden-service deployment target. Linux with `systemd --user` remains the supported persistent-host environment.
A prebuilt image containing Tor, OnionShare and the `voxvera` CLI is published to
`ghcr.io/voxvera/voxvera`.
@@ -7,10 +9,11 @@ A prebuilt image containing Tor, OnionShare and the `voxvera` CLI is published t
# pull the image
docker pull ghcr.io/voxvera/voxvera:latest
# run voxvera quickstart with flyers stored in ./flyers
# run the experimental container with flyers stored in ./flyers
mkdir -p flyers
docker run -it --rm -v "$(pwd)/flyers:/flyers" ghcr.io/voxvera/voxvera
```
The container uses `/flyers` as the working directory and runs `voxvera quickstart`
by default. All generated flyer files will appear in the mounted `flyers` folder.
The container stores runtime state under `/flyers`, seeds a default `config.json` if needed, and retries `voxvera start-all` periodically after the initial `quickstart`.
Use this path for testing only until the container runtime and background recovery behavior are validated end-to-end.
+15
View File
@@ -6,6 +6,10 @@ This page collects common issues encountered when hosting or accessing flyers.
- Ensure Tor is allowed through your firewall. On systems using `ufw` you may need to run `sudo ufw allow tor`.
- Some networks block Tor entirely. If you cannot reach onion services, try connecting over a different network or use a Tor bridge.
## Supported deployment target
- The supported persistent-host setup is Linux with `systemd --user`.
- Windows, macOS, Docker, Flatpak, AppImage, Homebrew, and Chocolatey are currently experimental and may not recover hosting automatically after reboot or network interruptions.
## Tor port detection
`voxvera serve` auto-detects Tor by probing common ports (9050/9150 for SOCKS, 9051/9151 for control). If detection fails -- for example when Tor runs on non-standard ports -- you can override with environment variables:
@@ -39,6 +43,17 @@ keypair (and URL) will be generated on the next `voxvera serve`. The
`voxvera import` command preserves this file automatically, but manual
deletion of the `host/` directory will destroy it.
## Hosting does not return after reboot or network loss
On Linux, install or verify the recovery timer:
```bash
voxvera autostart
systemctl --user status voxvera-start.timer
systemctl --user status voxvera-start.service
```
The supported recovery path is the recurring `systemd --user` timer, which reruns `voxvera start-all` every few minutes.
## QR codes not generated
If `voxvera build` reports "No URLs configured yet", this is expected before the first `voxvera serve`. QR codes for tear-off links are generated automatically once the .onion address is known. QR codes for the content link are generated if you provided a URL during `voxvera init`.
+6 -4
View File
@@ -16,9 +16,10 @@ Create a new JSON file in `voxvera/locales/{lang_code}.json`.
- **Formatting**: You can use Markdown-style strike-throughs (`~~redacted text~~`) in any of these strings; they will be automatically converted to the stylized "redacted" effect on the flyers.
### 2. The Landing Page
The main landing page (`site/index.html`) must be synchronized to include your new language.
- Open `site/index.html` and add your language to the `const locales` object in the `<script>` tag.
- Run `voxvera build-site` to refresh the public assets and ensure the pre-rendered HTML matches your new locale.
The main landing page is generated from the shared template and locale files.
- Add your language in `voxvera/locales/{lang_code}.json`.
- Do **not** edit `site/index.html` directly; it is generated output.
- Run `voxvera build-site` to refresh the public assets and inject your new locale into the generated HTML.
### 3. Documentation
VoxVera uses the **Doc-Sync Engine** to keep manuals in sync.
@@ -31,12 +32,13 @@ Run the following commands to verify your changes:
- `voxvera --lang {lang_code} check` (Verify CLI translation)
- `voxvera build-docs` (Generate localized manuals)
- `voxvera build-site` (Synchronize the public website and generate the latest portable bundle)
- Open `site/index.html` in a browser and test the language switcher.
- Open the generated `site/index.html` in a browser and test the language switcher.
## Code Contributions
- **Dependencies**: if you add a new Python library to `requirements.txt`, you MUST run `voxvera vendorize` to include it in the portable distribution.
- **Layout Integrity**: VoxVera uses visual width validation. If you add new UI elements, ensure they respect the physical boundaries of an 8.5"x11" flyer.
- **Tests**: Ensure all tests pass: `pytest tests/`. Run the integrity suite specifically when modifying locales: `pytest tests/test_integrity.py`.
- **Generated artifacts**: Treat `voxvera/resources/bin/` as local build output, not source files. Release binaries belong in GitHub Releases, not hand-edited repo state.
- **Style**: Follow the existing coding style (PEP 8 for Python).
- **Documentation**: If adding a new feature, update the master templates in `docs/templates/` and run `voxvera build-docs`.
+6 -3
View File
@@ -1,5 +1,7 @@
# Docker Usage
Experimental: the container path is not yet a production-grade hidden-service deployment target. Linux with `systemd --user` remains the supported persistent-host environment.
A prebuilt image containing Tor, OnionShare and the `voxvera` CLI is published to
`ghcr.io/voxvera/voxvera`.
@@ -7,10 +9,11 @@ A prebuilt image containing Tor, OnionShare and the `voxvera` CLI is published t
# pull the image
docker pull ghcr.io/voxvera/voxvera:latest
# run voxvera quickstart with flyers stored in ./flyers
# run the experimental container with flyers stored in ./flyers
mkdir -p flyers
docker run -it --rm -v "$(pwd)/flyers:/flyers" ghcr.io/voxvera/voxvera
```
The container uses `/flyers` as the working directory and runs `voxvera quickstart`
by default. All generated flyer files will appear in the mounted `flyers` folder.
The container stores runtime state under `/flyers`, seeds a default `config.json` if needed, and retries `voxvera start-all` periodically after the initial `quickstart`.
Use this path for testing only until the container runtime and background recovery behavior are validated end-to-end.
+15
View File
@@ -6,6 +6,10 @@ This page collects common issues encountered when hosting or accessing flyers.
- Ensure Tor is allowed through your firewall. On systems using `ufw` you may need to run `sudo ufw allow tor`.
- Some networks block Tor entirely. If you cannot reach onion services, try connecting over a different network or use a Tor bridge.
## Supported deployment target
- The supported persistent-host setup is Linux with `systemd --user`.
- Windows, macOS, Docker, Flatpak, AppImage, Homebrew, and Chocolatey are currently experimental and may not recover hosting automatically after reboot or network interruptions.
## Tor port detection
`voxvera serve` auto-detects Tor by probing common ports (9050/9150 for SOCKS, 9051/9151 for control). If detection fails -- for example when Tor runs on non-standard ports -- you can override with environment variables:
@@ -39,6 +43,17 @@ keypair (and URL) will be generated on the next `voxvera serve`. The
`voxvera import` command preserves this file automatically, but manual
deletion of the `host/` directory will destroy it.
## Hosting does not return after reboot or network loss
On Linux, install or verify the recovery timer:
```bash
voxvera autostart
systemctl --user status voxvera-start.timer
systemctl --user status voxvera-start.service
```
The supported recovery path is the recurring `systemd --user` timer, which reruns `voxvera start-all` every few minutes.
## QR codes not generated
If `voxvera build` reports "No URLs configured yet", this is expected before the first `voxvera serve`. QR codes for tear-off links are generated automatically once the .onion address is known. QR codes for the content link are generated if you provided a URL during `voxvera init`.
+6 -4
View File
@@ -16,9 +16,10 @@ Create a new JSON file in `voxvera/locales/{lang_code}.json`.
- **Formatting**: You can use Markdown-style strike-throughs (`~~redacted text~~`) in any of these strings; they will be automatically converted to the stylized "redacted" effect on the flyers.
### 2. The Landing Page
The main landing page (`site/index.html`) must be synchronized to include your new language.
- Open `site/index.html` and add your language to the `const locales` object in the `<script>` tag.
- Run `voxvera build-site` to refresh the public assets and ensure the pre-rendered HTML matches your new locale.
The main landing page is generated from the shared template and locale files.
- Add your language in `voxvera/locales/{lang_code}.json`.
- Do **not** edit `site/index.html` directly; it is generated output.
- Run `voxvera build-site` to refresh the public assets and inject your new locale into the generated HTML.
### 3. Documentation
VoxVera uses the **Doc-Sync Engine** to keep manuals in sync.
@@ -31,12 +32,13 @@ Run the following commands to verify your changes:
- `voxvera --lang {lang_code} check` (Verify CLI translation)
- `voxvera build-docs` (Generate localized manuals)
- `voxvera build-site` (Synchronize the public website and generate the latest portable bundle)
- Open `site/index.html` in a browser and test the language switcher.
- Open the generated `site/index.html` in a browser and test the language switcher.
## Code Contributions
- **Dependencies**: if you add a new Python library to `requirements.txt`, you MUST run `voxvera vendorize` to include it in the portable distribution.
- **Layout Integrity**: VoxVera uses visual width validation. If you add new UI elements, ensure they respect the physical boundaries of an 8.5"x11" flyer.
- **Tests**: Ensure all tests pass: `pytest tests/`. Run the integrity suite specifically when modifying locales: `pytest tests/test_integrity.py`.
- **Generated artifacts**: Treat `voxvera/resources/bin/` as local build output, not source files. Release binaries belong in GitHub Releases, not hand-edited repo state.
- **Style**: Follow the existing coding style (PEP 8 for Python).
- **Documentation**: If adding a new feature, update the master templates in `docs/templates/` and run `voxvera build-docs`.
+6 -3
View File
@@ -1,5 +1,7 @@
# Docker Usage
Experimental: the container path is not yet a production-grade hidden-service deployment target. Linux with `systemd --user` remains the supported persistent-host environment.
A prebuilt image containing Tor, OnionShare and the `voxvera` CLI is published to
`ghcr.io/voxvera/voxvera`.
@@ -7,10 +9,11 @@ A prebuilt image containing Tor, OnionShare and the `voxvera` CLI is published t
# pull the image
docker pull ghcr.io/voxvera/voxvera:latest
# run voxvera quickstart with flyers stored in ./flyers
# run the experimental container with flyers stored in ./flyers
mkdir -p flyers
docker run -it --rm -v "$(pwd)/flyers:/flyers" ghcr.io/voxvera/voxvera
```
The container uses `/flyers` as the working directory and runs `voxvera quickstart`
by default. All generated flyer files will appear in the mounted `flyers` folder.
The container stores runtime state under `/flyers`, seeds a default `config.json` if needed, and retries `voxvera start-all` periodically after the initial `quickstart`.
Use this path for testing only until the container runtime and background recovery behavior are validated end-to-end.
+15
View File
@@ -6,6 +6,10 @@ This page collects common issues encountered when hosting or accessing flyers.
- Ensure Tor is allowed through your firewall. On systems using `ufw` you may need to run `sudo ufw allow tor`.
- Some networks block Tor entirely. If you cannot reach onion services, try connecting over a different network or use a Tor bridge.
## Supported deployment target
- The supported persistent-host setup is Linux with `systemd --user`.
- Windows, macOS, Docker, Flatpak, AppImage, Homebrew, and Chocolatey are currently experimental and may not recover hosting automatically after reboot or network interruptions.
## Tor port detection
`voxvera serve` auto-detects Tor by probing common ports (9050/9150 for SOCKS, 9051/9151 for control). If detection fails -- for example when Tor runs on non-standard ports -- you can override with environment variables:
@@ -39,6 +43,17 @@ keypair (and URL) will be generated on the next `voxvera serve`. The
`voxvera import` command preserves this file automatically, but manual
deletion of the `host/` directory will destroy it.
## Hosting does not return after reboot or network loss
On Linux, install or verify the recovery timer:
```bash
voxvera autostart
systemctl --user status voxvera-start.timer
systemctl --user status voxvera-start.service
```
The supported recovery path is the recurring `systemd --user` timer, which reruns `voxvera start-all` every few minutes.
## QR codes not generated
If `voxvera build` reports "No URLs configured yet", this is expected before the first `voxvera serve`. QR codes for tear-off links are generated automatically once the .onion address is known. QR codes for the content link are generated if you provided a URL during `voxvera init`.
+19 -1
View File
@@ -6,18 +6,22 @@ This guide covers common CLI workflows. See `docs/docker.md` for Docker instruct
VoxVera is designed to be highly portable. You can run it directly from the source code on any platform with Python installed.
Linux with `systemd --user` is the supported persistent-host environment. Windows, macOS, Docker, Flatpak, AppImage, Homebrew, and Chocolatey are currently experimental and should not yet be treated as equally reliable hidden-service deployment targets.
### 1. Automated Install (Recommended)
The easiest way to install VoxVera is using our cross-platform installer script:
The easiest way to install VoxVera is using the Linux-first installer script:
**Linux / macOS:**
```bash
curl -fsSL https://raw.githubusercontent.com/PR0M3TH3AN/VoxVera/main/install.sh | bash
```
On Linux, this also installs the recurring recovery timer used to retry hidden-service startup after boot and after missed/offline periods.
**Windows (PowerShell):**
```powershell
irm https://raw.githubusercontent.com/PR0M3TH3AN/VoxVera/main/install.ps1 | iex
```
The Windows installer is experimental and does not yet guarantee production-ready background hosting behavior.
### 2. Manual Source Install
If you have downloaded the source code ZIP:
@@ -50,6 +54,16 @@ If you wish to remove VoxVera from your system, you can use the provided uninsta
```
This automatically detects your Tor instance, starts OnionShare, and writes the generated .onion address into the flyer's tear-off links.
### Persistent Hosting On Linux
For a machine that should resume hosting automatically after boot or after being offline, install the autostart recovery timer:
```bash
voxvera autostart
```
This installs a `systemd --user` timer that reruns `voxvera start-all` every few minutes. The retry loop is the supported way to recover hidden-service hosting after suspend, reboot, or temporary network loss.
## Language Support
VoxVera is fully localized. You can change your language preference permanently using either the interactive selector or a direct shortcut:
@@ -92,6 +106,10 @@ When you host a flyer, the **"Download Source Code"** button on the landing page
- Installation and setup scripts for all platforms.
- Complete documentation and templates.
To keep the mirrored download practical for low-bandwidth redistribution, the
source archive does not ship pre-downloaded Tor binaries. Normal installer and
release builds still fetch or bundle those artifacts separately.
This allows anyone who scans your flyer to become a new distributor of the VoxVera tool.
## Export & Backup
+6 -4
View File
@@ -16,9 +16,10 @@ Create a new JSON file in `voxvera/locales/{lang_code}.json`.
- **Formatting**: You can use Markdown-style strike-throughs (`~~redacted text~~`) in any of these strings; they will be automatically converted to the stylized "redacted" effect on the flyers.
### 2. The Landing Page
The main landing page (`site/index.html`) must be synchronized to include your new language.
- Open `site/index.html` and add your language to the `const locales` object in the `<script>` tag.
- Run `voxvera build-site` to refresh the public assets and ensure the pre-rendered HTML matches your new locale.
The main landing page is generated from the shared template and locale files.
- Add your language in `voxvera/locales/{lang_code}.json`.
- Do **not** edit `site/index.html` directly; it is generated output.
- Run `voxvera build-site` to refresh the public assets and inject your new locale into the generated HTML.
### 3. Documentation
VoxVera uses the **Doc-Sync Engine** to keep manuals in sync.
@@ -31,12 +32,13 @@ Run the following commands to verify your changes:
- `voxvera --lang {lang_code} check` (Verify CLI translation)
- `voxvera build-docs` (Generate localized manuals)
- `voxvera build-site` (Synchronize the public website and generate the latest portable bundle)
- Open `site/index.html` in a browser and test the language switcher.
- Open the generated `site/index.html` in a browser and test the language switcher.
## Code Contributions
- **Dependencies**: if you add a new Python library to `requirements.txt`, you MUST run `voxvera vendorize` to include it in the portable distribution.
- **Layout Integrity**: VoxVera uses visual width validation. If you add new UI elements, ensure they respect the physical boundaries of an 8.5"x11" flyer.
- **Tests**: Ensure all tests pass: `pytest tests/`. Run the integrity suite specifically when modifying locales: `pytest tests/test_integrity.py`.
- **Generated artifacts**: Treat `voxvera/resources/bin/` as local build output, not source files. Release binaries belong in GitHub Releases, not hand-edited repo state.
- **Style**: Follow the existing coding style (PEP 8 for Python).
- **Documentation**: If adding a new feature, update the master templates in `docs/templates/` and run `voxvera build-docs`.
+6 -3
View File
@@ -1,5 +1,7 @@
# Docker Usage
Experimental: the container path is not yet a production-grade hidden-service deployment target. Linux with `systemd --user` remains the supported persistent-host environment.
A prebuilt image containing Tor, OnionShare and the `voxvera` CLI is published to
`ghcr.io/voxvera/voxvera`.
@@ -7,10 +9,11 @@ A prebuilt image containing Tor, OnionShare and the `voxvera` CLI is published t
# pull the image
docker pull ghcr.io/voxvera/voxvera:latest
# run voxvera quickstart with flyers stored in ./flyers
# run the experimental container with flyers stored in ./flyers
mkdir -p flyers
docker run -it --rm -v "$(pwd)/flyers:/flyers" ghcr.io/voxvera/voxvera
```
The container uses `/flyers` as the working directory and runs `voxvera quickstart`
by default. All generated flyer files will appear in the mounted `flyers` folder.
The container stores runtime state under `/flyers`, seeds a default `config.json` if needed, and retries `voxvera start-all` periodically after the initial `quickstart`.
Use this path for testing only until the container runtime and background recovery behavior are validated end-to-end.
+15
View File
@@ -6,6 +6,10 @@ This page collects common issues encountered when hosting or accessing flyers.
- Ensure Tor is allowed through your firewall. On systems using `ufw` you may need to run `sudo ufw allow tor`.
- Some networks block Tor entirely. If you cannot reach onion services, try connecting over a different network or use a Tor bridge.
## Supported deployment target
- The supported persistent-host setup is Linux with `systemd --user`.
- Windows, macOS, Docker, Flatpak, AppImage, Homebrew, and Chocolatey are currently experimental and may not recover hosting automatically after reboot or network interruptions.
## Tor port detection
`voxvera serve` auto-detects Tor by probing common ports (9050/9150 for SOCKS, 9051/9151 for control). If detection fails -- for example when Tor runs on non-standard ports -- you can override with environment variables:
@@ -39,6 +43,17 @@ keypair (and URL) will be generated on the next `voxvera serve`. The
`voxvera import` command preserves this file automatically, but manual
deletion of the `host/` directory will destroy it.
## Hosting does not return after reboot or network loss
On Linux, install or verify the recovery timer:
```bash
voxvera autostart
systemctl --user status voxvera-start.timer
systemctl --user status voxvera-start.service
```
The supported recovery path is the recurring `systemd --user` timer, which reruns `voxvera start-all` every few minutes.
## QR codes not generated
If `voxvera build` reports "No URLs configured yet", this is expected before the first `voxvera serve`. QR codes for tear-off links are generated automatically once the .onion address is known. QR codes for the content link are generated if you provided a URL during `voxvera init`.
+6 -4
View File
@@ -16,9 +16,10 @@ Create a new JSON file in `voxvera/locales/{lang_code}.json`.
- **Formatting**: You can use Markdown-style strike-throughs (`~~redacted text~~`) in any of these strings; they will be automatically converted to the stylized "redacted" effect on the flyers.
### 2. The Landing Page
The main landing page (`site/index.html`) must be synchronized to include your new language.
- Open `site/index.html` and add your language to the `const locales` object in the `<script>` tag.
- Run `voxvera build-site` to refresh the public assets and ensure the pre-rendered HTML matches your new locale.
The main landing page is generated from the shared template and locale files.
- Add your language in `voxvera/locales/{lang_code}.json`.
- Do **not** edit `site/index.html` directly; it is generated output.
- Run `voxvera build-site` to refresh the public assets and inject your new locale into the generated HTML.
### 3. Documentation
VoxVera uses the **Doc-Sync Engine** to keep manuals in sync.
@@ -31,12 +32,13 @@ Run the following commands to verify your changes:
- `voxvera --lang {lang_code} check` (Verify CLI translation)
- `voxvera build-docs` (Generate localized manuals)
- `voxvera build-site` (Synchronize the public website and generate the latest portable bundle)
- Open `site/index.html` in a browser and test the language switcher.
- Open the generated `site/index.html` in a browser and test the language switcher.
## Code Contributions
- **Dependencies**: if you add a new Python library to `requirements.txt`, you MUST run `voxvera vendorize` to include it in the portable distribution.
- **Layout Integrity**: VoxVera uses visual width validation. If you add new UI elements, ensure they respect the physical boundaries of an 8.5"x11" flyer.
- **Tests**: Ensure all tests pass: `pytest tests/`. Run the integrity suite specifically when modifying locales: `pytest tests/test_integrity.py`.
- **Generated artifacts**: Treat `voxvera/resources/bin/` as local build output, not source files. Release binaries belong in GitHub Releases, not hand-edited repo state.
- **Style**: Follow the existing coding style (PEP 8 for Python).
- **Documentation**: If adding a new feature, update the master templates in `docs/templates/` and run `voxvera build-docs`.
+6 -3
View File
@@ -1,5 +1,7 @@
# Docker Usage
Experimental: the container path is not yet a production-grade hidden-service deployment target. Linux with `systemd --user` remains the supported persistent-host environment.
A prebuilt image containing Tor, OnionShare and the `voxvera` CLI is published to
`ghcr.io/voxvera/voxvera`.
@@ -7,10 +9,11 @@ A prebuilt image containing Tor, OnionShare and the `voxvera` CLI is published t
# pull the image
docker pull ghcr.io/voxvera/voxvera:latest
# run voxvera quickstart with flyers stored in ./flyers
# run the experimental container with flyers stored in ./flyers
mkdir -p flyers
docker run -it --rm -v "$(pwd)/flyers:/flyers" ghcr.io/voxvera/voxvera
```
The container uses `/flyers` as the working directory and runs `voxvera quickstart`
by default. All generated flyer files will appear in the mounted `flyers` folder.
The container stores runtime state under `/flyers`, seeds a default `config.json` if needed, and retries `voxvera start-all` periodically after the initial `quickstart`.
Use this path for testing only until the container runtime and background recovery behavior are validated end-to-end.
+15
View File
@@ -6,6 +6,10 @@ This page collects common issues encountered when hosting or accessing flyers.
- Ensure Tor is allowed through your firewall. On systems using `ufw` you may need to run `sudo ufw allow tor`.
- Some networks block Tor entirely. If you cannot reach onion services, try connecting over a different network or use a Tor bridge.
## Supported deployment target
- The supported persistent-host setup is Linux with `systemd --user`.
- Windows, macOS, Docker, Flatpak, AppImage, Homebrew, and Chocolatey are currently experimental and may not recover hosting automatically after reboot or network interruptions.
## Tor port detection
`voxvera serve` auto-detects Tor by probing common ports (9050/9150 for SOCKS, 9051/9151 for control). If detection fails -- for example when Tor runs on non-standard ports -- you can override with environment variables:
@@ -39,6 +43,17 @@ keypair (and URL) will be generated on the next `voxvera serve`. The
`voxvera import` command preserves this file automatically, but manual
deletion of the `host/` directory will destroy it.
## Hosting does not return after reboot or network loss
On Linux, install or verify the recovery timer:
```bash
voxvera autostart
systemctl --user status voxvera-start.timer
systemctl --user status voxvera-start.service
```
The supported recovery path is the recurring `systemd --user` timer, which reruns `voxvera start-all` every few minutes.
## QR codes not generated
If `voxvera build` reports "No URLs configured yet", this is expected before the first `voxvera serve`. QR codes for tear-off links are generated automatically once the .onion address is known. QR codes for the content link are generated if you provided a URL during `voxvera init`.
+6 -4
View File
@@ -16,9 +16,10 @@ Create a new JSON file in `voxvera/locales/{lang_code}.json`.
- **Formatting**: You can use Markdown-style strike-throughs (`~~redacted text~~`) in any of these strings; they will be automatically converted to the stylized "redacted" effect on the flyers.
### 2. The Landing Page
The main landing page (`site/index.html`) must be synchronized to include your new language.
- Open `site/index.html` and add your language to the `const locales` object in the `<script>` tag.
- Run `voxvera build-site` to refresh the public assets and ensure the pre-rendered HTML matches your new locale.
The main landing page is generated from the shared template and locale files.
- Add your language in `voxvera/locales/{lang_code}.json`.
- Do **not** edit `site/index.html` directly; it is generated output.
- Run `voxvera build-site` to refresh the public assets and inject your new locale into the generated HTML.
### 3. Documentation
VoxVera uses the **Doc-Sync Engine** to keep manuals in sync.
@@ -31,12 +32,13 @@ Run the following commands to verify your changes:
- `voxvera --lang {lang_code} check` (Verify CLI translation)
- `voxvera build-docs` (Generate localized manuals)
- `voxvera build-site` (Synchronize the public website and generate the latest portable bundle)
- Open `site/index.html` in a browser and test the language switcher.
- Open the generated `site/index.html` in a browser and test the language switcher.
## Code Contributions
- **Dependencies**: if you add a new Python library to `requirements.txt`, you MUST run `voxvera vendorize` to include it in the portable distribution.
- **Layout Integrity**: VoxVera uses visual width validation. If you add new UI elements, ensure they respect the physical boundaries of an 8.5"x11" flyer.
- **Tests**: Ensure all tests pass: `pytest tests/`. Run the integrity suite specifically when modifying locales: `pytest tests/test_integrity.py`.
- **Generated artifacts**: Treat `voxvera/resources/bin/` as local build output, not source files. Release binaries belong in GitHub Releases, not hand-edited repo state.
- **Style**: Follow the existing coding style (PEP 8 for Python).
- **Documentation**: If adding a new feature, update the master templates in `docs/templates/` and run `voxvera build-docs`.
+6 -3
View File
@@ -1,5 +1,7 @@
# Docker Usage
Experimental: the container path is not yet a production-grade hidden-service deployment target. Linux with `systemd --user` remains the supported persistent-host environment.
A prebuilt image containing Tor, OnionShare and the `voxvera` CLI is published to
`ghcr.io/voxvera/voxvera`.
@@ -7,10 +9,11 @@ A prebuilt image containing Tor, OnionShare and the `voxvera` CLI is published t
# pull the image
docker pull ghcr.io/voxvera/voxvera:latest
# run voxvera quickstart with flyers stored in ./flyers
# run the experimental container with flyers stored in ./flyers
mkdir -p flyers
docker run -it --rm -v "$(pwd)/flyers:/flyers" ghcr.io/voxvera/voxvera
```
The container uses `/flyers` as the working directory and runs `voxvera quickstart`
by default. All generated flyer files will appear in the mounted `flyers` folder.
The container stores runtime state under `/flyers`, seeds a default `config.json` if needed, and retries `voxvera start-all` periodically after the initial `quickstart`.
Use this path for testing only until the container runtime and background recovery behavior are validated end-to-end.
+15
View File
@@ -6,6 +6,10 @@ This page collects common issues encountered when hosting or accessing flyers.
- Ensure Tor is allowed through your firewall. On systems using `ufw` you may need to run `sudo ufw allow tor`.
- Some networks block Tor entirely. If you cannot reach onion services, try connecting over a different network or use a Tor bridge.
## Supported deployment target
- The supported persistent-host setup is Linux with `systemd --user`.
- Windows, macOS, Docker, Flatpak, AppImage, Homebrew, and Chocolatey are currently experimental and may not recover hosting automatically after reboot or network interruptions.
## Tor port detection
`voxvera serve` auto-detects Tor by probing common ports (9050/9150 for SOCKS, 9051/9151 for control). If detection fails -- for example when Tor runs on non-standard ports -- you can override with environment variables:
@@ -39,6 +43,17 @@ keypair (and URL) will be generated on the next `voxvera serve`. The
`voxvera import` command preserves this file automatically, but manual
deletion of the `host/` directory will destroy it.
## Hosting does not return after reboot or network loss
On Linux, install or verify the recovery timer:
```bash
voxvera autostart
systemctl --user status voxvera-start.timer
systemctl --user status voxvera-start.service
```
The supported recovery path is the recurring `systemd --user` timer, which reruns `voxvera start-all` every few minutes.
## QR codes not generated
If `voxvera build` reports "No URLs configured yet", this is expected before the first `voxvera serve`. QR codes for tear-off links are generated automatically once the .onion address is known. QR codes for the content link are generated if you provided a URL during `voxvera init`.
+6 -4
View File
@@ -16,9 +16,10 @@ Create a new JSON file in `voxvera/locales/{lang_code}.json`.
- **Formatting**: You can use Markdown-style strike-throughs (`~~redacted text~~`) in any of these strings; they will be automatically converted to the stylized "redacted" effect on the flyers.
### 2. The Landing Page
The main landing page (`site/index.html`) must be synchronized to include your new language.
- Open `site/index.html` and add your language to the `const locales` object in the `<script>` tag.
- Run `voxvera build-site` to refresh the public assets and ensure the pre-rendered HTML matches your new locale.
The main landing page is generated from the shared template and locale files.
- Add your language in `voxvera/locales/{lang_code}.json`.
- Do **not** edit `site/index.html` directly; it is generated output.
- Run `voxvera build-site` to refresh the public assets and inject your new locale into the generated HTML.
### 3. Documentation
VoxVera uses the **Doc-Sync Engine** to keep manuals in sync.
@@ -31,12 +32,13 @@ Run the following commands to verify your changes:
- `voxvera --lang {lang_code} check` (Verify CLI translation)
- `voxvera build-docs` (Generate localized manuals)
- `voxvera build-site` (Synchronize the public website and generate the latest portable bundle)
- Open `site/index.html` in a browser and test the language switcher.
- Open the generated `site/index.html` in a browser and test the language switcher.
## Code Contributions
- **Dependencies**: if you add a new Python library to `requirements.txt`, you MUST run `voxvera vendorize` to include it in the portable distribution.
- **Layout Integrity**: VoxVera uses visual width validation. If you add new UI elements, ensure they respect the physical boundaries of an 8.5"x11" flyer.
- **Tests**: Ensure all tests pass: `pytest tests/`. Run the integrity suite specifically when modifying locales: `pytest tests/test_integrity.py`.
- **Generated artifacts**: Treat `voxvera/resources/bin/` as local build output, not source files. Release binaries belong in GitHub Releases, not hand-edited repo state.
- **Style**: Follow the existing coding style (PEP 8 for Python).
- **Documentation**: If adding a new feature, update the master templates in `docs/templates/` and run `voxvera build-docs`.
+6 -3
View File
@@ -1,5 +1,7 @@
# Docker Usage
Experimental: the container path is not yet a production-grade hidden-service deployment target. Linux with `systemd --user` remains the supported persistent-host environment.
A prebuilt image containing Tor, OnionShare and the `voxvera` CLI is published to
`ghcr.io/voxvera/voxvera`.
@@ -7,10 +9,11 @@ A prebuilt image containing Tor, OnionShare and the `voxvera` CLI is published t
# pull the image
docker pull ghcr.io/voxvera/voxvera:latest
# run voxvera quickstart with flyers stored in ./flyers
# run the experimental container with flyers stored in ./flyers
mkdir -p flyers
docker run -it --rm -v "$(pwd)/flyers:/flyers" ghcr.io/voxvera/voxvera
```
The container uses `/flyers` as the working directory and runs `voxvera quickstart`
by default. All generated flyer files will appear in the mounted `flyers` folder.
The container stores runtime state under `/flyers`, seeds a default `config.json` if needed, and retries `voxvera start-all` periodically after the initial `quickstart`.
Use this path for testing only until the container runtime and background recovery behavior are validated end-to-end.
+15
View File
@@ -6,6 +6,10 @@ This page collects common issues encountered when hosting or accessing flyers.
- Ensure Tor is allowed through your firewall. On systems using `ufw` you may need to run `sudo ufw allow tor`.
- Some networks block Tor entirely. If you cannot reach onion services, try connecting over a different network or use a Tor bridge.
## Supported deployment target
- The supported persistent-host setup is Linux with `systemd --user`.
- Windows, macOS, Docker, Flatpak, AppImage, Homebrew, and Chocolatey are currently experimental and may not recover hosting automatically after reboot or network interruptions.
## Tor port detection
`voxvera serve` auto-detects Tor by probing common ports (9050/9150 for SOCKS, 9051/9151 for control). If detection fails -- for example when Tor runs on non-standard ports -- you can override with environment variables:
@@ -39,6 +43,17 @@ keypair (and URL) will be generated on the next `voxvera serve`. The
`voxvera import` command preserves this file automatically, but manual
deletion of the `host/` directory will destroy it.
## Hosting does not return after reboot or network loss
On Linux, install or verify the recovery timer:
```bash
voxvera autostart
systemctl --user status voxvera-start.timer
systemctl --user status voxvera-start.service
```
The supported recovery path is the recurring `systemd --user` timer, which reruns `voxvera start-all` every few minutes.
## QR codes not generated
If `voxvera build` reports "No URLs configured yet", this is expected before the first `voxvera serve`. QR codes for tear-off links are generated automatically once the .onion address is known. QR codes for the content link are generated if you provided a URL during `voxvera init`.
+19 -1
View File
@@ -6,18 +6,22 @@ This guide covers common CLI workflows. See `docs/docker.md` for Docker instruct
VoxVera is designed to be highly portable. You can run it directly from the source code on any platform with Python installed.
Linux with `systemd --user` is the supported persistent-host environment. Windows, macOS, Docker, Flatpak, AppImage, Homebrew, and Chocolatey are currently experimental and should not yet be treated as equally reliable hidden-service deployment targets.
### 1. Automated Install (Recommended)
The easiest way to install VoxVera is using our cross-platform installer script:
The easiest way to install VoxVera is using the Linux-first installer script:
**Linux / macOS:**
```bash
curl -fsSL https://raw.githubusercontent.com/PR0M3TH3AN/VoxVera/main/install.sh | bash
```
On Linux, this also installs the recurring recovery timer used to retry hidden-service startup after boot and after missed/offline periods.
**Windows (PowerShell):**
```powershell
irm https://raw.githubusercontent.com/PR0M3TH3AN/VoxVera/main/install.ps1 | iex
```
The Windows installer is experimental and does not yet guarantee production-ready background hosting behavior.
### 2. Manual Source Install
If you have downloaded the source code ZIP:
@@ -50,6 +54,16 @@ If you wish to remove VoxVera from your system, you can use the provided uninsta
```
This automatically detects your Tor instance, starts OnionShare, and writes the generated .onion address into the flyer's tear-off links.
### Persistent Hosting On Linux
For a machine that should resume hosting automatically after boot or after being offline, install the autostart recovery timer:
```bash
voxvera autostart
```
This installs a `systemd --user` timer that reruns `voxvera start-all` every few minutes. The retry loop is the supported way to recover hidden-service hosting after suspend, reboot, or temporary network loss.
## Language Support
VoxVera is fully localized. You can change your language preference permanently using either the interactive selector or a direct shortcut:
@@ -92,6 +106,10 @@ When you host a flyer, the **"הורד כלי וקוד מקור"** button on the
- Installation and setup scripts for all platforms.
- Complete documentation and templates.
To keep the mirrored download practical for low-bandwidth redistribution, the
source archive does not ship pre-downloaded Tor binaries. Normal installer and
release builds still fetch or bundle those artifacts separately.
This allows anyone who scans your flyer to become a new distributor of the VoxVera tool.
## Export & Backup
+6 -4
View File
@@ -16,9 +16,10 @@ Create a new JSON file in `voxvera/locales/{lang_code}.json`.
- **Formatting**: You can use Markdown-style strike-throughs (`~~redacted text~~`) in any of these strings; they will be automatically converted to the stylized "redacted" effect on the flyers.
### 2. The Landing Page
The main landing page (`site/index.html`) must be synchronized to include your new language.
- Open `site/index.html` and add your language to the `const locales` object in the `<script>` tag.
- Run `voxvera build-site` to refresh the public assets and ensure the pre-rendered HTML matches your new locale.
The main landing page is generated from the shared template and locale files.
- Add your language in `voxvera/locales/{lang_code}.json`.
- Do **not** edit `site/index.html` directly; it is generated output.
- Run `voxvera build-site` to refresh the public assets and inject your new locale into the generated HTML.
### 3. Documentation
VoxVera uses the **Doc-Sync Engine** to keep manuals in sync.
@@ -31,12 +32,13 @@ Run the following commands to verify your changes:
- `voxvera --lang {lang_code} check` (Verify CLI translation)
- `voxvera build-docs` (Generate localized manuals)
- `voxvera build-site` (Synchronize the public website and generate the latest portable bundle)
- Open `site/index.html` in a browser and test the language switcher.
- Open the generated `site/index.html` in a browser and test the language switcher.
## Code Contributions
- **Dependencies**: if you add a new Python library to `requirements.txt`, you MUST run `voxvera vendorize` to include it in the portable distribution.
- **Layout Integrity**: VoxVera uses visual width validation. If you add new UI elements, ensure they respect the physical boundaries of an 8.5"x11" flyer.
- **Tests**: Ensure all tests pass: `pytest tests/`. Run the integrity suite specifically when modifying locales: `pytest tests/test_integrity.py`.
- **Generated artifacts**: Treat `voxvera/resources/bin/` as local build output, not source files. Release binaries belong in GitHub Releases, not hand-edited repo state.
- **Style**: Follow the existing coding style (PEP 8 for Python).
- **Documentation**: If adding a new feature, update the master templates in `docs/templates/` and run `voxvera build-docs`.
+6 -3
View File
@@ -1,5 +1,7 @@
# Docker Usage
Experimental: the container path is not yet a production-grade hidden-service deployment target. Linux with `systemd --user` remains the supported persistent-host environment.
A prebuilt image containing Tor, OnionShare and the `voxvera` CLI is published to
`ghcr.io/voxvera/voxvera`.
@@ -7,10 +9,11 @@ A prebuilt image containing Tor, OnionShare and the `voxvera` CLI is published t
# pull the image
docker pull ghcr.io/voxvera/voxvera:latest
# run voxvera quickstart with flyers stored in ./flyers
# run the experimental container with flyers stored in ./flyers
mkdir -p flyers
docker run -it --rm -v "$(pwd)/flyers:/flyers" ghcr.io/voxvera/voxvera
```
The container uses `/flyers` as the working directory and runs `voxvera quickstart`
by default. All generated flyer files will appear in the mounted `flyers` folder.
The container stores runtime state under `/flyers`, seeds a default `config.json` if needed, and retries `voxvera start-all` periodically after the initial `quickstart`.
Use this path for testing only until the container runtime and background recovery behavior are validated end-to-end.
+15
View File
@@ -6,6 +6,10 @@ This page collects common issues encountered when hosting or accessing flyers.
- Ensure Tor is allowed through your firewall. On systems using `ufw` you may need to run `sudo ufw allow tor`.
- Some networks block Tor entirely. If you cannot reach onion services, try connecting over a different network or use a Tor bridge.
## Supported deployment target
- The supported persistent-host setup is Linux with `systemd --user`.
- Windows, macOS, Docker, Flatpak, AppImage, Homebrew, and Chocolatey are currently experimental and may not recover hosting automatically after reboot or network interruptions.
## Tor port detection
`voxvera serve` auto-detects Tor by probing common ports (9050/9150 for SOCKS, 9051/9151 for control). If detection fails -- for example when Tor runs on non-standard ports -- you can override with environment variables:
@@ -39,6 +43,17 @@ keypair (and URL) will be generated on the next `voxvera serve`. The
`voxvera import` command preserves this file automatically, but manual
deletion of the `host/` directory will destroy it.
## Hosting does not return after reboot or network loss
On Linux, install or verify the recovery timer:
```bash
voxvera autostart
systemctl --user status voxvera-start.timer
systemctl --user status voxvera-start.service
```
The supported recovery path is the recurring `systemd --user` timer, which reruns `voxvera start-all` every few minutes.
## QR codes not generated
If `voxvera build` reports "No URLs configured yet", this is expected before the first `voxvera serve`. QR codes for tear-off links are generated automatically once the .onion address is known. QR codes for the content link are generated if you provided a URL during `voxvera init`.
+6 -4
View File
@@ -16,9 +16,10 @@ Create a new JSON file in `voxvera/locales/{lang_code}.json`.
- **Formatting**: You can use Markdown-style strike-throughs (`~~redacted text~~`) in any of these strings; they will be automatically converted to the stylized "redacted" effect on the flyers.
### 2. The Landing Page
The main landing page (`site/index.html`) must be synchronized to include your new language.
- Open `site/index.html` and add your language to the `const locales` object in the `<script>` tag.
- Run `voxvera build-site` to refresh the public assets and ensure the pre-rendered HTML matches your new locale.
The main landing page is generated from the shared template and locale files.
- Add your language in `voxvera/locales/{lang_code}.json`.
- Do **not** edit `site/index.html` directly; it is generated output.
- Run `voxvera build-site` to refresh the public assets and inject your new locale into the generated HTML.
### 3. Documentation
VoxVera uses the **Doc-Sync Engine** to keep manuals in sync.
@@ -31,12 +32,13 @@ Run the following commands to verify your changes:
- `voxvera --lang {lang_code} check` (Verify CLI translation)
- `voxvera build-docs` (Generate localized manuals)
- `voxvera build-site` (Synchronize the public website and generate the latest portable bundle)
- Open `site/index.html` in a browser and test the language switcher.
- Open the generated `site/index.html` in a browser and test the language switcher.
## Code Contributions
- **Dependencies**: if you add a new Python library to `requirements.txt`, you MUST run `voxvera vendorize` to include it in the portable distribution.
- **Layout Integrity**: VoxVera uses visual width validation. If you add new UI elements, ensure they respect the physical boundaries of an 8.5"x11" flyer.
- **Tests**: Ensure all tests pass: `pytest tests/`. Run the integrity suite specifically when modifying locales: `pytest tests/test_integrity.py`.
- **Generated artifacts**: Treat `voxvera/resources/bin/` as local build output, not source files. Release binaries belong in GitHub Releases, not hand-edited repo state.
- **Style**: Follow the existing coding style (PEP 8 for Python).
- **Documentation**: If adding a new feature, update the master templates in `docs/templates/` and run `voxvera build-docs`.
+6 -3
View File
@@ -1,5 +1,7 @@
# Docker Usage
Experimental: the container path is not yet a production-grade hidden-service deployment target. Linux with `systemd --user` remains the supported persistent-host environment.
A prebuilt image containing Tor, OnionShare and the `voxvera` CLI is published to
`ghcr.io/voxvera/voxvera`.
@@ -7,10 +9,11 @@ A prebuilt image containing Tor, OnionShare and the `voxvera` CLI is published t
# pull the image
docker pull ghcr.io/voxvera/voxvera:latest
# run voxvera quickstart with flyers stored in ./flyers
# run the experimental container with flyers stored in ./flyers
mkdir -p flyers
docker run -it --rm -v "$(pwd)/flyers:/flyers" ghcr.io/voxvera/voxvera
```
The container uses `/flyers` as the working directory and runs `voxvera quickstart`
by default. All generated flyer files will appear in the mounted `flyers` folder.
The container stores runtime state under `/flyers`, seeds a default `config.json` if needed, and retries `voxvera start-all` periodically after the initial `quickstart`.
Use this path for testing only until the container runtime and background recovery behavior are validated end-to-end.
+15
View File
@@ -6,6 +6,10 @@ This page collects common issues encountered when hosting or accessing flyers.
- Ensure Tor is allowed through your firewall. On systems using `ufw` you may need to run `sudo ufw allow tor`.
- Some networks block Tor entirely. If you cannot reach onion services, try connecting over a different network or use a Tor bridge.
## Supported deployment target
- The supported persistent-host setup is Linux with `systemd --user`.
- Windows, macOS, Docker, Flatpak, AppImage, Homebrew, and Chocolatey are currently experimental and may not recover hosting automatically after reboot or network interruptions.
## Tor port detection
`voxvera serve` auto-detects Tor by probing common ports (9050/9150 for SOCKS, 9051/9151 for control). If detection fails -- for example when Tor runs on non-standard ports -- you can override with environment variables:
@@ -39,6 +43,17 @@ keypair (and URL) will be generated on the next `voxvera serve`. The
`voxvera import` command preserves this file automatically, but manual
deletion of the `host/` directory will destroy it.
## Hosting does not return after reboot or network loss
On Linux, install or verify the recovery timer:
```bash
voxvera autostart
systemctl --user status voxvera-start.timer
systemctl --user status voxvera-start.service
```
The supported recovery path is the recurring `systemd --user` timer, which reruns `voxvera start-all` every few minutes.
## QR codes not generated
If `voxvera build` reports "No URLs configured yet", this is expected before the first `voxvera serve`. QR codes for tear-off links are generated automatically once the .onion address is known. QR codes for the content link are generated if you provided a URL during `voxvera init`.
+178
View File
@@ -0,0 +1,178 @@
# Linux Hosting Validation
This runbook defines the operational validation standard for VoxVera's only supported hosting target:
- Linux CLI with `systemd --user`
Everything else remains below that bar until equivalent evidence exists.
## Prerequisites
- `voxvera` installed and on `PATH`
- `tor` installed
- `onionshare-cli` available
- a user account that can run `systemctl --user`
## Quick Verification
Use the helper script first:
```bash
bash scripts/linux-recovery-check.sh
```
This checks:
- `platform-status --json`
- `doctor --json`
- `autostart status --json`
- expected Linux support tier and platform id
- presence of `~/host`
- Tor and OnionShare discovery
- autostart artifacts and timer state
- per-site state for `.onionshare-session`, `onionshare.log`, and `server.pid`
## Core Commands
Install autostart:
```bash
voxvera autostart
```
Inspect autostart:
```bash
voxvera autostart status
systemctl --user status voxvera-start.timer
systemctl --user status voxvera-start.service
```
Remove autostart:
```bash
voxvera autostart uninstall
```
Inspect runtime diagnostics:
```bash
voxvera doctor
voxvera doctor --json
```
## Validation Checklist
### 1. Fresh install
Run the Linux installer or install through the supported CLI path, then confirm:
```bash
voxvera platform-status --json
voxvera doctor --json
voxvera autostart status --json
```
Expected:
- `platform_id` is `linux_cli_systemd`
- support tier is `supported`
- `voxvera_cli`, `onionshare_cli`, and `tor_binary` pass
### 2. Build and serve
Create or edit a site, then build and serve it:
```bash
voxvera build
voxvera serve
```
Expected:
- site output appears under `~/host/<folder_name>/`
- `index.html` and QR code images exist
- `onionshare.log` is written
- `.onionshare-session` appears after the hidden service is started
### 3. Identity persistence
Confirm the site keeps its identity across rebuilds:
```bash
ls -la ~/host/<folder_name>/
voxvera build --config ~/host/<folder_name>/config.json
```
Expected:
- `.onionshare-session` still exists after rebuild
### 4. Autostart persistence
Confirm the recovery timer exists and is enabled:
```bash
voxvera autostart status
systemctl --user is-enabled voxvera-start.timer
systemctl --user is-active voxvera-start.timer
```
Expected:
- timer artifacts exist under `~/.config/systemd/user/`
- timer is enabled
### 5. Reboot recovery
After a reboot, verify:
```bash
bash scripts/linux-recovery-check.sh
systemctl --user status voxvera-start.timer
journalctl --user -u voxvera-start.service --no-pager -n 50
```
Expected:
- timer remains enabled
- service has retried `start-all`
- configured sites are restorable without manual reconfiguration
### 6. Offline/reconnect recovery
Temporarily disconnect the host from the network, reconnect, then wait through at least one timer interval.
Verify:
```bash
systemctl --user status voxvera-start.timer
journalctl --user -u voxvera-start.service --no-pager -n 50
bash scripts/linux-recovery-check.sh
```
Expected:
- timer is still active
- `start-all` retries appear in the service logs
- Tor becomes reachable again
## Paths That Matter
- `~/.config/systemd/user/voxvera-start.service`
- `~/.config/systemd/user/voxvera-start.timer`
- `~/host/<folder_name>/config.json`
- `~/host/<folder_name>/.onionshare-session`
- `~/host/<folder_name>/onionshare.log`
- `~/host/<folder_name>/server.pid`
## Failure Interpretation
If `bash scripts/linux-recovery-check.sh` fails:
- missing `onionshare_cli` or `tor_binary` means the runtime is incomplete
- missing `host_root` means no sites are built yet
- missing autostart artifacts means recovery was never installed or was removed
- `tor_socks_reachable` warning means Tor is not currently reachable even if it is installed
Linux should remain the only supported target until this runbook is repeatable on real hosts without caveats.
+51
View File
@@ -0,0 +1,51 @@
# Platform Support Matrix
This file is the canonical human-readable summary of VoxVera's actual platform support posture.
The machine-readable companion file is [`support-matrix.json`](/home/user/Documents/GitHub/VoxVera/support-matrix.json).
## Support Tiers
- **Supported**: validated for persistent hidden-service hosting with documented recovery behavior.
- **Beta**: feature-complete enough for broader testing, but not yet validated for production-grade recovery claims.
- **Experimental**: may install or run, but is not yet validated for reliable background hidden-service hosting.
- **Unsupported**: not currently a target for reliable hosting.
## Current Matrix
| Platform | Tier | Canonical Runtime Model | Distribution Surfaces | Notes |
| :--- | :--- | :--- | :--- | :--- |
| Linux CLI with `systemd --user` | Supported | System Tor or Tor Browser plus `onionshare-cli`, with recurring `systemd --user` timer recovery | `install.sh`, `voxvera-install.sh`, `pip/pipx`, Linux binary | Current reference implementation |
| Docker CLI | Experimental | Containerized runtime with `VOXVERA_DIR=/flyers` and periodic `start-all` retries | Dockerfile, GHCR image | Needs end-to-end restart validation |
| macOS CLI | Experimental | CLI runtime with `launchd` autostart path and external Tor/OnionShare dependencies | `install.sh`, `pip/pipx`, macOS binary, DMG | No validated recovery claim yet |
| Windows CLI | Experimental | CLI runtime with Task Scheduler path and external Tor/OnionShare dependencies | `install.ps1`, Windows binary, Chocolatey | Binary naming and runtime model need cleanup |
| Electron GUI | Experimental | GUI frontend over the CLI runtime | `gui/electron` | Must converge on shared runtime behavior |
| Flatpak/AppImage/Homebrew/Chocolatey/Deb | Experimental | Distribution-only until runtime parity exists | packaging scripts/manifests | Package availability does not imply hosting parity |
## What "Supported" Means For VoxVera
To move a platform to **Supported**, VoxVera should have evidence for all of the following:
1. Fresh install works on a clean machine.
2. Manual `build` and `serve` work.
3. `.onionshare-session` survives rebuilds and restarts.
4. Hosting returns after reboot or login.
5. Hosting returns after a temporary offline period.
6. Logs and status are inspectable with documented commands.
7. The docs and release artifacts do not overstate capability.
## Promotion Rules
- Do not promote a package surface before the underlying runtime model is validated.
- Do not call a platform "recommended" unless it is also `supported`.
- If a platform lacks validated auto-recovery after reboot or network interruption, it must remain `experimental` or `beta`.
## Current Recommendation
If your goal is reliable Tor hidden-service hosting today, use the Linux CLI path and install the recurring recovery timer with:
```bash
voxvera autostart
```
That is the current reference deployment model the rest of the platform work should converge toward.
+6 -4
View File
@@ -16,9 +16,10 @@ Create a new JSON file in `voxvera/locales/{lang_code}.json`.
- **Formatting**: You can use Markdown-style strike-throughs (`~~redacted text~~`) in any of these strings; they will be automatically converted to the stylized "redacted" effect on the flyers.
### 2. The Landing Page
The main landing page (`site/index.html`) must be synchronized to include your new language.
- Open `site/index.html` and add your language to the `const locales` object in the `<script>` tag.
- Run `voxvera build-site` to refresh the public assets and ensure the pre-rendered HTML matches your new locale.
The main landing page is generated from the shared template and locale files.
- Add your language in `voxvera/locales/{lang_code}.json`.
- Do **not** edit `site/index.html` directly; it is generated output.
- Run `voxvera build-site` to refresh the public assets and inject your new locale into the generated HTML.
### 3. Documentation
VoxVera uses the **Doc-Sync Engine** to keep manuals in sync.
@@ -31,12 +32,13 @@ Run the following commands to verify your changes:
- `voxvera --lang {lang_code} check` (Verify CLI translation)
- `voxvera build-docs` (Generate localized manuals)
- `voxvera build-site` (Synchronize the public website and generate the latest portable bundle)
- Open `site/index.html` in a browser and test the language switcher.
- Open the generated `site/index.html` in a browser and test the language switcher.
## Code Contributions
- **Dependencies**: if you add a new Python library to `requirements.txt`, you MUST run `voxvera vendorize` to include it in the portable distribution.
- **Layout Integrity**: VoxVera uses visual width validation. If you add new UI elements, ensure they respect the physical boundaries of an 8.5"x11" flyer.
- **Tests**: Ensure all tests pass: `pytest tests/`. Run the integrity suite specifically when modifying locales: `pytest tests/test_integrity.py`.
- **Generated artifacts**: Treat `voxvera/resources/bin/` as local build output, not source files. Release binaries belong in GitHub Releases, not hand-edited repo state.
- **Style**: Follow the existing coding style (PEP 8 for Python).
- **Documentation**: If adding a new feature, update the master templates in `docs/templates/` and run `voxvera build-docs`.
+6 -3
View File
@@ -1,5 +1,7 @@
# Docker Usage
Experimental: the container path is not yet a production-grade hidden-service deployment target. Linux with `systemd --user` remains the supported persistent-host environment.
A prebuilt image containing Tor, OnionShare and the `voxvera` CLI is published to
`ghcr.io/voxvera/voxvera`.
@@ -7,10 +9,11 @@ A prebuilt image containing Tor, OnionShare and the `voxvera` CLI is published t
# pull the image
docker pull ghcr.io/voxvera/voxvera:latest
# run voxvera quickstart with flyers stored in ./flyers
# run the experimental container with flyers stored in ./flyers
mkdir -p flyers
docker run -it --rm -v "$(pwd)/flyers:/flyers" ghcr.io/voxvera/voxvera
```
The container uses `/flyers` as the working directory and runs `voxvera quickstart`
by default. All generated flyer files will appear in the mounted `flyers` folder.
The container stores runtime state under `/flyers`, seeds a default `config.json` if needed, and retries `voxvera start-all` periodically after the initial `quickstart`.
Use this path for testing only until the container runtime and background recovery behavior are validated end-to-end.
+15
View File
@@ -6,6 +6,10 @@ This page collects common issues encountered when hosting or accessing flyers.
- Ensure Tor is allowed through your firewall. On systems using `ufw` you may need to run `sudo ufw allow tor`.
- Some networks block Tor entirely. If you cannot reach onion services, try connecting over a different network or use a Tor bridge.
## Supported deployment target
- The supported persistent-host setup is Linux with `systemd --user`.
- Windows, macOS, Docker, Flatpak, AppImage, Homebrew, and Chocolatey are currently experimental and may not recover hosting automatically after reboot or network interruptions.
## Tor port detection
`voxvera serve` auto-detects Tor by probing common ports (9050/9150 for SOCKS, 9051/9151 for control). If detection fails -- for example when Tor runs on non-standard ports -- you can override with environment variables:
@@ -39,6 +43,17 @@ keypair (and URL) will be generated on the next `voxvera serve`. The
`voxvera import` command preserves this file automatically, but manual
deletion of the `host/` directory will destroy it.
## Hosting does not return after reboot or network loss
On Linux, install or verify the recovery timer:
```bash
voxvera autostart
systemctl --user status voxvera-start.timer
systemctl --user status voxvera-start.service
```
The supported recovery path is the recurring `systemd --user` timer, which reruns `voxvera start-all` every few minutes.
## QR codes not generated
If `voxvera build` reports "No URLs configured yet", this is expected before the first `voxvera serve`. QR codes for tear-off links are generated automatically once the .onion address is known. QR codes for the content link are generated if you provided a URL during `voxvera init`.
+270
View File
@@ -0,0 +1,270 @@
# Platform Parity Roadmap
This roadmap focuses on one question:
How do we expand VoxVera beyond Linux without overstating reliability, while still converging on real cross-platform parity for Tor hidden-service hosting?
## Strategic Principle
Parity is not a packaging milestone. It is a runtime-contract milestone.
Every platform must eventually implement the same hosting contract:
1. install VoxVera
2. ensure Tor and OnionShare are available
3. build site assets
4. serve the hidden service
5. preserve `.onionshare-session`
6. recover hosting after reboot, login, suspend, or offline periods
7. surface logs and status
Package formats should inherit that contract. They should not define independent behavior.
## Phase 0: Truth Alignment
Goal: bring repo claims, docs, and release posture into line with current reality.
Deliverables:
- canonical support matrix
- machine-readable support declaration
- installer/package warnings for experimental paths
- README and docs aligned to the actual supported target
Definition of done:
- no docs imply parity that tests do not support
- every platform surface has an explicit support tier
- release artifacts are not treated as proof of hosting reliability
## Phase 1: Harden Linux As Reference
Goal: make Linux CLI with `systemd --user` the clearly defensible model that other platforms copy.
Work items:
1. Add `voxvera doctor` for runtime validation.
- Check Tor reachability.
- Check OnionShare availability.
- Check `start-all` surfaces.
- Check autostart timer installation.
- Check host directories and session files.
2. Add `voxvera autostart status`.
- Show service and timer files.
- Show whether the timer is enabled.
- Show next/last run timestamps if available.
3. Normalize Linux log/status guidance.
- Document host log paths.
- Document timer/service status commands.
- Document expected recovery flow after reboot/network loss.
4. Validate restart behavior.
- fresh install
- first serve
- reboot
- suspend/resume if possible
- temporary offline period followed by reconnect
Definition of done:
- Linux remains the only `supported` path until the above evidence exists and is documented.
## Phase 2: Platform Adapter Layer
Goal: stop encoding platform logic in ad hoc installer scripts.
Recommended structure:
- `voxvera/platforms/base.py`
- `voxvera/platforms/linux.py`
- `voxvera/platforms/macos.py`
- `voxvera/platforms/windows.py`
- `voxvera/platforms/docker.py`
Each adapter should define:
- support tier
- Tor discovery strategy
- OnionShare discovery strategy
- state root
- autostart installation strategy
- autostart status strategy
- log/status surface
- known limitations
CLI surfaces to add:
- `voxvera doctor`
- `voxvera platform-status`
- `voxvera autostart`
- `voxvera autostart status`
- `voxvera autostart uninstall`
Definition of done:
- shell installers become thin wrappers over shared Python logic instead of owning runtime policy themselves
## Phase 3: Docker Beta Candidate
Goal: make Docker a coherent, testable deployment surface.
Decisions to settle:
- single-container runtime vs compose-based runtime
- host Tor vs container Tor
- log and state persistence model
Recommended direction:
- keep runtime state under one explicit volume root
- document restart policy clearly
- make health and recovery behavior observable
Validation checklist:
- container restart preserves onion identity
- host reboot with restart policy restores hosting
- state remains in mounted volume
- logs remain inspectable
Promotion target:
- `experimental` -> `beta`
## Phase 4: macOS CLI
Goal: establish one honest CLI-based macOS hosting model before packaging.
Work items:
1. define Tor discovery strategy
2. define OnionShare discovery/install strategy
3. validate manual `build` + `serve`
4. implement and validate `launchd` recovery
5. document known limitations
Promotion target:
- remain `experimental` until `launchd` recovery is validated
- move to `beta` only after login/reboot/offline recovery evidence exists
## Phase 5: Windows CLI
Goal: establish one canonical Windows runtime model before claiming package parity.
Work items:
1. unify binary naming across installer, Chocolatey, docs, and release assets
2. define Tor discovery strategy
3. define OnionShare discovery strategy
4. validate manual `build` + `serve`
5. implement Task Scheduler recovery path
6. add Windows-specific status/log guidance
Promotion target:
- remain `experimental` until scheduled recovery and state persistence are proven
- move to `beta` only after reboot/login/offline recovery evidence exists
## Phase 6: Electron GUI Convergence
Goal: make the GUI a frontend to the supported runtime, not a separate runtime.
Work items:
1. route the GUI through shared CLI/runtime surfaces
2. reduce duplicated Tor startup behavior
3. add visibility for:
- Tor status
- OnionShare status
- hosting status
- onion URL
- logs
4. make window-close behavior explicit and safe
Promotion target:
- GUI remains `experimental` until it stops diverging from the CLI runtime contract
## Phase 7: Package Surface Promotion
Goal: package formats should become trustworthy wrappers around validated runtimes.
Work items:
1. Debian package
- declare actual runtime dependencies
- validate install + host + recovery path
2. AppImage
- decide whether it is distribution-only or a real hosting target
3. Homebrew
- fix artifact URL/sha/version policy
- clarify whether it installs a hosting target or just a CLI
4. Chocolatey
- align artifact naming and installer semantics
5. Flatpak
- decide whether Tor/OnionShare can be supported sanely in-sandbox
Rule:
- no package surface should be promoted above the support tier of its underlying runtime
## Testing Program
### 1. Unit tests
- config/state logic
- autostart file generation
- platform adapter selection
### 2. Mocked integration tests
- `serve`
- `autostart`
- `doctor`
- `platform-status`
### 3. Platform smoke tests
- fresh install
- build
- serve
- persist identity
- restart
- recover
Current smoke target names:
- `linux-cli`
- `macos-cli`
- `windows-cli`
- `docker-cli`
These target names are contract-level validation entry points. Only `linux-cli` currently represents a supported hosting path; the others should be used to validate experimental posture and dependency discovery on their native platforms until their recovery behavior is proven.
### 4. Release checks
- support matrix is current
- docs match support tiers
- artifact names match scripts and package manifests
## Immediate Next Tasks
1. Add `voxvera doctor`.
2. Add `voxvera autostart status`.
3. Add the platform adapter scaffold.
4. Capture Linux reboot/offline recovery validation steps in docs and CI notes.
5. Pick Docker's canonical runtime model.
6. Normalize Windows binary naming before further Windows work.
## Non-Goals
- claiming parity before recovery behavior is validated
- treating package availability as support proof
- maintaining separate runtime semantics in GUI vs CLI
+6 -4
View File
@@ -16,9 +16,10 @@ Create a new JSON file in `voxvera/locales/{lang_code}.json`.
- **Formatting**: You can use Markdown-style strike-throughs (`~~redacted text~~`) in any of these strings; they will be automatically converted to the stylized "redacted" effect on the flyers.
### 2. The Landing Page
The main landing page (`site/index.html`) must be synchronized to include your new language.
- Open `site/index.html` and add your language to the `const locales` object in the `<script>` tag.
- Run `voxvera build-site` to refresh the public assets and ensure the pre-rendered HTML matches your new locale.
The main landing page is generated from the shared template and locale files.
- Add your language in `voxvera/locales/{lang_code}.json`.
- Do **not** edit `site/index.html` directly; it is generated output.
- Run `voxvera build-site` to refresh the public assets and inject your new locale into the generated HTML.
### 3. Documentation
VoxVera uses the **Doc-Sync Engine** to keep manuals in sync.
@@ -31,12 +32,13 @@ Run the following commands to verify your changes:
- `voxvera --lang {lang_code} check` (Verify CLI translation)
- `voxvera build-docs` (Generate localized manuals)
- `voxvera build-site` (Synchronize the public website and generate the latest portable bundle)
- Open `site/index.html` in a browser and test the language switcher.
- Open the generated `site/index.html` in a browser and test the language switcher.
## Code Contributions
- **Dependencies**: if you add a new Python library to `requirements.txt`, you MUST run `voxvera vendorize` to include it in the portable distribution.
- **Layout Integrity**: VoxVera uses visual width validation. If you add new UI elements, ensure they respect the physical boundaries of an 8.5"x11" flyer.
- **Tests**: Ensure all tests pass: `pytest tests/`. Run the integrity suite specifically when modifying locales: `pytest tests/test_integrity.py`.
- **Generated artifacts**: Treat `voxvera/resources/bin/` as local build output, not source files. Release binaries belong in GitHub Releases, not hand-edited repo state.
- **Style**: Follow the existing coding style (PEP 8 for Python).
- **Documentation**: If adding a new feature, update the master templates in `docs/templates/` and run `voxvera build-docs`.
+6 -3
View File
@@ -1,5 +1,7 @@
# Docker Usage
Experimental: the container path is not yet a production-grade hidden-service deployment target. Linux with `systemd --user` remains the supported persistent-host environment.
A prebuilt image containing Tor, OnionShare and the `voxvera` CLI is published to
`ghcr.io/voxvera/voxvera`.
@@ -7,10 +9,11 @@ A prebuilt image containing Tor, OnionShare and the `voxvera` CLI is published t
# pull the image
docker pull ghcr.io/voxvera/voxvera:latest
# run voxvera quickstart with flyers stored in ./flyers
# run the experimental container with flyers stored in ./flyers
mkdir -p flyers
docker run -it --rm -v "$(pwd)/flyers:/flyers" ghcr.io/voxvera/voxvera
```
The container uses `/flyers` as the working directory and runs `voxvera quickstart`
by default. All generated flyer files will appear in the mounted `flyers` folder.
The container stores runtime state under `/flyers`, seeds a default `config.json` if needed, and retries `voxvera start-all` periodically after the initial `quickstart`.
Use this path for testing only until the container runtime and background recovery behavior are validated end-to-end.
+15
View File
@@ -6,6 +6,10 @@ This page collects common issues encountered when hosting or accessing flyers.
- Ensure Tor is allowed through your firewall. On systems using `ufw` you may need to run `sudo ufw allow tor`.
- Some networks block Tor entirely. If you cannot reach onion services, try connecting over a different network or use a Tor bridge.
## Supported deployment target
- The supported persistent-host setup is Linux with `systemd --user`.
- Windows, macOS, Docker, Flatpak, AppImage, Homebrew, and Chocolatey are currently experimental and may not recover hosting automatically after reboot or network interruptions.
## Tor port detection
`voxvera serve` auto-detects Tor by probing common ports (9050/9150 for SOCKS, 9051/9151 for control). If detection fails -- for example when Tor runs on non-standard ports -- you can override with environment variables:
@@ -39,6 +43,17 @@ keypair (and URL) will be generated on the next `voxvera serve`. The
`voxvera import` command preserves this file automatically, but manual
deletion of the `host/` directory will destroy it.
## Hosting does not return after reboot or network loss
On Linux, install or verify the recovery timer:
```bash
voxvera autostart
systemctl --user status voxvera-start.timer
systemctl --user status voxvera-start.service
```
The supported recovery path is the recurring `systemd --user` timer, which reruns `voxvera start-all` every few minutes.
## QR codes not generated
If `voxvera build` reports "No URLs configured yet", this is expected before the first `voxvera serve`. QR codes for tear-off links are generated automatically once the .onion address is known. QR codes for the content link are generated if you provided a URL during `voxvera init`.
+6 -4
View File
@@ -16,9 +16,10 @@ Create a new JSON file in `voxvera/locales/{lang_code}.json`.
- **Formatting**: You can use Markdown-style strike-throughs (`~~redacted text~~`) in any of these strings; they will be automatically converted to the stylized "redacted" effect on the flyers.
### 2. The Landing Page
The main landing page (`site/index.html`) must be synchronized to include your new language.
- Open `site/index.html` and add your language to the `const locales` object in the `<script>` tag.
- Run `voxvera build-site` to refresh the public assets and ensure the pre-rendered HTML matches your new locale.
The main landing page is generated from the shared template and locale files.
- Add your language in `voxvera/locales/{lang_code}.json`.
- Do **not** edit `site/index.html` directly; it is generated output.
- Run `voxvera build-site` to refresh the public assets and inject your new locale into the generated HTML.
### 3. Documentation
VoxVera uses the **Doc-Sync Engine** to keep manuals in sync.
@@ -31,12 +32,13 @@ Run the following commands to verify your changes:
- `voxvera --lang {lang_code} check` (Verify CLI translation)
- `voxvera build-docs` (Generate localized manuals)
- `voxvera build-site` (Synchronize the public website and generate the latest portable bundle)
- Open `site/index.html` in a browser and test the language switcher.
- Open the generated `site/index.html` in a browser and test the language switcher.
## Code Contributions
- **Dependencies**: if you add a new Python library to `requirements.txt`, you MUST run `voxvera vendorize` to include it in the portable distribution.
- **Layout Integrity**: VoxVera uses visual width validation. If you add new UI elements, ensure they respect the physical boundaries of an 8.5"x11" flyer.
- **Tests**: Ensure all tests pass: `pytest tests/`. Run the integrity suite specifically when modifying locales: `pytest tests/test_integrity.py`.
- **Generated artifacts**: Treat `voxvera/resources/bin/` as local build output, not source files. Release binaries belong in GitHub Releases, not hand-edited repo state.
- **Style**: Follow the existing coding style (PEP 8 for Python).
- **Documentation**: If adding a new feature, update the master templates in `docs/templates/` and run `voxvera build-docs`.
+6 -3
View File
@@ -1,5 +1,7 @@
# Docker Usage
Experimental: the container path is not yet a production-grade hidden-service deployment target. Linux with `systemd --user` remains the supported persistent-host environment.
A prebuilt image containing Tor, OnionShare and the `voxvera` CLI is published to
`ghcr.io/voxvera/voxvera`.
@@ -7,10 +9,11 @@ A prebuilt image containing Tor, OnionShare and the `voxvera` CLI is published t
# pull the image
docker pull ghcr.io/voxvera/voxvera:latest
# run voxvera quickstart with flyers stored in ./flyers
# run the experimental container with flyers stored in ./flyers
mkdir -p flyers
docker run -it --rm -v "$(pwd)/flyers:/flyers" ghcr.io/voxvera/voxvera
```
The container uses `/flyers` as the working directory and runs `voxvera quickstart`
by default. All generated flyer files will appear in the mounted `flyers` folder.
The container stores runtime state under `/flyers`, seeds a default `config.json` if needed, and retries `voxvera start-all` periodically after the initial `quickstart`.
Use this path for testing only until the container runtime and background recovery behavior are validated end-to-end.
+15
View File
@@ -6,6 +6,10 @@ This page collects common issues encountered when hosting or accessing flyers.
- Ensure Tor is allowed through your firewall. On systems using `ufw` you may need to run `sudo ufw allow tor`.
- Some networks block Tor entirely. If you cannot reach onion services, try connecting over a different network or use a Tor bridge.
## Supported deployment target
- The supported persistent-host setup is Linux with `systemd --user`.
- Windows, macOS, Docker, Flatpak, AppImage, Homebrew, and Chocolatey are currently experimental and may not recover hosting automatically after reboot or network interruptions.
## Tor port detection
`voxvera serve` auto-detects Tor by probing common ports (9050/9150 for SOCKS, 9051/9151 for control). If detection fails -- for example when Tor runs on non-standard ports -- you can override with environment variables:
@@ -39,6 +43,17 @@ keypair (and URL) will be generated on the next `voxvera serve`. The
`voxvera import` command preserves this file automatically, but manual
deletion of the `host/` directory will destroy it.
## Hosting does not return after reboot or network loss
On Linux, install or verify the recovery timer:
```bash
voxvera autostart
systemctl --user status voxvera-start.timer
systemctl --user status voxvera-start.service
```
The supported recovery path is the recurring `systemd --user` timer, which reruns `voxvera start-all` every few minutes.
## QR codes not generated
If `voxvera build` reports "No URLs configured yet", this is expected before the first `voxvera serve`. QR codes for tear-off links are generated automatically once the .onion address is known. QR codes for the content link are generated if you provided a URL during `voxvera init`.
+6 -4
View File
@@ -16,9 +16,10 @@ Create a new JSON file in `voxvera/locales/{lang_code}.json`.
- **Formatting**: You can use Markdown-style strike-throughs (`~~redacted text~~`) in any of these strings; they will be automatically converted to the stylized "redacted" effect on the flyers.
### 2. The Landing Page
The main landing page (`site/index.html`) must be synchronized to include your new language.
- Open `site/index.html` and add your language to the `const locales` object in the `<script>` tag.
- Run `voxvera build-site` to refresh the public assets and ensure the pre-rendered HTML matches your new locale.
The main landing page is generated from the shared template and locale files.
- Add your language in `voxvera/locales/{lang_code}.json`.
- Do **not** edit `site/index.html` directly; it is generated output.
- Run `voxvera build-site` to refresh the public assets and inject your new locale into the generated HTML.
### 3. Documentation
VoxVera uses the **Doc-Sync Engine** to keep manuals in sync.
@@ -31,12 +32,13 @@ Run the following commands to verify your changes:
- `voxvera --lang {lang_code} check` (Verify CLI translation)
- `voxvera build-docs` (Generate localized manuals)
- `voxvera build-site` (Synchronize the public website and generate the latest portable bundle)
- Open `site/index.html` in a browser and test the language switcher.
- Open the generated `site/index.html` in a browser and test the language switcher.
## Code Contributions
- **Dependencies**: if you add a new Python library to `requirements.txt`, you MUST run `voxvera vendorize` to include it in the portable distribution.
- **Layout Integrity**: VoxVera uses visual width validation. If you add new UI elements, ensure they respect the physical boundaries of an 8.5"x11" flyer.
- **Tests**: Ensure all tests pass: `pytest tests/`. Run the integrity suite specifically when modifying locales: `pytest tests/test_integrity.py`.
- **Generated artifacts**: Treat `voxvera/resources/bin/` as local build output, not source files. Release binaries belong in GitHub Releases, not hand-edited repo state.
- **Style**: Follow the existing coding style (PEP 8 for Python).
- **Documentation**: If adding a new feature, update the master templates in `docs/templates/` and run `voxvera build-docs`.
+6 -3
View File
@@ -1,5 +1,7 @@
# Docker Usage
Experimental: the container path is not yet a production-grade hidden-service deployment target. Linux with `systemd --user` remains the supported persistent-host environment.
A prebuilt image containing Tor, OnionShare and the `voxvera` CLI is published to
`ghcr.io/voxvera/voxvera`.
@@ -7,10 +9,11 @@ A prebuilt image containing Tor, OnionShare and the `voxvera` CLI is published t
# pull the image
docker pull ghcr.io/voxvera/voxvera:latest
# run voxvera quickstart with flyers stored in ./flyers
# run the experimental container with flyers stored in ./flyers
mkdir -p flyers
docker run -it --rm -v "$(pwd)/flyers:/flyers" ghcr.io/voxvera/voxvera
```
The container uses `/flyers` as the working directory and runs `voxvera quickstart`
by default. All generated flyer files will appear in the mounted `flyers` folder.
The container stores runtime state under `/flyers`, seeds a default `config.json` if needed, and retries `voxvera start-all` periodically after the initial `quickstart`.
Use this path for testing only until the container runtime and background recovery behavior are validated end-to-end.
+15
View File
@@ -6,6 +6,10 @@ This page collects common issues encountered when hosting or accessing flyers.
- Ensure Tor is allowed through your firewall. On systems using `ufw` you may need to run `sudo ufw allow tor`.
- Some networks block Tor entirely. If you cannot reach onion services, try connecting over a different network or use a Tor bridge.
## Supported deployment target
- The supported persistent-host setup is Linux with `systemd --user`.
- Windows, macOS, Docker, Flatpak, AppImage, Homebrew, and Chocolatey are currently experimental and may not recover hosting automatically after reboot or network interruptions.
## Tor port detection
`voxvera serve` auto-detects Tor by probing common ports (9050/9150 for SOCKS, 9051/9151 for control). If detection fails -- for example when Tor runs on non-standard ports -- you can override with environment variables:
@@ -39,6 +43,17 @@ keypair (and URL) will be generated on the next `voxvera serve`. The
`voxvera import` command preserves this file automatically, but manual
deletion of the `host/` directory will destroy it.
## Hosting does not return after reboot or network loss
On Linux, install or verify the recovery timer:
```bash
voxvera autostart
systemctl --user status voxvera-start.timer
systemctl --user status voxvera-start.service
```
The supported recovery path is the recurring `systemd --user` timer, which reruns `voxvera start-all` every few minutes.
## QR codes not generated
If `voxvera build` reports "No URLs configured yet", this is expected before the first `voxvera serve`. QR codes for tear-off links are generated automatically once the .onion address is known. QR codes for the content link are generated if you provided a URL during `voxvera init`.
+19 -1
View File
@@ -6,18 +6,22 @@ This guide covers common CLI workflows. See `docs/docker.md` for Docker instruct
VoxVera is designed to be highly portable. You can run it directly from the source code on any platform with Python installed.
Linux with `systemd --user` is the supported persistent-host environment. Windows, macOS, Docker, Flatpak, AppImage, Homebrew, and Chocolatey are currently experimental and should not yet be treated as equally reliable hidden-service deployment targets.
### 1. Automated Install (Recommended)
The easiest way to install VoxVera is using our cross-platform installer script:
The easiest way to install VoxVera is using the Linux-first installer script:
**Linux / macOS:**
```bash
curl -fsSL https://raw.githubusercontent.com/PR0M3TH3AN/VoxVera/main/install.sh | bash
```
On Linux, this also installs the recurring recovery timer used to retry hidden-service startup after boot and after missed/offline periods.
**Windows (PowerShell):**
```powershell
irm https://raw.githubusercontent.com/PR0M3TH3AN/VoxVera/main/install.ps1 | iex
```
The Windows installer is experimental and does not yet guarantee production-ready background hosting behavior.
### 2. Manual Source Install
If you have downloaded the source code ZIP:
@@ -50,6 +54,16 @@ If you wish to remove VoxVera from your system, you can use the provided uninsta
```
This automatically detects your Tor instance, starts OnionShare, and writes the generated .onion address into the flyer's tear-off links.
### Persistent Hosting On Linux
For a machine that should resume hosting automatically after boot or after being offline, install the autostart recovery timer:
```bash
voxvera autostart
```
This installs a `systemd --user` timer that reruns `voxvera start-all` every few minutes. The retry loop is the supported way to recover hidden-service hosting after suspend, reboot, or temporary network loss.
## Language Support
VoxVera is fully localized. You can change your language preference permanently using either the interactive selector or a direct shortcut:
@@ -92,6 +106,10 @@ When you host a flyer, the **"{{t('web.download_button')}}"** button on the land
- Installation and setup scripts for all platforms.
- Complete documentation and templates.
To keep the mirrored download practical for low-bandwidth redistribution, the
source archive does not ship pre-downloaded Tor binaries. Normal installer and
release builds still fetch or bundle those artifacts separately.
This allows anyone who scans your flyer to become a new distributor of the VoxVera tool.
## Export & Backup
+6 -4
View File
@@ -16,9 +16,10 @@ Create a new JSON file in `voxvera/locales/{lang_code}.json`.
- **Formatting**: You can use Markdown-style strike-throughs (`~~redacted text~~`) in any of these strings; they will be automatically converted to the stylized "redacted" effect on the flyers.
### 2. The Landing Page
The main landing page (`site/index.html`) must be synchronized to include your new language.
- Open `site/index.html` and add your language to the `const locales` object in the `<script>` tag.
- Run `voxvera build-site` to refresh the public assets and ensure the pre-rendered HTML matches your new locale.
The main landing page is generated from the shared template and locale files.
- Add your language in `voxvera/locales/{lang_code}.json`.
- Do **not** edit `site/index.html` directly; it is generated output.
- Run `voxvera build-site` to refresh the public assets and inject your new locale into the generated HTML.
### 3. Documentation
VoxVera uses the **Doc-Sync Engine** to keep manuals in sync.
@@ -31,12 +32,13 @@ Run the following commands to verify your changes:
- `voxvera --lang {lang_code} check` (Verify CLI translation)
- `voxvera build-docs` (Generate localized manuals)
- `voxvera build-site` (Synchronize the public website and generate the latest portable bundle)
- Open `site/index.html` in a browser and test the language switcher.
- Open the generated `site/index.html` in a browser and test the language switcher.
## Code Contributions
- **Dependencies**: if you add a new Python library to `requirements.txt`, you MUST run `voxvera vendorize` to include it in the portable distribution.
- **Layout Integrity**: VoxVera uses visual width validation. If you add new UI elements, ensure they respect the physical boundaries of an 8.5"x11" flyer.
- **Tests**: Ensure all tests pass: `pytest tests/`. Run the integrity suite specifically when modifying locales: `pytest tests/test_integrity.py`.
- **Generated artifacts**: Treat `voxvera/resources/bin/` as local build output, not source files. Release binaries belong in GitHub Releases, not hand-edited repo state.
- **Style**: Follow the existing coding style (PEP 8 for Python).
- **Documentation**: If adding a new feature, update the master templates in `docs/templates/` and run `voxvera build-docs`.
+6 -3
View File
@@ -1,5 +1,7 @@
# Docker Usage
Experimental: the container path is not yet a production-grade hidden-service deployment target. Linux with `systemd --user` remains the supported persistent-host environment.
A prebuilt image containing Tor, OnionShare and the `voxvera` CLI is published to
`ghcr.io/voxvera/voxvera`.
@@ -7,10 +9,11 @@ A prebuilt image containing Tor, OnionShare and the `voxvera` CLI is published t
# pull the image
docker pull ghcr.io/voxvera/voxvera:latest
# run voxvera quickstart with flyers stored in ./flyers
# run the experimental container with flyers stored in ./flyers
mkdir -p flyers
docker run -it --rm -v "$(pwd)/flyers:/flyers" ghcr.io/voxvera/voxvera
```
The container uses `/flyers` as the working directory and runs `voxvera quickstart`
by default. All generated flyer files will appear in the mounted `flyers` folder.
The container stores runtime state under `/flyers`, seeds a default `config.json` if needed, and retries `voxvera start-all` periodically after the initial `quickstart`.
Use this path for testing only until the container runtime and background recovery behavior are validated end-to-end.
+15
View File
@@ -6,6 +6,10 @@ This page collects common issues encountered when hosting or accessing flyers.
- Ensure Tor is allowed through your firewall. On systems using `ufw` you may need to run `sudo ufw allow tor`.
- Some networks block Tor entirely. If you cannot reach onion services, try connecting over a different network or use a Tor bridge.
## Supported deployment target
- The supported persistent-host setup is Linux with `systemd --user`.
- Windows, macOS, Docker, Flatpak, AppImage, Homebrew, and Chocolatey are currently experimental and may not recover hosting automatically after reboot or network interruptions.
## Tor port detection
`voxvera serve` auto-detects Tor by probing common ports (9050/9150 for SOCKS, 9051/9151 for control). If detection fails -- for example when Tor runs on non-standard ports -- you can override with environment variables:
@@ -39,6 +43,17 @@ keypair (and URL) will be generated on the next `voxvera serve`. The
`voxvera import` command preserves this file automatically, but manual
deletion of the `host/` directory will destroy it.
## Hosting does not return after reboot or network loss
On Linux, install or verify the recovery timer:
```bash
voxvera autostart
systemctl --user status voxvera-start.timer
systemctl --user status voxvera-start.service
```
The supported recovery path is the recurring `systemd --user` timer, which reruns `voxvera start-all` every few minutes.
## QR codes not generated
If `voxvera build` reports "No URLs configured yet", this is expected before the first `voxvera serve`. QR codes for tear-off links are generated automatically once the .onion address is known. QR codes for the content link are generated if you provided a URL during `voxvera init`.
+6 -4
View File
@@ -16,9 +16,10 @@ Create a new JSON file in `voxvera/locales/{lang_code}.json`.
- **Formatting**: You can use Markdown-style strike-throughs (`~~redacted text~~`) in any of these strings; they will be automatically converted to the stylized "redacted" effect on the flyers.
### 2. The Landing Page
The main landing page (`site/index.html`) must be synchronized to include your new language.
- Open `site/index.html` and add your language to the `const locales` object in the `<script>` tag.
- Run `voxvera build-site` to refresh the public assets and ensure the pre-rendered HTML matches your new locale.
The main landing page is generated from the shared template and locale files.
- Add your language in `voxvera/locales/{lang_code}.json`.
- Do **not** edit `site/index.html` directly; it is generated output.
- Run `voxvera build-site` to refresh the public assets and inject your new locale into the generated HTML.
### 3. Documentation
VoxVera uses the **Doc-Sync Engine** to keep manuals in sync.
@@ -31,12 +32,13 @@ Run the following commands to verify your changes:
- `voxvera --lang {lang_code} check` (Verify CLI translation)
- `voxvera build-docs` (Generate localized manuals)
- `voxvera build-site` (Synchronize the public website and generate the latest portable bundle)
- Open `site/index.html` in a browser and test the language switcher.
- Open the generated `site/index.html` in a browser and test the language switcher.
## Code Contributions
- **Dependencies**: if you add a new Python library to `requirements.txt`, you MUST run `voxvera vendorize` to include it in the portable distribution.
- **Layout Integrity**: VoxVera uses visual width validation. If you add new UI elements, ensure they respect the physical boundaries of an 8.5"x11" flyer.
- **Tests**: Ensure all tests pass: `pytest tests/`. Run the integrity suite specifically when modifying locales: `pytest tests/test_integrity.py`.
- **Generated artifacts**: Treat `voxvera/resources/bin/` as local build output, not source files. Release binaries belong in GitHub Releases, not hand-edited repo state.
- **Style**: Follow the existing coding style (PEP 8 for Python).
- **Documentation**: If adding a new feature, update the master templates in `docs/templates/` and run `voxvera build-docs`.
+6 -3
View File
@@ -1,5 +1,7 @@
# Docker Usage
Experimental: the container path is not yet a production-grade hidden-service deployment target. Linux with `systemd --user` remains the supported persistent-host environment.
A prebuilt image containing Tor, OnionShare and the `voxvera` CLI is published to
`ghcr.io/voxvera/voxvera`.
@@ -7,10 +9,11 @@ A prebuilt image containing Tor, OnionShare and the `voxvera` CLI is published t
# pull the image
docker pull ghcr.io/voxvera/voxvera:latest
# run voxvera quickstart with flyers stored in ./flyers
# run the experimental container with flyers stored in ./flyers
mkdir -p flyers
docker run -it --rm -v "$(pwd)/flyers:/flyers" ghcr.io/voxvera/voxvera
```
The container uses `/flyers` as the working directory and runs `voxvera quickstart`
by default. All generated flyer files will appear in the mounted `flyers` folder.
The container stores runtime state under `/flyers`, seeds a default `config.json` if needed, and retries `voxvera start-all` periodically after the initial `quickstart`.
Use this path for testing only until the container runtime and background recovery behavior are validated end-to-end.
+15
View File
@@ -6,6 +6,10 @@ This page collects common issues encountered when hosting or accessing flyers.
- Ensure Tor is allowed through your firewall. On systems using `ufw` you may need to run `sudo ufw allow tor`.
- Some networks block Tor entirely. If you cannot reach onion services, try connecting over a different network or use a Tor bridge.
## Supported deployment target
- The supported persistent-host setup is Linux with `systemd --user`.
- Windows, macOS, Docker, Flatpak, AppImage, Homebrew, and Chocolatey are currently experimental and may not recover hosting automatically after reboot or network interruptions.
## Tor port detection
`voxvera serve` auto-detects Tor by probing common ports (9050/9150 for SOCKS, 9051/9151 for control). If detection fails -- for example when Tor runs on non-standard ports -- you can override with environment variables:
@@ -39,6 +43,17 @@ keypair (and URL) will be generated on the next `voxvera serve`. The
`voxvera import` command preserves this file automatically, but manual
deletion of the `host/` directory will destroy it.
## Hosting does not return after reboot or network loss
On Linux, install or verify the recovery timer:
```bash
voxvera autostart
systemctl --user status voxvera-start.timer
systemctl --user status voxvera-start.service
```
The supported recovery path is the recurring `systemd --user` timer, which reruns `voxvera start-all` every few minutes.
## QR codes not generated
If `voxvera build` reports "No URLs configured yet", this is expected before the first `voxvera serve`. QR codes for tear-off links are generated automatically once the .onion address is known. QR codes for the content link are generated if you provided a URL during `voxvera init`.
+3
View File
@@ -4,6 +4,9 @@ This directory contains a minimal Electron wrapper around the `voxvera` CLI.
It exposes a simple "Quickstart" button so non-technical users can generate
flyers without touching the command line.
Status: experimental. The GUI is not the supported path for reliable background hidden-service hosting or automatic recovery after reboot/network loss.
The GUI now reads `voxvera platform-status --json`, `voxvera doctor --json`, and `voxvera autostart status --json` so its runtime messaging reflects the same platform contract as the CLI instead of inventing a separate compatibility story.
## Development
```
+84 -2
View File
@@ -4,6 +4,36 @@
<meta charset="UTF-8">
<title>VoxVera GUI</title>
<style>
body {
font-family: sans-serif;
margin: 1rem;
}
#status {
border: 1px solid #ccc;
padding: 0.75rem;
margin-bottom: 1rem;
background: #fafafa;
}
.status-badge {
display: inline-block;
padding: 0.15rem 0.4rem;
border-radius: 999px;
font-size: 0.85rem;
font-weight: bold;
text-transform: uppercase;
}
.status-supported {
background: #dff3e4;
color: #14532d;
}
.status-experimental {
background: #fff4d6;
color: #854d0e;
}
.status-error {
background: #fee2e2;
color: #991b1b;
}
#log {
border: 1px solid #ccc;
padding: 5px;
@@ -16,6 +46,13 @@
</head>
<body>
<h1>VoxVera</h1>
<div id="status">
<strong>Runtime status</strong>
<div id="platform-tier"></div>
<div id="platform-model"></div>
<div id="autostart-state"></div>
<div id="doctor-summary"></div>
</div>
<form id="config-form">
<div><label>Name <input type="text" id="name"></label></div>
<div><label>Subdomain <input type="text" id="folder_name"></label></div>
@@ -26,29 +63,74 @@
<div><label>URL Message <input type="text" id="url_message"></label></div>
<div><label>URL <input type="text" id="url"></label></div>
<div><label>Tear-off link <input type="text" id="tear_off_link"></label></div>
<div><label>Binary message <input type="text" id="binary_message"></label></div>
<div><label>Footer message <input type="text" id="footer_message"></label></div>
<button type="button" id="quickstart">Generate &amp; Serve</button>
</form>
<p id="onion-address"></p>
<pre id="log"></pre>
<script>
function setStatusBadge(targetId, label, tier) {
const el = document.getElementById(targetId);
const className = tier === 'supported' ? 'status-supported'
: tier === 'experimental' ? 'status-experimental'
: 'status-error';
el.innerHTML = `<span class="status-badge ${className}">${label}</span>`;
}
function summarizeDoctorChecks(doctor) {
const sections = doctor && doctor.sections ? Object.values(doctor.sections) : [];
if (sections.length > 0) {
const failedSections = sections.filter(section => section.checks && section.checks.length > 0 && !section.ok);
if (failedSections.length === 0) return 'All doctor sections passed.';
return `Doctor warnings in: ${failedSections.map(section => section.title).join(', ')}`;
}
const checks = doctor && Array.isArray(doctor.checks) ? doctor.checks : [];
if (checks.length === 0) return 'No diagnostics available.';
const failed = checks.filter(check => !check.ok);
if (failed.length === 0) return 'All doctor checks passed.';
return `Doctor reports ${failed.length} failing check(s): ${failed.map(check => check.name).join(', ')}`;
}
async function loadRuntimeStatus() {
const runtime = await window.voxvera.loadRuntimeStatus();
if (!runtime.ok) {
setStatusBadge('platform-tier', 'error', 'error');
document.getElementById('platform-model').textContent = runtime.error;
document.getElementById('autostart-state').textContent = '';
document.getElementById('doctor-summary').textContent = '';
return;
}
const { platformStatus, autostart, doctor } = runtime;
setStatusBadge('platform-tier', platformStatus.tier, platformStatus.tier);
document.getElementById('platform-model').textContent = platformStatus.hosting_model || '';
if (autostart && !autostart.error) {
document.getElementById('autostart-state').textContent = `Autostart: ${autostart.message}`;
} else if (autostart && autostart.error) {
document.getElementById('autostart-state').textContent = `Autostart status unavailable: ${autostart.error}`;
}
document.getElementById('doctor-summary').textContent = summarizeDoctorChecks(doctor);
}
async function load() {
const cfg = await window.voxvera.loadConfig();
for (const [k, v] of Object.entries(cfg)) {
const el = document.getElementById(k);
if (el) el.value = v;
}
await loadRuntimeStatus();
}
document.addEventListener('DOMContentLoaded', load);
document.getElementById('quickstart').addEventListener('click', async () => {
const ids = ['name','folder_name','title','subtitle','headline','content','url_message','url','tear_off_link','binary_message'];
const ids = ['name','folder_name','title','subtitle','headline','content','url_message','url','tear_off_link','footer_message'];
const cfg = {};
ids.forEach(id => {
const el = document.getElementById(id);
if (el) cfg[id] = el.value;
});
await window.voxvera.quickstart(cfg);
await loadRuntimeStatus();
});
window.voxvera.onOnionUrl(url => {
+65 -67
View File
@@ -3,12 +3,10 @@ const { spawn } = require('child_process');
const path = require('path');
const which = require('which');
const fs = require('fs');
const { launchTor } = require('./tor.js');
const { extractOnionUrl } = require('./runtime-utils');
let mainWindow;
let onionProc;
// 🔧 merged conflicting changes from codex/start-onionshare-and-generate-static-site vs main
let restarting = false;
function createWindow() {
mainWindow = new BrowserWindow({
@@ -21,70 +19,42 @@ function createWindow() {
mainWindow.loadFile('index.html');
}
// 🔧 merged conflicting changes from codex/start-onionshare-and-generate-static-site vs main
function startOnionShare() {
const configPath = getConfigPath();
function getVoxveraPath() {
const voxveraPath = which.sync('voxvera', { nothrow: true });
if (!voxveraPath) {
dialog.showErrorBox(
'voxvera not found',
'Install the voxvera CLI and ensure it is in your PATH.'
);
return;
throw new Error('Install the voxvera CLI and ensure it is in your PATH.');
}
const build = spawn(voxveraPath, ['--config', configPath, 'build']);
build.on('error', err => {
dialog.showErrorBox('voxvera build error', err.message);
});
build.on('close', code => {
if (code !== 0) {
dialog.showErrorBox('voxvera build error', `build exited with code ${code}.`);
return;
}
runServe();
});
return voxveraPath;
}
async function runServe (retry = false) {
let torProc, socksPort, controlPort;
try {
({ torProc, socksPort, controlPort } = await launchTor());
} catch (err) {
dialog.showErrorBox('Tor error', err.message);
return;
}
const env = { ...process.env,
TOR_SOCKS_PORT: socksPort.toString(),
TOR_CONTROL_PORT: controlPort.toString() };
const configPath = getConfigPath();
const voxveraPath = which.sync('voxvera', { nothrow: true });
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 });
function runJsonCommand(args) {
return new Promise((resolve, reject) => {
let stdout = '';
let stderr = '';
const proc = spawn(getVoxveraPath(), args);
proc.stdout.on('data', data => {
stdout += data.toString();
});
proc.stderr.on('data', data => {
stderr += data.toString();
});
proc.on('close', code => {
if (code !== 0) {
reject(new Error(stderr || `voxvera exited with code ${code}`));
return;
}
try {
resolve(JSON.parse(stdout));
} catch (err) {
reject(new Error(`Could not parse VoxVera JSON output: ${err.message}`));
}
});
proc.on('error', reject);
});
onionProc.on('exit', () => torProc.kill());
}
app.whenReady().then(() => {
createWindow();
startOnionShare();
});
function getConfigPath() {
@@ -105,7 +75,36 @@ function getConfigPath() {
ipcMain.handle('load-config', async () => {
const p = getConfigPath();
const raw = fs.readFileSync(p, 'utf8');
return JSON.parse(raw);
const config = JSON.parse(raw);
if (config.footer_message === undefined && config.binary_message !== undefined) {
config.footer_message = config.binary_message;
}
delete config.binary_message;
return config;
});
ipcMain.handle('load-runtime-status', async () => {
try {
const platformStatus = await runJsonCommand(['platform-status', '--json']);
const doctor = await runJsonCommand(['doctor', '--json']);
let autostart = null;
try {
autostart = await runJsonCommand(['autostart', 'status', '--json']);
} catch (err) {
autostart = { error: err.message };
}
return {
ok: true,
platformStatus,
doctor,
autostart
};
} catch (err) {
return {
ok: false,
error: err.message
};
}
});
ipcMain.handle('run-quickstart', async (_, config) => {
@@ -113,12 +112,11 @@ ipcMain.handle('run-quickstart', async (_, config) => {
if (config && typeof config === 'object') {
fs.writeFileSync(configPath, JSON.stringify(config, null, 2));
}
const voxveraPath = which.sync('voxvera', { nothrow: true });
if (!voxveraPath) {
dialog.showErrorBox(
'voxvera not found',
'Install the voxvera CLI and ensure it is in your PATH.'
);
let voxveraPath;
try {
voxveraPath = getVoxveraPath();
} catch (err) {
dialog.showErrorBox('voxvera not found', err.message);
return -1;
}
return new Promise((resolve) => {
@@ -134,9 +132,9 @@ ipcMain.handle('run-quickstart', async (_, config) => {
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]);
const onionUrl = extractOnionUrl(line);
if (onionUrl && mainWindow) {
mainWindow.webContents.send('onion-url', onionUrl);
}
});
proc.stderr.on('data', data => {
+2 -1
View File
@@ -4,7 +4,8 @@
"main": "main.js",
"scripts": {
"start": "electron .",
"lint": "echo 'lint pass'"
"lint": "echo 'lint pass'",
"test": "node --test runtime-utils.test.js"
},
"devDependencies": {
"electron": "^29.0.0",
+1
View File
@@ -2,6 +2,7 @@ const { contextBridge, ipcRenderer } = require('electron');
contextBridge.exposeInMainWorld('voxvera', {
loadConfig: () => ipcRenderer.invoke('load-config'),
loadRuntimeStatus: () => ipcRenderer.invoke('load-runtime-status'),
quickstart: (config) => ipcRenderer.invoke('run-quickstart', config),
onOnionUrl: (cb) => ipcRenderer.on('onion-url', (_, url) => cb(url)),
onLog: (cb) => ipcRenderer.on('log', (_, data) => cb(data.text, data.isError))
+16
View File
@@ -0,0 +1,16 @@
function extractOnionUrl(line) {
const match = line.match(/Onion URL:\s*(https?:\/\/[a-z0-9.-]+\.onion)/i)
|| line.match(/OnionShare is hosting at\s*(https?:\/\/[a-z0-9.-]+\.onion)/i);
return match ? match[1] : null;
}
function platformDirName(platform = process.platform) {
if (platform === 'win32') return 'win';
if (platform === 'darwin') return 'mac';
return 'linux';
}
module.exports = {
extractOnionUrl,
platformDirName,
};
+28
View File
@@ -0,0 +1,28 @@
const test = require('node:test');
const assert = require('node:assert/strict');
const { extractOnionUrl, platformDirName } = require('./runtime-utils');
test('extractOnionUrl handles current CLI output', () => {
assert.equal(
extractOnionUrl('[voxvera] Onion URL: http://test123.onion'),
'http://test123.onion'
);
});
test('extractOnionUrl handles legacy OnionShare output', () => {
assert.equal(
extractOnionUrl('OnionShare is hosting at http://legacy456.onion'),
'http://legacy456.onion'
);
});
test('extractOnionUrl returns null when no onion URL is present', () => {
assert.equal(extractOnionUrl('still bootstrapping'), null);
});
test('platformDirName maps bundled resource folders correctly', () => {
assert.equal(platformDirName('linux'), 'linux');
assert.equal(platformDirName('darwin'), 'mac');
assert.equal(platformDirName('win32'), 'win');
});
+4 -2
View File
@@ -3,14 +3,16 @@ const path = require('path');
const fs = require('fs');
const which = require('which');
const getPort = (...args) => import('get-port').then(m => m.default(...args));
const { platformDirName } = require('./runtime-utils');
async function launchTor() {
const socks = await getPort();
const control = await getPort();
const torBase = path.join(__dirname, '..', '..', 'voxvera', 'resources', 'tor');
let exe = path.join(torBase, process.platform,
const platformDir = platformDirName(process.platform);
let exe = path.join(torBase, platformDir,
process.platform === 'win32' ? 'tor.exe' : 'tor');
let obfs4 = path.join(torBase, process.platform,
let obfs4 = path.join(torBase, platformDir,
process.platform === 'win32' ? 'obfs4proxy.exe' : 'obfs4proxy');
const missing = p => {
+2 -2
View File
@@ -7,6 +7,6 @@
"content": "The Android Open Source Project (AOSP) represents the essence of what Android was originally intended to be: an open, transparent, and adaptable platform. It strips away the layers of proprietary software and corporate oversight that have become synonymous with mainstream Android distributions. With AOSP, you gain access to a raw, unfiltered version of Android, allowing you to truly make your device your own.<br><br>For developers, AOSP offers a playground of possibilities, where the only limits are your skills and imagination. It serves as the foundation for countless custom ROMs, each tailored to specific needs, whether for enhanced performance, privacy, or unique features that mainstream versions lack. Privacy enthusiasts will appreciate the absence of pre-installed bloatware and tracking services that often come wit stock Android, providing a cleaner, more secure user experience.<br><br>AOSP isn't just for developers or tech enthusiasts; it's for anyone who values digital freedom. It allows you to reclaim control over your device, tailoring it to your needs without the restrictions of carriers or manufacturers. Discover how AOSP can transform your mobile experience. Start your journey towards a more open, customizable future today.",
"url_message": "Visit the link below to learn more.",
"url": "https://source.android.com",
"tear_off_link": "http://aosp.6dshf2gnj7yzxlfcaczlyi57up4mvbtd5orinuj5bjsfycnhz2w456yd.onion",
"binary_message": "0110010 0101011 0110010 0111101 0110100"
"tear_off_link": "",
"footer_message": "0110010 0101011 0110010 0111101 0110100"
}
+3 -3
View File
@@ -7,6 +7,6 @@
"content": "What is Bitcoin? It's more than just digital money; it's a revolutionary technology designed to fix some of the world's most pressing problems. Bitcoin operates on a decentralized network, free from the control of any government or central authority. This ensures that no one can manipulate its supply or value, making it a sound form of money that protects your wealth from inflation.<br><br>Our current financial system is built on flawed incentives—encouraging debt, dependency, and centralization of power. Bitcoin changes that by creating a system where honesty, transparency, and individual responsibility are rewarded. In the Bitcoin network, there is no room for corruption or manipulation because the rules are enforced by mathematics, not by people.<br><br>Bitcoin aligns incentives in a way that promotes long-term thinking, savings, and innovation. It empowers individuals to take control of their financial future, fostering a global community where value can be transferred freely and securely without intermediaries. This is why we need Bitcoin—because it creates a world where incentives are properly aligned, leading to a more fair and prosperous society for all.<br><br>Take the step to learn more about Bitcoin and how it can transform not just your financial life, but the world. Tear off a link to the left and explore this groundbreaking technology. Join the movement towards a better future.",
"url_message": "Follow this link to learn more.",
"url": "https://bitcoin.org/en",
"tear_off_link": "http://bitcoin.6dshf2gnj7yzxlfcaczlyi57up4mvbtd5orinuj5bjsfycnhz2w456yd.onion",
"binary_message": "0110010 0101011 0110010 0111101 0110100"
}
"tear_off_link": "",
"footer_message": "0110010 0101011 0110010 0111101 0110100"
}
+1 -1
View File
@@ -8,5 +8,5 @@
"url_message": "",
"url": "",
"tear_off_link": "",
"binary_message": "0110010 0101011 0110010 0111101 0110100"
"footer_message": "0110010 0101011 0110010 0111101 0110100"
}
+3 -3
View File
@@ -7,6 +7,6 @@
"content": "content",
"url_message": "url message",
"url": "url",
"tear_off_link": "http://example-folder_name.6dshf2gnj7yzxlfcaczlyi57up4mvbtd5orinuj5bjsfycnhz2w456yd.onion",
"binary_message": "0110010 0101011 0110010 0111101 0110100"
}
"tear_off_link": "",
"footer_message": "0110010 0101011 0110010 0111101 0110100"
}
+3 -3
View File
@@ -7,6 +7,6 @@
"content": "Who is Jesus? He is more than just a historical figure; He is the Son of God, the Savior of the world. Jesus Himself said, 'I am the way, the truth, and the life. No one comes to the Father except through Me' (John 14:6). He came to bridge the gap between God and humanity, offering us a path to forgiveness, peace, and eternal life.<br><br>We all face brokenness in our lives—whether it's in our relationships, our hearts, or our world. Jesus came to heal that brokenness. He lived a perfect life, died on the cross for our sins, and rose again, defeating death and offering us the hope of eternal life with God.<br><br>Without Jesus, we are lost in our sin, separated from God. But through Him, we can be forgiven and restored. He offers us not just a better life now, but the promise of an eternal life with Him. This is why we need Him—because only in Jesus can we find true peace, purpose, and the assurance of eternal life.<br><br>Take the step to learn more about Jesus and the life-changing impact He can have on your life. Tear off a link to the left and explore His teachings. Find the hope you've been searching for.",
"url_message": "Follow this link to learn more.",
"url": "http://media.faithforthefamily.com/ra/godsoloved/english.mp3",
"tear_off_link": "http://jesus.6dshf2gnj7yzxlfcaczlyi57up4mvbtd5orinuj5bjsfycnhz2w456yd.onion",
"binary_message": "0110010 0101011 0110010 0111101 0110100"
}
"tear_off_link": "",
"footer_message": "0110010 0101011 0110010 0111101 0110100"
}
+3 -3
View File
@@ -7,6 +7,6 @@
"content": "Why Meshtastic? In times of crisis, traditional communication networks can fail, leaving us isolated and vulnerable. Meshtastic provides a powerful solution—an alternative local communication network that operates independently of centralized systems. It’s designed for emergency situations, allowing you to stay connected when it matters most.<br><br>Meshtastic devices create a mesh network that can relay messages over long distances, even without internet or cellular coverage. This decentralized approach ensures that your communications are resilient and reliable, no matter the circumstances. But it’s not just about staying connected—Meshtastic also offers encryption, so your messages remain secure and private.<br><br>In a world where privacy is increasingly compromised, Meshtastic empowers you to take control of your communication. Whether you’re coordinating a local community response, staying in touch with loved ones during a disaster, or simply preparing for the unexpected, Meshtastic provides the tools you need to communicate securely and effectively.<br><br>Take the step to learn more about Meshtastic and how you can build your own emergency communication network. Tear off a link to the left and explore this innovative technology. Ensure you’re prepared for whatever comes your way.",
"url_message": "Follow this link to learn more.",
"url": "https://meshtastic.org",
"tear_off_link": "http://meshtastic.6dshf2gnj7yzxlfcaczlyi57up4mvbtd5orinuj5bjsfycnhz2w456yd.onion",
"binary_message": "0110010 0101011 0110010 0111101 0110100"
}
"tear_off_link": "",
"footer_message": "0110010 0101011 0110010 0111101 0110100"
}
+3 -3
View File
@@ -7,6 +7,6 @@
"content": "What is Nostr? Unlike traditional social media platforms, Nostr is a protocol—a decentralized system that empowers users to connect and share without the constraints of a central authority. Nostr is censorship-resistant, meaning your voice can’t be silenced by corporations or governments. It’s designed to give you control over your online presence, free from the influence of algorithms or ads that prioritize engagement over meaningful connections.<br><br>On Nostr, there are no hidden agendas to keep you doomscrolling. You engage with content on your terms, not based on what an algorithm thinks will keep you online the longest. Nostr allows you to build genuine connections and participate in conversations that matter, all while maintaining your privacy and autonomy.<br><br>In a world where online speech is increasingly controlled and manipulated, Nostr offers a refreshing alternative. It’s not just a platform; it’s a movement towards true digital freedom, where your interactions are driven by your interests and values, not by what generates the most ad revenue.<br><br>Take the step to learn more about Nostr and how it can transform the way you connect online. Tear off a link the left and explore this groundbreaking protocol. Join a community that values freedom and authentic interaction.",
"url_message": "Follow this link to learn more.",
"url": "https://nostr.how/en/what-is-nostr",
"tear_off_link": "http://nostr.6dshf2gnj7yzxlfcaczlyi57up4mvbtd5orinuj5bjsfycnhz2w456yd.onion/",
"binary_message": "0110010 0101011 0110010 0111101 0110100"
}
"tear_off_link": "",
"footer_message": "0110010 0101011 0110010 0111101 0110100"
}
+4 -4
View File
@@ -5,8 +5,8 @@
"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.<br><br>Use 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.<br><br>Privacy can be maintained. Flyers can be shared via the Tor network, protecting hoster and users from censorship.<br><br>Join us in a revolution that values truth and transparency. Together, we can build a network of informed citizens who are unafraid to speak out.",
"url_message": "Follow this link to learn more. Use Tor Browser.",
"url_message": "Follow this link to learn more.",
"url": "https://voxvera.org/",
"tear_off_link": "https://voxvera.org/",
"binary_message": "0110010 0101011 0110010 0111101 0110100"
}
"tear_off_link": "",
"footer_message": "0110010 0101011 0110010 0111101 0110100"
}
+41 -3
View File
@@ -1,6 +1,8 @@
$ErrorActionPreference = 'Stop'
$packages = @('tor', 'onionshare', 'jq', 'qrencode', 'imagemagick')
Write-Warning 'The Windows installer path is experimental. It is not yet validated for reliable background Tor hidden-service hosting or automatic recovery after connectivity changes.'
$packages = @('tor', 'onionshare')
function Install-Choco {
param([string]$Name)
@@ -76,6 +78,41 @@ function Check-LocalBin {
}
}
function Show-VoxVeraStatus {
if (-not (Get-Command voxvera -ErrorAction SilentlyContinue)) {
return
}
try {
$platform = voxvera platform-status --json | ConvertFrom-Json
Write-Host ("Platform tier: {0} ({1})" -f $platform.tier, $platform.label)
if ($platform.hosting_model) {
Write-Host ("Hosting model: {0}" -f $platform.hosting_model)
}
} catch {
Write-Warning "Could not read platform status from voxvera."
}
try {
$doctor = voxvera doctor --json | ConvertFrom-Json
$failed = @($doctor.checks | Where-Object { -not $_.ok } | ForEach-Object { $_.name })
if ($failed.Count -gt 0) {
Write-Host ("Doctor checks needing attention: {0}" -f ($failed -join ', '))
} else {
Write-Host 'Doctor checks: all passed'
}
} catch {
Write-Warning "Could not read doctor output from voxvera."
}
try {
$autostart = voxvera autostart status --json | ConvertFrom-Json
Write-Host ("Autostart: {0}" -f $autostart.message)
} catch {
Write-Warning "Could not read autostart status from voxvera."
}
}
if (Get-Command pipx -ErrorAction SilentlyContinue) {
try {
pipx install voxvera --force
@@ -84,7 +121,7 @@ if (Get-Command pipx -ErrorAction SilentlyContinue) {
$home = [Environment]::GetFolderPath('UserProfile')
$dest = Join-Path $home '.local\bin'
New-Item -ItemType Directory -Path $dest -Force | Out-Null
$url = 'https://github.com/PR0M3TH3AN/VoxVera/releases/latest/download/voxvera.exe'
$url = 'https://github.com/PR0M3TH3AN/VoxVera/releases/latest/download/voxvera-windows.exe'
$status = Download-Binary $url "$dest/voxvera.exe"
if ($status -eq 200) {
Check-LocalBin
@@ -100,7 +137,7 @@ if (Get-Command pipx -ErrorAction SilentlyContinue) {
$home = [Environment]::GetFolderPath('UserProfile')
$dest = Join-Path $home '.local\bin'
New-Item -ItemType Directory -Path $dest -Force | Out-Null
$url = 'https://github.com/PR0M3TH3AN/VoxVera/releases/latest/download/voxvera.exe'
$url = 'https://github.com/PR0M3TH3AN/VoxVera/releases/latest/download/voxvera-windows.exe'
$status = Download-Binary $url "$dest/voxvera.exe"
if ($status -eq 200) {
Check-LocalBin
@@ -114,3 +151,4 @@ if (Get-Command pipx -ErrorAction SilentlyContinue) {
}
Write-Host 'VoxVera installed successfully.'
Show-VoxVeraStatus
+81
View File
@@ -4,6 +4,7 @@ set -euo pipefail
REPO="PR0M3TH3AN/VoxVera"
REPO_URL="https://github.com/${REPO}"
BRANCH="main"
OS_NAME="$(uname -s | tr '[:upper:]' '[:lower:]')"
# --- Network resilience settings ---
# Kill git operations that stall below 1 KB/s for 30 seconds
@@ -49,6 +50,45 @@ msg() { echo -e "\e[32m$*\e[0m"; }
warn() { echo -e "\e[33m$*\e[0m" >&2; }
die() { echo -e "\e[31m$*\e[0m" >&2; exit 1; }
report_cli_status() {
command_exists voxvera || return 0
local platform_json doctor_json autostart_json
platform_json="$(voxvera platform-status --json 2>/dev/null || true)"
doctor_json="$(voxvera doctor --json 2>/dev/null || true)"
autostart_json="$(voxvera autostart status --json 2>/dev/null || true)"
if [ -n "$platform_json" ]; then
PLATFORM_JSON="$platform_json" python3 - <<'PY'
import json, os
data = json.loads(os.environ["PLATFORM_JSON"])
print(f"Platform tier: {data.get('tier', 'unknown')} ({data.get('label', 'unknown')})")
if data.get("hosting_model"):
print(f"Hosting model: {data['hosting_model']}")
PY
fi
if [ -n "$doctor_json" ]; then
DOCTOR_JSON="$doctor_json" python3 - <<'PY'
import json, os
data = json.loads(os.environ["DOCTOR_JSON"])
failed = [check["name"] for check in data.get("checks", []) if not check.get("ok")]
if failed:
print("Doctor checks needing attention: " + ", ".join(failed))
else:
print("Doctor checks: all passed")
PY
fi
if [ -n "$autostart_json" ]; then
AUTOSTART_JSON="$autostart_json" python3 - <<'PY'
import json, os
data = json.loads(os.environ["AUTOSTART_JSON"])
print("Autostart: " + data.get("message", "unknown"))
PY
fi
}
install_pkg() {
case "$PM" in
apt) $SUDO apt-get install -y "$@" ;;
@@ -108,6 +148,9 @@ get_latest_tag() {
# Ensure core dependencies are present
msg "Checking and updating system dependencies..."
if [[ "$OS_NAME" != linux* ]]; then
warn "This installer is optimized for Linux. Non-Linux paths are experimental and are not validated for reliable background hidden-service hosting."
fi
if [ "$PM" = "apt" ]; then
$SUDO apt-get update
SYSTEM_PKGS=(tor curl wget git python3-pip python3-venv)
@@ -182,6 +225,23 @@ if [ -d "$HOME/imports" ] && ls "$HOME/imports"/*.json &>/dev/null; then
fi
fi
# Clean generated files from existing site directories so rebuild-all
# regenerates them from the current template. Preserves:
# config.json — site content and settings
# .onionshare-session — Tor domain identity key
# server.pid — running process marker
# onionshare.log — current log (may be in use)
# Everything else is regenerated by build_assets().
if [ -d "$HOME/host" ]; then
for site_dir in "$HOME"/host/*/; do
[ -f "${site_dir}config.json" ] || continue
msg "Cleaning generated files in ${site_dir}..."
rm -f "${site_dir}index.html" 2>/dev/null || true
rm -f "${site_dir}qrcode-content.png" "${site_dir}qrcode-tear-offs.png" 2>/dev/null || true
rm -rf "${site_dir}download" 2>/dev/null || true
done
fi
# --- Helper: build pip flags for this distro ---
pip_flags() {
local flags="--user --upgrade"
@@ -364,7 +424,28 @@ for strategy in install_tarball install_shallow install_pipx install_pip_git ins
fi
# Clear bash's cached command path so the new binary is found immediately
hash -r 2>/dev/null || true
# Rebuild all existing sites so they pick up template/CSS changes
if command_exists voxvera; then
msg "Rebuilding existing sites with updated template..."
voxvera rebuild-all 2>/dev/null || true
if [[ "$OS_NAME" == linux* ]]; then
msg "Installing Linux autostart recovery timer..."
voxvera autostart 2>/dev/null || warn "Could not install the VoxVera autostart timer automatically. Run 'voxvera autostart' manually."
else
warn "Autostart was not configured automatically because this platform is experimental."
fi
msg "Inspecting installed runtime posture..."
report_cli_status
fi
msg "\nVoxVera installed/updated successfully. Run 'voxvera check' to verify."
if [[ "$OS_NAME" == linux* ]]; then
msg "Linux systemd user autostart should now retry hidden-service startup every few minutes."
msg "Inspect with: voxvera autostart status"
msg "Remove with: voxvera autostart uninstall"
msg "Validate with: bash scripts/linux-recovery-check.sh"
fi
# Detect if the parent shell will still have a stale hash
msg "If 'voxvera' is not found, run: hash -r (or open a new terminal)"
exit 0
+13 -3
View File
@@ -1,18 +1,27 @@
#!/usr/bin/env bash
set -e
echo "Warning: AppImage packaging is experimental and is not yet validated for reliable background hidden-service hosting."
# Ensure we are in project root
cd "$(dirname "$0")/.."
ARCH=$(uname -m)
BINARY=${1:-"voxvera/resources/bin/voxvera-linux-$ARCH"}
OUTPUT="voxvera/resources/bin/VoxVera-$ARCH.AppImage"
if [ ! -f "$BINARY" ]; then
echo "Binary not found: $BINARY. Run scripts/build_binaries.sh first." >&2
echo "Binary not found: $BINARY. Building it first..."
bash scripts/build_binaries.sh
fi
if [ ! -f "$BINARY" ]; then
echo "Binary still not found after build: $BINARY." >&2
exit 1
fi
APPDIR=build/AppDir
rm -rf "$APPDIR"
mkdir -p "$APPDIR/usr/bin"
cp "$BINARY" "$APPDIR/usr/bin/voxvera"
chmod +x "$APPDIR/usr/bin/voxvera"
@@ -46,7 +55,8 @@ chmod +x 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 -f "$OUTPUT"
./appimagetool --appimage-extract-and-run "$APPDIR" "$OUTPUT"
rm appimagetool
echo "AppImage created at voxvera/resources/bin/VoxVera-x86_64.AppImage"
echo "AppImage created at $OUTPUT"
+12 -3
View File
@@ -1,6 +1,8 @@
#!/usr/bin/env bash
set -e
echo "Warning: Debian packaging is currently experimental until runtime dependencies and hidden-service recovery are validated end-to-end."
# Ensure we are in project root
cd "$(dirname "$0")/.."
@@ -16,13 +18,19 @@ fi
BINARY=${1:-"voxvera/resources/bin/voxvera-linux-$ARCH"}
if [ ! -f "$BINARY" ]; then
echo "Binary not found: $BINARY." >&2
echo "Binary not found: $BINARY. Building it first..."
bash scripts/build_binaries.sh
fi
if [ ! -f "$BINARY" ]; then
echo "Binary still not found after build: $BINARY." >&2
exit 1
fi
VERSION=$(grep "__version__" voxvera/__init__.py | cut -d '"' -f 2)
PKG_DIR="build/voxvera_${VERSION}_${DEB_ARCH}"
OUTPUT="voxvera/resources/bin/voxvera_${VERSION}_${DEB_ARCH}.deb"
echo "Building .deb package for version $VERSION ($DEB_ARCH)..."
@@ -40,6 +48,7 @@ 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"
rm -f "$OUTPUT"
dpkg-deb --build "$PKG_DIR" "$OUTPUT"
echo "Debian package created at voxvera/resources/bin/voxvera_${VERSION}_${DEB_ARCH}.deb"
echo "Debian package created at $OUTPUT"
+2
View File
@@ -1,6 +1,8 @@
#!/usr/bin/env bash
set -e
echo "Warning: DMG packaging is experimental and is not yet validated for reliable background hidden-service hosting."
# Ensure we are in project root
cd "$(dirname "$0")/.."
@@ -1,5 +1,6 @@
# Experimental package surface: not yet validated for reliable background hidden-service hosting.
$toolsDir = Split-Path -Parent $MyInvocation.MyCommand.Definition
$url = 'https://github.com/PR0M3TH3AN/VoxVera/releases/latest/download/voxvera.exe'
$url = 'https://github.com/PR0M3TH3AN/VoxVera/releases/latest/download/voxvera-windows.exe'
$packageArgs = @{
packageName = 'voxvera'
@@ -1,4 +1,5 @@
app-id: org.voxvera.VoxVera
# Experimental package surface: not yet validated for reliable background hidden-service hosting.
runtime: org.freedesktop.Platform
runtime-version: '23.08'
sdk: org.freedesktop.Sdk
+1 -1
View File
@@ -1,5 +1,5 @@
class Voxvera < Formula
desc "Generate QR-coded flyers"
desc "Experimental VoxVera formula"
homepage "https://github.com/PR0M3TH3AN/VoxVera"
url "https://github.com/PR0M3TH3AN/VoxVera/releases/latest/download/voxvera"
version "0.1.0"
+1 -1
View File
@@ -14,7 +14,7 @@ voxvera = ["templates/**", "src/**", "resources/**", "locales/**", "resources/bi
[project]
name = "voxvera"
version = "0.3.1"
version = "0.3.11"
description = "VoxVera CLI utilities"
readme = "README.md"
authors = [{name = "VoxVera"}]
+17 -6
View File
@@ -13,9 +13,19 @@ if ! command -v pyinstaller &> /dev/null; then
pip install pyinstaller --break-system-packages
fi
ARTIFACT_DIR="voxvera/resources/bin"
BUILD_BINARY_NAME="voxvera-$(uname -s | tr '[:upper:]' '[:lower:]')-$(uname -m)"
# Clean previous builds
rm -rf build/ dist_bin/
mkdir -p voxvera/resources/bin
python3 - <<'PY'
import shutil
from pathlib import Path
for path in (Path("build"), Path("dist_bin")):
if path.exists():
shutil.rmtree(path, ignore_errors=True)
PY
mkdir -p "$ARTIFACT_DIR"
echo "Building VoxVera binary for $(uname)..."
@@ -34,11 +44,12 @@ $PY_CMD --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 "voxvera/resources/tor:voxvera/resources/tor" \
--add-data "support-matrix.json:." \
--add-data "requirements.txt:." \
--name "$BINARY_NAME" \
--distpath voxvera/resources/bin \
--distpath "$ARTIFACT_DIR" \
voxvera/cli.py
echo "Binary build complete. Located in voxvera/resources/bin/"
ls -lh voxvera/resources/bin/
echo "Binary build complete. Located in $ARTIFACT_DIR/"
ls -lh "$ARTIFACT_DIR"/
+32
View File
@@ -0,0 +1,32 @@
#!/usr/bin/env bash
set -euo pipefail
cd "$(dirname "$0")/.."
echo "==> flake8"
python3 -m flake8 voxvera tests \
--jobs=1 \
--exclude=voxvera/vendor \
--ignore=E501,W503,W293,E402,E306,E704,E261,F841,E302,F401,E401,F541
echo "==> pytest"
python3 -m pytest tests/ -v
echo "==> electron smoke tests"
(cd gui/electron && npm test)
echo "==> dependency check"
python3 -m voxvera.cli --lang en check
if [[ "$(uname -s)" == "Linux" ]]; then
echo "==> platform smoke"
bash scripts/platform-smoke.sh linux-cli
fi
echo "==> build docs"
python3 -m voxvera.cli build-docs
echo "==> build site"
python3 -m voxvera.cli build-site
echo "All local checks passed."
+126
View File
@@ -0,0 +1,126 @@
#!/usr/bin/env bash
set -euo pipefail
cd "$(dirname "$0")/.."
if [[ "$(uname -s)" != "Linux" ]]; then
echo "linux-recovery-check.sh is only for Linux hosts." >&2
exit 1
fi
command -v voxvera >/dev/null 2>&1 || {
echo "voxvera is not on PATH" >&2
exit 1
}
TMP_DIR="${VOXVERA_RECOVERY_TMPDIR:-$(mktemp -d /tmp/voxvera-linux-recovery-XXXXXX)}"
cleanup() {
if [[ -z "${VOXVERA_RECOVERY_TMPDIR:-}" ]]; then
rm -rf "$TMP_DIR"
fi
}
trap cleanup EXIT
PLATFORM_JSON="$TMP_DIR/platform-status.json"
DOCTOR_JSON="$TMP_DIR/doctor.json"
AUTOSTART_JSON="$TMP_DIR/autostart-status.json"
voxvera platform-status --json >"$PLATFORM_JSON"
voxvera doctor --json >"$DOCTOR_JSON"
voxvera autostart status --json >"$AUTOSTART_JSON"
PLATFORM_JSON="$PLATFORM_JSON" \
DOCTOR_JSON="$DOCTOR_JSON" \
AUTOSTART_JSON="$AUTOSTART_JSON" \
python3 - <<'PY'
import json
import os
import sys
from pathlib import Path
platform_status = json.loads(Path(os.environ["PLATFORM_JSON"]).read_text(encoding="utf-8"))
doctor = json.loads(Path(os.environ["DOCTOR_JSON"]).read_text(encoding="utf-8"))
autostart = json.loads(Path(os.environ["AUTOSTART_JSON"]).read_text(encoding="utf-8"))
errors = []
warnings = []
if platform_status.get("platform_id") != "linux_cli_systemd":
errors.append(f"expected linux_cli_systemd, got {platform_status.get('platform_id')}")
if platform_status.get("tier") != "supported":
errors.append(f"expected supported tier, got {platform_status.get('tier')}")
checks = {check["name"]: check for check in doctor.get("checks", [])}
required_ok = ["platform", "voxvera_cli", "onionshare_cli", "tor_binary", "host_root"]
for name in required_ok:
check = checks.get(name)
if not check:
errors.append(f"missing doctor check: {name}")
elif not check.get("ok"):
errors.append(f"{name} failed: {check.get('detail')}")
tor_check = checks.get("tor_socks_reachable")
if not tor_check:
errors.append("missing doctor check: tor_socks_reachable")
elif not tor_check.get("ok"):
warnings.append(f"tor_socks_reachable: {tor_check.get('detail')}")
timer_check = next((check for check in autostart.get("checks", []) if check["name"] == "systemd_timer_enabled"), None)
if not timer_check:
errors.append("missing autostart check: systemd_timer_enabled")
elif not timer_check.get("ok"):
warnings.append(f"systemd_timer_enabled: {timer_check.get('detail')}")
artifacts = [Path(path) for path in autostart.get("artifacts", [])]
missing = [str(path) for path in artifacts if not path.exists()]
if missing:
warnings.append("missing autostart artifacts: " + ", ".join(missing))
host_root = Path.home() / "host"
sites = []
if host_root.exists():
for site_dir in sorted(path for path in host_root.iterdir() if path.is_dir()):
config = site_dir / "config.json"
if not config.exists():
continue
session = site_dir / ".onionshare-session"
log = site_dir / "onionshare.log"
pid = site_dir / "server.pid"
sites.append(
{
"name": site_dir.name,
"session": session.exists(),
"log": log.exists(),
"pid": pid.exists(),
}
)
if errors:
print("Linux recovery check failed:", file=sys.stderr)
for error in errors:
print(f"- {error}", file=sys.stderr)
sys.exit(1)
print(f"Platform: {platform_status['label']} [{platform_status['tier']}]")
print(f"Hosting model: {platform_status.get('hosting_model', '')}")
print(f"Autostart: {autostart.get('message', '')}")
if warnings:
print("Warnings:")
for warning in warnings:
print(f"- {warning}")
else:
print("Warnings: none")
if sites:
print("Site state:")
for site in sites:
print(
f"- {site['name']}: session={'yes' if site['session'] else 'no'}, "
f"log={'yes' if site['log'] else 'no'}, pid={'yes' if site['pid'] else 'no'}"
)
else:
print("Site state: no configured sites under ~/host")
PY
echo "Detailed JSON artifacts written to $TMP_DIR"
+142
View File
@@ -0,0 +1,142 @@
#!/usr/bin/env bash
set -euo pipefail
cd "$(dirname "$0")/.."
TARGET="${1:-linux-cli}"
TMP_DIR="${VOXVERA_SMOKE_TMPDIR:-$(mktemp -d /tmp/voxvera-platform-smoke-XXXXXX)}"
mkdir -p "$TMP_DIR"
cleanup() {
if [[ -z "${VOXVERA_SMOKE_TMPDIR:-}" ]]; then
rm -rf "$TMP_DIR"
fi
}
trap cleanup EXIT
command -v voxvera >/dev/null 2>&1 || {
echo "voxvera is not on PATH" >&2
exit 1
}
case "$TARGET" in
linux-cli)
EXPECTED_PLATFORM_ID="linux_cli_systemd"
EXPECTED_TIER="supported"
REQUIRE_ONIONSHARE="${VOXVERA_REQUIRE_ONIONSHARE:-1}"
;;
macos-cli)
EXPECTED_PLATFORM_ID="macos_cli"
EXPECTED_TIER="experimental"
REQUIRE_ONIONSHARE="${VOXVERA_REQUIRE_ONIONSHARE:-0}"
;;
windows-cli)
EXPECTED_PLATFORM_ID="windows_cli"
EXPECTED_TIER="experimental"
REQUIRE_ONIONSHARE="${VOXVERA_REQUIRE_ONIONSHARE:-0}"
;;
docker-cli)
EXPECTED_PLATFORM_ID="linux_cli_systemd"
EXPECTED_TIER="${VOXVERA_EXPECTED_TIER_OVERRIDE:-supported}"
REQUIRE_ONIONSHARE="${VOXVERA_REQUIRE_ONIONSHARE:-0}"
;;
list)
printf '%s\n' linux-cli macos-cli windows-cli docker-cli
exit 0
;;
*)
echo "Unsupported smoke target: $TARGET" >&2
exit 1
;;
esac
PLATFORM_JSON="$TMP_DIR/platform-status.json"
DOCTOR_JSON="$TMP_DIR/doctor.json"
AUTOSTART_JSON="$TMP_DIR/autostart-status.json"
voxvera platform-status --json >"$PLATFORM_JSON"
voxvera doctor --json >"$DOCTOR_JSON"
voxvera autostart status --json >"$AUTOSTART_JSON"
PLATFORM_JSON="$PLATFORM_JSON" \
DOCTOR_JSON="$DOCTOR_JSON" \
AUTOSTART_JSON="$AUTOSTART_JSON" \
EXPECTED_PLATFORM_ID="$EXPECTED_PLATFORM_ID" \
EXPECTED_TIER="$EXPECTED_TIER" \
VOXVERA_REQUIRE_TOR="${VOXVERA_REQUIRE_TOR:-0}" \
VOXVERA_REQUIRE_AUTOSTART_ARTIFACTS="${VOXVERA_REQUIRE_AUTOSTART_ARTIFACTS:-0}" \
VOXVERA_REQUIRE_ONIONSHARE="$REQUIRE_ONIONSHARE" \
python3 - <<'PY'
import json
import os
import sys
from pathlib import Path
platform_status = json.loads(Path(os.environ["PLATFORM_JSON"]).read_text(encoding="utf-8"))
doctor = json.loads(Path(os.environ["DOCTOR_JSON"]).read_text(encoding="utf-8"))
autostart = json.loads(Path(os.environ["AUTOSTART_JSON"]).read_text(encoding="utf-8"))
expected_platform_id = os.environ["EXPECTED_PLATFORM_ID"]
expected_tier = os.environ["EXPECTED_TIER"]
require_tor = os.environ.get("VOXVERA_REQUIRE_TOR") == "1"
require_autostart_artifacts = os.environ.get("VOXVERA_REQUIRE_AUTOSTART_ARTIFACTS") == "1"
require_onionshare = os.environ.get("VOXVERA_REQUIRE_ONIONSHARE") == "1"
errors = []
if platform_status.get("platform_id") != expected_platform_id:
errors.append(
f"platform-status platform_id mismatch: {platform_status.get('platform_id')} != {expected_platform_id}"
)
if platform_status.get("tier") != expected_tier:
errors.append(f"platform-status tier mismatch: {platform_status.get('tier')} != {expected_tier}")
if doctor.get("platform_id") != expected_platform_id:
errors.append(f"doctor platform_id mismatch: {doctor.get('platform_id')} != {expected_platform_id}")
if autostart.get("platform_id") != expected_platform_id:
errors.append(f"autostart platform_id mismatch: {autostart.get('platform_id')} != {expected_platform_id}")
check_map = {check["name"]: check for check in doctor.get("checks", [])}
for required_check in ("platform", "voxvera_cli"):
if required_check not in check_map:
errors.append(f"doctor missing required check: {required_check}")
elif not check_map[required_check].get("ok"):
errors.append(f"doctor check failed: {required_check} ({check_map[required_check].get('detail')})")
if require_onionshare:
required_check = "onionshare_cli"
if required_check not in check_map:
errors.append(f"doctor missing required check: {required_check}")
elif not check_map[required_check].get("ok"):
errors.append(f"doctor check failed: {required_check} ({check_map[required_check].get('detail')})")
if require_tor:
for required_check in ("tor_binary", "tor_socks_reachable"):
if required_check not in check_map:
errors.append(f"doctor missing Tor check: {required_check}")
elif not check_map[required_check].get("ok"):
errors.append(f"doctor Tor check failed: {required_check} ({check_map[required_check].get('detail')})")
if require_autostart_artifacts:
if not autostart.get("artifacts"):
errors.append("autostart status returned no artifacts")
missing_artifacts = [artifact for artifact in autostart.get("artifacts", []) if not Path(artifact).exists()]
if missing_artifacts:
errors.append("autostart artifacts missing: " + ", ".join(missing_artifacts))
if errors:
print("Platform smoke validation failed:", file=sys.stderr)
for error in errors:
print(f"- {error}", file=sys.stderr)
sys.exit(1)
print(f"Platform smoke OK: {platform_status['label']} [{platform_status['tier']}]")
print(f"Hosting model: {platform_status.get('hosting_model', '')}")
failing_checks = [check["name"] for check in doctor.get("checks", []) if not check.get("ok")]
if failing_checks:
print("Doctor warnings: " + ", ".join(failing_checks))
else:
print("Doctor warnings: none")
print("Autostart message: " + autostart.get("message", ""))
PY
echo "Artifacts written to $TMP_DIR"
+110
View File
@@ -0,0 +1,110 @@
#!/usr/bin/env bash
set -euo pipefail
cd "$(dirname "$0")/.."
ARCH="${ARCH_OVERRIDE:-$(uname -m)}"
if [[ "$ARCH" == "x86_64" ]]; then
DEB_ARCH="amd64"
else
DEB_ARCH="$ARCH"
fi
DEB_PATH="${1:-voxvera/resources/bin/voxvera_$(python3 - <<'PY'
from voxvera import __version__
print(__version__)
PY
)_${DEB_ARCH}.deb}"
APPIMAGE_PATH="${2:-voxvera/resources/bin/VoxVera-${ARCH}.AppImage}"
tmpdir="$(mktemp -d)"
cleanup() {
rm -rf "$tmpdir"
}
trap cleanup EXIT
json_get() {
local file="$1"
local key="$2"
python3 - "$file" "$key" <<'PY'
import json
import sys
path = sys.argv[2].split(".")
data = json.load(open(sys.argv[1], "r", encoding="utf-8"))
for part in path:
data = data[part]
if isinstance(data, list):
print("\n".join(str(item) for item in data))
else:
print(data)
PY
}
require_non_empty_json_field() {
local file="$1"
local key="$2"
local value
value="$(json_get "$file" "$key")"
if [[ -z "${value// }" ]]; then
echo "Expected non-empty JSON field '$key' in $file" >&2
exit 1
fi
}
echo "Validating Debian artifact: $DEB_PATH"
if [[ ! -f "$DEB_PATH" ]]; then
echo "Debian package not found: $DEB_PATH" >&2
exit 1
fi
mkdir -p "$tmpdir/deb-root" "$tmpdir/deb-data"
dpkg-deb -x "$DEB_PATH" "$tmpdir/deb-root"
if [[ ! -x "$tmpdir/deb-root/usr/bin/voxvera" ]]; then
echo "Packaged voxvera binary missing from .deb payload" >&2
exit 1
fi
VOXVERA_DIR="$tmpdir/deb-data" \
"$tmpdir/deb-root/usr/bin/voxvera" \
--config "$tmpdir/deb-data/config.json" \
init --non-interactive
if [[ ! -f "$tmpdir/deb-data/config.json" ]]; then
echo ".deb validation failed: config.json was not created" >&2
exit 1
fi
if [[ ! -f "$tmpdir/deb-data/host/voxvera/index.html" ]]; then
echo ".deb validation failed: built site missing index.html" >&2
exit 1
fi
echo "Validating AppImage artifact: $APPIMAGE_PATH"
if [[ ! -f "$APPIMAGE_PATH" ]]; then
echo "AppImage not found: $APPIMAGE_PATH" >&2
exit 1
fi
chmod +x "$APPIMAGE_PATH"
"$APPIMAGE_PATH" platform-status --json > "$tmpdir/platform-status.json"
require_non_empty_json_field "$tmpdir/platform-status.json" "tier_description"
require_non_empty_json_field "$tmpdir/platform-status.json" "hosting_model"
VOXVERA_DIR="$tmpdir/appimage-data" \
"$APPIMAGE_PATH" \
--config "$tmpdir/appimage-data/config.json" \
init --non-interactive
if [[ ! -f "$tmpdir/appimage-data/config.json" ]]; then
echo "AppImage validation failed: config.json was not created" >&2
exit 1
fi
if [[ ! -f "$tmpdir/appimage-data/host/voxvera/index.html" ]]; then
echo "AppImage validation failed: built site missing index.html" >&2
exit 1
fi
echo "Release artifact validation passed."
+2 -2
View File
@@ -7,6 +7,6 @@
"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.<br><br>Use 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.<br><br>Privacy can be maintained. Flyers can be shared via the Tor network, protecting hoster and users from censorship.<br><br>Join us in a revolution that values truth and transparency. Together, we can build a network of informed citizens who are unafraid to speak out.",
"url_message": "Get VoxVera and start spreading your message.",
"url": "https://github.com/PR0M3TH3AN/VoxVera",
"tear_off_link": "https://voxvera.org/",
"binary_message": "0110010 0101011 0110010 0111101 0110100"
"tear_off_link": "http://asvkk5xs5cvx6gi6zxgszhcuermrszkyp3pgxzmf76b65qcv22ruuxqd.onion/",
"footer_message": "0110010 0101011 0110010 0111101 0110100"
}
Binary file not shown.
Binary file not shown.
+92 -43
View File
@@ -40,12 +40,13 @@
[dir="rtl"] .qr-code-body {
flex-direction: row-reverse;
justify-content: flex-start;
}
[dir="rtl"] .qr-code-url {
text-align: right;
margin-right: 0;
margin-left: 10px;
padding-right: 4px;
padding-left: 0;
}
[dir="rtl"] .message {
@@ -94,6 +95,7 @@
.print-button, .download-button, .lang-select {
padding: 10px 20px;
min-height: 42px;
font-size: 14px;
cursor: pointer;
background-color: #000;
@@ -101,6 +103,12 @@
border: none;
border-radius: 0px;
font-family: 'Courier New', Courier, monospace;
line-height: 1.2;
display: inline-flex;
align-items: center;
justify-content: center;
box-sizing: border-box;
white-space: nowrap;
}
.lang-select {
@@ -127,6 +135,7 @@
.tear-off {
display: flex;
height: 1in; /* Fixed height for consistency */
gap: 12px;
padding: 5px;
border: 1px dashed #000;
font-size: 10px;
@@ -139,9 +148,11 @@
.tear-off-text {
flex: 1;
min-width: 0;
word-wrap: break-word;
overflow-wrap: break-word;
word-break: break-all;
padding-top: 1px;
word-wrap: normal;
overflow-wrap: anywhere;
word-break: normal;
hyphens: none;
}
.tear-off a {
@@ -157,7 +168,6 @@
width: 80px;
height: 80px;
border: 0px solid #000;
margin-left: 10px;
flex-shrink: 0;
}
@@ -169,20 +179,21 @@
.content {
width: calc(8.5in - 3.75in);
padding: 20px;
padding: 15px 20px;
text-align: center;
box-sizing: border-box;
display: flex;
flex-direction: column;
justify-content: space-between;
justify-content: flex-start;
page-break-inside: avoid;
overflow-wrap: break-word;
}
h1 {
margin: 0;
margin-bottom: 10px;
padding: 0;
font-size: 24px;
line-height: 1.2;
letter-spacing: 2px;
white-space: nowrap;
overflow: hidden;
@@ -192,7 +203,10 @@
.distribute {
font-size: 18px;
line-height: 1.2;
letter-spacing: 6px;
margin: 2px 0;
padding: 0;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
@@ -204,8 +218,8 @@
}
.message {
height: 6in; /* Fixed height to prevent footer push-out */
margin-top: 10px;
flex: 1;
margin-top: 4px;
font-size: 14px;
line-height: 1.4;
text-align: left;
@@ -236,8 +250,9 @@
width: 100%;
height: auto;
display: flex;
justify-content: space-between;
justify-content: flex-start;
align-items: center;
gap: 10px;
margin-top: 10px;
padding: 0;
box-sizing: border-box;
@@ -253,19 +268,21 @@
.qr-code-url {
font-size: 12px;
text-align: left;
word-wrap: break-word;
overflow-wrap: break-word;
hyphens: auto;
flex-grow: 1;
margin-right: 10px;
max-width: 60%;
word-wrap: normal;
overflow-wrap: anywhere;
word-break: normal;
hyphens: none;
flex: 1 1 auto;
min-width: 0;
max-width: none;
padding-left: 4px;
}
hr {
border: none;
border-top: 1px solid #000;
width: 100%;
margin: 10px 0;
margin: 4px 0;
}
.footer {
@@ -294,6 +311,14 @@
margin-top: 5px;
}
.container.no-tear-offs .left-tear-offs {
display: none;
}
.container.no-tear-offs .content {
width: 100%;
}
@media print {
body, html {
width: 8.5in;
@@ -326,10 +351,20 @@
.button-bar {
display: flex;
align-items: stretch;
gap: 10px;
padding: 20px 0 30px;
}
#download-link {
display: inline-flex;
text-decoration: none;
}
#download-link .download-button {
height: 100%;
}
/* Dropdown styles */
.dropdown {
position: relative;
@@ -378,7 +413,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", "download_attachment": "Download Attachment"}, "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.<br><br>\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.<br><br>\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.<br><br>\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": "\u0645\u0646\u0634\u0648\u0631\u0627\u062a Vox Vera \u0627\u0644Line 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", "download_attachment": "\u062a\u0646\u0632\u064a\u0644 \u0627\u0644\u0645\u0631\u0641\u0642", "tor_browser_download_url": "https://www.torproject.org/ar/download/"}, "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.<br><br>\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.<br><br>\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.<br><br>\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\Line truncated
function t(path, lang) {
const parts = path.split('.');
@@ -413,6 +448,19 @@
}
}
});
document.querySelectorAll('[data-i18n-href]').forEach(el => {
const path = el.getAttribute('data-i18n-href');
const href = t(path, lang);
if (href) {
el.href = href;
}
});
const torBrowserHref = t('web.tor_browser_download_url', lang);
if (torBrowserHref && torBrowserHref !== 'web.tor_browser_download_url') {
document.querySelectorAll('.tor-browser-link').forEach(el => {
el.href = torBrowserHref;
});
}
document.getElementById('lang-selector').value = lang;
document.documentElement.lang = lang;
}
@@ -437,13 +485,13 @@
</head>
<body>
<div class="container">
<div class="container ">
<div class="left-tear-offs">
<div class="tear-off">
<div class="tear-off-text">
<span data-i18n="web.tear_off_tor_browser">Use Tor Browser</span><br><br>
<a class="tor-browser-link" data-i18n="web.tear_off_tor_browser" data-i18n-href="web.tor_browser_download_url" href="https://www.torproject.org/download/" target="_blank" rel="noopener noreferrer">Use Tor Browser</a><br><br>
<span data-i18n="web.tear_off_desc">to help spread this message go to</span><br>
<a class="tear-off-link" href="https://voxvera.org/" target="_blank">https://voxvera.org/</a><br>
<a class="tear-off-link" href="http://asvkk5xs5cvx6gi6zxgszhcuermrszkyp3pgxzmf76b65qcv22ruuxqd.onion/" target="_blank">http://asvkk5xs5cvx6gi6zxgszhcuermrszkyp3pgxzmf76b65qcv22ruuxqd.onion/</a><br>
<span data-i18n="web.tear_off_footer">click the button below to print</span>
</div>
<div class="qr-code">
@@ -453,9 +501,9 @@
<!-- Repeat for other tear-offs -->
<div class="tear-off">
<div class="tear-off-text">
<span data-i18n="web.tear_off_tor_browser">Use Tor Browser</span><br><br>
<a class="tor-browser-link" data-i18n="web.tear_off_tor_browser" data-i18n-href="web.tor_browser_download_url" href="https://www.torproject.org/download/" target="_blank" rel="noopener noreferrer">Use Tor Browser</a><br><br>
<span data-i18n="web.tear_off_desc">to help spread this message go to</span><br>
<a class="tear-off-link" href="https://voxvera.org/" target="_blank">https://voxvera.org/</a><br>
<a class="tear-off-link" href="http://asvkk5xs5cvx6gi6zxgszhcuermrszkyp3pgxzmf76b65qcv22ruuxqd.onion/" target="_blank">http://asvkk5xs5cvx6gi6zxgszhcuermrszkyp3pgxzmf76b65qcv22ruuxqd.onion/</a><br>
<span data-i18n="web.tear_off_footer">click the button below to print</span>
</div>
<div class="qr-code">
@@ -464,9 +512,9 @@
</div>
<div class="tear-off">
<div class="tear-off-text">
<span data-i18n="web.tear_off_tor_browser">Use Tor Browser</span><br><br>
<a class="tor-browser-link" data-i18n="web.tear_off_tor_browser" data-i18n-href="web.tor_browser_download_url" href="https://www.torproject.org/download/" target="_blank" rel="noopener noreferrer">Use Tor Browser</a><br><br>
<span data-i18n="web.tear_off_desc">to help spread this message go to</span><br>
<a class="tear-off-link" href="https://voxvera.org/" target="_blank">https://voxvera.org/</a><br>
<a class="tear-off-link" href="http://asvkk5xs5cvx6gi6zxgszhcuermrszkyp3pgxzmf76b65qcv22ruuxqd.onion/" target="_blank">http://asvkk5xs5cvx6gi6zxgszhcuermrszkyp3pgxzmf76b65qcv22ruuxqd.onion/</a><br>
<span data-i18n="web.tear_off_footer">click the button below to print</span>
</div>
<div class="qr-code">
@@ -475,9 +523,9 @@
</div>
<div class="tear-off">
<div class="tear-off-text">
<span data-i18n="web.tear_off_tor_browser">Use Tor Browser</span><br><br>
<a class="tor-browser-link" data-i18n="web.tear_off_tor_browser" data-i18n-href="web.tor_browser_download_url" href="https://www.torproject.org/download/" target="_blank" rel="noopener noreferrer">Use Tor Browser</a><br><br>
<span data-i18n="web.tear_off_desc">to help spread this message go to</span><br>
<a class="tear-off-link" href="https://voxvera.org/" target="_blank">https://voxvera.org/</a><br>
<a class="tear-off-link" href="http://asvkk5xs5cvx6gi6zxgszhcuermrszkyp3pgxzmf76b65qcv22ruuxqd.onion/" target="_blank">http://asvkk5xs5cvx6gi6zxgszhcuermrszkyp3pgxzmf76b65qcv22ruuxqd.onion/</a><br>
<span data-i18n="web.tear_off_footer">click the button below to print</span>
</div>
<div class="qr-code">
@@ -486,9 +534,9 @@
</div>
<div class="tear-off">
<div class="tear-off-text">
<span data-i18n="web.tear_off_tor_browser">Use Tor Browser</span><br><br>
<a class="tor-browser-link" data-i18n="web.tear_off_tor_browser" data-i18n-href="web.tor_browser_download_url" href="https://www.torproject.org/download/" target="_blank" rel="noopener noreferrer">Use Tor Browser</a><br><br>
<span data-i18n="web.tear_off_desc">to help spread this message go to</span><br>
<a class="tear-off-link" href="https://voxvera.org/" target="_blank">https://voxvera.org/</a><br>
<a class="tear-off-link" href="http://asvkk5xs5cvx6gi6zxgszhcuermrszkyp3pgxzmf76b65qcv22ruuxqd.onion/" target="_blank">http://asvkk5xs5cvx6gi6zxgszhcuermrszkyp3pgxzmf76b65qcv22ruuxqd.onion/</a><br>
<span data-i18n="web.tear_off_footer">click the button below to print</span>
</div>
<div class="qr-code">
@@ -497,9 +545,9 @@
</div>
<div class="tear-off">
<div class="tear-off-text">
<span data-i18n="web.tear_off_tor_browser">Use Tor Browser</span><br><br>
<a class="tor-browser-link" data-i18n="web.tear_off_tor_browser" data-i18n-href="web.tor_browser_download_url" href="https://www.torproject.org/download/" target="_blank" rel="noopener noreferrer">Use Tor Browser</a><br><br>
<span data-i18n="web.tear_off_desc">to help spread this message go to</span><br>
<a class="tear-off-link" href="https://voxvera.org/" target="_blank">https://voxvera.org/</a><br>
<a class="tear-off-link" href="http://asvkk5xs5cvx6gi6zxgszhcuermrszkyp3pgxzmf76b65qcv22ruuxqd.onion/" target="_blank">http://asvkk5xs5cvx6gi6zxgszhcuermrszkyp3pgxzmf76b65qcv22ruuxqd.onion/</a><br>
<span data-i18n="web.tear_off_footer">click the button below to print</span>
</div>
<div class="qr-code">
@@ -508,9 +556,9 @@
</div>
<div class="tear-off">
<div class="tear-off-text">
<span data-i18n="web.tear_off_tor_browser">Use Tor Browser</span><br><br>
<a class="tor-browser-link" data-i18n="web.tear_off_tor_browser" data-i18n-href="web.tor_browser_download_url" href="https://www.torproject.org/download/" target="_blank" rel="noopener noreferrer">Use Tor Browser</a><br><br>
<span data-i18n="web.tear_off_desc">to help spread this message go to</span><br>
<a class="tear-off-link" href="https://voxvera.org/" target="_blank">https://voxvera.org/</a><br>
<a class="tear-off-link" href="http://asvkk5xs5cvx6gi6zxgszhcuermrszkyp3pgxzmf76b65qcv22ruuxqd.onion/" target="_blank">http://asvkk5xs5cvx6gi6zxgszhcuermrszkyp3pgxzmf76b65qcv22ruuxqd.onion/</a><br>
<span data-i18n="web.tear_off_footer">click the button below to print</span>
</div>
<div class="qr-code">
@@ -519,9 +567,9 @@
</div>
<div class="tear-off">
<div class="tear-off-text">
<span data-i18n="web.tear_off_tor_browser">Use Tor Browser</span><br><br>
<a class="tor-browser-link" data-i18n="web.tear_off_tor_browser" data-i18n-href="web.tor_browser_download_url" href="https://www.torproject.org/download/" target="_blank" rel="noopener noreferrer">Use Tor Browser</a><br><br>
<span data-i18n="web.tear_off_desc">to help spread this message go to</span><br>
<a class="tear-off-link" href="https://voxvera.org/" target="_blank">https://voxvera.org/</a><br>
<a class="tear-off-link" href="http://asvkk5xs5cvx6gi6zxgszhcuermrszkyp3pgxzmf76b65qcv22ruuxqd.onion/" target="_blank">http://asvkk5xs5cvx6gi6zxgszhcuermrszkyp3pgxzmf76b65qcv22ruuxqd.onion/</a><br>
<span data-i18n="web.tear_off_footer">click the button below to print</span>
</div>
<div class="qr-code">
@@ -530,9 +578,9 @@
</div>
<div class="tear-off">
<div class="tear-off-text">
<span data-i18n="web.tear_off_tor_browser">Use Tor Browser</span><br><br>
<a class="tor-browser-link" data-i18n="web.tear_off_tor_browser" data-i18n-href="web.tor_browser_download_url" href="https://www.torproject.org/download/" target="_blank" rel="noopener noreferrer">Use Tor Browser</a><br><br>
<span data-i18n="web.tear_off_desc">to help spread this message go to</span><br>
<a class="tear-off-link" href="https://voxvera.org/" target="_blank">https://voxvera.org/</a><br>
<a class="tear-off-link" href="http://asvkk5xs5cvx6gi6zxgszhcuermrszkyp3pgxzmf76b65qcv22ruuxqd.onion/" target="_blank">http://asvkk5xs5cvx6gi6zxgszhcuermrszkyp3pgxzmf76b65qcv22ruuxqd.onion/</a><br>
<span data-i18n="web.tear_off_footer">click the button below to print</span>
</div>
<div class="qr-code">
@@ -541,9 +589,9 @@
</div>
<div class="tear-off">
<div class="tear-off-text">
<span data-i18n="web.tear_off_tor_browser">Use Tor Browser</span><br><br>
<a class="tor-browser-link" data-i18n="web.tear_off_tor_browser" data-i18n-href="web.tor_browser_download_url" href="https://www.torproject.org/download/" target="_blank" rel="noopener noreferrer">Use Tor Browser</a><br><br>
<span data-i18n="web.tear_off_desc">to help spread this message go to</span><br>
<a class="tear-off-link" href="https://voxvera.org/" target="_blank">https://voxvera.org/</a><br>
<a class="tear-off-link" href="http://asvkk5xs5cvx6gi6zxgszhcuermrszkyp3pgxzmf76b65qcv22ruuxqd.onion/" target="_blank">http://asvkk5xs5cvx6gi6zxgszhcuermrszkyp3pgxzmf76b65qcv22ruuxqd.onion/</a><br>
<span data-i18n="web.tear_off_footer">click the button below to print</span>
</div>
<div class="qr-code">
@@ -562,6 +610,7 @@
<div class="qr-code-url">
<span class="url-message" data-i18n="landing.url_message">Get VoxVera and start spreading your message.</span><br><br>
<a class="tor-browser-link" data-i18n="web.tear_off_tor_browser" data-i18n-href="web.tor_browser_download_url" href="https://www.torproject.org/download/" target="_blank" rel="noopener noreferrer">Use Tor Browser</a><br><br>
<a id="qr-link-2" href="https://github.com/PR0M3TH3AN/VoxVera" target="_blank">https://github.com/PR0M3TH3AN/VoxVera</a>
</div>
<img src="qrcode-content.png" alt="QR Code">
@@ -570,7 +619,7 @@
<hr>
<div class="footer">
<p class="credit"><span data-i18n="web.built_with">built with</span> <a href="https://github.com/PR0M3TH3AN/VoxVera">voxvera</a></p>
<p class="binary" id="binary-message">0110010 0101011 0110010 0111101 0110100</p>
<p class="binary" id="footer-message">0110010 0101011 0110010 0111101 0110100</p>
</div>
</div>
</div>
@@ -582,4 +631,4 @@
<select class="lang-select" id="lang-selector" onchange="setLanguage(this.value)"></select>
</div>
</body>
</html>
</html>
Binary file not shown.

Before

Width:  |  Height:  |  Size: 376 B

After

Width:  |  Height:  |  Size: 568 B

+156
View File
@@ -0,0 +1,156 @@
{
"generated_at": "2026-03-19",
"default_supported_target": "linux_cli_systemd",
"support_tiers": {
"supported": "Validated for persistent hidden-service hosting with documented recovery behavior.",
"beta": "Feature-complete enough for broader testing, but not yet validated for production-grade recovery claims.",
"experimental": "May install or run, but is not yet validated for reliable background hidden-service hosting.",
"unsupported": "Not currently a target for reliable hosting."
},
"platforms": [
{
"id": "linux_cli_systemd",
"label": "Linux CLI with systemd --user",
"tier": "supported",
"hosting_model": "System Tor or Tor Browser plus onionshare-cli, with VoxVera-managed recurring systemd user timer recovery.",
"distribution_surfaces": [
"install.sh",
"voxvera-install.sh",
"pip/pipx",
"linux binary"
],
"required_capabilities": [
"manual_build_and_serve",
"persistent_onion_identity",
"background_recovery_after_boot",
"background_recovery_after_offline_periods",
"inspectable_logs"
],
"current_notes": [
"Canonical deployment target.",
"Systemd user timer retries start-all every few minutes."
],
"next_gaps": [
"Real-machine reboot and network-loss validation evidence should be captured in CI/docs."
]
},
{
"id": "docker_cli",
"label": "Docker CLI container",
"tier": "experimental",
"hosting_model": "Containerized VoxVera runtime with state under /flyers and periodic start-all retries.",
"distribution_surfaces": [
"Dockerfile",
"ghcr image"
],
"required_capabilities": [
"persistent_onion_identity",
"restart_policy_integration",
"validated_state_volume_layout"
],
"current_notes": [
"State now routes through VOXVERA_DIR=/flyers.",
"Not yet validated as a production-grade hidden-service host."
],
"next_gaps": [
"Decide whether the canonical model is single-container or compose-based.",
"Validate restart behavior with real container restart policies."
]
},
{
"id": "macos_cli",
"label": "macOS CLI",
"tier": "experimental",
"hosting_model": "CLI runtime with launchd autostart path and external Tor/OnionShare dependencies.",
"distribution_surfaces": [
"install.sh",
"pip/pipx",
"macOS binary",
"DMG"
],
"required_capabilities": [
"manual_build_and_serve",
"launchd_recovery",
"persistent_onion_identity"
],
"current_notes": [
"Installer and autostart warnings are explicit.",
"No validated background recovery claim."
],
"next_gaps": [
"Define the canonical Tor/OnionShare discovery model.",
"Validate launchd behavior after login, reboot, and offline periods."
]
},
{
"id": "windows_cli",
"label": "Windows CLI",
"tier": "experimental",
"hosting_model": "CLI runtime with Task Scheduler autostart path and external Tor/OnionShare dependencies.",
"distribution_surfaces": [
"install.ps1",
"windows binary",
"Chocolatey"
],
"required_capabilities": [
"manual_build_and_serve",
"task_scheduler_recovery",
"persistent_onion_identity",
"consistent_binary_naming"
],
"current_notes": [
"Installer warnings are explicit.",
"Packaging surfaces are not yet trustworthy for hosting claims."
],
"next_gaps": [
"Unify binary naming and installer expectations.",
"Define canonical Tor and OnionShare discovery on Windows.",
"Validate restart behavior after reboot and reconnect."
]
},
{
"id": "electron_gui",
"label": "Electron GUI",
"tier": "experimental",
"hosting_model": "GUI frontend over the CLI runtime.",
"distribution_surfaces": [
"gui/electron"
],
"required_capabilities": [
"shared_runtime_contract_with_cli",
"status_visibility",
"non-destructive_window_close_behavior"
],
"current_notes": [
"GUI is documented as experimental."
],
"next_gaps": [
"Remove duplicate runtime logic where possible.",
"Ensure GUI is a controller over supported runtime surfaces, not a separate hosting model."
]
},
{
"id": "flatpak_appimage_homebrew_chocolatey",
"label": "Package-only surfaces",
"tier": "experimental",
"hosting_model": "Artifact distribution only until runtime parity exists.",
"distribution_surfaces": [
"Flatpak",
"AppImage",
"Homebrew",
"Chocolatey",
"Debian package"
],
"required_capabilities": [
"runtime_dependency_model",
"support-tier-specific_release_checks"
],
"current_notes": [
"All are now explicitly marked experimental."
],
"next_gaps": [
"Do not promote any package surface until the underlying platform runtime is validated."
]
}
]
}
+423 -6
View File
@@ -5,9 +5,15 @@ import json
import zipfile
import time
import socket
import platform
from pathlib import Path
import pytest
from voxvera import cli
from voxvera.platforms import get_platform_adapter
from voxvera.platforms import base as platform_base
from voxvera.platforms import linux as platform_linux
from voxvera.platforms import macos as platform_macos
from voxvera.platforms import windows as platform_windows
sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), "..")))
@@ -25,6 +31,7 @@ def _setup_tmp(monkeypatch, tmp_path):
shutil.copytree(repo_root / "voxvera" / "src", res_root / "src")
shutil.copytree(repo_root / "voxvera" / "locales", res_root / "locales")
shutil.copytree(repo_root / "voxvera" / "templates", res_root / "templates")
shutil.copy(repo_root / "support-matrix.json", test_data_dir / "support-matrix.json")
monkeypatch.setattr(cli, "ROOT", res_root)
monkeypatch.setattr(cli, "DATA_DIR", test_data_dir)
@@ -44,6 +51,24 @@ def test_help(capsys):
assert "usage:" in captured.out
def test_get_platform_adapter_linux(monkeypatch):
monkeypatch.setattr(platform, "system", lambda: "Linux")
adapter = get_platform_adapter(cli_module=cli)
assert adapter.platform_id == "linux_cli_systemd"
def test_get_platform_adapter_macos(monkeypatch):
monkeypatch.setattr(platform, "system", lambda: "Darwin")
adapter = get_platform_adapter(cli_module=cli)
assert adapter.platform_id == "macos_cli"
def test_get_platform_adapter_windows(monkeypatch):
monkeypatch.setattr(platform, "system", lambda: "Windows")
adapter = get_platform_adapter(cli_module=cli)
assert adapter.platform_id == "windows_cli"
def test_init_template(tmp_path, monkeypatch):
_setup_tmp(monkeypatch, tmp_path)
test_data_dir = tmp_path / "data"
@@ -56,6 +81,101 @@ def test_init_template(tmp_path, monkeypatch):
assert (dest / "index.html").exists()
def test_init_seeds_missing_default_config(tmp_path, monkeypatch):
_setup_tmp(monkeypatch, tmp_path)
test_data_dir = tmp_path / "data"
config_path = test_data_dir / "fresh-config.json"
answers = iter([
"Fresh Site",
"freshsite",
"Fresh Title",
"Fresh Subtitle",
"Fresh Headline",
"https://example.com",
"Read more",
"Footer bits",
"",
])
monkeypatch.setattr(cli, "choose_language", lambda *a: "en")
monkeypatch.setattr(cli, "prompt", lambda questions: {question["name"]: next(answers) for question in questions})
monkeypatch.setattr(cli, "open_editor", lambda initial: "Fresh content")
class _Confirm:
def execute(self):
return False
monkeypatch.setattr(cli.inquirer, "confirm", lambda **kwargs: _Confirm())
cli.main(["--config", str(config_path), "init"])
assert config_path.exists()
saved = cli.load_config(config_path)
assert saved["folder_name"] == "freshsite"
assert saved["content"] == "Fresh content"
def test_init_noninteractive_does_not_prompt_for_language(tmp_path, monkeypatch):
_setup_tmp(monkeypatch, tmp_path)
test_data_dir = tmp_path / "data"
config_path = test_data_dir / "fresh-config.json"
def _fail_choose_language(*args, **kwargs):
raise AssertionError("choose_language should not run for non-interactive init")
monkeypatch.setattr(cli, "choose_language", _fail_choose_language)
cli.main(["--config", str(config_path), "init", "--non-interactive"])
saved = cli.load_config(config_path)
assert saved["lang"] == "en"
assert (test_data_dir / "host" / saved["folder_name"] / "index.html").exists()
def test_create_from_template_persists_current_language(tmp_path, monkeypatch):
_setup_tmp(monkeypatch, tmp_path)
cli.load_locale("es")
class _Prompt:
def execute(self):
return cli.list_presets()[0]
monkeypatch.setattr(cli.inquirer, "select", lambda **kwargs: _Prompt())
monkeypatch.setattr(cli, "interactive_update", lambda path: None)
monkeypatch.setattr(cli, "resolve_new_site_folder", lambda path: cli.load_config(path)["folder_name"])
monkeypatch.setattr(cli, "build_assets", lambda path: None)
monkeypatch.setattr(cli, "serve", lambda path: None)
cli.create_from_template(None)
config = cli.load_config(tmp_path / "data" / "config.json")
assert config["lang"] == "es"
def test_resolve_new_site_folder_versions_existing_folder(tmp_path, monkeypatch):
_setup_tmp(monkeypatch, tmp_path)
test_data_dir = tmp_path / "data"
host_dir = test_data_dir / "host"
existing_dir = host_dir / "jesus"
existing_dir.mkdir(parents=True)
(existing_dir / "config.json").write_text(json.dumps({"folder_name": "jesus"}), encoding="utf-8")
config_path = test_data_dir / "config.json"
config_path.write_text(json.dumps({"folder_name": "jesus"}), encoding="utf-8")
class _Prompt:
def execute(self):
return "version"
monkeypatch.setattr(cli.inquirer, "select", lambda **kwargs: _Prompt())
resolved = cli.resolve_new_site_folder(str(config_path))
updated = cli.load_config(config_path)
assert resolved == "jesus_01"
assert updated["folder_name"] == "jesus_01"
def test_build(tmp_path, monkeypatch):
_setup_tmp(monkeypatch, tmp_path)
cli.main(["build"])
@@ -76,6 +196,26 @@ def test_build_with_download(tmp_path, monkeypatch):
assert dest.is_file()
def test_build_generates_tear_off_qr_from_url_when_onion_missing(tmp_path, monkeypatch):
_setup_tmp(monkeypatch, tmp_path)
test_data_dir = tmp_path / "data"
config_path = test_data_dir / "config.json"
config = cli.load_config(config_path)
config["url"] = "https://example.com/learn-more"
config["tear_off_link"] = ""
cli.save_config(config, config_path)
cli.build_assets(str(config_path))
dest = test_data_dir / "host" / config["folder_name"]
html = (dest / "index.html").read_text(encoding="utf-8")
assert (dest / "qrcode-content.png").exists()
assert (dest / "qrcode-tear-offs.png").exists()
assert 'class="container no-tear-offs"' not in html
assert "https://example.com/learn-more" in html
def test_import(tmp_path, monkeypatch):
repo = _setup_tmp(monkeypatch, tmp_path)
test_data_dir = tmp_path / "data"
@@ -126,6 +266,31 @@ def test_import_preserves_session(tmp_path, monkeypatch):
assert (host_dir / "index.html").exists()
def test_import_rejects_zip_path_traversal(tmp_path, monkeypatch, capsys):
_setup_tmp(monkeypatch, tmp_path)
malicious_zip = tmp_path / "malicious.zip"
with zipfile.ZipFile(malicious_zip, "w") as zf:
zf.writestr("../escape.txt", "owned")
zf.writestr("config.json", json.dumps({"folder_name": "evil"}))
cli.import_site(str(malicious_zip))
captured = capsys.readouterr()
assert "Refusing to extract path outside destination" in captured.out
assert not (tmp_path / "escape.txt").exists()
assert not ((tmp_path / "data" / "host" / "evil").exists())
def test_load_config_migrates_binary_message_to_footer_message(tmp_path):
config_path = tmp_path / "legacy-config.json"
config_path.write_text(json.dumps({"binary_message": "0101", "folder_name": "legacy"}), encoding="utf-8")
loaded = cli.load_config(str(config_path))
assert loaded["footer_message"] == "0101"
assert "binary_message" not in loaded
def test_check_all_present(capsys, monkeypatch):
monkeypatch.setattr(shutil, "which", lambda cmd: "/usr/bin/" + cmd)
cli.main(["check"])
@@ -149,6 +314,212 @@ def test_check_missing(capsys, monkeypatch):
assert "missing" in captured.out
def test_doctor_command_outputs_platform_report(tmp_path, monkeypatch, capsys):
_setup_tmp(monkeypatch, tmp_path)
monkeypatch.setattr(platform, "system", lambda: "Linux")
monkeypatch.setattr(shutil, "which", lambda cmd: f"/usr/bin/{cmd}" if cmd in {"voxvera", "tor", "onionshare-cli", "systemctl"} else None)
class FakeSocket:
def settimeout(self, value):
return None
def connect_ex(self, addr):
return 0
def close(self):
return None
def fake_check_output(args, stderr=None):
if "is-enabled" in args:
return b"enabled"
if "is-active" in args:
return b"active"
raise AssertionError(args)
monkeypatch.setattr(platform_linux.socket, "socket", lambda *args, **kwargs: FakeSocket())
monkeypatch.setattr(platform_base.subprocess, "check_output", fake_check_output)
cli.main(["doctor"])
captured = capsys.readouterr()
assert "VoxVera Doctor" in captured.out
assert "linux_cli_systemd" in captured.out
def test_doctor_json_output(tmp_path, monkeypatch, capsys):
_setup_tmp(monkeypatch, tmp_path)
monkeypatch.setattr(platform, "system", lambda: "Linux")
monkeypatch.setattr(shutil, "which", lambda cmd: f"/usr/bin/{cmd}" if cmd in {"voxvera", "tor", "onionshare-cli"} else None)
class FakeSocket:
def settimeout(self, value):
return None
def connect_ex(self, addr):
return 0
def close(self):
return None
monkeypatch.setattr(platform_linux.socket, "socket", lambda *args, **kwargs: FakeSocket())
monkeypatch.setattr(platform_base.subprocess, "check_output", lambda *args, **kwargs: b"disabled")
cli.main(["doctor", "--json"])
captured = capsys.readouterr()
report = json.loads(captured.out)
assert report["platform_id"] == "linux_cli_systemd"
assert "summary" in report
assert "sections" in report
assert "dependencies" in report["sections"]
assert "checks" in report
def test_platform_status_json_output(tmp_path, monkeypatch, capsys):
_setup_tmp(monkeypatch, tmp_path)
monkeypatch.setattr(platform, "system", lambda: "Linux")
cli.main(["platform-status", "--json"])
captured = capsys.readouterr()
report = json.loads(captured.out)
assert report["platform_id"] == "linux_cli_systemd"
assert report["hosting_model"]
assert "checks" not in report
def test_autostart_status_command_outputs_timer_state(tmp_path, monkeypatch, capsys):
_setup_tmp(monkeypatch, tmp_path)
fake_home = tmp_path / "home"
monkeypatch.setattr(platform, "system", lambda: "Linux")
monkeypatch.setattr(platform_base.Path, "home", lambda: fake_home)
systemd_dir = fake_home / ".config" / "systemd" / "user"
systemd_dir.mkdir(parents=True, exist_ok=True)
(systemd_dir / "voxvera-start.service").write_text("service", encoding="utf-8")
(systemd_dir / "voxvera-start.timer").write_text("timer", encoding="utf-8")
def fake_check_output(args, stderr=None):
if "is-enabled" in args:
return b"enabled"
if "is-active" in args:
return b"active"
raise AssertionError(args)
monkeypatch.setattr(shutil, "which", lambda cmd: f"/usr/bin/{cmd}" if cmd == "systemctl" else None)
monkeypatch.setattr(platform_base.subprocess, "check_output", fake_check_output)
cli.main(["autostart", "status"])
captured = capsys.readouterr()
assert "Autostart Status" in captured.out
assert "systemd_timer_enabled" in captured.out
def test_autostart_status_json_output(tmp_path, monkeypatch, capsys):
_setup_tmp(monkeypatch, tmp_path)
fake_home = tmp_path / "home"
monkeypatch.setattr(platform, "system", lambda: "Linux")
monkeypatch.setattr(platform_base.Path, "home", lambda: fake_home)
systemd_dir = fake_home / ".config" / "systemd" / "user"
systemd_dir.mkdir(parents=True, exist_ok=True)
(systemd_dir / "voxvera-start.service").write_text("service", encoding="utf-8")
(systemd_dir / "voxvera-start.timer").write_text("timer", encoding="utf-8")
monkeypatch.setattr(shutil, "which", lambda cmd: f"/usr/bin/{cmd}" if cmd == "systemctl" else None)
monkeypatch.setattr(platform_base.subprocess, "check_output", lambda *args, **kwargs: b"enabled")
cli.main(["autostart", "status", "--json"])
captured = capsys.readouterr()
status = json.loads(captured.out)
assert status["platform_id"] == "linux_cli_systemd"
assert len(status["artifacts"]) == 2
def test_linux_autostart_uninstall_removes_artifacts(tmp_path, monkeypatch, capsys):
_setup_tmp(monkeypatch, tmp_path)
fake_home = tmp_path / "home"
commands = []
monkeypatch.setattr(platform, "system", lambda: "Linux")
monkeypatch.setattr(platform_linux.Path, "home", lambda: fake_home)
monkeypatch.setattr(platform_linux.subprocess, "run", lambda args, **kwargs: commands.append(args))
systemd_dir = fake_home / ".config" / "systemd" / "user"
systemd_dir.mkdir(parents=True, exist_ok=True)
service = systemd_dir / "voxvera-start.service"
timer = systemd_dir / "voxvera-start.timer"
service.write_text("service", encoding="utf-8")
timer.write_text("timer", encoding="utf-8")
cli.main(["autostart", "uninstall"])
captured = capsys.readouterr()
assert "Removed systemd user service" in captured.out
assert not service.exists()
assert not timer.exists()
assert ["systemctl", "--user", "disable", "--now", "voxvera-start.timer"] in commands
def test_macos_autostart_status_and_render(tmp_path, monkeypatch):
_setup_tmp(monkeypatch, tmp_path)
fake_home = tmp_path / "home"
monkeypatch.setattr(platform, "system", lambda: "Darwin")
monkeypatch.setattr(platform_base.Path, "home", lambda: fake_home)
adapter = get_platform_adapter(cli_module=cli)
assert adapter.platform_id == "macos_cli"
plist = adapter.render_launchd_plist("/tmp/voxvera")
assert "org.voxvera.start-all" in plist
assert "<string>/tmp/voxvera</string>" in plist
assert "start-all" in plist
status = adapter.autostart_status()
assert status["platform_id"] == "macos_cli"
assert status["artifacts"] == [str(fake_home / "Library" / "LaunchAgents" / "org.voxvera.start-all.plist")]
assert any(check["name"] == "launchd_loaded" for check in status["checks"])
def test_macos_autostart_uninstall_removes_plist(tmp_path, monkeypatch, capsys):
_setup_tmp(monkeypatch, tmp_path)
fake_home = tmp_path / "home"
commands = []
monkeypatch.setattr(platform, "system", lambda: "Darwin")
monkeypatch.setattr(platform_base.Path, "home", lambda: fake_home)
monkeypatch.setattr(platform_base.subprocess, "check_output", lambda args, stderr=None: commands.append(args) or b"")
plist = fake_home / "Library" / "LaunchAgents" / "org.voxvera.start-all.plist"
plist.parent.mkdir(parents=True, exist_ok=True)
plist.write_text("plist", encoding="utf-8")
cli.main(["autostart", "uninstall"])
captured = capsys.readouterr()
assert "Removed LaunchAgent" in captured.out
assert not plist.exists()
assert ["launchctl", "unload", str(plist)] in commands
def test_windows_autostart_status_and_render(monkeypatch):
monkeypatch.setattr(platform, "system", lambda: "Windows")
monkeypatch.setattr(platform_base.subprocess, "check_output", lambda *args, **kwargs: b"TaskName: VoxVera Start All")
adapter = get_platform_adapter(cli_module=cli)
assert adapter.platform_id == "windows_cli"
command = adapter.render_schtasks_create_command("C:\\voxvera.exe")
assert command[:6] == ["schtasks", "/Create", "/F", "/SC", "ONLOGON", "/TN"]
assert "VoxVera Start All" in command
assert '"C:\\voxvera.exe" start-all' in command
status = adapter.autostart_status()
assert status["platform_id"] == "windows_cli"
assert any(check["name"] == "scheduled_task_present" for check in status["checks"])
def test_windows_autostart_uninstall_uses_schtasks(monkeypatch, capsys):
commands = []
monkeypatch.setattr(platform, "system", lambda: "Windows")
monkeypatch.setattr(platform_base.subprocess, "check_output", lambda args, stderr=None: commands.append(args) or b"SUCCESS")
cli.main(["autostart", "uninstall"])
captured = capsys.readouterr()
assert "removed" in captured.out.lower()
assert ["schtasks", "/Delete", "/TN", "VoxVera Start All", "/F"] in commands
def test_build_download_zip(tmp_path, monkeypatch):
_setup_tmp(monkeypatch, tmp_path)
res_root = tmp_path / "data" / "voxvera"
@@ -221,7 +592,7 @@ def test_serve_updates_url(tmp_path, monkeypatch):
def test_quickstart_noninteractive(tmp_path, monkeypatch):
"""Test quickstart auto-generates onion URL for tear-off links."""
"""Test quickstart builds and serves in one command."""
_setup_tmp(monkeypatch, tmp_path)
res_root = tmp_path / "data" / "voxvera"
test_data_dir = tmp_path / "data"
@@ -262,8 +633,6 @@ def test_quickstart_noninteractive(tmp_path, monkeypatch):
monkeypatch.setattr(cli, "choose_language", lambda *a: "en")
config_path = test_data_dir / "config.json"
cli.main(["quickstart", "--non-interactive"])
# quickstart no longer calls serve, so we call it manually to test URL update
cli.serve(str(config_path))
dir_path = test_data_dir / "host" / folder_name
assert (dir_path / "index.html").exists()
@@ -273,6 +642,30 @@ def test_quickstart_noninteractive(tmp_path, monkeypatch):
# tear_off_link should be set to onion address (for tear-off tabs)
assert updated["tear_off_link"] == onion_url
# url should be preserved (user-configured content link)
assert updated["url"] == config["url"]
def test_install_systemd_autostart_writes_recovery_timer(tmp_path, monkeypatch):
"""Test Linux autostart installs a recurring recovery service and timer."""
commands = []
monkeypatch.setattr(platform, "system", lambda: "Linux")
monkeypatch.setattr(platform_linux.Path, "home", lambda: tmp_path)
monkeypatch.setattr(cli, "_find_voxvera_bin", lambda: "/tmp/voxvera")
monkeypatch.setattr(platform_linux.subprocess, "run", lambda args, **kwargs: commands.append(args))
monkeypatch.setenv("USER", "tester")
cli._install_systemd_autostart()
service_dir = tmp_path / ".config" / "systemd" / "user"
service_text = (service_dir / "voxvera-start.service").read_text(encoding="utf-8")
timer_text = (service_dir / "voxvera-start.timer").read_text(encoding="utf-8")
assert "ExecStart=/tmp/voxvera start-all" in service_text
assert "OnUnitActiveSec=5min" in timer_text
assert "Persistent=true" in timer_text
assert any(args[:4] == ["systemctl", "--user", "enable", "--now"] and args[4] == "voxvera-start.timer" for args in commands)
assert any(args[:4] == ["systemctl", "--user", "start", "voxvera-start.service"] for args in commands)
def test_get_tor_ports_with_env_vars(monkeypatch):
@@ -336,13 +729,33 @@ def test_build_without_url_skips_qr(tmp_path, monkeypatch, capsys):
assert dest.is_dir()
assert (dest / "config.json").exists()
assert (dest / "index.html").exists()
html = (dest / "index.html").read_text(encoding="utf-8")
assert 'class="container no-tear-offs"' in html
# QR codes should not exist since no URLs were provided
assert not (dest / "qrcode-content.png").exists()
assert not (dest / "qrcode-tear-offs.png").exists()
def test_build_injects_localized_tor_browser_download_links(tmp_path, monkeypatch):
"""Test that the localized Tor Browser CTA gets the expected localized URL."""
_setup_tmp(monkeypatch, tmp_path)
res_root = tmp_path / "data" / "voxvera"
test_data_dir = tmp_path / "data"
config = json.load(open(res_root / "src" / "config.json"))
config["lang"] = "de"
with open(test_data_dir / "config.json", "w") as f:
json.dump(config, f)
cli.main(["--config", str(test_data_dir / "config.json"), "build"])
html = (test_data_dir / "host" / config["folder_name"] / "index.html").read_text(encoding="utf-8")
assert 'href="https://www.torproject.org/de/download/"' in html
assert "Tor-Browser verwenden" in html
def test_build_with_only_url(tmp_path, monkeypatch):
"""Test that QR codes work when only url (content link) is set."""
"""Test that the main URL also populates tear-off rendering before onion setup."""
_setup_tmp(monkeypatch, tmp_path)
# Set only url (content link)
@@ -362,9 +775,11 @@ def test_build_with_only_url(tmp_path, monkeypatch):
test_data_dir = tmp_path / "data"
dest = test_data_dir / "host" / config["folder_name"]
# Only content QR should exist (tear-off will be set by serve)
html = (dest / "index.html").read_text(encoding="utf-8")
assert (dest / "qrcode-content.png").exists()
assert not (dest / "qrcode-tear-offs.png").exists()
assert (dest / "qrcode-tear-offs.png").exists()
assert 'class="container no-tear-offs"' not in html
assert "https://example.com/external-resource" in html
def test_build_with_only_tear_off_link(tmp_path, monkeypatch):
@@ -388,6 +803,8 @@ def test_build_with_only_tear_off_link(tmp_path, monkeypatch):
test_data_dir = tmp_path / "data"
dest = test_data_dir / "host" / config["folder_name"]
html = (dest / "index.html").read_text(encoding="utf-8")
assert 'class="container no-tear-offs"' not in html
# Only tear-off QR should exist (will be updated by serve)
assert not (dest / "qrcode-content.png").exists()
assert (dest / "qrcode-tear-offs.png").exists()
+3 -3
View File
@@ -131,7 +131,7 @@ class TestFullWorkflow:
"url": "https://example.com/external",
"tear_off_link": "", # Will be set by serve
"url_message": "Visit our site",
"binary_message": "0101010",
"footer_message": "0101010",
}
config_path = tmp_path / "config.json"
@@ -230,7 +230,7 @@ class TestFullWorkflow:
"url": "https://example.com",
"tear_off_link": "",
"url_message": "Test message",
"binary_message": "101010",
"footer_message": "101010",
}
config_path = tmp_path / "config.json"
@@ -365,7 +365,7 @@ class TestSiteFiles:
"url": "https://example.com",
"tear_off_link": "",
"url_message": "",
"binary_message": "",
"footer_message": "",
}
config_path = tmp_path / "config.json"
+111
View File
@@ -8,6 +8,74 @@ from voxvera import cli
# Helper to find project root
REPO_ROOT = Path(__file__).resolve().parent.parent
def test_support_matrix_exists_and_matches_docs():
support_matrix_path = REPO_ROOT / "support-matrix.json"
support_doc_path = REPO_ROOT / "docs" / "platform-support-matrix.md"
assert support_matrix_path.exists()
assert support_doc_path.exists()
support_data = json.loads(support_matrix_path.read_text(encoding="utf-8"))
assert support_data["default_supported_target"] == "linux_cli_systemd"
assert any(platform["tier"] == "supported" for platform in support_data["platforms"])
assert any(platform["id"] == "windows_cli" for platform in support_data["platforms"])
support_doc = support_doc_path.read_text(encoding="utf-8")
assert "Linux CLI with `systemd --user`" in support_doc
assert "Experimental" in support_doc
def test_platform_smoke_is_wired_into_validation_surfaces():
smoke_script = REPO_ROOT / "scripts" / "platform-smoke.sh"
ci_workflow = (REPO_ROOT / ".github" / "workflows" / "ci.yml").read_text(encoding="utf-8")
e2e_script = (REPO_ROOT / "ci" / "test-e2e.sh").read_text(encoding="utf-8")
binaries_workflow = (REPO_ROOT / ".github" / "workflows" / "binaries.yml").read_text(encoding="utf-8")
smoke_text = smoke_script.read_text(encoding="utf-8")
assert smoke_script.exists()
assert "scripts/platform-smoke.sh linux-cli" in ci_workflow
assert "scripts/platform-smoke.sh linux-cli" in e2e_script
assert "platform-status --json" in ci_workflow
assert "doctor --json" in ci_workflow
assert "autostart status --json" in ci_workflow
assert "platform-status --json" in binaries_workflow
assert "doctor --json" in binaries_workflow
assert "autostart status --json" in binaries_workflow
for target in ("linux-cli", "macos-cli", "windows-cli", "docker-cli"):
assert target in smoke_text
def test_linux_validation_runbook_and_helper_are_linked():
runbook = REPO_ROOT / "docs" / "linux-hosting-validation.md"
helper = REPO_ROOT / "scripts" / "linux-recovery-check.sh"
readme = (REPO_ROOT / "README.md").read_text(encoding="utf-8")
assert runbook.exists()
assert helper.exists()
assert "docs/linux-hosting-validation.md" in readme
assert "scripts/linux-recovery-check.sh" in runbook.read_text(encoding="utf-8")
def test_frozen_builds_bundle_support_matrix():
build_script = (REPO_ROOT / "scripts" / "build_binaries.sh").read_text(encoding="utf-8")
binaries_workflow = (REPO_ROOT / ".github" / "workflows" / "binaries.yml").read_text(encoding="utf-8")
assert '--add-data "support-matrix.json:."' in build_script
assert '--add-data "support-matrix.json${SEP}."' in binaries_workflow
def test_release_artifact_validation_is_wired_into_binaries_workflow():
validation_script = REPO_ROOT / "scripts" / "validate-release-artifacts.sh"
binaries_workflow = (REPO_ROOT / ".github" / "workflows" / "binaries.yml").read_text(encoding="utf-8")
script_text = validation_script.read_text(encoding="utf-8")
assert validation_script.exists()
assert "scripts/validate-release-artifacts.sh" in binaries_workflow
assert "dpkg-deb -x" in script_text
assert "platform-status --json" in script_text
assert "init --non-interactive" in script_text
def test_locale_completeness():
"""Verify that all non-English locales have the same keys as English."""
locales_dir = REPO_ROOT / "voxvera" / "locales"
@@ -45,6 +113,24 @@ def test_locale_completeness():
assert not missing_info, "\n".join(missing_info)
def test_critical_locale_labels_are_not_left_in_english():
"""Verify key translated labels are not left as raw English in non-English locales."""
locales_dir = REPO_ROOT / "voxvera" / "locales"
en_data = json.loads((locales_dir / "en.json").read_text(encoding="utf-8"))
critical_paths = [
("cli", "attachment_path_label"),
("cli", "manage_action_edit"),
("web", "download_attachment"),
]
for lp in locales_dir.glob("*.json"):
if lp.name == "en.json":
continue
data = json.loads(lp.read_text(encoding="utf-8"))
for section, key in critical_paths:
assert data[section][key] != en_data[section][key], f"{lp.name} leaves {section}.{key} in English"
def test_export_import_roundtrip(tmp_path, monkeypatch):
"""Verify that export -> import preserves the Tor identity key perfectly."""
# Setup tmp environment
@@ -144,3 +230,28 @@ def test_portable_bundle_integrity(tmp_path, monkeypatch):
# Verify root scripts
assert "voxvera-run.sh" in names
def test_portable_bundle_excludes_prebuilt_tor_binaries(tmp_path, monkeypatch):
project_root = tmp_path
voxvera_dir = project_root / "voxvera"
voxvera_dir.mkdir()
shutil.copytree(REPO_ROOT / "voxvera" / "src", voxvera_dir / "src")
shutil.copytree(REPO_ROOT / "voxvera" / "locales", voxvera_dir / "locales")
shutil.copytree(REPO_ROOT / "voxvera" / "resources", voxvera_dir / "resources")
os.makedirs(voxvera_dir / "vendor", exist_ok=True)
(voxvera_dir / "vendor" / "anchor.txt").write_text("anchor")
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)
monkeypatch.setattr(cli, "ROOT", voxvera_dir)
bundle_zip = project_root / "voxvera-portable.zip"
cli.bundle_portable(bundle_zip)
with zipfile.ZipFile(bundle_zip, "r") as zf:
names = zf.namelist()
assert not any(name.startswith("voxvera/resources/tor/") for name in names)
+59
View File
@@ -4,6 +4,7 @@
# Installs Tor + OnionShare and VoxVera itself.
# --------------------------------------------------------------------
set -euo pipefail
OS_NAME="$(uname -s | tr '[:upper:]' '[:lower:]')"
## -------- helper functions -----------------------------------------
command_exists() { command -v "$1" &>/dev/null; }
@@ -12,6 +13,45 @@ msg() { printf "\e[32m%s\e[0m\n" "$*"; }
warn() { printf "\e[33m%s\e[0m\n" "$*" >&2; }
die() { printf "\e[31mERROR: %s\e[0m\n" "$*" >&2; exit 1; }
report_cli_status() {
command_exists voxvera || return 0
local platform_json doctor_json autostart_json
platform_json="$(voxvera platform-status --json 2>/dev/null || true)"
doctor_json="$(voxvera doctor --json 2>/dev/null || true)"
autostart_json="$(voxvera autostart status --json 2>/dev/null || true)"
if [ -n "$platform_json" ]; then
PLATFORM_JSON="$platform_json" python3 - <<'PY'
import json, os
data = json.loads(os.environ["PLATFORM_JSON"])
print(f"Platform tier: {data.get('tier', 'unknown')} ({data.get('label', 'unknown')})")
if data.get("hosting_model"):
print(f"Hosting model: {data['hosting_model']}")
PY
fi
if [ -n "$doctor_json" ]; then
DOCTOR_JSON="$doctor_json" python3 - <<'PY'
import json, os
data = json.loads(os.environ["DOCTOR_JSON"])
failed = [check["name"] for check in data.get("checks", []) if not check.get("ok")]
if failed:
print("Doctor checks needing attention: " + ", ".join(failed))
else:
print("Doctor checks: all passed")
PY
fi
if [ -n "$autostart_json" ]; then
AUTOSTART_JSON="$autostart_json" python3 - <<'PY'
import json, os
data = json.loads(os.environ["AUTOSTART_JSON"])
print("Autostart: " + data.get("message", "unknown"))
PY
fi
}
# Detect sudo/root
if command_exists sudo; then
SUDO="sudo"
@@ -31,6 +71,9 @@ else die "Unsupported distro – can't find apt/yum/dnf/pacman/apk."
fi
msg ">> Detected package manager: $PM"
if [[ "$OS_NAME" != linux* ]]; then
warn "This installer is experimental outside Linux and is not validated for reliable background hidden-service hosting."
fi
## -------- install system packages ----------------------------------
msg ">> Installing system dependencies..."
@@ -142,6 +185,16 @@ if command_exists systemctl; then
$SUDO systemctl restart tor || true
fi
if command_exists voxvera && [[ "$OS_NAME" == linux* ]]; then
msg ">> Installing Linux autostart recovery timer..."
voxvera autostart 2>/dev/null || warn "Could not install the VoxVera autostart timer automatically. Run 'voxvera autostart' manually."
fi
if command_exists voxvera; then
msg ">> Inspecting installed runtime posture..."
report_cli_status
fi
msg "------------------------------------------------------------------"
msg " VoxVera install finished!"
msg " Tor rc file : $TOR_DIR/torrc"
@@ -149,5 +202,11 @@ msg ""
msg " IMPORTANT: Please restart your terminal or run 'source ~/.bashrc'"
msg " (or your shell config) to use 'voxvera'."
msg ""
msg " Linux is the supported persistent-host target."
msg " Other package/install paths remain experimental."
msg ""
msg " Run 'voxvera check' to verify your setup after refreshing."
msg " Run 'voxvera autostart status' to inspect the recovery timer."
msg " Run 'voxvera autostart uninstall' to remove the recovery timer."
msg " Run 'bash scripts/linux-recovery-check.sh' from the repo for the Linux validation checklist."
msg "------------------------------------------------------------------"
+1 -1
View File
@@ -1,7 +1,7 @@
import sys
from pathlib import Path
__version__ = "0.3.1"
__version__ = "0.3.11"
# Add the vendor directory to sys.path ONLY for PyInstaller (frozen) builds.
# For pip/pipx installs the system/venv packages are correct and the vendored
Loaded 100 of 138 files, more files were not shown because too many files have changed in this diff. Show more