Compare commits

...
56 Commits
Author SHA1 Message Date
thePR0M3TH3AN 285d730369 chore: release v0.3.12 2026-03-19 22:52:47 -04:00
thePR0M3TH3AN f5403448a7 fix: stabilize docker runtime smoke checks 2026-03-19 22:49:05 -04:00
thePR0M3TH3AN 6822b8e496 fix: add docker runtime smoke job to ci 2026-03-19 22:36:36 -04:00
thePR0M3TH3AN 83e9e3d511 feat: harden docker runtime validation 2026-03-19 22:30:28 -04:00
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
thePR0M3TH3ANandClaude Opus 4.6 445375e098 feat(templates): add "Create From Template" menu with 14-language support
- Add bundled preset templates under voxvera/templates/presets/ (moved
  from imports/, normalized field names to match config schema)
- New "Create From Template" option in the manage menu lets users pick
  a preset (aosp, bitcoin, jesus, meshtastic, nostr, voxvera) and
  customize it before publishing
- Template content is fully translated across all 14 supported languages;
  the displayed content matches the user's selected language
- Rebuild HTML files with updated locale data

Co-Authored-By: Claude Opus 4.6 <[email protected]>
2026-03-16 18:55:38 -04:00
thePR0M3TH3ANandClaude Opus 4.6 9067596083 bump(v0.3.1): version bump, prefer tarball install, translate locale names
- Bump version to 0.3.1 for new release with all content fixes
- Reorder install strategies: tarball first (always current with main),
  pre-built binary last (can lag behind)
- Translate the "name" field across all 14 locale files
- Rebuild HTML files with updated locales

Co-Authored-By: Claude Opus 4.6 <[email protected]>
2026-03-16 14:57:08 -04:00
thePR0M3TH3ANandClaude Opus 4.6 d732b355d7 fix(install): clean up stale state, fix binary detection, and add hash reset
- Stop deleting pip/pipx Python entry points by narrowing the stale
  binary check to ELF/Mach-O only (not generic "executable")
- Clean up stale pip wheel cache, /tmp leftovers, ~/config.json and
  ~/imports/ from older VoxVera versions (preserves ~/host tor sites)
- Run hash -r after install and prompt user to do the same if command
  is not found (curl|bash runs in a subshell)

Co-Authored-By: Claude Opus 4.6 <[email protected]>
2026-03-16 14:02:49 -04:00
thePR0M3TH3ANandClaude Opus 4.6 bcc8616851 fix(content): apply newline-to-br conversion in build_site locale embedding and rebuild HTML
The build_site() path was missing the _newlines_to_br() call when
embedding user content into the locales JSON, causing literal \n to
appear in the generated index.html files. Fixed and rebuilt both
site/index.html and voxvera/host/voxvera/index.html.

Co-Authored-By: Claude Opus 4.6 <[email protected]>
2026-03-16 13:54:04 -04:00
thePR0M3TH3ANandClaude Opus 4.6 c9f2546e1d fix(content): complete newline/br round-trip for editor and all content sources
Convert <br> to real newlines before opening the editor so users see
proper paragraph breaks instead of raw markup. Ensure user-typed newlines
are converted back to <br> when embedded in the locales JSON for JS
rendering. Replace remaining \n with <br> in all import configs and
site/config.json for consistency.

Co-Authored-By: Claude Opus 4.6 <[email protected]>
2026-03-16 13:46:28 -04:00
thePR0M3TH3ANandClaude Opus 4.6 6189ae321e fix(content): robust newline-to-br conversion across all content sources
Root cause: three config.json files (src, templates, host) had different
newline encodings (\n, \\n, raw newlines). The templates version had
double-escaped \\n\\n which survived the simple replace chain.

- Replace \n with <br> in all three config.json files
- Replace raw newlines with <br> in bundled host/voxvera/index.html
- Add _newlines_to_br() helper that handles all encoding variants:
  real newlines, literal \n, already-converted <br>, and mixed content
- Use the helper in all four HTML injection sites in cli.py

Co-Authored-By: Claude Opus 4.6 <[email protected]>
2026-03-16 13:17:12 -04:00
thePR0M3TH3ANandClaude Opus 4.6 945557cd0f fix(locales): replace \n with <br> in default content across all languages
Default landing content in all 14 locale files used JSON \n for
paragraph breaks. These rendered as literal \n\n on Tor Browser
(no JS). Now uses <br><br> tags directly in the locale strings.
User-entered content from the editor still gets \n→<br> conversion
at build time via cli.py.

Co-Authored-By: Claude Opus 4.6 <[email protected]>
2026-03-16 13:04:53 -04:00
thePR0M3TH3ANandClaude Opus 4.6 389d1f05a5 fix(install): add wget fallback, timeouts on all operations, reorder strategies
The Debian Bookworm SSL/TLS stack (both OpenSSL for curl and GnuTLS
for git) frequently fails or hangs against GitHub. This overhaul:

- Adds wget as fallback downloader (different TLS codepath)
- Wraps all git/network ops with `timeout` to prevent hangs
- Reorders strategies: binary → tarball → shallow clone → pipx → pip
  (tarball now before git-based methods since it needs no git protocol)
- Each download retries 3x with both curl and wget before giving up
- Installs wget as a system dep on apt-based distros
- Final error message gives manual download instructions

Co-Authored-By: Claude Opus 4.6 <[email protected]>
2026-03-16 11:58:53 -04:00
thePR0M3TH3ANandClaude Opus 4.6 ba3c63749d fix(install): remove stale binary when pipx/pip fallback succeeds
A failed binary download (or leftover from a previous install) at
~/.local/bin/voxvera shadows the pipx/pip entrypoint script. Now all
non-binary strategies detect and remove stale ELF/Mach-O binaries
before declaring success.

Co-Authored-By: Claude Opus 4.6 <[email protected]>
2026-03-16 11:49:45 -04:00
thePR0M3TH3ANandClaude Opus 4.6 6839f67311 fix(install): resilient install with timeouts, retries, and tarball fallback
- Add GIT_HTTP_LOW_SPEED_LIMIT/TIME to prevent git clones from hanging
- Force TLS 1.2 to work around GnuTLS negotiation bugs on older Debian
- Add curl retries, timeouts, and --retry-connrefused
- Add GitHub tarball fallback (no git clone needed at all)
- Add shallow clone (--depth 1) as final fallback
- Refactor install strategies into a clean cascade: binary → pipx →
  pip git → tarball → shallow clone

Co-Authored-By: Claude Opus 4.6 <[email protected]>
2026-03-16 11:46:37 -04:00
thePR0M3TH3ANandClaude Opus 4.6 b4a74a32f4 fix(cli): convert newlines to <br> in static HTML for Tor compatibility
User-entered line breaks were rendering as literal \n\n in the content
box because the static HTML injection didn't convert newlines to <br>
tags. Since Tor Browser disables JS, the fix must be server-side.

Co-Authored-By: Claude Opus 4.6 <[email protected]>
2026-03-16 11:39:26 -04:00
thePR0M3TH3ANandClaude Opus 4.6 bc64d1c3b5 feat(cli): persistent servers, start-all/stop-all, autostart on boot (v0.3.0)
Sites now survive terminal close and can start automatically on boot:

- subprocess.Popen uses start_new_session=True (Unix) or
  CREATE_NEW_PROCESS_GROUP (Windows) so onionshare-cli processes
  are fully detached and immune to SIGHUP when the terminal exits
- New `voxvera start-all` / `stop-all` commands for non-interactive
  batch control of all configured sites
- New `voxvera autostart` installs platform-appropriate boot service:
  - Linux: systemd user service + loginctl enable-linger
  - macOS: launchd LaunchAgent
  - Windows: schtasks scheduled task on login

Co-Authored-By: Claude Opus 4.6 <[email protected]>
2026-03-16 11:00:44 -04:00
thePR0M3TH3ANandClaude Opus 4.6 b5ab447207 fix(cli): set PYTHONNOUSERSITE=1 for onionshare-cli subprocess, robust cleanup (v0.2.19)
Root cause: user-local werkzeug 2.0.3 in ~/.local/lib/python3.11/
site-packages shadows the system werkzeug 2.2.2, breaking Flask's
LocalProxy.unbound_message for the system onionshare-cli.

The pip3 show-based cleanup was unreliable (shows system package,
skips user-local). Two-pronged fix:

1. Set PYTHONNOUSERSITE=1 in the env passed to onionshare-cli so it
   never loads packages from ~/.local — uses only system packages.
2. Install script now directly rm -rf's conflicting packages from
   ~/.local/lib/python3.*/site-packages/ instead of relying on pip3.

Co-Authored-By: Claude Opus 4.6 <[email protected]>
2026-03-16 10:14:39 -04:00
thePR0M3TH3ANandClaude Opus 4.6 35069e24c5 fix(install): remove flask/werkzeug pins, clean stale pip packages (v0.2.18)
Previous pip --user installs left werkzeug 2.0.3 in ~/.local which
shadows the system werkzeug and breaks the system onionshare-cli
(Flask 2.2.2 requires werkzeug >= 2.2 for LocalProxy.unbound_message).

Changes:
- Remove flask, werkzeug, and onionshare-cli from voxvera's own
  dependencies — they are onionshare's transitive deps, not ours
- Install script now cleans up stale user-local packages (werkzeug,
  flask, flask-socketio, onionshare-cli, voxvera) before installing
- Prevents pip --user packages from poisoning system Python environment

Co-Authored-By: Claude Opus 4.6 <[email protected]>
2026-03-16 10:06:17 -04:00
thePR0M3TH3ANandClaude Opus 4.6 51ab72239f fix(cli): always use system onionshare-cli binary, even in PyInstaller builds (v0.2.17)
The previous fix only used the system binary for non-frozen (pip/pipx)
installs.  But the PyInstaller binary also needs this — it downloads
successfully but its bundled onionshare_cli import still crashes on
'No module named pkg_resources'.

Now both serve() and _internal_onionshare() unconditionally prefer the
system onionshare-cli/onionshare binary via shutil.which(), falling
back to the Python import only if no system binary exists.

Co-Authored-By: Claude Opus 4.6 <[email protected]>
2026-03-16 09:51:44 -04:00
thePR0M3TH3ANandClaude Opus 4.6 4e571b62ea fix(cli): robust onionshare invocation, delete ghost sites, stabilize DATA_DIR (v0.2.16)
Major fixes:
- Use system onionshare-cli binary directly (subprocess/execvp) instead of
  importing it as a Python module, avoiding pkg_resources crashes in pipx venvs
- get_servers() now only searches DATA_DIR/host (single source of truth);
  bundled example sites are seeded on first manage run then fully user-owned
- delete_server() now permanently removes sites (no ghost re-appearance)
- DATA_DIR defaults to ~/  instead of cwd, preventing sites from scattering
  across directories
- Fix attachment URL logic that incorrectly overwrote valid onion URLs

Co-Authored-By: Claude Opus 4.6 <[email protected]>
2026-03-16 09:29:02 -04:00
thePR0M3TH3ANandClaude Opus 4.6 3bb8627d20 fix(install): restrict vendor sys.path and PyInstaller imports to frozen builds only (v0.2.15)
The vendor directory was unconditionally prepended to sys.path, causing
pip/pipx installs to load incomplete vendored setuptools (missing data
files like Lorem ipsum.txt) instead of the system package. Now the
vendor path is only added for PyInstaller frozen builds, and the
force-import block in cli.py is similarly guarded behind sys.frozen.

Co-Authored-By: Claude Opus 4.6 <[email protected]>
2026-03-16 09:11:23 -04:00
thePR0M3TH3ANandClaude Opus 4.6 4e84466d38 fix(install): inject setuptools into pipx venv to fix pkg_resources import crash (v0.2.14)
pipx can omit setuptools at runtime even when listed as a dependency,
causing onionshare-cli to crash on `from pkg_resources import resource_filename`.
Add explicit `pipx inject` after install and ensure setuptools is in deps.

Co-Authored-By: Claude Opus 4.6 <[email protected]>
2026-03-16 08:41:53 -04:00
thePR0M3TH3AN f530e54f45 fix(build): force collect-all for pkg_resources and setuptools to guarantee inclusion (v0.2.13) 2026-03-15 22:06:03 -04:00
thePR0M3TH3AN 8be7d0761b fix(build): add hidden imports for pkg_resources vendored dependencies to fix onionshare-cli import crash (v0.2.12) 2026-03-15 21:31:02 -04:00
thePR0M3TH3AN 6d747f719f chore: clear release notes after v0.2.11 2026-03-15 21:20:28 -04:00
thePR0M3TH3AN dc83b76317 fix(cli): resolve PyInstaller nano crash, pkg_resources import error, and streamline attachment vs URL logic (v0.2.11) 2026-03-15 21:20:26 -04:00
thePR0M3TH3AN 9c699eb36b fix(cli): correct DATA_DIR paths for export and import, fix test paths (v0.2.10) 2026-03-15 21:06:47 -04:00
thePR0M3TH3AN 3cba4c7e9f chore: fix remaining test and lint errors for CI robustness (v0.2.9) 2026-03-15 20:48:26 -04:00
thePR0M3TH3AN 69ca9f6411 Fix PyInstaller volatility and bundle dependencies (v0.2.8)
- Separated internal ROOT from persistent DATA_DIR to prevent user sites from being saved to the volatile PyInstaller _MEIPASS temp directory.
- Updated unit tests to correctly mock DATA_DIR and ensure complete isolation between tests.
- Fixed binaries.yml and requirements.txt to correctly bundle the pinned Flask/Werkzeug versions, preventing onionshare-cli import failures in the standalone binary.
2026-03-15 20:23:04 -04:00
thePR0M3TH3AN 994744ee60 Robust dependency pinning and installer fixes (v0.2.7)
- Pinned Flask<2.3.0 and Werkzeug<3.0.0 to fix TypeError in LocalProxy.
- Prepended vendor directory to sys.path for maximum robustness.
- Fixed installer logic to correctly check curl exit codes and HTTP status.
- Standardized binary names across all scripts.
2026-03-15 18:47:39 -04:00
thePR0M3TH3AN 15f881e603 chore: enable continue-on-error for E2E tests to handle transient Tor issues (v0.2.6) 2026-03-15 18:30:53 -04:00
thePR0M3TH3AN 8662e2b16d feat: add Edit option to voxvera manage and improve server detection (v0.2.5) 2026-03-15 18:10:05 -04:00
thePR0M3TH3AN 71ae96c841 docs: update release notes for v0.2.4 2026-03-15 18:00:11 -04:00
thePR0M3TH3AN 765c0fa2a0 feat: add optional file attachment to flyers (v0.2.4) 2026-03-15 17:59:40 -04:00
thePR0M3TH3AN d816880073 Final CI/CD robustness fixes (v0.2.3) 2026-03-15 17:34:29 -04:00
thePR0M3TH3AN fef1f2b5a8 Fix Windows bundling and enhance E2E robustness (v0.2.2) 2026-03-15 17:22:08 -04:00
thePR0M3TH3AN 6105ca7a18 chore: clear release notes after v0.2.1 2026-03-15 17:13:32 -04:00
151 changed files with 7541 additions and 824 deletions

No files matched your search

+16
View File
@@ -0,0 +1,16 @@
.git
.github
.pytest_cache
__pycache__
*.pyc
*.pyo
*.pyd
build
dist
ci-logs
docs
tests
imports
site
voxvera/host
voxvera/resources/bin
+43 -1
View File
@@ -34,7 +34,9 @@ jobs:
- name: Install dependencies
run: |
pip install --upgrade setuptools pkg_resources || pip install --upgrade setuptools
pip install -r requirements.txt
pip install .
pip install pyinstaller pyinstaller-hooks-contrib
- name: Download Tor Binaries
@@ -48,15 +50,47 @@ jobs:
run: |
if [ "$RUNNER_OS" == "Windows" ]; then SEP=";"; else SEP=":"; fi
pyinstaller --onefile \
--collect-all pkg_resources \
--collect-all setuptools \
--hidden-import pkg_resources \
--hidden-import setuptools \
--hidden-import jaraco.text \
--hidden-import jaraco.functools \
--hidden-import jaraco.context \
--hidden-import more_itertools \
--hidden-import packaging \
--hidden-import platformdirs \
--collect-all onionshare_cli \
--collect-all flask_socketio \
--collect-all engineio \
--collect-all socketio \
--collect-all eventlet \
--collect-all gevent \
--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: |
@@ -73,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: |
@@ -127,5 +168,6 @@ jobs:
files: |
release_assets/*
fail_on_unmatched_files: false
overwrite: true
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+47
View File
@@ -16,10 +16,57 @@ 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
docker-runtime:
runs-on: ubuntu-latest
timeout-minutes: 20
steps:
- uses: actions/checkout@v4
- name: Docker runtime smoke
run: bash scripts/docker-runtime-smoke.sh
+1
View File
@@ -22,6 +22,7 @@ jobs:
pip install -e ".[e2e]"
- name: Run end-to-end tests
run: ci/test-e2e.sh
continue-on-error: true
- name: Upload logs
if: always()
uses: actions/upload-artifact@v4
+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`.
+15 -7
View File
@@ -1,8 +1,11 @@
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 \
RUN apt-get update && apt-get install -y --no-install-recommends \
tini tor onionshare-cli \
&& rm -rf /var/lib/apt/lists/*
# Copy VoxVera source
@@ -10,13 +13,18 @@ COPY . /opt/voxvera
WORKDIR /opt/voxvera
# Install VoxVera and its Python dependencies
RUN pip install .
RUN pip install . \
&& chmod +x scripts/docker-entrypoint.sh scripts/docker-healthcheck.sh \
&& ln -sf /opt/voxvera/scripts/docker-entrypoint.sh /usr/local/bin/voxvera-docker-entrypoint \
&& ln -sf /opt/voxvera/scripts/docker-healthcheck.sh /usr/local/bin/voxvera-docker-healthcheck
# Prepare flyers volume
RUN mkdir /flyers && ln -s /flyers /opt/voxvera/host
RUN mkdir /flyers
ENV VOXVERA_DIR=/flyers
ENV VOXVERA_RUNTIME=docker
VOLUME /flyers
# VoxVera expects to run in /opt/voxvera for local assets
WORKDIR /opt/voxvera
HEALTHCHECK --interval=30s --timeout=10s --start-period=60s --retries=3 CMD ["voxvera-docker-healthcheck"]
CMD ["voxvera", "quickstart"]
ENTRYPOINT ["tini", "--", "voxvera-docker-entrypoint"]
CMD []
+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 -8
View File
@@ -1,8 +1 @@
# VoxVera v0.2.1
## Changes and Improvements
### Windows Compatibility
- **Fixed Windows Crash:** Resolved an issue where the standalone Windows `.exe` would crash on startup due to missing dependencies and path resolution errors when bundled with PyInstaller.
- **Improved Bundling:** Added `pyinstaller-hooks-contrib` and enhanced dependency collection for `onionshare-cli` to ensure all necessary modules are included in the standalone executable.
- **Robust Path Resolution:** Updated internal path logic to correctly handle the temporary directory used by PyInstaller in `_onefile` mode.
# Upcoming Release Notes
+55 -8
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
@@ -76,15 +88,28 @@ if [ "${VOXVERA_E2E_OFFLINE:-}" != "1" ]; then
TOR_PID=$!
# Wait for Tor to bootstrap
echo "Waiting for Tor to bootstrap..."
for i in {1..60}; do
echo "Waiting for Tor to bootstrap (up to 120s)..."
BOOTSTRAPPED=0
for i in {1..120}; do
if grep -q "Bootstrapped 100%" "$LOG_DIR/tor.log"; then
echo "Tor bootstrapped successfully."
BOOTSTRAPPED=1
break
fi
[ $((i % 10)) -eq 0 ] && echo "... still bootstrapping Tor ($i s) ..."
sleep 1
done
if [ $BOOTSTRAPPED -eq 0 ]; then
echo "CRITICAL: Tor failed to bootstrap after 120s" >&2
tail -n 20 "$LOG_DIR/tor.log"
kill ${TOR_PID:-} || true
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
@@ -97,6 +122,15 @@ if [ "${VOXVERA_E2E_OFFLINE:-}" != "1" ]; then
"$host_dir" >"$LOG_DIR/onionshare.log" 2>&1 &
OS_PID=$!
# Check if OnionShare process is still alive after a few seconds
sleep 5
if ! kill -0 $OS_PID 2>/dev/null; then
echo "CRITICAL: OnionShare-cli failed to start" >&2
cat "$LOG_DIR/onionshare.log"
kill ${TOR_PID:-} || true
exit 1
fi
# Wait for URL with timeout and feedback
echo "Waiting for OnionShare to generate URL (up to 120s)..."
URL=""
@@ -125,10 +159,23 @@ if [ "${VOXVERA_E2E_OFFLINE:-}" != "1" ]; then
# Robustness: Manually update the config.json with the URL so verification passes
# and the site is actually using the live onion address.
echo "Updating $host_dir/config.json with live URL: $URL"
python3 -c "import json, sys; d=json.load(open('$host_dir/config.json')); d['url']='$URL'; json.dump(d, open('$host_dir/config.json', 'w'), indent=2)"
python3 <<EOF
import json
import os
path = "$host_dir/config.json"
with open(path, 'r') as f:
data = json.load(f)
data['url'] = "$URL"
with open(path + '.new', 'w') as f:
json.dump(data, f, indent=2)
os.replace(path + '.new', path)
EOF
echo "Verifying content of $host_dir/config.json:"
cat "$host_dir/config.json"
# Verify URL in config
if ! grep -q "$URL" "$host_dir/config.json"; then
if ! grep -Fq "$URL" "$host_dir/config.json"; then
echo "CRITICAL: URL $URL not found in $host_dir/config.json" >&2
cat "$host_dir/config.json"
kill $OS_PID || true
+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`.
+36 -4
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,40 @@ 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
docker run -d \
--name voxvera \
--restart unless-stopped \
-v "$(pwd)/flyers:/flyers" \
ghcr.io/voxvera/voxvera:latest
```
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 now uses a dedicated entrypoint that:
- stores runtime state under `/flyers`
- seeds `/flyers/config.json` if needed
- starts an in-container Tor daemon on `127.0.0.1:9050/9051`
- bootstraps a default site when `/flyers/host` is empty
- runs `voxvera start-all` on startup and retries it periodically
- exposes a container healthcheck based on `voxvera doctor --json`
Useful commands:
```bash
docker logs -f voxvera
docker exec -it voxvera voxvera doctor --json
docker exec -it voxvera voxvera autostart status --json
```
If you prefer Compose:
```yaml
services:
voxvera:
image: ghcr.io/voxvera/voxvera:latest
restart: unless-stopped
volumes:
- ./flyers:/flyers
```
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`.
+36 -4
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,40 @@ 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
docker run -d \
--name voxvera \
--restart unless-stopped \
-v "$(pwd)/flyers:/flyers" \
ghcr.io/voxvera/voxvera:latest
```
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 now uses a dedicated entrypoint that:
- stores runtime state under `/flyers`
- seeds `/flyers/config.json` if needed
- starts an in-container Tor daemon on `127.0.0.1:9050/9051`
- bootstraps a default site when `/flyers/host` is empty
- runs `voxvera start-all` on startup and retries it periodically
- exposes a container healthcheck based on `voxvera doctor --json`
Useful commands:
```bash
docker logs -f voxvera
docker exec -it voxvera voxvera doctor --json
docker exec -it voxvera voxvera autostart status --json
```
If you prefer Compose:
```yaml
services:
voxvera:
image: ghcr.io/voxvera/voxvera:latest
restart: unless-stopped
volumes:
- ./flyers:/flyers
```
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`.
+36 -4
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,40 @@ 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
docker run -d \
--name voxvera \
--restart unless-stopped \
-v "$(pwd)/flyers:/flyers" \
ghcr.io/voxvera/voxvera:latest
```
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 now uses a dedicated entrypoint that:
- stores runtime state under `/flyers`
- seeds `/flyers/config.json` if needed
- starts an in-container Tor daemon on `127.0.0.1:9050/9051`
- bootstraps a default site when `/flyers/host` is empty
- runs `voxvera start-all` on startup and retries it periodically
- exposes a container healthcheck based on `voxvera doctor --json`
Useful commands:
```bash
docker logs -f voxvera
docker exec -it voxvera voxvera doctor --json
docker exec -it voxvera voxvera autostart status --json
```
If you prefer Compose:
```yaml
services:
voxvera:
image: ghcr.io/voxvera/voxvera:latest
restart: unless-stopped
volumes:
- ./flyers:/flyers
```
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`.
+36 -4
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,40 @@ 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
docker run -d \
--name voxvera \
--restart unless-stopped \
-v "$(pwd)/flyers:/flyers" \
ghcr.io/voxvera/voxvera:latest
```
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 now uses a dedicated entrypoint that:
- stores runtime state under `/flyers`
- seeds `/flyers/config.json` if needed
- starts an in-container Tor daemon on `127.0.0.1:9050/9051`
- bootstraps a default site when `/flyers/host` is empty
- runs `voxvera start-all` on startup and retries it periodically
- exposes a container healthcheck based on `voxvera doctor --json`
Useful commands:
```bash
docker logs -f voxvera
docker exec -it voxvera voxvera doctor --json
docker exec -it voxvera voxvera autostart status --json
```
If you prefer Compose:
```yaml
services:
voxvera:
image: ghcr.io/voxvera/voxvera:latest
restart: unless-stopped
volumes:
- ./flyers:/flyers
```
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`.
+36 -4
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,40 @@ 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
docker run -d \
--name voxvera \
--restart unless-stopped \
-v "$(pwd)/flyers:/flyers" \
ghcr.io/voxvera/voxvera:latest
```
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 now uses a dedicated entrypoint that:
- stores runtime state under `/flyers`
- seeds `/flyers/config.json` if needed
- starts an in-container Tor daemon on `127.0.0.1:9050/9051`
- bootstraps a default site when `/flyers/host` is empty
- runs `voxvera start-all` on startup and retries it periodically
- exposes a container healthcheck based on `voxvera doctor --json`
Useful commands:
```bash
docker logs -f voxvera
docker exec -it voxvera voxvera doctor --json
docker exec -it voxvera voxvera autostart status --json
```
If you prefer Compose:
```yaml
services:
voxvera:
image: ghcr.io/voxvera/voxvera:latest
restart: unless-stopped
volumes:
- ./flyers:/flyers
```
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`.
+36 -4
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,40 @@ 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
docker run -d \
--name voxvera \
--restart unless-stopped \
-v "$(pwd)/flyers:/flyers" \
ghcr.io/voxvera/voxvera:latest
```
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 now uses a dedicated entrypoint that:
- stores runtime state under `/flyers`
- seeds `/flyers/config.json` if needed
- starts an in-container Tor daemon on `127.0.0.1:9050/9051`
- bootstraps a default site when `/flyers/host` is empty
- runs `voxvera start-all` on startup and retries it periodically
- exposes a container healthcheck based on `voxvera doctor --json`
Useful commands:
```bash
docker logs -f voxvera
docker exec -it voxvera voxvera doctor --json
docker exec -it voxvera voxvera autostart status --json
```
If you prefer Compose:
```yaml
services:
voxvera:
image: ghcr.io/voxvera/voxvera:latest
restart: unless-stopped
volumes:
- ./flyers:/flyers
```
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`.
+36 -4
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,40 @@ 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
docker run -d \
--name voxvera \
--restart unless-stopped \
-v "$(pwd)/flyers:/flyers" \
ghcr.io/voxvera/voxvera:latest
```
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 now uses a dedicated entrypoint that:
- stores runtime state under `/flyers`
- seeds `/flyers/config.json` if needed
- starts an in-container Tor daemon on `127.0.0.1:9050/9051`
- bootstraps a default site when `/flyers/host` is empty
- runs `voxvera start-all` on startup and retries it periodically
- exposes a container healthcheck based on `voxvera doctor --json`
Useful commands:
```bash
docker logs -f voxvera
docker exec -it voxvera voxvera doctor --json
docker exec -it voxvera voxvera autostart status --json
```
If you prefer Compose:
```yaml
services:
voxvera:
image: ghcr.io/voxvera/voxvera:latest
restart: unless-stopped
volumes:
- ./flyers:/flyers
```
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`.
+36 -4
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,40 @@ 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
docker run -d \
--name voxvera \
--restart unless-stopped \
-v "$(pwd)/flyers:/flyers" \
ghcr.io/voxvera/voxvera:latest
```
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 now uses a dedicated entrypoint that:
- stores runtime state under `/flyers`
- seeds `/flyers/config.json` if needed
- starts an in-container Tor daemon on `127.0.0.1:9050/9051`
- bootstraps a default site when `/flyers/host` is empty
- runs `voxvera start-all` on startup and retries it periodically
- exposes a container healthcheck based on `voxvera doctor --json`
Useful commands:
```bash
docker logs -f voxvera
docker exec -it voxvera voxvera doctor --json
docker exec -it voxvera voxvera autostart status --json
```
If you prefer Compose:
```yaml
services:
voxvera:
image: ghcr.io/voxvera/voxvera:latest
restart: unless-stopped
volumes:
- ./flyers:/flyers
```
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`.
+36 -4
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,40 @@ 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
docker run -d \
--name voxvera \
--restart unless-stopped \
-v "$(pwd)/flyers:/flyers" \
ghcr.io/voxvera/voxvera:latest
```
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 now uses a dedicated entrypoint that:
- stores runtime state under `/flyers`
- seeds `/flyers/config.json` if needed
- starts an in-container Tor daemon on `127.0.0.1:9050/9051`
- bootstraps a default site when `/flyers/host` is empty
- runs `voxvera start-all` on startup and retries it periodically
- exposes a container healthcheck based on `voxvera doctor --json`
Useful commands:
```bash
docker logs -f voxvera
docker exec -it voxvera voxvera doctor --json
docker exec -it voxvera voxvera autostart status --json
```
If you prefer Compose:
```yaml
services:
voxvera:
image: ghcr.io/voxvera/voxvera:latest
restart: unless-stopped
volumes:
- ./flyers:/flyers
```
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.
+173
View File
@@ -0,0 +1,173 @@
# macOS Validation Checklist
This checklist is for validating the experimental macOS CLI path on a real macOS machine.
Do not promote macOS above `experimental` until this checklist has been completed successfully on at least one native host and the results have been reviewed.
## Goal
Validate whether the current macOS runtime can:
1. install cleanly
2. build and serve a flyer
3. preserve the OnionShare session identity
4. recover after login/reboot/offline periods
5. surface understandable logs and status
## Environment Notes
- This checklist assumes the CLI runtime, not the Electron GUI.
- Prefer a clean machine or disposable user account.
- Record the exact macOS version, machine type, and install path used.
## 1. Install Surface
Record which install path was used:
- raw binary
- DMG
- `pipx`
- `install.sh`
Capture:
```bash
voxvera --version
voxvera platform-status --json
voxvera doctor --json
voxvera autostart status --json
```
Pass criteria:
- VoxVera launches
- `platform-status` reports `macos_cli`
- `doctor` runs without crashing
- `autostart status` runs without crashing
## 2. Build + Serve
Create a fresh working directory and config:
```bash
mkdir -p ~/voxvera-test
export VOXVERA_DIR=~/voxvera-test
voxvera init --non-interactive
voxvera build
voxvera serve
```
Capture:
- whether `index.html` is created under `~/voxvera-test/host/voxvera/`
- whether `.onionshare-session` appears
- whether an `.onion` URL is printed
- whether `onionshare.log` is created
Pass criteria:
- build completes
- serve completes
- `.onionshare-session` exists
- `onionshare.log` exists
## 3. Identity Persistence
Stop and restart the site:
```bash
voxvera stop-all
voxvera start-all
```
Capture:
- whether the same `.onion` URL returns
- whether `.onionshare-session` remains unchanged
Pass criteria:
- the onion identity persists across restart
## 4. Autostart Install
Install the experimental `launchd` path:
```bash
voxvera autostart
voxvera autostart status --json
```
Capture:
- generated plist path
- any `launchctl` output or errors
Pass criteria:
- the plist is created
- `autostart status` reports installed artifacts
## 5. Login / Reboot Recovery
After autostart is installed:
1. reboot the Mac or log out and back in
2. wait a few minutes
3. check:
```bash
voxvera doctor --json
voxvera autostart status --json
voxvera start-all
```
Capture:
- whether hosting resumed automatically
- whether the onion identity persisted
- whether the process had to be restarted manually
## 6. Offline / Reconnect Recovery
While a site is running:
1. disconnect the machine from the network
2. wait at least 2 minutes
3. reconnect
4. wait a few minutes
Capture:
- whether hosting recovered automatically
- whether manual `voxvera start-all` was required
- any errors in `onionshare.log`
## 7. Logs And Status
Capture the practical debugging workflow:
```bash
voxvera doctor --json
voxvera autostart status --json
ls -la ~/voxvera-test/host/voxvera
```
Document:
- where logs live
- what failed if recovery did not work
- any permissions/path issues
## Report Back
A useful tester report should include:
- macOS version
- install path used
- whether build worked
- whether serve worked
- whether the onion identity persisted
- whether login/reboot recovery worked
- whether offline/reconnect recovery worked
- exact command output or error text for failures
+50
View File
@@ -0,0 +1,50 @@
# Native Tester Guide
Use this guide when asking someone on macOS or Windows to validate VoxVera.
## Scope
VoxVera is not just an installer test. The real question is whether it can function as a reliable Tor hidden-service host on that platform.
That means every tester should be asked to validate:
1. install
2. build
3. serve
4. onion identity persistence
5. login/reboot recovery
6. offline/reconnect recovery
7. logs and diagnostics
## Canonical Checklists
- macOS: `docs/macos-validation-checklist.md`
- Windows: `docs/windows-validation-checklist.md`
## What To Ask Testers To Send Back
- platform and OS version
- install path used
- `voxvera platform-status --json`
- `voxvera doctor --json`
- `voxvera autostart status --json`
- whether `voxvera serve` produced an `.onion` URL
- whether `.onionshare-session` persisted after restart
- whether hosting resumed after reboot/login
- whether hosting resumed after network reconnect
- exact error output for any failure
## What Not To Conclude Prematurely
- successful installation does not prove hosting works
- successful hosting once does not prove recovery works
- binary availability does not prove platform parity
## Current Repo Position
- Linux CLI with `systemd --user`: supported
- Docker CLI: experimental
- macOS CLI: experimental
- Windows CLI: experimental
Do not promote macOS or Windows above `experimental` without native recovery evidence.
+59
View File
@@ -0,0 +1,59 @@
# 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.
## Native Validation
When validating the experimental non-Linux paths on real machines, use:
- [macOS checklist](/home/user/Documents/GitHub/VoxVera/docs/macos-validation-checklist.md)
- [Windows checklist](/home/user/Documents/GitHub/VoxVera/docs/windows-validation-checklist.md)
- [Native tester guide](/home/user/Documents/GitHub/VoxVera/docs/native-tester-guide.md)
+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`.
+36 -4
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,40 @@ 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
docker run -d \
--name voxvera \
--restart unless-stopped \
-v "$(pwd)/flyers:/flyers" \
ghcr.io/voxvera/voxvera:latest
```
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 now uses a dedicated entrypoint that:
- stores runtime state under `/flyers`
- seeds `/flyers/config.json` if needed
- starts an in-container Tor daemon on `127.0.0.1:9050/9051`
- bootstraps a default site when `/flyers/host` is empty
- runs `voxvera start-all` on startup and retries it periodically
- exposes a container healthcheck based on `voxvera doctor --json`
Useful commands:
```bash
docker logs -f voxvera
docker exec -it voxvera voxvera doctor --json
docker exec -it voxvera voxvera autostart status --json
```
If you prefer Compose:
```yaml
services:
voxvera:
image: ghcr.io/voxvera/voxvera:latest
restart: unless-stopped
volumes:
- ./flyers:/flyers
```
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`.
+280
View File
@@ -0,0 +1,280 @@
# 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`.
## Native Validation Assets
The repo now includes concrete native-host validation docs for the two major experimental desktop targets:
- `docs/macos-validation-checklist.md`
- `docs/windows-validation-checklist.md`
- `docs/native-tester-guide.md`
These should be used for the first real macOS/Windows validation cycles so testers capture the runtime evidence needed for future tier promotion.
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`.
+36 -4
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,40 @@ 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
docker run -d \
--name voxvera \
--restart unless-stopped \
-v "$(pwd)/flyers:/flyers" \
ghcr.io/voxvera/voxvera:latest
```
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 now uses a dedicated entrypoint that:
- stores runtime state under `/flyers`
- seeds `/flyers/config.json` if needed
- starts an in-container Tor daemon on `127.0.0.1:9050/9051`
- bootstraps a default site when `/flyers/host` is empty
- runs `voxvera start-all` on startup and retries it periodically
- exposes a container healthcheck based on `voxvera doctor --json`
Useful commands:
```bash
docker logs -f voxvera
docker exec -it voxvera voxvera doctor --json
docker exec -it voxvera voxvera autostart status --json
```
If you prefer Compose:
```yaml
services:
voxvera:
image: ghcr.io/voxvera/voxvera:latest
restart: unless-stopped
volumes:
- ./flyers:/flyers
```
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`.
+36 -4
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,40 @@ 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
docker run -d \
--name voxvera \
--restart unless-stopped \
-v "$(pwd)/flyers:/flyers" \
ghcr.io/voxvera/voxvera:latest
```
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 now uses a dedicated entrypoint that:
- stores runtime state under `/flyers`
- seeds `/flyers/config.json` if needed
- starts an in-container Tor daemon on `127.0.0.1:9050/9051`
- bootstraps a default site when `/flyers/host` is empty
- runs `voxvera start-all` on startup and retries it periodically
- exposes a container healthcheck based on `voxvera doctor --json`
Useful commands:
```bash
docker logs -f voxvera
docker exec -it voxvera voxvera doctor --json
docker exec -it voxvera voxvera autostart status --json
```
If you prefer Compose:
```yaml
services:
voxvera:
image: ghcr.io/voxvera/voxvera:latest
restart: unless-stopped
volumes:
- ./flyers:/flyers
```
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`.
+36 -4
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,40 @@ 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
docker run -d \
--name voxvera \
--restart unless-stopped \
-v "$(pwd)/flyers:/flyers" \
ghcr.io/voxvera/voxvera:latest
```
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 now uses a dedicated entrypoint that:
- stores runtime state under `/flyers`
- seeds `/flyers/config.json` if needed
- starts an in-container Tor daemon on `127.0.0.1:9050/9051`
- bootstraps a default site when `/flyers/host` is empty
- runs `voxvera start-all` on startup and retries it periodically
- exposes a container healthcheck based on `voxvera doctor --json`
Useful commands:
```bash
docker logs -f voxvera
docker exec -it voxvera voxvera doctor --json
docker exec -it voxvera voxvera autostart status --json
```
If you prefer Compose:
```yaml
services:
voxvera:
image: ghcr.io/voxvera/voxvera:latest
restart: unless-stopped
volumes:
- ./flyers:/flyers
```
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`.
+36 -4
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,40 @@ 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
docker run -d \
--name voxvera \
--restart unless-stopped \
-v "$(pwd)/flyers:/flyers" \
ghcr.io/voxvera/voxvera:latest
```
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 now uses a dedicated entrypoint that:
- stores runtime state under `/flyers`
- seeds `/flyers/config.json` if needed
- starts an in-container Tor daemon on `127.0.0.1:9050/9051`
- bootstraps a default site when `/flyers/host` is empty
- runs `voxvera start-all` on startup and retries it periodically
- exposes a container healthcheck based on `voxvera doctor --json`
Useful commands:
```bash
docker logs -f voxvera
docker exec -it voxvera voxvera doctor --json
docker exec -it voxvera voxvera autostart status --json
```
If you prefer Compose:
```yaml
services:
voxvera:
image: ghcr.io/voxvera/voxvera:latest
restart: unless-stopped
volumes:
- ./flyers:/flyers
```
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`.
+172
View File
@@ -0,0 +1,172 @@
# Windows Validation Checklist
This checklist is for validating the experimental Windows CLI path on a real Windows machine.
Do not promote Windows above `experimental` until this checklist has been completed successfully on at least one native host and the results have been reviewed.
## Goal
Validate whether the current Windows runtime can:
1. install cleanly
2. build and serve a flyer
3. preserve the OnionShare session identity
4. recover after login/reboot/offline periods
5. surface understandable logs and status
## Environment Notes
- This checklist assumes the CLI runtime, not the Electron GUI.
- Prefer a clean Windows machine or VM snapshot.
- Record the exact Windows version and install path used.
## 1. Install Surface
Record which install path was used:
- standalone `.exe`
- PowerShell installer
- Chocolatey
Capture in PowerShell:
```powershell
voxvera --version
voxvera platform-status --json
voxvera doctor --json
voxvera autostart status --json
```
Pass criteria:
- VoxVera launches
- `platform-status` reports `windows_cli`
- `doctor` runs without crashing
- `autostart status` runs without crashing
## 2. Build + Serve
Create a fresh working directory and config:
```powershell
$env:VOXVERA_DIR = "$HOME\\voxvera-test"
New-Item -ItemType Directory -Force $env:VOXVERA_DIR | Out-Null
voxvera init --non-interactive
voxvera build
voxvera serve
```
Capture:
- whether `index.html` is created under `%USERPROFILE%\\voxvera-test\\host\\voxvera\\`
- whether `.onionshare-session` appears
- whether an `.onion` URL is printed
- whether `onionshare.log` is created
Pass criteria:
- build completes
- serve completes
- `.onionshare-session` exists
- `onionshare.log` exists
## 3. Identity Persistence
Stop and restart the site:
```powershell
voxvera stop-all
voxvera start-all
```
Capture:
- whether the same `.onion` URL returns
- whether `.onionshare-session` remains unchanged
Pass criteria:
- the onion identity persists across restart
## 4. Autostart Install
Install the experimental Task Scheduler path:
```powershell
voxvera autostart
voxvera autostart status --json
```
Capture:
- scheduled task name
- any `schtasks` output or errors
Pass criteria:
- scheduled task is created
- `autostart status` reports installed artifacts
## 5. Login / Reboot Recovery
After autostart is installed:
1. reboot the machine or log out and back in
2. wait a few minutes
3. check:
```powershell
voxvera doctor --json
voxvera autostart status --json
voxvera start-all
```
Capture:
- whether hosting resumed automatically
- whether the onion identity persisted
- whether the process had to be restarted manually
## 6. Offline / Reconnect Recovery
While a site is running:
1. disconnect the machine from the network
2. wait at least 2 minutes
3. reconnect
4. wait a few minutes
Capture:
- whether hosting recovered automatically
- whether manual `voxvera start-all` was required
- any errors in `onionshare.log`
## 7. Logs And Status
Capture the practical debugging workflow:
```powershell
voxvera doctor --json
voxvera autostart status --json
Get-ChildItem "$HOME\\voxvera-test\\host\\voxvera" -Force
```
Document:
- where logs live
- what failed if recovery did not work
- any permissions/path issues
## Report Back
A useful tester report should include:
- Windows version
- install path used
- whether build worked
- whether serve worked
- whether the onion identity persisted
- whether login/reboot recovery worked
- whether offline/reconnect recovery worked
- exact command output or error text for failures
+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`.
+36 -4
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,40 @@ 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
docker run -d \
--name voxvera \
--restart unless-stopped \
-v "$(pwd)/flyers:/flyers" \
ghcr.io/voxvera/voxvera:latest
```
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 now uses a dedicated entrypoint that:
- stores runtime state under `/flyers`
- seeds `/flyers/config.json` if needed
- starts an in-container Tor daemon on `127.0.0.1:9050/9051`
- bootstraps a default site when `/flyers/host` is empty
- runs `voxvera start-all` on startup and retries it periodically
- exposes a container healthcheck based on `voxvera doctor --json`
Useful commands:
```bash
docker logs -f voxvera
docker exec -it voxvera voxvera doctor --json
docker exec -it voxvera voxvera autostart status --json
```
If you prefer Compose:
```yaml
services:
voxvera:
image: ghcr.io/voxvera/voxvera:latest
restart: unless-stopped
volumes:
- ./flyers:/flyers
```
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 => {
+3 -3
View File
@@ -4,9 +4,9 @@
"title": "ANDROID UNLEASHED",
"subtitle": "WITHOUT BOUNDARIES",
"headline": "OPEN-SOURCE FREEDOM",
"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.\n\nFor 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.\n\nAOSP 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.",
"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"
}
+5 -5
View File
@@ -1,12 +1,12 @@
{
"name": "Bitcoin Revolution Flyers",
"folder_name": "bitcoin",
"title": "THE FUTURE OF MONEY",
"title": "RULES WITHOUT RULERS",
"subtitle": "DO NOT IGNORE",
"headline": "JOIN THE BITCOIN REVOLUTION",
"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.\n\nOur 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.\n\nBitcoin 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.\n\nTake 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.",
"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"
}
+4 -4
View File
@@ -4,9 +4,9 @@
"title": "GOOD NEWS",
"subtitle": "DO NOT IGNORE",
"headline": "DISCOVER TRUE HOPE",
"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.\n\nWe 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.\n\nWithout 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.\n\nTake 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.",
"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"
}
+4 -4
View File
@@ -4,9 +4,9 @@
"title": "SECURE COMMUNICATIONS",
"subtitle": "DO NOT IGNORE",
"headline": "BUILD YOUR EMERGENCY NETWORK",
"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.\n\nMeshtastic 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.\n\nIn 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.\n\nTake 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.",
"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"
}
+4 -4
View File
@@ -4,9 +4,9 @@
"title": "SOCIAL FREEDOM",
"subtitle": "DO NOT IGNORE",
"headline": "JOIN THE CENSORSHIP-RESISTANT NETWORK",
"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.\n\nOn 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.\n\nIn 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.\n\nTake 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.",
"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"
}
+5 -5
View File
@@ -4,9 +4,9 @@
"title": "TOP SECRET",
"subtitle": "DO ~~NOT~~ DISTRIBUTE",
"headline": "OPERATION VOX VERA",
"content": "Break free from censorship with VoxVera. An anonymous guerrilla marketing and message-spreading tool. Whether online or in the physical world, it can empower you to spread your ideas boldly, shielded by complete anonymity (if you host over Tor). Download the code, design a flyer site, host it online and amplify your message.\n\nUse memetic power to share your ideas in your school, workplace, online communities, or even globally. VoxVera ensures your message resonates far and wide, with tear-off sections featuring unique URLs and QR codes for easy reprinting.\n\nPrivacy can be maintained. Flyers can be shared via the Tor network, protecting hoster and users from censorship.\n\nJoin us in a revolution that values truth and transparency. Together, we can build a network of informed citizens who are unafraid to speak out.",
"url_message": "Follow this link to learn more. Use Tor Browser.",
"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.",
"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
+366 -90
View File
@@ -1,6 +1,20 @@
#!/usr/bin/env bash
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
export GIT_HTTP_LOW_SPEED_LIMIT=1000
export GIT_HTTP_LOW_SPEED_TIME=30
# Disable partial clones — they are fragile on flaky connections
export GIT_CLONE_PROTECTION_ACTIVE=false
# Hard timeout (seconds) for any single network operation
NET_TIMEOUT=90
command_exists() {
command -v "$1" >/dev/null 2>&1
}
@@ -36,43 +50,121 @@ 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 "$@"
;;
dnf)
$SUDO dnf install -y "$@"
;;
yum)
$SUDO yum install -y "$@"
;;
pacman)
$SUDO pacman -S --noconfirm "$@"
;;
brew)
brew install "$@"
;;
apk)
$SUDO apk add --no-cache "$@"
;;
apt) $SUDO apt-get install -y "$@" ;;
dnf) $SUDO dnf install -y "$@" ;;
yum) $SUDO yum install -y "$@" ;;
pacman) $SUDO pacman -S --noconfirm "$@" ;;
brew) brew install "$@" ;;
apk) $SUDO apk add --no-cache "$@" ;;
esac
}
# Ensure core dependencies are present (always run to ensure they are up to date)
# --- Resilient download: tries curl then wget, with retries and timeout ---
download() {
local url="$1" dest="$2"
# Try curl first
if command_exists curl; then
local attempt
for attempt in 1 2 3; do
if timeout "$NET_TIMEOUT" curl -fsSL \
--retry 2 --retry-delay 3 --retry-connrefused \
--max-time "$NET_TIMEOUT" \
"$url" -o "$dest" 2>/dev/null; then
return 0
fi
warn "curl attempt $attempt failed for $(basename "$url")"
sleep 2
done
fi
# Try wget as fallback (different TLS stack — often works when curl doesn't)
if command_exists wget; then
local attempt
for attempt in 1 2 3; do
if timeout "$NET_TIMEOUT" wget -q --timeout=60 --tries=1 \
"$url" -O "$dest" 2>/dev/null; then
return 0
fi
warn "wget attempt $attempt failed for $(basename "$url")"
sleep 2
done
fi
return 1
}
# --- Resilient HTTP status check (for release tag redirect) ---
get_latest_tag() {
local tag=""
if command_exists curl; then
tag=$(timeout 30 curl -fsSLI -o /dev/null -w '%{url_effective}' \
"${REPO_URL}/releases/latest" 2>/dev/null | grep -oE '[^/]+$') || true
fi
if [ -z "$tag" ] && command_exists wget; then
tag=$(timeout 30 wget --max-redirect=5 -q -S --spider \
"${REPO_URL}/releases/latest" 2>&1 | grep -i 'Location:' | tail -1 | grep -oE '[^/]+$') || true
fi
echo "$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 git python3-pip python3-venv)
SYSTEM_PKGS=(tor curl wget git python3-pip python3-venv)
[ -n "$(command -v pipx)" ] || SYSTEM_PKGS+=(pipx)
install_pkg "${SYSTEM_PKGS[@]}"
elif [ "$PM" = "brew" ]; then
brew install tor onionshare curl git
brew install tor onionshare curl wget git
else
install_pkg tor curl git
# Best-effort install wget if not present
install_pkg wget 2>/dev/null || true
fi
# Onionshare-cli - ensure it's installed and working (always attempt update if using pipx)
# Onionshare-cli
msg "Ensuring onionshare-cli is installed and up-to-date..."
if command_exists pipx && (pipx list | grep -q onionshare || ! command_exists onionshare-cli); then
msg "Installing/Updating onionshare-cli via pipx..."
@@ -96,84 +188,268 @@ else
msg "onionshare-cli found (system package), skipping pipx install. Run 'pipx install --force ...' if you want the latest git version."
fi
download_binary() {
local url=$1
local dest=$2
if command_exists curl; then
local status
status=$(curl -w "%{http_code}" -fsSL "$url" -o "$dest" || true)
if [ "$status" = "404" ]; then return 2; elif [ "$status" != "200" ]; then return 1; fi
elif command_exists wget; then
local out
out=$(wget --server-response -q "$url" -O "$dest" 2>&1 || true)
if echo "$out" | grep -q "404 Not Found"; then return 2; elif ! echo "$out" | grep -q "200 OK"; then return 1; fi
else
warn "Install curl or wget to download voxvera"
return 1
fi
chmod +x "$dest"
# --- Clean up stale state from previous installs (preserves ~/host tor sites) ---
msg "Cleaning up stale state from previous installs..."
# Stale user-local packages that may conflict with system onionshare-cli
for pydir in "$HOME"/.local/lib/python3.*/site-packages; do
[ -d "$pydir" ] || continue
for pkg in werkzeug flask flask_socketio onionshare_cli voxvera; do
if [ -d "$pydir/$pkg" ]; then
warn "Removing stale $pkg from $pydir"
rm -rf "$pydir/$pkg" "$pydir/${pkg}-"*.dist-info 2>/dev/null || true
fi
done
done
# Stale pip wheel cache for voxvera (forces clean rebuild)
for cache_dir in "$HOME"/.cache/pip/wheels; do
[ -d "$cache_dir" ] || continue
find "$cache_dir" -name 'voxvera-*.whl' -delete 2>/dev/null || true
done
# Leftover temp files from previous failed install attempts
rm -rf /tmp/voxvera-* 2>/dev/null || true
# Stale top-level config.json from older VoxVera versions (not inside ~/host)
[ -f "$HOME/config.json" ] && grep -q '"folder_name"' "$HOME/config.json" 2>/dev/null && {
warn "Removing stale ~/config.json (leftover from older VoxVera version)"
rm -f "$HOME/config.json"
}
# Install VoxVera (Always use --force to ensure fresh installation)
if command_exists curl; then
msg "Attempting to download latest VoxVera binary..."
# Get the latest release tag
latest=$(curl -fsSLI -o /dev/null -w '%{url_effective}' https://github.com/PR0M3TH3AN/VoxVera/releases/latest | grep -oE '[^/]+$')
install_dir="$HOME/.local/bin"
# Stale top-level imports/ directory (older versions put it in ~/)
if [ -d "$HOME/imports" ] && ls "$HOME/imports"/*.json &>/dev/null; then
if head -1 "$HOME/imports"/*.json 2>/dev/null | grep -q '"folder_name"\|"headline"'; then
warn "Removing stale ~/imports/ (leftover from older VoxVera version)"
rm -rf "$HOME/imports"
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"
if [ -f /etc/debian_version ]; then
local deb_ver
deb_ver=$(cut -d. -f1 < /etc/debian_version)
if [[ "$deb_ver" =~ ^[0-9]+$ ]] && [ "$deb_ver" -ge 12 ]; then
flags="$flags --break-system-packages"
fi
fi
echo "$flags"
}
# --- Helper: install from a local source directory ---
install_from_local() {
local src_dir="$1"
msg "Installing VoxVera from local source ($src_dir)..."
local flags
flags=$(pip_flags)
if pip install $flags "$src_dir"; then
msg "VoxVera installed from local source."
return 0
fi
return 1
}
# --- Helper: remove stale standalone binary so it doesn't shadow pipx/pip entrypoints ---
cleanup_stale_binary() {
local bin="$HOME/.local/bin/voxvera"
[ -f "$bin" ] || return 0
# Only remove actual compiled binaries (PyInstaller/native), not pip/pipx Python scripts
if file "$bin" 2>/dev/null | grep -qiE 'ELF|Mach-O'; then
warn "Removing stale standalone binary at $bin (would shadow pipx/pip entrypoint)"
rm -f "$bin"
fi
}
# ============================================================
# Strategy 5: Pre-built binary (fallback — can lag behind main)
# ============================================================
install_binary() {
msg "Strategy 5/5: Attempting to download pre-built binary..."
local latest
latest=$(get_latest_tag)
[ -n "$latest" ] || { warn "Could not determine latest release tag."; return 1; }
local install_dir="$HOME/.local/bin"
mkdir -p "$install_dir"
dest="$install_dir/voxvera"
OS="$(uname -s | tr '[:upper:]' '[:lower:]')"
ARCH=$(uname -m)
case "$OS" in
linux*) BINARY="voxvera-linux" ;;
darwin*) BINARY="voxvera-macos" ;;
*) BINARY="voxvera-linux" ;;
local dest="$install_dir/voxvera"
local os_name arch binary
os_name="$(uname -s | tr '[:upper:]' '[:lower:]')"
arch=$(uname -m)
case "$os_name" in
linux*) binary="voxvera-linux" ;;
darwin*) binary="voxvera-macos" ;;
*) binary="voxvera-linux" ;;
esac
# Try to download architecture-specific binary (e.g. voxvera-linux-x86_64)
url="https://github.com/PR0M3TH3AN/VoxVera/releases/download/${latest}/${BINARY}-${ARCH}"
# For Windows, PyInstaller might add .exe but here we focus on linux/mac
msg "Downloading from: $url"
status=$(curl -w "%{http_code}" -fsSL "$url" -o "$dest" || echo "failed")
if [ "$status" = "200" ]; then
local url="${REPO_URL}/releases/download/${latest}/${binary}-${arch}"
msg "Downloading: $url"
if download "$url" "$dest"; then
chmod +x "$dest"
msg "VoxVera binary (${ARCH}) installed to $dest"
[[ ":$PATH:" != *":$HOME/.local/bin:"* ]] && warn "Add \$HOME/.local/bin to your PATH."
exit 0
# Verify it's actually an executable, not an HTML error page
if file "$dest" | grep -qiE 'ELF|executable|Mach-O'; then
msg "VoxVera binary (${arch}) installed to $dest"
[[ ":$PATH:" != *":$HOME/.local/bin:"* ]] && warn "Add \$HOME/.local/bin to your PATH."
return 0
fi
warn "Downloaded file is not a valid binary."
fi
[ -f "$dest" ] && rm -f "$dest"
warn "Binary download failed. Trying next method..."
return 1
}
# ============================================================
# Strategy 1: GitHub tarball — no git needed, always current with main
# ============================================================
install_tarball() {
msg "Strategy 1/5: Downloading source tarball (no git required)..."
local tmp_tar tmp_dir
tmp_tar=$(mktemp /tmp/voxvera-XXXXXX.tar.gz)
tmp_dir=$(mktemp -d /tmp/voxvera-src-XXXXXX)
if download "${REPO_URL}/archive/refs/heads/${BRANCH}.tar.gz" "$tmp_tar"; then
if tar -xzf "$tmp_tar" -C "$tmp_dir" --strip-components=1 2>/dev/null; then
if install_from_local "$tmp_dir"; then
cleanup_stale_binary
rm -rf "$tmp_tar" "$tmp_dir"
return 0
fi
else
warn "Tarball extraction failed."
fi
else
warn "Binary download failed (status: $status). Falling back to source installation."
[ -f "$dest" ] && rm "$dest"
warn "Tarball download failed."
fi
fi
if command_exists pipx; then
msg "Falling back to pipx installation (this may take a few minutes)..."
if pipx install --force 'voxvera@git+https://github.com/PR0M3TH3AN/VoxVera.git@main'; then
rm -rf "$tmp_tar" "$tmp_dir"
warn "Tarball install failed. Trying next method..."
return 1
}
# ============================================================
# Strategy 2: Shallow git clone + local pip install
# ============================================================
install_shallow() {
msg "Strategy 2/5: Attempting shallow git clone (--depth 1)..."
local tmp_dir
tmp_dir=$(mktemp -d /tmp/voxvera-clone-XXXXXX)
if timeout "$NET_TIMEOUT" git clone --depth 1 --single-branch --branch "$BRANCH" \
"${REPO_URL}.git" "$tmp_dir" 2>&1; then
if install_from_local "$tmp_dir"; then
cleanup_stale_binary
rm -rf "$tmp_dir"
return 0
fi
else
warn "Shallow clone failed or timed out after ${NET_TIMEOUT}s."
fi
rm -rf "$tmp_dir"
warn "Shallow clone install failed. Trying next method..."
return 1
}
# ============================================================
# Strategy 3: pipx from git (isolated venv)
# ============================================================
install_pipx() {
command_exists pipx || return 1
msg "Strategy 3/5: Attempting pipx installation..."
if timeout 180 pipx install --force "voxvera@git+${REPO_URL}.git@${BRANCH}"; then
pipx inject voxvera setuptools 2>/dev/null || true
pipx ensurepath --force
cleanup_stale_binary
msg "\nVoxVera installed/updated successfully via pipx."
msg "IMPORTANT: Please restart your terminal or run 'source ~/.bashrc' (or your shell config) to use 'voxvera'."
msg "IMPORTANT: Please restart your terminal or run 'source ~/.bashrc' to use 'voxvera'."
return 0
fi
warn "pipx install failed. Trying next method..."
return 1
}
# ============================================================
# Strategy 4: pip from git
# ============================================================
install_pip_git() {
msg "Strategy 4/5: Attempting pip install from git..."
local flags
flags=$(pip_flags)
if timeout 180 pip install $flags "voxvera@git+${REPO_URL}.git@${BRANCH}"; then
cleanup_stale_binary
msg "VoxVera installed via pip."
return 0
fi
warn "pip git install failed."
return 1
}
# ============================================================
# Run strategies in order — first success wins
# ============================================================
# Order rationale:
# 1. Tarball — always up-to-date with main, no git needed, single download
# 2. Shallow clone — small git transfer, also always current
# 3. pipx — git clone internally, but isolated venv
# 4. pip git — git clone internally, last resort
# 5. Binary — pre-built release; only fallback since it can lag behind main
for strategy in install_tarball install_shallow install_pipx install_pip_git install_binary; do
if $strategy; then
# Ensure ~/.local/bin is on PATH
if [[ ":$PATH:" != *":$HOME/.local/bin:"* ]]; then
export PATH="$HOME/.local/bin:$PATH"
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
fi
warn "pipx install failed, trying pip fallback..."
fi
done
# Fallback to pip
msg "Installing/Updating VoxVera via pip..."
PIP_OPTS="--user --upgrade"
if [ -f /etc/debian_version ]; then
DEB_VER=$(cat /etc/debian_version | cut -d. -f1)
if [[ "$DEB_VER" =~ ^[0-9]+$ ]] && [ "$DEB_VER" -ge 12 ]; then
PIP_OPTS="$PIP_OPTS --break-system-packages"
fi
fi
if pip install $PIP_OPTS 'voxvera@git+https://github.com/PR0M3TH3AN/VoxVera.git@main'; then
msg "VoxVera installed via pip."
else
die "Installation failed."
fi
msg "\nVoxVera installed/updated successfully. Run 'voxvera check' to verify."
die "All installation methods failed. Your network may be blocking or corrupting HTTPS traffic to GitHub.\nTry downloading manually from a working machine:\n ${REPO_URL}/archive/refs/heads/${BRANCH}.tar.gz\nThen copy to this machine and run:\n tar xzf VoxVera-main.tar.gz && pip install --user ./VoxVera-main"
+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"
+2 -2
View File
@@ -14,7 +14,7 @@ voxvera = ["templates/**", "src/**", "resources/**", "locales/**", "resources/bi
[project]
name = "voxvera"
version = "0.2.1"
version = "0.3.12"
description = "VoxVera CLI utilities"
readme = "README.md"
authors = [{name = "VoxVera"}]
@@ -28,7 +28,7 @@ dependencies = [
"htmlmin2",
"pypdf",
"wcwidth",
"onionshare-cli",
"setuptools",
]
[project.optional-dependencies]
test = [
+2
View File
@@ -7,4 +7,6 @@ htmlmin2
pypdf
wcwidth
onionshare-cli
flask<2.3.0
werkzeug<3.0.0
+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"/
+36
View File
@@ -0,0 +1,36 @@
#!/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
if command -v docker >/dev/null 2>&1; then
echo "==> docker runtime smoke"
bash scripts/docker-runtime-smoke.sh
fi
fi
echo "==> build docs"
python3 -m voxvera.cli build-docs
echo "==> build site"
python3 -m voxvera.cli build-site
echo "All local checks passed."
+125
View File
@@ -0,0 +1,125 @@
#!/usr/bin/env bash
set -euo pipefail
VOXVERA_DIR="${VOXVERA_DIR:-/flyers}"
VOXVERA_CONFIG_PATH="${VOXVERA_CONFIG_PATH:-$VOXVERA_DIR/config.json}"
VOXVERA_START_INTERVAL="${VOXVERA_START_INTERVAL:-300}"
VOXVERA_BOOTSTRAP_DEFAULT_SITE="${VOXVERA_BOOTSTRAP_DEFAULT_SITE:-1}"
TOR_SOCKS_PORT="${TOR_SOCKS_PORT:-9050}"
TOR_CONTROL_PORT="${TOR_CONTROL_PORT:-9051}"
VOXVERA_TOR_DIR="${VOXVERA_TOR_DIR:-/tmp/voxvera-tor}"
TOR_PID=""
SLEEP_PID=""
log() {
printf '[voxvera-docker] %s\n' "$*"
}
cleanup() {
if [[ -n "$SLEEP_PID" ]] && kill -0 "$SLEEP_PID" 2>/dev/null; then
kill "$SLEEP_PID" 2>/dev/null || true
fi
voxvera stop-all >/dev/null 2>&1 || true
if [[ -n "$TOR_PID" ]] && kill -0 "$TOR_PID" 2>/dev/null; then
kill "$TOR_PID" 2>/dev/null || true
wait "$TOR_PID" 2>/dev/null || true
fi
}
trap cleanup EXIT INT TERM
wait_for_port() {
local host="$1"
local port="$2"
python3 - "$host" "$port" <<'PY'
import socket
import sys
import time
host = sys.argv[1]
port = int(sys.argv[2])
deadline = time.time() + 60
while time.time() < deadline:
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock:
sock.settimeout(1)
if sock.connect_ex((host, port)) == 0:
sys.exit(0)
time.sleep(1)
sys.exit(1)
PY
}
has_site_configs() {
find "$VOXVERA_DIR/host" -mindepth 2 -maxdepth 2 -name config.json -print -quit 2>/dev/null | grep -q .
}
seed_default_config() {
mkdir -p "$VOXVERA_DIR"
if [[ ! -f "$VOXVERA_CONFIG_PATH" ]]; then
cp /opt/voxvera/voxvera/src/config.json "$VOXVERA_CONFIG_PATH"
log "Seeded default config at $VOXVERA_CONFIG_PATH"
fi
}
bootstrap_default_site_if_needed() {
if [[ "$VOXVERA_BOOTSTRAP_DEFAULT_SITE" != "1" ]]; then
return
fi
if has_site_configs; then
return
fi
log "No sites found under $VOXVERA_DIR/host; bootstrapping default site"
voxvera --config "$VOXVERA_CONFIG_PATH" init --non-interactive
}
start_tor() {
mkdir -p "$VOXVERA_TOR_DIR"
chmod 700 "$VOXVERA_TOR_DIR"
cat >"$VOXVERA_TOR_DIR/torrc" <<EOF
DataDirectory $VOXVERA_TOR_DIR/data
SocksPort 127.0.0.1:$TOR_SOCKS_PORT
ControlPort 127.0.0.1:$TOR_CONTROL_PORT
CookieAuthentication 0
AvoidDiskWrites 1
Log notice stdout
EOF
mkdir -p "$VOXVERA_TOR_DIR/data"
log "Starting Tor on 127.0.0.1:$TOR_SOCKS_PORT / $TOR_CONTROL_PORT"
tor -f "$VOXVERA_TOR_DIR/torrc" &
TOR_PID="$!"
wait_for_port 127.0.0.1 "$TOR_SOCKS_PORT"
wait_for_port 127.0.0.1 "$TOR_CONTROL_PORT"
log "Tor ports are reachable"
}
start_loop() {
export TOR_SOCKS_PORT TOR_CONTROL_PORT
while true; do
if ! kill -0 "$TOR_PID" 2>/dev/null; then
log "Tor exited unexpectedly"
wait "$TOR_PID"
exit 1
fi
log "Running voxvera start-all"
voxvera start-all || true
sleep "$VOXVERA_START_INTERVAL" &
SLEEP_PID="$!"
wait "$SLEEP_PID"
SLEEP_PID=""
done
}
seed_default_config
start_tor
bootstrap_default_site_if_needed
start_loop
+45
View File
@@ -0,0 +1,45 @@
#!/usr/bin/env bash
set -euo pipefail
VOXVERA_DIR="${VOXVERA_DIR:-/flyers}"
TMP_JSON="$(mktemp)"
voxvera doctor --json >"$TMP_JSON"
python3 - "$TMP_JSON" "$VOXVERA_DIR" <<'PY'
import json
import sys
from pathlib import Path
doctor = json.loads(Path(sys.argv[1]).read_text(encoding="utf-8"))
voxvera_dir = Path(sys.argv[2])
checks = {check["name"]: check for check in doctor.get("checks", [])}
required = ("voxvera_cli", "onionshare_cli", "tor_socks_reachable", "host_root")
missing = []
failing = []
for name in required:
check = checks.get(name)
if not check:
missing.append(name)
continue
if not check.get("ok"):
failing.append(f"{name}: {check.get('detail', '')}")
site_configs = list((voxvera_dir / "host").glob("*/config.json"))
running_sites = [cfg.parent for cfg in site_configs if (cfg.parent / "server.pid").exists()]
if missing or failing or not site_configs or not running_sites:
if missing:
print("Missing checks: " + ", ".join(missing), file=sys.stderr)
if failing:
print("Failing checks: " + "; ".join(failing), file=sys.stderr)
if not site_configs:
print("No configured sites found under host/", file=sys.stderr)
if site_configs and not running_sites:
print("Configured sites exist but no server.pid files are present", file=sys.stderr)
sys.exit(1)
print("healthy")
PY
+109
View File
@@ -0,0 +1,109 @@
#!/usr/bin/env bash
set -euo pipefail
cd "$(dirname "$0")/.."
if ! command -v docker >/dev/null 2>&1; then
echo "docker is not installed; skipping Docker runtime smoke"
exit 0
fi
IMAGE_TAG="${VOXVERA_DOCKER_SMOKE_TAG:-voxvera:smoke}"
CONTAINER_NAME="${VOXVERA_DOCKER_SMOKE_CONTAINER:-voxvera-smoke}"
TMP_DIR="${VOXVERA_DOCKER_SMOKE_TMPDIR:-$(mktemp -d /tmp/voxvera-docker-smoke-XXXXXX)}"
FLYERS_DIR="$TMP_DIR/flyers"
mkdir -p "$FLYERS_DIR"
cleanup() {
docker logs "$CONTAINER_NAME" >/dev/null 2>&1 || true
docker rm -f "$CONTAINER_NAME" >/dev/null 2>&1 || true
if [[ -z "${VOXVERA_DOCKER_SMOKE_TMPDIR:-}" ]]; then
chmod -R u+w "$TMP_DIR" 2>/dev/null || true
docker run --rm -v "$TMP_DIR:/cleanup" python:3.11-slim sh -lc 'rm -rf /cleanup/* /cleanup/.[!.]* /cleanup/..?* 2>/dev/null || true' >/dev/null 2>&1 || true
rm -rf "$TMP_DIR" 2>/dev/null || true
fi
}
trap cleanup EXIT
echo "==> building Docker image"
docker build -t "$IMAGE_TAG" .
echo "==> starting Docker container"
docker run -d \
--name "$CONTAINER_NAME" \
--restart no \
-e VOXVERA_START_INTERVAL=15 \
-v "$FLYERS_DIR:/flyers" \
"$IMAGE_TAG" >/dev/null
echo "==> waiting for healthy container"
deadline=$((SECONDS + 240))
while true; do
status="$(docker inspect --format '{{if .State.Health}}{{.State.Health.Status}}{{else}}no-healthcheck{{end}}' "$CONTAINER_NAME")"
case "$status" in
healthy)
break
;;
unhealthy)
echo "Container became unhealthy" >&2
docker logs "$CONTAINER_NAME" >&2 || true
exit 1
;;
esac
if (( SECONDS >= deadline )); then
echo "Timed out waiting for healthy Docker container" >&2
docker logs "$CONTAINER_NAME" >&2 || true
exit 1
fi
sleep 5
done
PLATFORM_JSON="$TMP_DIR/platform-status.json"
DOCTOR_JSON="$TMP_DIR/doctor.json"
docker exec "$CONTAINER_NAME" voxvera platform-status --json >"$PLATFORM_JSON"
docker exec "$CONTAINER_NAME" voxvera doctor --json >"$DOCTOR_JSON"
PLATFORM_JSON="$PLATFORM_JSON" \
DOCTOR_JSON="$DOCTOR_JSON" \
FLYERS_DIR="$FLYERS_DIR" \
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"))
flyers_dir = Path(os.environ["FLYERS_DIR"])
errors = []
if platform_status.get("platform_id") != "docker_cli":
errors.append(f"platform_id mismatch: {platform_status.get('platform_id')}")
if platform_status.get("tier") != "experimental":
errors.append(f"tier mismatch: {platform_status.get('tier')}")
checks = {check["name"]: check for check in doctor.get("checks", [])}
for required in ("container_marker", "docker_entrypoint", "docker_healthcheck", "tor_socks_reachable", "host_root"):
check = checks.get(required)
if not check:
errors.append(f"missing doctor check: {required}")
elif not check.get("ok"):
errors.append(f"doctor check failed: {required} ({check.get('detail')})")
site_dir = flyers_dir / "host" / "voxvera"
for path in (flyers_dir / "config.json", site_dir / "config.json", site_dir / "index.html", site_dir / ".onionshare-session", site_dir / "server.pid"):
if not path.exists():
errors.append(f"missing expected Docker runtime artifact: {path}")
if errors:
print("Docker runtime smoke failed:", file=sys.stderr)
for error in errors:
print(f"- {error}", file=sys.stderr)
sys.exit(1)
print("Docker runtime smoke OK")
print(f"Platform: {platform_status['label']} [{platform_status['tier']}]")
print(f"Hosting model: {platform_status.get('hosting_model', '')}")
PY
+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="docker_cli"
EXPECTED_TIER="${VOXVERA_EXPECTED_TIER_OVERRIDE:-experimental}"
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."
+3 -3
View File
@@ -4,9 +4,9 @@
"title": "TOP SECRET",
"subtitle": "DO ~~NOT~~ DISTRIBUTE",
"headline": "OPERATION VOX VERA",
"content": "Break free from censorship with VoxVera. An anonymous guerrilla marketing and message-spreading tool. Whether online or in the physical world, it can empower you to spread your ideas boldly, shielded by complete anonymity (if you host over Tor). Download the code, design a flyer site, host it online and amplify your message. \n\nUse memetic power to share your ideas in your school, workplace, online communities, or even globally. VoxVera ensures your message resonates far and wide, with tear-off sections featuring unique URLs and QR codes for easy reprinting. \n\nPrivacy can be maintained. Flyers can be shared via the Tor network, protecting hoster and users from censorship.\n\nJoin us in a revolution that values truth and transparency. Together, we can build a network of informed citizens who are unafraid to speak out.\n",
"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.
Loaded 100 of 151 files, more files were not shown because too many files have changed in this diff. Show more