mirror of
https://github.com/PR0M3TH3AN/VoxVera.git
synced 2026-10-27 17:50:42 +00:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
285d730369 | ||
|
|
f5403448a7 | ||
|
|
6822b8e496 | ||
|
|
83e9e3d511 | ||
|
|
004cce2a4f | ||
|
|
ec0abd856c | ||
|
|
7724ac78a7 | ||
|
|
c1a5e608de | ||
|
|
44cf452f67 | ||
|
|
4e49762a85 | ||
|
|
2de7ea073c | ||
|
|
2dd0cf4ed8 | ||
|
|
6c4d6d6aef | ||
|
|
e6ff21c069 | ||
|
|
1fcb86ac8b |
No files matched your search
@@ -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
|
||||
@@ -69,11 +69,28 @@ jobs:
|
||||
--add-data "voxvera/locales${SEP}voxvera/locales" \
|
||||
--add-data "voxvera/src${SEP}voxvera/src" \
|
||||
--add-data "voxvera/templates${SEP}voxvera/templates" \
|
||||
--add-data "voxvera/resources${SEP}voxvera/resources" \
|
||||
--add-data "voxvera/resources/tor${SEP}voxvera/resources/tor" \
|
||||
--add-data "support-matrix.json${SEP}." \
|
||||
--add-data "requirements.txt${SEP}." \
|
||||
--name "${{ matrix.artifact_name }}" \
|
||||
voxvera/cli.py
|
||||
|
||||
- name: Validate built binary contract surface (Linux/macOS)
|
||||
if: matrix.os != 'windows-latest'
|
||||
shell: bash
|
||||
run: |
|
||||
./dist/${{ matrix.artifact_name }} platform-status --json
|
||||
./dist/${{ matrix.artifact_name }} doctor --json
|
||||
./dist/${{ matrix.artifact_name }} autostart status --json
|
||||
|
||||
- name: Validate built binary contract surface (Windows)
|
||||
if: matrix.os == 'windows-latest'
|
||||
shell: pwsh
|
||||
run: |
|
||||
& ".\dist\${{ matrix.artifact_name }}" platform-status --json
|
||||
& ".\dist\${{ matrix.artifact_name }}" doctor --json
|
||||
& ".\dist\${{ matrix.artifact_name }}" autostart status --json
|
||||
|
||||
- name: Build .deb (Linux)
|
||||
if: matrix.os == 'ubuntu-22.04'
|
||||
run: |
|
||||
@@ -90,6 +107,13 @@ jobs:
|
||||
chmod +x packaging/build_appimage.sh
|
||||
./packaging/build_appimage.sh dist/${{ matrix.artifact_name }}
|
||||
|
||||
- name: Validate Linux release artifacts
|
||||
if: matrix.os == 'ubuntu-22.04'
|
||||
shell: bash
|
||||
run: |
|
||||
chmod +x scripts/validate-release-artifacts.sh
|
||||
./scripts/validate-release-artifacts.sh
|
||||
|
||||
- name: Build Flatpak (Linux)
|
||||
if: matrix.os == 'ubuntu-22.04'
|
||||
run: |
|
||||
|
||||
@@ -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
|
||||
+6
-4
@@ -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,13 +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
@@ -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 []
|
||||
@@ -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
|
||||
|
||||
+8
-1
@@ -46,37 +46,44 @@ python3 -m voxvera.cli build-site
|
||||
|
||||
## 3. Build Distribution Binaries
|
||||
|
||||
Standardized binaries and AppImages must be generated for the current platform.
|
||||
Linux CLI delivery is the supported deployment target today. Other package formats should be treated as experimental release artifacts until their Tor/OnionShare background-hosting behavior is validated end-to-end.
|
||||
Capture platform evidence before promoting any support tier: run `bash scripts/platform-smoke.sh linux-cli` locally for the supported Linux path, and keep CI/workflow evidence aligned with the same command.
|
||||
As other runtime targets mature, use `bash scripts/platform-smoke.sh macos-cli`, `bash scripts/platform-smoke.sh windows-cli`, or `bash scripts/platform-smoke.sh docker-cli` on their native environments before changing their support labels.
|
||||
|
||||
### Standard Binary
|
||||
Generates an architecture-specific binary (e.g., `voxvera-linux-x86_64`) in `voxvera/resources/bin/`.
|
||||
```bash
|
||||
./scripts/build_binaries.sh
|
||||
```
|
||||
This directory is local build output only. Do not treat old files in `voxvera/resources/bin/` as canonical release history; the GitHub Release artifacts are the publish surface.
|
||||
|
||||
### AppImage (Linux)
|
||||
Wraps the standard binary into a self-contained AppImage.
|
||||
```bash
|
||||
./packaging/build_appimage.sh
|
||||
```
|
||||
Experimental package surface.
|
||||
|
||||
### Flatpak (Linux)
|
||||
Generates a standalone `.flatpak` bundle. Requires `flatpak-builder`.
|
||||
```bash
|
||||
./packaging/build_flatpak.sh
|
||||
```
|
||||
Experimental package surface.
|
||||
|
||||
### Debian Package (Linux)
|
||||
Generates a `.deb` package for Debian/Ubuntu systems.
|
||||
```bash
|
||||
./packaging/build_deb.sh
|
||||
```
|
||||
Experimental package surface unless its runtime dependencies and recovery behavior are explicitly validated for the release.
|
||||
|
||||
### DMG (macOS)
|
||||
Generates a `.dmg` installer for macOS (must be run on macOS).
|
||||
```bash
|
||||
./packaging/build_dmg.sh
|
||||
```
|
||||
Experimental package surface.
|
||||
|
||||
## 4. Release Execution
|
||||
|
||||
|
||||
+1
-1
@@ -1 +1 @@
|
||||
|
||||
# Upcoming Release Notes
|
||||
@@ -26,6 +26,9 @@ 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
|
||||
@@ -104,6 +107,9 @@ if [ "${VOXVERA_E2E_OFFLINE:-}" != "1" ]; then
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "Checking platform contract with live Tor..."
|
||||
VOXVERA_REQUIRE_TOR=1 bash scripts/platform-smoke.sh linux-cli
|
||||
|
||||
# Start OnionShare
|
||||
echo "Starting OnionShare..."
|
||||
# Explicitly set environment variables that OnionShare-cli respects
|
||||
|
||||
@@ -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
@@ -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.
|
||||
@@ -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`.
|
||||
|
||||
|
||||
@@ -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
@@ -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.
|
||||
@@ -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`.
|
||||
|
||||
|
||||
@@ -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
@@ -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.
|
||||
@@ -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
@@ -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
|
||||
|
||||
@@ -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
@@ -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.
|
||||
@@ -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`.
|
||||
|
||||
|
||||
@@ -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
@@ -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.
|
||||
@@ -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`.
|
||||
|
||||
|
||||
@@ -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
@@ -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.
|
||||
@@ -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`.
|
||||
|
||||
|
||||
@@ -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
@@ -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.
|
||||
@@ -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
@@ -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
|
||||
|
||||
@@ -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
@@ -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.
|
||||
@@ -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`.
|
||||
|
||||
|
||||
@@ -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
@@ -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.
|
||||
@@ -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`.
|
||||
|
||||
|
||||
@@ -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.
|
||||
@@ -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
|
||||
@@ -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.
|
||||
@@ -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)
|
||||
@@ -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
@@ -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.
|
||||
@@ -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`.
|
||||
|
||||
|
||||
@@ -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
|
||||
@@ -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
@@ -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.
|
||||
@@ -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`.
|
||||
|
||||
|
||||
@@ -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
@@ -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.
|
||||
@@ -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`.
|
||||
|
||||
|
||||
Vendored
+6
-4
@@ -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`.
|
||||
|
||||
|
||||
Vendored
+36
-4
@@ -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.
|
||||
Vendored
+15
@@ -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`.
|
||||
|
||||
|
||||
Vendored
+19
-1
@@ -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
|
||||
|
||||
@@ -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
@@ -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.
|
||||
@@ -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`.
|
||||
|
||||
|
||||
@@ -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
|
||||
@@ -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
@@ -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.
|
||||
@@ -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`.
|
||||
|
||||
|
||||
@@ -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
@@ -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 & 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 => {
|
||||
|
||||
+61
-68
@@ -3,13 +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({
|
||||
@@ -22,74 +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 onionUrl = extractOnionUrl(line);
|
||||
if (onionUrl) {
|
||||
gotURL = true;
|
||||
clearTimeout(softTimeout);
|
||||
mainWindow.webContents.send('onion-url', onionUrl);
|
||||
}
|
||||
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() {
|
||||
@@ -110,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) => {
|
||||
@@ -118,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) => {
|
||||
|
||||
@@ -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))
|
||||
|
||||
+2
-2
@@ -7,6 +7,6 @@
|
||||
"content": "The Android Open Source Project (AOSP) represents the essence of what Android was originally intended to be: an open, transparent, and adaptable platform. It strips away the layers of proprietary software and corporate oversight that have become synonymous with mainstream Android distributions. With AOSP, you gain access to a raw, unfiltered version of Android, allowing you to truly make your device your own.<br><br>For developers, AOSP offers a playground of possibilities, where the only limits are your skills and imagination. It serves as the foundation for countless custom ROMs, each tailored to specific needs, whether for enhanced performance, privacy, or unique features that mainstream versions lack. Privacy enthusiasts will appreciate the absence of pre-installed bloatware and tracking services that often come wit stock Android, providing a cleaner, more secure user experience.<br><br>AOSP isn't just for developers or tech enthusiasts; it's for anyone who values digital freedom. It allows you to reclaim control over your device, tailoring it to your needs without the restrictions of carriers or manufacturers. Discover how AOSP can transform your mobile experience. Start your journey towards a more open, customizable future today.",
|
||||
"url_message": "Visit the link below to learn more.",
|
||||
"url": "https://source.android.com",
|
||||
"tear_off_link": "http://aosp.6dshf2gnj7yzxlfcaczlyi57up4mvbtd5orinuj5bjsfycnhz2w456yd.onion",
|
||||
"binary_message": "0110010 0101011 0110010 0111101 0110100"
|
||||
"tear_off_link": "",
|
||||
"footer_message": "0110010 0101011 0110010 0111101 0110100"
|
||||
}
|
||||
@@ -7,6 +7,6 @@
|
||||
"content": "What is Bitcoin? It's more than just digital money; it's a revolutionary technology designed to fix some of the world's most pressing problems. Bitcoin operates on a decentralized network, free from the control of any government or central authority. This ensures that no one can manipulate its supply or value, making it a sound form of money that protects your wealth from inflation.<br><br>Our current financial system is built on flawed incentives—encouraging debt, dependency, and centralization of power. Bitcoin changes that by creating a system where honesty, transparency, and individual responsibility are rewarded. In the Bitcoin network, there is no room for corruption or manipulation because the rules are enforced by mathematics, not by people.<br><br>Bitcoin aligns incentives in a way that promotes long-term thinking, savings, and innovation. It empowers individuals to take control of their financial future, fostering a global community where value can be transferred freely and securely without intermediaries. This is why we need Bitcoin—because it creates a world where incentives are properly aligned, leading to a more fair and prosperous society for all.<br><br>Take the step to learn more about Bitcoin and how it can transform not just your financial life, but the world. Tear off a link to the left and explore this groundbreaking technology. Join the movement towards a better future.",
|
||||
"url_message": "Follow this link to learn more.",
|
||||
"url": "https://bitcoin.org/en",
|
||||
"tear_off_link": "http://bitcoin.6dshf2gnj7yzxlfcaczlyi57up4mvbtd5orinuj5bjsfycnhz2w456yd.onion",
|
||||
"binary_message": "0110010 0101011 0110010 0111101 0110100"
|
||||
}
|
||||
"tear_off_link": "",
|
||||
"footer_message": "0110010 0101011 0110010 0111101 0110100"
|
||||
}
|
||||
+1
-1
@@ -8,5 +8,5 @@
|
||||
"url_message": "",
|
||||
"url": "",
|
||||
"tear_off_link": "",
|
||||
"binary_message": "0110010 0101011 0110010 0111101 0110100"
|
||||
"footer_message": "0110010 0101011 0110010 0111101 0110100"
|
||||
}
|
||||
@@ -7,6 +7,6 @@
|
||||
"content": "content",
|
||||
"url_message": "url message",
|
||||
"url": "url",
|
||||
"tear_off_link": "http://example-folder_name.6dshf2gnj7yzxlfcaczlyi57up4mvbtd5orinuj5bjsfycnhz2w456yd.onion",
|
||||
"binary_message": "0110010 0101011 0110010 0111101 0110100"
|
||||
}
|
||||
"tear_off_link": "",
|
||||
"footer_message": "0110010 0101011 0110010 0111101 0110100"
|
||||
}
|
||||
+3
-3
@@ -7,6 +7,6 @@
|
||||
"content": "Who is Jesus? He is more than just a historical figure; He is the Son of God, the Savior of the world. Jesus Himself said, 'I am the way, the truth, and the life. No one comes to the Father except through Me' (John 14:6). He came to bridge the gap between God and humanity, offering us a path to forgiveness, peace, and eternal life.<br><br>We all face brokenness in our lives—whether it's in our relationships, our hearts, or our world. Jesus came to heal that brokenness. He lived a perfect life, died on the cross for our sins, and rose again, defeating death and offering us the hope of eternal life with God.<br><br>Without Jesus, we are lost in our sin, separated from God. But through Him, we can be forgiven and restored. He offers us not just a better life now, but the promise of an eternal life with Him. This is why we need Him—because only in Jesus can we find true peace, purpose, and the assurance of eternal life.<br><br>Take the step to learn more about Jesus and the life-changing impact He can have on your life. Tear off a link to the left and explore His teachings. Find the hope you've been searching for.",
|
||||
"url_message": "Follow this link to learn more.",
|
||||
"url": "http://media.faithforthefamily.com/ra/godsoloved/english.mp3",
|
||||
"tear_off_link": "http://jesus.6dshf2gnj7yzxlfcaczlyi57up4mvbtd5orinuj5bjsfycnhz2w456yd.onion",
|
||||
"binary_message": "0110010 0101011 0110010 0111101 0110100"
|
||||
}
|
||||
"tear_off_link": "",
|
||||
"footer_message": "0110010 0101011 0110010 0111101 0110100"
|
||||
}
|
||||
@@ -7,6 +7,6 @@
|
||||
"content": "Why Meshtastic? In times of crisis, traditional communication networks can fail, leaving us isolated and vulnerable. Meshtastic provides a powerful solution—an alternative local communication network that operates independently of centralized systems. It’s designed for emergency situations, allowing you to stay connected when it matters most.<br><br>Meshtastic devices create a mesh network that can relay messages over long distances, even without internet or cellular coverage. This decentralized approach ensures that your communications are resilient and reliable, no matter the circumstances. But it’s not just about staying connected—Meshtastic also offers encryption, so your messages remain secure and private.<br><br>In a world where privacy is increasingly compromised, Meshtastic empowers you to take control of your communication. Whether you’re coordinating a local community response, staying in touch with loved ones during a disaster, or simply preparing for the unexpected, Meshtastic provides the tools you need to communicate securely and effectively.<br><br>Take the step to learn more about Meshtastic and how you can build your own emergency communication network. Tear off a link to the left and explore this innovative technology. Ensure you’re prepared for whatever comes your way.",
|
||||
"url_message": "Follow this link to learn more.",
|
||||
"url": "https://meshtastic.org",
|
||||
"tear_off_link": "http://meshtastic.6dshf2gnj7yzxlfcaczlyi57up4mvbtd5orinuj5bjsfycnhz2w456yd.onion",
|
||||
"binary_message": "0110010 0101011 0110010 0111101 0110100"
|
||||
}
|
||||
"tear_off_link": "",
|
||||
"footer_message": "0110010 0101011 0110010 0111101 0110100"
|
||||
}
|
||||
+3
-3
@@ -7,6 +7,6 @@
|
||||
"content": "What is Nostr? Unlike traditional social media platforms, Nostr is a protocol—a decentralized system that empowers users to connect and share without the constraints of a central authority. Nostr is censorship-resistant, meaning your voice can’t be silenced by corporations or governments. It’s designed to give you control over your online presence, free from the influence of algorithms or ads that prioritize engagement over meaningful connections.<br><br>On Nostr, there are no hidden agendas to keep you doomscrolling. You engage with content on your terms, not based on what an algorithm thinks will keep you online the longest. Nostr allows you to build genuine connections and participate in conversations that matter, all while maintaining your privacy and autonomy.<br><br>In a world where online speech is increasingly controlled and manipulated, Nostr offers a refreshing alternative. It’s not just a platform; it’s a movement towards true digital freedom, where your interactions are driven by your interests and values, not by what generates the most ad revenue.<br><br>Take the step to learn more about Nostr and how it can transform the way you connect online. Tear off a link the left and explore this groundbreaking protocol. Join a community that values freedom and authentic interaction.",
|
||||
"url_message": "Follow this link to learn more.",
|
||||
"url": "https://nostr.how/en/what-is-nostr",
|
||||
"tear_off_link": "http://nostr.6dshf2gnj7yzxlfcaczlyi57up4mvbtd5orinuj5bjsfycnhz2w456yd.onion/",
|
||||
"binary_message": "0110010 0101011 0110010 0111101 0110100"
|
||||
}
|
||||
"tear_off_link": "",
|
||||
"footer_message": "0110010 0101011 0110010 0111101 0110100"
|
||||
}
|
||||
@@ -5,8 +5,8 @@
|
||||
"subtitle": "DO ~~NOT~~ DISTRIBUTE",
|
||||
"headline": "OPERATION VOX VERA",
|
||||
"content": "Break free from censorship with VoxVera. An anonymous guerrilla marketing and message-spreading tool. Whether online or in the physical world, it can empower you to spread your ideas boldly, shielded by complete anonymity (if you host over Tor). Download the code, design a flyer site, host it online and amplify your message.<br><br>Use memetic power to share your ideas in your school, workplace, online communities, or even globally. VoxVera ensures your message resonates far and wide, with tear-off sections featuring unique URLs and QR codes for easy reprinting.<br><br>Privacy can be maintained. Flyers can be shared via the Tor network, protecting hoster and users from censorship.<br><br>Join us in a revolution that values truth and transparency. Together, we can build a network of informed citizens who are unafraid to speak out.",
|
||||
"url_message": "Follow this link to learn more. Use Tor Browser.",
|
||||
"url_message": "Follow this link to learn more.",
|
||||
"url": "https://voxvera.org/",
|
||||
"tear_off_link": "https://voxvera.org/",
|
||||
"binary_message": "0110010 0101011 0110010 0111101 0110100"
|
||||
}
|
||||
"tear_off_link": "",
|
||||
"footer_message": "0110010 0101011 0110010 0111101 0110100"
|
||||
}
|
||||
+41
-3
@@ -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
|
||||
+57
@@ -4,6 +4,7 @@ set -euo pipefail
|
||||
REPO="PR0M3TH3AN/VoxVera"
|
||||
REPO_URL="https://github.com/${REPO}"
|
||||
BRANCH="main"
|
||||
OS_NAME="$(uname -s | tr '[:upper:]' '[:lower:]')"
|
||||
|
||||
# --- Network resilience settings ---
|
||||
# Kill git operations that stall below 1 KB/s for 30 seconds
|
||||
@@ -49,6 +50,45 @@ msg() { echo -e "\e[32m$*\e[0m"; }
|
||||
warn() { echo -e "\e[33m$*\e[0m" >&2; }
|
||||
die() { echo -e "\e[31m$*\e[0m" >&2; exit 1; }
|
||||
|
||||
report_cli_status() {
|
||||
command_exists voxvera || return 0
|
||||
|
||||
local platform_json doctor_json autostart_json
|
||||
platform_json="$(voxvera platform-status --json 2>/dev/null || true)"
|
||||
doctor_json="$(voxvera doctor --json 2>/dev/null || true)"
|
||||
autostart_json="$(voxvera autostart status --json 2>/dev/null || true)"
|
||||
|
||||
if [ -n "$platform_json" ]; then
|
||||
PLATFORM_JSON="$platform_json" python3 - <<'PY'
|
||||
import json, os
|
||||
data = json.loads(os.environ["PLATFORM_JSON"])
|
||||
print(f"Platform tier: {data.get('tier', 'unknown')} ({data.get('label', 'unknown')})")
|
||||
if data.get("hosting_model"):
|
||||
print(f"Hosting model: {data['hosting_model']}")
|
||||
PY
|
||||
fi
|
||||
|
||||
if [ -n "$doctor_json" ]; then
|
||||
DOCTOR_JSON="$doctor_json" python3 - <<'PY'
|
||||
import json, os
|
||||
data = json.loads(os.environ["DOCTOR_JSON"])
|
||||
failed = [check["name"] for check in data.get("checks", []) if not check.get("ok")]
|
||||
if failed:
|
||||
print("Doctor checks needing attention: " + ", ".join(failed))
|
||||
else:
|
||||
print("Doctor checks: all passed")
|
||||
PY
|
||||
fi
|
||||
|
||||
if [ -n "$autostart_json" ]; then
|
||||
AUTOSTART_JSON="$autostart_json" python3 - <<'PY'
|
||||
import json, os
|
||||
data = json.loads(os.environ["AUTOSTART_JSON"])
|
||||
print("Autostart: " + data.get("message", "unknown"))
|
||||
PY
|
||||
fi
|
||||
}
|
||||
|
||||
install_pkg() {
|
||||
case "$PM" in
|
||||
apt) $SUDO apt-get install -y "$@" ;;
|
||||
@@ -108,6 +148,9 @@ get_latest_tag() {
|
||||
|
||||
# Ensure core dependencies are present
|
||||
msg "Checking and updating system dependencies..."
|
||||
if [[ "$OS_NAME" != linux* ]]; then
|
||||
warn "This installer is optimized for Linux. Non-Linux paths are experimental and are not validated for reliable background hidden-service hosting."
|
||||
fi
|
||||
if [ "$PM" = "apt" ]; then
|
||||
$SUDO apt-get update
|
||||
SYSTEM_PKGS=(tor curl wget git python3-pip python3-venv)
|
||||
@@ -386,9 +429,23 @@ for strategy in install_tarball install_shallow install_pipx install_pip_git ins
|
||||
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
|
||||
|
||||
@@ -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
@@ -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"
|
||||
@@ -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,5 +1,5 @@
|
||||
class Voxvera < Formula
|
||||
desc "Generate QR-coded flyers"
|
||||
desc "Experimental VoxVera formula"
|
||||
homepage "https://github.com/PR0M3TH3AN/VoxVera"
|
||||
url "https://github.com/PR0M3TH3AN/VoxVera/releases/latest/download/voxvera"
|
||||
version "0.1.0"
|
||||
|
||||
+1
-1
@@ -14,7 +14,7 @@ voxvera = ["templates/**", "src/**", "resources/**", "locales/**", "resources/bi
|
||||
|
||||
[project]
|
||||
name = "voxvera"
|
||||
version = "0.3.4"
|
||||
version = "0.3.12"
|
||||
description = "VoxVera CLI utilities"
|
||||
readme = "README.md"
|
||||
authors = [{name = "VoxVera"}]
|
||||
|
||||
@@ -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"/
|
||||
@@ -18,6 +18,15 @@ echo "==> electron smoke tests"
|
||||
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
|
||||
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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"
|
||||
@@ -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"
|
||||
@@ -0,0 +1,110 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
cd "$(dirname "$0")/.."
|
||||
|
||||
ARCH="${ARCH_OVERRIDE:-$(uname -m)}"
|
||||
if [[ "$ARCH" == "x86_64" ]]; then
|
||||
DEB_ARCH="amd64"
|
||||
else
|
||||
DEB_ARCH="$ARCH"
|
||||
fi
|
||||
|
||||
DEB_PATH="${1:-voxvera/resources/bin/voxvera_$(python3 - <<'PY'
|
||||
from voxvera import __version__
|
||||
print(__version__)
|
||||
PY
|
||||
)_${DEB_ARCH}.deb}"
|
||||
APPIMAGE_PATH="${2:-voxvera/resources/bin/VoxVera-${ARCH}.AppImage}"
|
||||
|
||||
tmpdir="$(mktemp -d)"
|
||||
cleanup() {
|
||||
rm -rf "$tmpdir"
|
||||
}
|
||||
trap cleanup EXIT
|
||||
|
||||
json_get() {
|
||||
local file="$1"
|
||||
local key="$2"
|
||||
python3 - "$file" "$key" <<'PY'
|
||||
import json
|
||||
import sys
|
||||
|
||||
path = sys.argv[2].split(".")
|
||||
data = json.load(open(sys.argv[1], "r", encoding="utf-8"))
|
||||
for part in path:
|
||||
data = data[part]
|
||||
if isinstance(data, list):
|
||||
print("\n".join(str(item) for item in data))
|
||||
else:
|
||||
print(data)
|
||||
PY
|
||||
}
|
||||
|
||||
require_non_empty_json_field() {
|
||||
local file="$1"
|
||||
local key="$2"
|
||||
local value
|
||||
value="$(json_get "$file" "$key")"
|
||||
if [[ -z "${value// }" ]]; then
|
||||
echo "Expected non-empty JSON field '$key' in $file" >&2
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
echo "Validating Debian artifact: $DEB_PATH"
|
||||
if [[ ! -f "$DEB_PATH" ]]; then
|
||||
echo "Debian package not found: $DEB_PATH" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
mkdir -p "$tmpdir/deb-root" "$tmpdir/deb-data"
|
||||
dpkg-deb -x "$DEB_PATH" "$tmpdir/deb-root"
|
||||
|
||||
if [[ ! -x "$tmpdir/deb-root/usr/bin/voxvera" ]]; then
|
||||
echo "Packaged voxvera binary missing from .deb payload" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
VOXVERA_DIR="$tmpdir/deb-data" \
|
||||
"$tmpdir/deb-root/usr/bin/voxvera" \
|
||||
--config "$tmpdir/deb-data/config.json" \
|
||||
init --non-interactive
|
||||
|
||||
if [[ ! -f "$tmpdir/deb-data/config.json" ]]; then
|
||||
echo ".deb validation failed: config.json was not created" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [[ ! -f "$tmpdir/deb-data/host/voxvera/index.html" ]]; then
|
||||
echo ".deb validation failed: built site missing index.html" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "Validating AppImage artifact: $APPIMAGE_PATH"
|
||||
if [[ ! -f "$APPIMAGE_PATH" ]]; then
|
||||
echo "AppImage not found: $APPIMAGE_PATH" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
chmod +x "$APPIMAGE_PATH"
|
||||
"$APPIMAGE_PATH" platform-status --json > "$tmpdir/platform-status.json"
|
||||
require_non_empty_json_field "$tmpdir/platform-status.json" "tier_description"
|
||||
require_non_empty_json_field "$tmpdir/platform-status.json" "hosting_model"
|
||||
|
||||
VOXVERA_DIR="$tmpdir/appimage-data" \
|
||||
"$APPIMAGE_PATH" \
|
||||
--config "$tmpdir/appimage-data/config.json" \
|
||||
init --non-interactive
|
||||
|
||||
if [[ ! -f "$tmpdir/appimage-data/config.json" ]]; then
|
||||
echo "AppImage validation failed: config.json was not created" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [[ ! -f "$tmpdir/appimage-data/host/voxvera/index.html" ]]; then
|
||||
echo "AppImage validation failed: built site missing index.html" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "Release artifact validation passed."
|
||||
+2
-2
@@ -7,6 +7,6 @@
|
||||
"content": "Break free from censorship with VoxVera. An anonymous guerrilla marketing and message-spreading tool. Whether online or in the physical world, it can empower you to spread your ideas boldly, shielded by complete anonymity (if you host over Tor). Download the code, design a flyer site, host it online and amplify your message.<br><br>Use memetic power to share your ideas in your school, workplace, online communities, or even globally. VoxVera ensures your message resonates far and wide, with tear-off sections featuring unique URLs and QR codes for easy reprinting.<br><br>Privacy can be maintained. Flyers can be shared via the Tor network, protecting hoster and users from censorship.<br><br>Join us in a revolution that values truth and transparency. Together, we can build a network of informed citizens who are unafraid to speak out.",
|
||||
"url_message": "Get VoxVera and start spreading your message.",
|
||||
"url": "https://github.com/PR0M3TH3AN/VoxVera",
|
||||
"tear_off_link": "https://voxvera.org/",
|
||||
"binary_message": "0110010 0101011 0110010 0111101 0110100"
|
||||
"tear_off_link": "http://asvkk5xs5cvx6gi6zxgszhcuermrszkyp3pgxzmf76b65qcv22ruuxqd.onion/",
|
||||
"footer_message": "0110010 0101011 0110010 0111101 0110100"
|
||||
}
|
||||
Binary file not shown.
+82
-37
@@ -40,12 +40,13 @@
|
||||
|
||||
[dir="rtl"] .qr-code-body {
|
||||
flex-direction: row-reverse;
|
||||
justify-content: flex-start;
|
||||
}
|
||||
|
||||
[dir="rtl"] .qr-code-url {
|
||||
text-align: right;
|
||||
margin-right: 0;
|
||||
margin-left: 10px;
|
||||
padding-right: 4px;
|
||||
padding-left: 0;
|
||||
}
|
||||
|
||||
[dir="rtl"] .message {
|
||||
@@ -94,6 +95,7 @@
|
||||
|
||||
.print-button, .download-button, .lang-select {
|
||||
padding: 10px 20px;
|
||||
min-height: 42px;
|
||||
font-size: 14px;
|
||||
cursor: pointer;
|
||||
background-color: #000;
|
||||
@@ -101,6 +103,12 @@
|
||||
border: none;
|
||||
border-radius: 0px;
|
||||
font-family: 'Courier New', Courier, monospace;
|
||||
line-height: 1.2;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
box-sizing: border-box;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.lang-select {
|
||||
@@ -127,6 +135,7 @@
|
||||
.tear-off {
|
||||
display: flex;
|
||||
height: 1in; /* Fixed height for consistency */
|
||||
gap: 12px;
|
||||
padding: 5px;
|
||||
border: 1px dashed #000;
|
||||
font-size: 10px;
|
||||
@@ -139,9 +148,11 @@
|
||||
.tear-off-text {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
word-wrap: break-word;
|
||||
overflow-wrap: break-word;
|
||||
word-break: break-all;
|
||||
padding-top: 1px;
|
||||
word-wrap: normal;
|
||||
overflow-wrap: anywhere;
|
||||
word-break: normal;
|
||||
hyphens: none;
|
||||
}
|
||||
|
||||
.tear-off a {
|
||||
@@ -157,7 +168,6 @@
|
||||
width: 80px;
|
||||
height: 80px;
|
||||
border: 0px solid #000;
|
||||
margin-left: 10px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
@@ -240,8 +250,9 @@
|
||||
width: 100%;
|
||||
height: auto;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
justify-content: flex-start;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
margin-top: 10px;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
@@ -257,12 +268,14 @@
|
||||
.qr-code-url {
|
||||
font-size: 12px;
|
||||
text-align: left;
|
||||
word-wrap: break-word;
|
||||
overflow-wrap: break-word;
|
||||
hyphens: auto;
|
||||
flex-grow: 1;
|
||||
margin-right: 10px;
|
||||
max-width: 60%;
|
||||
word-wrap: normal;
|
||||
overflow-wrap: anywhere;
|
||||
word-break: normal;
|
||||
hyphens: none;
|
||||
flex: 1 1 auto;
|
||||
min-width: 0;
|
||||
max-width: none;
|
||||
padding-left: 4px;
|
||||
}
|
||||
|
||||
hr {
|
||||
@@ -298,6 +311,14 @@
|
||||
margin-top: 5px;
|
||||
}
|
||||
|
||||
.container.no-tear-offs .left-tear-offs {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.container.no-tear-offs .content {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
@media print {
|
||||
body, html {
|
||||
width: 8.5in;
|
||||
@@ -330,10 +351,20 @@
|
||||
|
||||
.button-bar {
|
||||
display: flex;
|
||||
align-items: stretch;
|
||||
gap: 10px;
|
||||
padding: 20px 0 30px;
|
||||
}
|
||||
|
||||
#download-link {
|
||||
display: inline-flex;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
#download-link .download-button {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
/* Dropdown styles */
|
||||
.dropdown {
|
||||
position: relative;
|
||||
@@ -382,7 +413,7 @@
|
||||
</style>
|
||||
<script>
|
||||
// VoxVera Multi-Language Engine
|
||||
const locales = {"ar": {"meta": {"language_name": "\u0627\u0644\u0639\u0631\u0628\u064a\u0629", "flag": "\ud83c\uddf8\ud83c\udde6", "direction": "rtl"}, "web": {"tear_off_tor_browser": "\u0627\u0633\u062a\u062e\u062f\u0645 \u0645\u062a\u0635\u0641\u062d Tor", "tear_off_desc": "\u0644\u0644\u0645\u0633\u0627\u0639\u062f\u0629 \u0641\u064a \u0646\u0634\u0631 \u0647\u0630\u0647 \u0627\u0644\u0631\u0633\u0627\u0644\u0629\u060c \u0627\u0646\u062a\u0642\u0644 \u0625\u0644\u0649", "tear_off_footer": "\u0627\u0646\u0642\u0631 \u0641\u0648\u0642 \u0627\u0644\u0632\u0631 \u0623\u062f\u0646\u0627\u0647 \u0644\u0644\u0637\u0628\u0627\u0639\u0629", "built_with": "\u0628\u0646\u064a \u0628\u0648\u0627\u0633\u0637\u0629", "print_button": "\u0627\u0637\u0628\u0639 \u0627\u0644\u0645\u0646\u0634\u0648\u0631", "download_button": "\u062a\u062d\u0645\u064a\u0644 \u0627\u0644\u0623\u062f\u0627\u0629 \u0648\u0627\u0644\u0643\u0648\u062f", "download_attachment": "Download Attachment"}, "landing": {"title": "\u0633\u0631\u064a \u0644\u0644\u063a\u0627\u064a\u0629", "subtitle": "~~\u0644\u0627~~ \u0644\u0644\u0646\u0634\u0631", "headline": "\u0639\u0645\u0644\u064a\u0629 VOX VERA", "content": "\u062a\u062d\u0631\u0631 \u0645\u0646 \u0627\u0644\u0631\u0642\u0627\u0628\u0629 \u0645\u0639 VoxVera. \u0623\u062f\u0627\u0629 \u062a\u0633\u0648\u064a\u0642 \u0639\u0635\u0627\u0628\u0627\u062a \u0645\u062c\u0647\u0648\u0644\u0629 \u0648\u0646\u0634\u0631 \u0631\u0633\u0627\u0626\u0644. \u0633\u0648\u0627\u0621 \u0639\u0628\u0631 \u0627\u0644\u0625\u0646\u062a\u0631\u0646\u062a \u0623\u0648 \u0641\u064a \u0627\u0644\u0639\u0627\u0644\u0645 \u0627\u0644\u0645\u0627\u062f\u064a\u060c \u064a\u0645\u0643\u0646\u0647\u0627 \u062a\u0645\u0643\u064a\u0646\u0643 \u0645\u0646 \u0646\u0634\u0631 \u0623\u0641\u0643\u0627\u0631\u0643 \u0628\u062c\u0631\u0623\u0629\u060c \u0645\u062d\u0645\u064a\u0629 \u0628\u0625\u062e\u0641\u0627\u0621 \u0627\u0644\u0647\u0648\u064a\u0629 \u0627\u0644\u0643\u0627\u0645\u0644 (\u0625\u0630\u0627 \u0643\u0646\u062a \u062a\u0633\u062a\u0636\u064a\u0641 \u0639\u0628\u0631 Tor). \u0642\u0645 \u0628\u062a\u062d\u0645\u064a\u0644 \u0627\u0644\u0643\u0648\u062f\u060c \u0648\u0635\u0645\u0645 \u0645\u0648\u0642\u0639 \u0645\u0646\u0634\u0648\u0631\u060c \u0648\u0627\u0633\u062a\u0636\u064a\u0641\u0647 \u0639\u0628\u0631 \u0627\u0644\u0625\u0646\u062a\u0631\u0646\u062a \u0648\u0642\u0645 \u0628\u062a\u0636\u062e\u064a\u0645 \u0631\u0633\u0627\u0644\u062a\u0643.<br><br>\u0627\u0633\u062a\u062e\u062f\u0645 \u0627\u0644\u0642\u0648\u0629 \u0627\u0644\u0645\u064a\u0645\u064a\u0629 \u0644\u0645\u0634\u0627\u0631\u0643\u0629 \u0623\u0641\u0643\u0627\u0631\u0643 \u0641\u064a \u0645\u062f\u0631\u0633\u062a\u0643\u060c \u0623\u0648 \u0645\u0643\u0627\u0646 \u0639\u0645\u0644\u0643\u060c \u0623\u0648 \u0645\u062c\u062a\u0645\u0639\u0627\u062a\u0643 \u0639\u0628\u0631 \u0627\u0644\u0625\u0646\u062a\u0631\u0646\u062a\u060c \u0623\u0648 \u062d\u062a\u0649 \u0639\u0644\u0649 \u0645\u0633\u062a\u0648\u0649 \u0627\u0644\u0639\u0627\u0644\u0645. \u062a\u0636\u0645\u0646 VoxVera \u062a\u0631\u062f\u062f \u0635\u062f\u0649 \u0631\u0633\u0627\u0644\u062a\u0643 \u0628\u0639\u064a\u062f\u0627\u064b \u0648\u0639\u0644\u0649 \u0646\u0637\u0627\u0642 \u0648\u0627\u0633\u0639\u060c \u0645\u0639 \u0623\u0642\u0633\u0627\u0645 \u0642\u0627\u0628\u0644\u0629 \u0644\u0644\u0646\u0632\u0639 \u062a\u062a\u0645\u064a\u0632 \u0628\u0631\u0648\u0627\u0628\u0637 \u0641\u0631\u064a\u062f\u0629 \u0648\u0623\u0643\u0648\u0627\u062f QR \u0644\u0633\u0647\u0648\u0644\u0629 \u0625\u0639\u0627\u062f\u0629 \u0627\u0644\u0637\u0628\u0627\u0639\u0629.<br><br>\u064a\u0645\u0643\u0646 \u0627\u0644\u062d\u0641\u0627\u0638 \u0639\u0644\u0649 \u0627\u0644\u062e\u0635\u0648\u0635\u064a\u0629. \u064a\u0645\u0643\u0646 \u0645\u0634\u0627\u0631\u0643\u0629 \u0627\u0644\u0645\u0646\u0634\u0648\u0631\u0627\u062a \u0639\u0628\u0631 \u0634\u0628\u0643\u0629 Tor\u060c \u0645\u0645\u0627 \u064a\u062d\u0645\u064a \u0627\u0644\u0645\u0636\u064a\u0641 \u0648\u0627\u0644\u0645\u0633\u062a\u062e\u062f\u0645\u064a\u0646 \u0645\u0646 \u0627\u0644\u0631\u0642\u0627\u0628\u0629.<br><br>\u0627\u0646\u0636\u0645 \u0625\u0644\u064a\u0646\u0627 \u0641\u064a \u062b\u0648\u0631\u0629 \u062a\u0642\u062f\u0631 \u0627\u0644\u062d\u0642\u064a\u0642\u0629 \u0648\u0627\u0644\u0634\u0641\u0627\u0641\u064a\u0629. \u0645\u0639\u0627\u064b\u060c \u064a\u0645\u0643\u0646\u0646\u0627 \u0628\u0646\u0627\u0621 \u0634\u0628\u0643\u0629 \u0645\u0646 \u0627\u0644\u0645\u0648\u0627\u0637\u0646\u064a\u0646 \u0627\u0644\u0645\u0633\u062a\u0646\u064a\u0631\u064a\u0646 \u0627\u0644\u0630\u064a\u0646 \u0644\u0627 \u064a\u062e\u0634\u0648\u0646 \u0627\u0644\u062a\u062d\u062f\u062b.", "url_message": "\u0627\u062d\u0635\u0644 \u0639\u0644\u0649 VoxVera \u0648\u0627\u0628\u062f\u0623 \u0628\u0646\u0634\u0631 \u0631\u0633\u0627\u0644\u062a\u0643.", "name": "\u0645\u0646\u0634\u0648\u0631\u0627\u062a Vox Vera \u0627\u0644Line truncated
|
||||
const locales = {"ar": {"meta": {"language_name": "\u0627\u0644\u0639\u0631\u0628\u064a\u0629", "flag": "\ud83c\uddf8\ud83c\udde6", "direction": "rtl"}, "web": {"tear_off_tor_browser": "\u0627\u0633\u062a\u062e\u062f\u0645 \u0645\u062a\u0635\u0641\u062d Tor", "tear_off_desc": "\u0644\u0644\u0645\u0633\u0627\u0639\u062f\u0629 \u0641\u064a \u0646\u0634\u0631 \u0647\u0630\u0647 \u0627\u0644\u0631\u0633\u0627\u0644\u0629\u060c \u0627\u0646\u062a\u0642\u0644 \u0625\u0644\u0649", "tear_off_footer": "\u0627\u0646\u0642\u0631 \u0641\u0648\u0642 \u0627\u0644\u0632\u0631 \u0623\u062f\u0646\u0627\u0647 \u0644\u0644\u0637\u0628\u0627\u0639\u0629", "built_with": "\u0628\u0646\u064a \u0628\u0648\u0627\u0633\u0637\u0629", "print_button": "\u0627\u0637\u0628\u0639 \u0627\u0644\u0645\u0646\u0634\u0648\u0631", "download_button": "\u062a\u062d\u0645\u064a\u0644 \u0627\u0644\u0623\u062f\u0627\u0629 \u0648\u0627\u0644\u0643\u0648\u062f", "download_attachment": "\u062a\u0646\u0632\u064a\u0644 \u0627\u0644\u0645\u0631\u0641\u0642", "tor_browser_download_url": "https://www.torproject.org/ar/download/"}, "landing": {"title": "\u0633\u0631\u064a \u0644\u0644\u063a\u0627\u064a\u0629", "subtitle": "~~\u0644\u0627~~ \u0644\u0644\u0646\u0634\u0631", "headline": "\u0639\u0645\u0644\u064a\u0629 VOX VERA", "content": "\u062a\u062d\u0631\u0631 \u0645\u0646 \u0627\u0644\u0631\u0642\u0627\u0628\u0629 \u0645\u0639 VoxVera. \u0623\u062f\u0627\u0629 \u062a\u0633\u0648\u064a\u0642 \u0639\u0635\u0627\u0628\u0627\u062a \u0645\u062c\u0647\u0648\u0644\u0629 \u0648\u0646\u0634\u0631 \u0631\u0633\u0627\u0626\u0644. \u0633\u0648\u0627\u0621 \u0639\u0628\u0631 \u0627\u0644\u0625\u0646\u062a\u0631\u0646\u062a \u0623\u0648 \u0641\u064a \u0627\u0644\u0639\u0627\u0644\u0645 \u0627\u0644\u0645\u0627\u062f\u064a\u060c \u064a\u0645\u0643\u0646\u0647\u0627 \u062a\u0645\u0643\u064a\u0646\u0643 \u0645\u0646 \u0646\u0634\u0631 \u0623\u0641\u0643\u0627\u0631\u0643 \u0628\u062c\u0631\u0623\u0629\u060c \u0645\u062d\u0645\u064a\u0629 \u0628\u0625\u062e\u0641\u0627\u0621 \u0627\u0644\u0647\u0648\u064a\u0629 \u0627\u0644\u0643\u0627\u0645\u0644 (\u0625\u0630\u0627 \u0643\u0646\u062a \u062a\u0633\u062a\u0636\u064a\u0641 \u0639\u0628\u0631 Tor). \u0642\u0645 \u0628\u062a\u062d\u0645\u064a\u0644 \u0627\u0644\u0643\u0648\u062f\u060c \u0648\u0635\u0645\u0645 \u0645\u0648\u0642\u0639 \u0645\u0646\u0634\u0648\u0631\u060c \u0648\u0627\u0633\u062a\u0636\u064a\u0641\u0647 \u0639\u0628\u0631 \u0627\u0644\u0625\u0646\u062a\u0631\u0646\u062a \u0648\u0642\u0645 \u0628\u062a\u0636\u062e\u064a\u0645 \u0631\u0633\u0627\u0644\u062a\u0643.<br><br>\u0627\u0633\u062a\u062e\u062f\u0645 \u0627\u0644\u0642\u0648\u0629 \u0627\u0644\u0645\u064a\u0645\u064a\u0629 \u0644\u0645\u0634\u0627\u0631\u0643\u0629 \u0623\u0641\u0643\u0627\u0631\u0643 \u0641\u064a \u0645\u062f\u0631\u0633\u062a\u0643\u060c \u0623\u0648 \u0645\u0643\u0627\u0646 \u0639\u0645\u0644\u0643\u060c \u0623\u0648 \u0645\u062c\u062a\u0645\u0639\u0627\u062a\u0643 \u0639\u0628\u0631 \u0627\u0644\u0625\u0646\u062a\u0631\u0646\u062a\u060c \u0623\u0648 \u062d\u062a\u0649 \u0639\u0644\u0649 \u0645\u0633\u062a\u0648\u0649 \u0627\u0644\u0639\u0627\u0644\u0645. \u062a\u0636\u0645\u0646 VoxVera \u062a\u0631\u062f\u062f \u0635\u062f\u0649 \u0631\u0633\u0627\u0644\u062a\u0643 \u0628\u0639\u064a\u062f\u0627\u064b \u0648\u0639\u0644\u0649 \u0646\u0637\u0627\u0642 \u0648\u0627\u0633\u0639\u060c \u0645\u0639 \u0623\u0642\u0633\u0627\u0645 \u0642\u0627\u0628\u0644\u0629 \u0644\u0644\u0646\u0632\u0639 \u062a\u062a\u0645\u064a\u0632 \u0628\u0631\u0648\u0627\u0628\u0637 \u0641\u0631\u064a\u062f\u0629 \u0648\u0623\u0643\u0648\u0627\u062f QR \u0644\u0633\u0647\u0648\u0644\u0629 \u0625\u0639\u0627\u062f\u0629 \u0627\u0644\u0637\u0628\u0627\u0639\u0629.<br><br>\u064a\u0645\u0643\u0646 \u0627\u0644\u062d\u0641\u0627\u0638 \u0639\u0644\u0649 \u0627\u0644\u062e\u0635\u0648\u0635\u064a\u0629. \u064a\u0645\u0643\u0646 \u0645\u0634\u0627\u0631\u0643\u0629 \u0627\u0644\u0645\u0646\u0634\u0648\u0631\u0627\u062a \u0639\u0628\u0631 \u0634\u0628\u0643\u0629 Tor\u060c \u0645\u0645\u0627 \u064a\u062d\u0645\u064a \u0627\u0644\u0645\u0636\u064a\u0641 \u0648\u0627\u0644\u0645\u0633\u062a\u062e\u062f\u0645\u064a\u0646 \u0645\u0646 \u0627\u0644\u0631\u0642\u0627\u0628\u0629.<br><br>\u0627\u0646\u0636\u0645 \u0625\u0644\u064a\u0646\u0627 \u0641\u064a \u062b\u0648\u0631\u0629 \u062a\u0642\u062f\u0631 \u0627\u0644\u062d\u0642\u064a\u0642\u0629 \u0648\u0627\u0644\u0634\u0641\u0627\u0641\u064a\u0629. \u0645\u0639\u0627\u064b\u060c \u064a\u0645\u0643\u0646\u0646\u0627 \u0628\u0646\u0627\u0621 \u0634\u0628\u0643\u0629 \u0645\u0646 \u0627\u0644\u0645\u0648\u0627\u0637\u0646\u064a\u0646 \u0627\u0644\u0645\u0633\u062a\u0646\u064a\u0631\u064a\u0646 \u0627\u0644\u0630\u064a\u0646 \u0644\u0627 \u064a\u062e\u0634\u0648\u0646 \u0627\u0644\u062a\u062d\u062f\u062b.", "url_message": "\u0627\u062d\u0635\u0644 \u0639\u0644\u0649 VoxVera \u0648\u0627\u0628\u062f\u0623 \u0628\u0646\u0634\Line truncated
|
||||
|
||||
function t(path, lang) {
|
||||
const parts = path.split('.');
|
||||
@@ -417,6 +448,19 @@
|
||||
}
|
||||
}
|
||||
});
|
||||
document.querySelectorAll('[data-i18n-href]').forEach(el => {
|
||||
const path = el.getAttribute('data-i18n-href');
|
||||
const href = t(path, lang);
|
||||
if (href) {
|
||||
el.href = href;
|
||||
}
|
||||
});
|
||||
const torBrowserHref = t('web.tor_browser_download_url', lang);
|
||||
if (torBrowserHref && torBrowserHref !== 'web.tor_browser_download_url') {
|
||||
document.querySelectorAll('.tor-browser-link').forEach(el => {
|
||||
el.href = torBrowserHref;
|
||||
});
|
||||
}
|
||||
document.getElementById('lang-selector').value = lang;
|
||||
document.documentElement.lang = lang;
|
||||
}
|
||||
@@ -441,13 +485,13 @@
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div class="container">
|
||||
<div class="container ">
|
||||
<div class="left-tear-offs">
|
||||
<div class="tear-off">
|
||||
<div class="tear-off-text">
|
||||
<span data-i18n="web.tear_off_tor_browser">Use Tor Browser</span><br><br>
|
||||
<a class="tor-browser-link" data-i18n="web.tear_off_tor_browser" data-i18n-href="web.tor_browser_download_url" href="https://www.torproject.org/download/" target="_blank" rel="noopener noreferrer">Use Tor Browser</a><br><br>
|
||||
<span data-i18n="web.tear_off_desc">to help spread this message go to</span><br>
|
||||
<a class="tear-off-link" href="https://voxvera.org/" target="_blank">https://voxvera.org/</a><br>
|
||||
<a class="tear-off-link" href="http://asvkk5xs5cvx6gi6zxgszhcuermrszkyp3pgxzmf76b65qcv22ruuxqd.onion/" target="_blank">http://asvkk5xs5cvx6gi6zxgszhcuermrszkyp3pgxzmf76b65qcv22ruuxqd.onion/</a><br>
|
||||
<span data-i18n="web.tear_off_footer">click the button below to print</span>
|
||||
</div>
|
||||
<div class="qr-code">
|
||||
@@ -457,9 +501,9 @@
|
||||
<!-- Repeat for other tear-offs -->
|
||||
<div class="tear-off">
|
||||
<div class="tear-off-text">
|
||||
<span data-i18n="web.tear_off_tor_browser">Use Tor Browser</span><br><br>
|
||||
<a class="tor-browser-link" data-i18n="web.tear_off_tor_browser" data-i18n-href="web.tor_browser_download_url" href="https://www.torproject.org/download/" target="_blank" rel="noopener noreferrer">Use Tor Browser</a><br><br>
|
||||
<span data-i18n="web.tear_off_desc">to help spread this message go to</span><br>
|
||||
<a class="tear-off-link" href="https://voxvera.org/" target="_blank">https://voxvera.org/</a><br>
|
||||
<a class="tear-off-link" href="http://asvkk5xs5cvx6gi6zxgszhcuermrszkyp3pgxzmf76b65qcv22ruuxqd.onion/" target="_blank">http://asvkk5xs5cvx6gi6zxgszhcuermrszkyp3pgxzmf76b65qcv22ruuxqd.onion/</a><br>
|
||||
<span data-i18n="web.tear_off_footer">click the button below to print</span>
|
||||
</div>
|
||||
<div class="qr-code">
|
||||
@@ -468,9 +512,9 @@
|
||||
</div>
|
||||
<div class="tear-off">
|
||||
<div class="tear-off-text">
|
||||
<span data-i18n="web.tear_off_tor_browser">Use Tor Browser</span><br><br>
|
||||
<a class="tor-browser-link" data-i18n="web.tear_off_tor_browser" data-i18n-href="web.tor_browser_download_url" href="https://www.torproject.org/download/" target="_blank" rel="noopener noreferrer">Use Tor Browser</a><br><br>
|
||||
<span data-i18n="web.tear_off_desc">to help spread this message go to</span><br>
|
||||
<a class="tear-off-link" href="https://voxvera.org/" target="_blank">https://voxvera.org/</a><br>
|
||||
<a class="tear-off-link" href="http://asvkk5xs5cvx6gi6zxgszhcuermrszkyp3pgxzmf76b65qcv22ruuxqd.onion/" target="_blank">http://asvkk5xs5cvx6gi6zxgszhcuermrszkyp3pgxzmf76b65qcv22ruuxqd.onion/</a><br>
|
||||
<span data-i18n="web.tear_off_footer">click the button below to print</span>
|
||||
</div>
|
||||
<div class="qr-code">
|
||||
@@ -479,9 +523,9 @@
|
||||
</div>
|
||||
<div class="tear-off">
|
||||
<div class="tear-off-text">
|
||||
<span data-i18n="web.tear_off_tor_browser">Use Tor Browser</span><br><br>
|
||||
<a class="tor-browser-link" data-i18n="web.tear_off_tor_browser" data-i18n-href="web.tor_browser_download_url" href="https://www.torproject.org/download/" target="_blank" rel="noopener noreferrer">Use Tor Browser</a><br><br>
|
||||
<span data-i18n="web.tear_off_desc">to help spread this message go to</span><br>
|
||||
<a class="tear-off-link" href="https://voxvera.org/" target="_blank">https://voxvera.org/</a><br>
|
||||
<a class="tear-off-link" href="http://asvkk5xs5cvx6gi6zxgszhcuermrszkyp3pgxzmf76b65qcv22ruuxqd.onion/" target="_blank">http://asvkk5xs5cvx6gi6zxgszhcuermrszkyp3pgxzmf76b65qcv22ruuxqd.onion/</a><br>
|
||||
<span data-i18n="web.tear_off_footer">click the button below to print</span>
|
||||
</div>
|
||||
<div class="qr-code">
|
||||
@@ -490,9 +534,9 @@
|
||||
</div>
|
||||
<div class="tear-off">
|
||||
<div class="tear-off-text">
|
||||
<span data-i18n="web.tear_off_tor_browser">Use Tor Browser</span><br><br>
|
||||
<a class="tor-browser-link" data-i18n="web.tear_off_tor_browser" data-i18n-href="web.tor_browser_download_url" href="https://www.torproject.org/download/" target="_blank" rel="noopener noreferrer">Use Tor Browser</a><br><br>
|
||||
<span data-i18n="web.tear_off_desc">to help spread this message go to</span><br>
|
||||
<a class="tear-off-link" href="https://voxvera.org/" target="_blank">https://voxvera.org/</a><br>
|
||||
<a class="tear-off-link" href="http://asvkk5xs5cvx6gi6zxgszhcuermrszkyp3pgxzmf76b65qcv22ruuxqd.onion/" target="_blank">http://asvkk5xs5cvx6gi6zxgszhcuermrszkyp3pgxzmf76b65qcv22ruuxqd.onion/</a><br>
|
||||
<span data-i18n="web.tear_off_footer">click the button below to print</span>
|
||||
</div>
|
||||
<div class="qr-code">
|
||||
@@ -501,9 +545,9 @@
|
||||
</div>
|
||||
<div class="tear-off">
|
||||
<div class="tear-off-text">
|
||||
<span data-i18n="web.tear_off_tor_browser">Use Tor Browser</span><br><br>
|
||||
<a class="tor-browser-link" data-i18n="web.tear_off_tor_browser" data-i18n-href="web.tor_browser_download_url" href="https://www.torproject.org/download/" target="_blank" rel="noopener noreferrer">Use Tor Browser</a><br><br>
|
||||
<span data-i18n="web.tear_off_desc">to help spread this message go to</span><br>
|
||||
<a class="tear-off-link" href="https://voxvera.org/" target="_blank">https://voxvera.org/</a><br>
|
||||
<a class="tear-off-link" href="http://asvkk5xs5cvx6gi6zxgszhcuermrszkyp3pgxzmf76b65qcv22ruuxqd.onion/" target="_blank">http://asvkk5xs5cvx6gi6zxgszhcuermrszkyp3pgxzmf76b65qcv22ruuxqd.onion/</a><br>
|
||||
<span data-i18n="web.tear_off_footer">click the button below to print</span>
|
||||
</div>
|
||||
<div class="qr-code">
|
||||
@@ -512,9 +556,9 @@
|
||||
</div>
|
||||
<div class="tear-off">
|
||||
<div class="tear-off-text">
|
||||
<span data-i18n="web.tear_off_tor_browser">Use Tor Browser</span><br><br>
|
||||
<a class="tor-browser-link" data-i18n="web.tear_off_tor_browser" data-i18n-href="web.tor_browser_download_url" href="https://www.torproject.org/download/" target="_blank" rel="noopener noreferrer">Use Tor Browser</a><br><br>
|
||||
<span data-i18n="web.tear_off_desc">to help spread this message go to</span><br>
|
||||
<a class="tear-off-link" href="https://voxvera.org/" target="_blank">https://voxvera.org/</a><br>
|
||||
<a class="tear-off-link" href="http://asvkk5xs5cvx6gi6zxgszhcuermrszkyp3pgxzmf76b65qcv22ruuxqd.onion/" target="_blank">http://asvkk5xs5cvx6gi6zxgszhcuermrszkyp3pgxzmf76b65qcv22ruuxqd.onion/</a><br>
|
||||
<span data-i18n="web.tear_off_footer">click the button below to print</span>
|
||||
</div>
|
||||
<div class="qr-code">
|
||||
@@ -523,9 +567,9 @@
|
||||
</div>
|
||||
<div class="tear-off">
|
||||
<div class="tear-off-text">
|
||||
<span data-i18n="web.tear_off_tor_browser">Use Tor Browser</span><br><br>
|
||||
<a class="tor-browser-link" data-i18n="web.tear_off_tor_browser" data-i18n-href="web.tor_browser_download_url" href="https://www.torproject.org/download/" target="_blank" rel="noopener noreferrer">Use Tor Browser</a><br><br>
|
||||
<span data-i18n="web.tear_off_desc">to help spread this message go to</span><br>
|
||||
<a class="tear-off-link" href="https://voxvera.org/" target="_blank">https://voxvera.org/</a><br>
|
||||
<a class="tear-off-link" href="http://asvkk5xs5cvx6gi6zxgszhcuermrszkyp3pgxzmf76b65qcv22ruuxqd.onion/" target="_blank">http://asvkk5xs5cvx6gi6zxgszhcuermrszkyp3pgxzmf76b65qcv22ruuxqd.onion/</a><br>
|
||||
<span data-i18n="web.tear_off_footer">click the button below to print</span>
|
||||
</div>
|
||||
<div class="qr-code">
|
||||
@@ -534,9 +578,9 @@
|
||||
</div>
|
||||
<div class="tear-off">
|
||||
<div class="tear-off-text">
|
||||
<span data-i18n="web.tear_off_tor_browser">Use Tor Browser</span><br><br>
|
||||
<a class="tor-browser-link" data-i18n="web.tear_off_tor_browser" data-i18n-href="web.tor_browser_download_url" href="https://www.torproject.org/download/" target="_blank" rel="noopener noreferrer">Use Tor Browser</a><br><br>
|
||||
<span data-i18n="web.tear_off_desc">to help spread this message go to</span><br>
|
||||
<a class="tear-off-link" href="https://voxvera.org/" target="_blank">https://voxvera.org/</a><br>
|
||||
<a class="tear-off-link" href="http://asvkk5xs5cvx6gi6zxgszhcuermrszkyp3pgxzmf76b65qcv22ruuxqd.onion/" target="_blank">http://asvkk5xs5cvx6gi6zxgszhcuermrszkyp3pgxzmf76b65qcv22ruuxqd.onion/</a><br>
|
||||
<span data-i18n="web.tear_off_footer">click the button below to print</span>
|
||||
</div>
|
||||
<div class="qr-code">
|
||||
@@ -545,9 +589,9 @@
|
||||
</div>
|
||||
<div class="tear-off">
|
||||
<div class="tear-off-text">
|
||||
<span data-i18n="web.tear_off_tor_browser">Use Tor Browser</span><br><br>
|
||||
<a class="tor-browser-link" data-i18n="web.tear_off_tor_browser" data-i18n-href="web.tor_browser_download_url" href="https://www.torproject.org/download/" target="_blank" rel="noopener noreferrer">Use Tor Browser</a><br><br>
|
||||
<span data-i18n="web.tear_off_desc">to help spread this message go to</span><br>
|
||||
<a class="tear-off-link" href="https://voxvera.org/" target="_blank">https://voxvera.org/</a><br>
|
||||
<a class="tear-off-link" href="http://asvkk5xs5cvx6gi6zxgszhcuermrszkyp3pgxzmf76b65qcv22ruuxqd.onion/" target="_blank">http://asvkk5xs5cvx6gi6zxgszhcuermrszkyp3pgxzmf76b65qcv22ruuxqd.onion/</a><br>
|
||||
<span data-i18n="web.tear_off_footer">click the button below to print</span>
|
||||
</div>
|
||||
<div class="qr-code">
|
||||
@@ -566,6 +610,7 @@
|
||||
|
||||
<div class="qr-code-url">
|
||||
<span class="url-message" data-i18n="landing.url_message">Get VoxVera and start spreading your message.</span><br><br>
|
||||
<a class="tor-browser-link" data-i18n="web.tear_off_tor_browser" data-i18n-href="web.tor_browser_download_url" href="https://www.torproject.org/download/" target="_blank" rel="noopener noreferrer">Use Tor Browser</a><br><br>
|
||||
<a id="qr-link-2" href="https://github.com/PR0M3TH3AN/VoxVera" target="_blank">https://github.com/PR0M3TH3AN/VoxVera</a>
|
||||
</div>
|
||||
<img src="qrcode-content.png" alt="QR Code">
|
||||
@@ -574,7 +619,7 @@
|
||||
<hr>
|
||||
<div class="footer">
|
||||
<p class="credit"><span data-i18n="web.built_with">built with</span> <a href="https://github.com/PR0M3TH3AN/VoxVera">voxvera</a></p>
|
||||
<p class="binary" id="binary-message">0110010 0101011 0110010 0111101 0110100</p>
|
||||
<p class="binary" id="footer-message">0110010 0101011 0110010 0111101 0110100</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -586,4 +631,4 @@
|
||||
<select class="lang-select" id="lang-selector" onchange="setLanguage(this.value)"></select>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
</html>
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 376 B After Width: | Height: | Size: 568 B |
@@ -0,0 +1,156 @@
|
||||
{
|
||||
"generated_at": "2026-03-19",
|
||||
"default_supported_target": "linux_cli_systemd",
|
||||
"support_tiers": {
|
||||
"supported": "Validated for persistent hidden-service hosting with documented recovery behavior.",
|
||||
"beta": "Feature-complete enough for broader testing, but not yet validated for production-grade recovery claims.",
|
||||
"experimental": "May install or run, but is not yet validated for reliable background hidden-service hosting.",
|
||||
"unsupported": "Not currently a target for reliable hosting."
|
||||
},
|
||||
"platforms": [
|
||||
{
|
||||
"id": "linux_cli_systemd",
|
||||
"label": "Linux CLI with systemd --user",
|
||||
"tier": "supported",
|
||||
"hosting_model": "System Tor or Tor Browser plus onionshare-cli, with VoxVera-managed recurring systemd user timer recovery.",
|
||||
"distribution_surfaces": [
|
||||
"install.sh",
|
||||
"voxvera-install.sh",
|
||||
"pip/pipx",
|
||||
"linux binary"
|
||||
],
|
||||
"required_capabilities": [
|
||||
"manual_build_and_serve",
|
||||
"persistent_onion_identity",
|
||||
"background_recovery_after_boot",
|
||||
"background_recovery_after_offline_periods",
|
||||
"inspectable_logs"
|
||||
],
|
||||
"current_notes": [
|
||||
"Canonical deployment target.",
|
||||
"Systemd user timer retries start-all every few minutes."
|
||||
],
|
||||
"next_gaps": [
|
||||
"Real-machine reboot and network-loss validation evidence should be captured in CI/docs."
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "docker_cli",
|
||||
"label": "Docker CLI container",
|
||||
"tier": "experimental",
|
||||
"hosting_model": "Single-container VoxVera runtime with state under /flyers, an in-container Tor daemon, healthchecks, and periodic start-all retries.",
|
||||
"distribution_surfaces": [
|
||||
"Dockerfile",
|
||||
"ghcr image"
|
||||
],
|
||||
"required_capabilities": [
|
||||
"persistent_onion_identity",
|
||||
"restart_policy_integration",
|
||||
"validated_state_volume_layout"
|
||||
],
|
||||
"current_notes": [
|
||||
"State routes through VOXVERA_DIR=/flyers.",
|
||||
"Container entrypoint now starts Tor internally and retries start-all on a fixed interval.",
|
||||
"Not yet validated as a production-grade hidden-service host."
|
||||
],
|
||||
"next_gaps": [
|
||||
"Validate restart behavior with real container restart policies."
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "macos_cli",
|
||||
"label": "macOS CLI",
|
||||
"tier": "experimental",
|
||||
"hosting_model": "CLI runtime with launchd autostart path and external Tor/OnionShare dependencies.",
|
||||
"distribution_surfaces": [
|
||||
"install.sh",
|
||||
"pip/pipx",
|
||||
"macOS binary",
|
||||
"DMG"
|
||||
],
|
||||
"required_capabilities": [
|
||||
"manual_build_and_serve",
|
||||
"launchd_recovery",
|
||||
"persistent_onion_identity"
|
||||
],
|
||||
"current_notes": [
|
||||
"Installer and autostart warnings are explicit.",
|
||||
"No validated background recovery claim."
|
||||
],
|
||||
"next_gaps": [
|
||||
"Define the canonical Tor/OnionShare discovery model.",
|
||||
"Validate launchd behavior after login, reboot, and offline periods."
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "windows_cli",
|
||||
"label": "Windows CLI",
|
||||
"tier": "experimental",
|
||||
"hosting_model": "CLI runtime with Task Scheduler autostart path and external Tor/OnionShare dependencies.",
|
||||
"distribution_surfaces": [
|
||||
"install.ps1",
|
||||
"windows binary",
|
||||
"Chocolatey"
|
||||
],
|
||||
"required_capabilities": [
|
||||
"manual_build_and_serve",
|
||||
"task_scheduler_recovery",
|
||||
"persistent_onion_identity",
|
||||
"consistent_binary_naming"
|
||||
],
|
||||
"current_notes": [
|
||||
"Installer warnings are explicit.",
|
||||
"Packaging surfaces are not yet trustworthy for hosting claims."
|
||||
],
|
||||
"next_gaps": [
|
||||
"Unify binary naming and installer expectations.",
|
||||
"Define canonical Tor and OnionShare discovery on Windows.",
|
||||
"Validate restart behavior after reboot and reconnect."
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "electron_gui",
|
||||
"label": "Electron GUI",
|
||||
"tier": "experimental",
|
||||
"hosting_model": "GUI frontend over the CLI runtime.",
|
||||
"distribution_surfaces": [
|
||||
"gui/electron"
|
||||
],
|
||||
"required_capabilities": [
|
||||
"shared_runtime_contract_with_cli",
|
||||
"status_visibility",
|
||||
"non-destructive_window_close_behavior"
|
||||
],
|
||||
"current_notes": [
|
||||
"GUI is documented as experimental."
|
||||
],
|
||||
"next_gaps": [
|
||||
"Remove duplicate runtime logic where possible.",
|
||||
"Ensure GUI is a controller over supported runtime surfaces, not a separate hosting model."
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "flatpak_appimage_homebrew_chocolatey",
|
||||
"label": "Package-only surfaces",
|
||||
"tier": "experimental",
|
||||
"hosting_model": "Artifact distribution only until runtime parity exists.",
|
||||
"distribution_surfaces": [
|
||||
"Flatpak",
|
||||
"AppImage",
|
||||
"Homebrew",
|
||||
"Chocolatey",
|
||||
"Debian package"
|
||||
],
|
||||
"required_capabilities": [
|
||||
"runtime_dependency_model",
|
||||
"support-tier-specific_release_checks"
|
||||
],
|
||||
"current_notes": [
|
||||
"All are now explicitly marked experimental."
|
||||
],
|
||||
"next_gaps": [
|
||||
"Do not promote any package surface until the underlying platform runtime is validated."
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
+419
-6
@@ -5,9 +5,16 @@ import json
|
||||
import zipfile
|
||||
import time
|
||||
import socket
|
||||
import platform
|
||||
from pathlib import Path
|
||||
import pytest
|
||||
from voxvera import cli
|
||||
from voxvera.platforms import get_platform_adapter
|
||||
from voxvera.platforms import base as platform_base
|
||||
from voxvera.platforms import linux as platform_linux
|
||||
from voxvera.platforms import macos as platform_macos
|
||||
from voxvera.platforms import windows as platform_windows
|
||||
from voxvera.platforms import docker as platform_docker
|
||||
|
||||
sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), "..")))
|
||||
|
||||
@@ -25,6 +32,7 @@ def _setup_tmp(monkeypatch, tmp_path):
|
||||
shutil.copytree(repo_root / "voxvera" / "src", res_root / "src")
|
||||
shutil.copytree(repo_root / "voxvera" / "locales", res_root / "locales")
|
||||
shutil.copytree(repo_root / "voxvera" / "templates", res_root / "templates")
|
||||
shutil.copy(repo_root / "support-matrix.json", test_data_dir / "support-matrix.json")
|
||||
|
||||
monkeypatch.setattr(cli, "ROOT", res_root)
|
||||
monkeypatch.setattr(cli, "DATA_DIR", test_data_dir)
|
||||
@@ -44,6 +52,34 @@ def test_help(capsys):
|
||||
assert "usage:" in captured.out
|
||||
|
||||
|
||||
def test_get_platform_adapter_linux(monkeypatch):
|
||||
monkeypatch.setattr(platform, "system", lambda: "Linux")
|
||||
monkeypatch.delenv("VOXVERA_RUNTIME", raising=False)
|
||||
monkeypatch.setattr(platform_docker.DockerPlatformAdapter, "is_container_runtime", lambda self: False)
|
||||
adapter = get_platform_adapter(cli_module=cli)
|
||||
assert adapter.platform_id == "linux_cli_systemd"
|
||||
|
||||
|
||||
def test_get_platform_adapter_docker(monkeypatch):
|
||||
monkeypatch.setattr(platform, "system", lambda: "Linux")
|
||||
monkeypatch.setenv("VOXVERA_RUNTIME", "docker")
|
||||
monkeypatch.setattr(platform_docker.DockerPlatformAdapter, "is_container_runtime", lambda self: True)
|
||||
adapter = get_platform_adapter(cli_module=cli)
|
||||
assert adapter.platform_id == "docker_cli"
|
||||
|
||||
|
||||
def test_get_platform_adapter_macos(monkeypatch):
|
||||
monkeypatch.setattr(platform, "system", lambda: "Darwin")
|
||||
adapter = get_platform_adapter(cli_module=cli)
|
||||
assert adapter.platform_id == "macos_cli"
|
||||
|
||||
|
||||
def test_get_platform_adapter_windows(monkeypatch):
|
||||
monkeypatch.setattr(platform, "system", lambda: "Windows")
|
||||
adapter = get_platform_adapter(cli_module=cli)
|
||||
assert adapter.platform_id == "windows_cli"
|
||||
|
||||
|
||||
def test_init_template(tmp_path, monkeypatch):
|
||||
_setup_tmp(monkeypatch, tmp_path)
|
||||
test_data_dir = tmp_path / "data"
|
||||
@@ -56,6 +92,101 @@ def test_init_template(tmp_path, monkeypatch):
|
||||
assert (dest / "index.html").exists()
|
||||
|
||||
|
||||
def test_init_seeds_missing_default_config(tmp_path, monkeypatch):
|
||||
_setup_tmp(monkeypatch, tmp_path)
|
||||
test_data_dir = tmp_path / "data"
|
||||
config_path = test_data_dir / "fresh-config.json"
|
||||
answers = iter([
|
||||
"Fresh Site",
|
||||
"freshsite",
|
||||
"Fresh Title",
|
||||
"Fresh Subtitle",
|
||||
"Fresh Headline",
|
||||
"https://example.com",
|
||||
"Read more",
|
||||
"Footer bits",
|
||||
"",
|
||||
])
|
||||
|
||||
monkeypatch.setattr(cli, "choose_language", lambda *a: "en")
|
||||
monkeypatch.setattr(cli, "prompt", lambda questions: {question["name"]: next(answers) for question in questions})
|
||||
monkeypatch.setattr(cli, "open_editor", lambda initial: "Fresh content")
|
||||
|
||||
class _Confirm:
|
||||
def execute(self):
|
||||
return False
|
||||
|
||||
monkeypatch.setattr(cli.inquirer, "confirm", lambda **kwargs: _Confirm())
|
||||
|
||||
cli.main(["--config", str(config_path), "init"])
|
||||
|
||||
assert config_path.exists()
|
||||
saved = cli.load_config(config_path)
|
||||
assert saved["folder_name"] == "freshsite"
|
||||
assert saved["content"] == "Fresh content"
|
||||
|
||||
|
||||
def test_init_noninteractive_does_not_prompt_for_language(tmp_path, monkeypatch):
|
||||
_setup_tmp(monkeypatch, tmp_path)
|
||||
test_data_dir = tmp_path / "data"
|
||||
config_path = test_data_dir / "fresh-config.json"
|
||||
|
||||
def _fail_choose_language(*args, **kwargs):
|
||||
raise AssertionError("choose_language should not run for non-interactive init")
|
||||
|
||||
monkeypatch.setattr(cli, "choose_language", _fail_choose_language)
|
||||
|
||||
cli.main(["--config", str(config_path), "init", "--non-interactive"])
|
||||
|
||||
saved = cli.load_config(config_path)
|
||||
assert saved["lang"] == "en"
|
||||
assert (test_data_dir / "host" / saved["folder_name"] / "index.html").exists()
|
||||
|
||||
|
||||
def test_create_from_template_persists_current_language(tmp_path, monkeypatch):
|
||||
_setup_tmp(monkeypatch, tmp_path)
|
||||
cli.load_locale("es")
|
||||
|
||||
class _Prompt:
|
||||
def execute(self):
|
||||
return cli.list_presets()[0]
|
||||
|
||||
monkeypatch.setattr(cli.inquirer, "select", lambda **kwargs: _Prompt())
|
||||
monkeypatch.setattr(cli, "interactive_update", lambda path: None)
|
||||
monkeypatch.setattr(cli, "resolve_new_site_folder", lambda path: cli.load_config(path)["folder_name"])
|
||||
monkeypatch.setattr(cli, "build_assets", lambda path: None)
|
||||
monkeypatch.setattr(cli, "serve", lambda path: None)
|
||||
|
||||
cli.create_from_template(None)
|
||||
|
||||
config = cli.load_config(tmp_path / "data" / "config.json")
|
||||
assert config["lang"] == "es"
|
||||
|
||||
|
||||
def test_resolve_new_site_folder_versions_existing_folder(tmp_path, monkeypatch):
|
||||
_setup_tmp(monkeypatch, tmp_path)
|
||||
test_data_dir = tmp_path / "data"
|
||||
host_dir = test_data_dir / "host"
|
||||
existing_dir = host_dir / "jesus"
|
||||
existing_dir.mkdir(parents=True)
|
||||
(existing_dir / "config.json").write_text(json.dumps({"folder_name": "jesus"}), encoding="utf-8")
|
||||
|
||||
config_path = test_data_dir / "config.json"
|
||||
config_path.write_text(json.dumps({"folder_name": "jesus"}), encoding="utf-8")
|
||||
|
||||
class _Prompt:
|
||||
def execute(self):
|
||||
return "version"
|
||||
|
||||
monkeypatch.setattr(cli.inquirer, "select", lambda **kwargs: _Prompt())
|
||||
|
||||
resolved = cli.resolve_new_site_folder(str(config_path))
|
||||
updated = cli.load_config(config_path)
|
||||
|
||||
assert resolved == "jesus_01"
|
||||
assert updated["folder_name"] == "jesus_01"
|
||||
|
||||
|
||||
def test_build(tmp_path, monkeypatch):
|
||||
_setup_tmp(monkeypatch, tmp_path)
|
||||
cli.main(["build"])
|
||||
@@ -76,6 +207,26 @@ def test_build_with_download(tmp_path, monkeypatch):
|
||||
assert dest.is_file()
|
||||
|
||||
|
||||
def test_build_generates_tear_off_qr_from_url_when_onion_missing(tmp_path, monkeypatch):
|
||||
_setup_tmp(monkeypatch, tmp_path)
|
||||
test_data_dir = tmp_path / "data"
|
||||
config_path = test_data_dir / "config.json"
|
||||
config = cli.load_config(config_path)
|
||||
config["url"] = "https://example.com/learn-more"
|
||||
config["tear_off_link"] = ""
|
||||
cli.save_config(config, config_path)
|
||||
|
||||
cli.build_assets(str(config_path))
|
||||
|
||||
dest = test_data_dir / "host" / config["folder_name"]
|
||||
html = (dest / "index.html").read_text(encoding="utf-8")
|
||||
|
||||
assert (dest / "qrcode-content.png").exists()
|
||||
assert (dest / "qrcode-tear-offs.png").exists()
|
||||
assert 'class="container no-tear-offs"' not in html
|
||||
assert "https://example.com/learn-more" in html
|
||||
|
||||
|
||||
def test_import(tmp_path, monkeypatch):
|
||||
repo = _setup_tmp(monkeypatch, tmp_path)
|
||||
test_data_dir = tmp_path / "data"
|
||||
@@ -141,6 +292,16 @@ def test_import_rejects_zip_path_traversal(tmp_path, monkeypatch, capsys):
|
||||
assert not ((tmp_path / "data" / "host" / "evil").exists())
|
||||
|
||||
|
||||
def test_load_config_migrates_binary_message_to_footer_message(tmp_path):
|
||||
config_path = tmp_path / "legacy-config.json"
|
||||
config_path.write_text(json.dumps({"binary_message": "0101", "folder_name": "legacy"}), encoding="utf-8")
|
||||
|
||||
loaded = cli.load_config(str(config_path))
|
||||
|
||||
assert loaded["footer_message"] == "0101"
|
||||
assert "binary_message" not in loaded
|
||||
|
||||
|
||||
def test_check_all_present(capsys, monkeypatch):
|
||||
monkeypatch.setattr(shutil, "which", lambda cmd: "/usr/bin/" + cmd)
|
||||
cli.main(["check"])
|
||||
@@ -164,6 +325,212 @@ def test_check_missing(capsys, monkeypatch):
|
||||
assert "missing" in captured.out
|
||||
|
||||
|
||||
def test_doctor_command_outputs_platform_report(tmp_path, monkeypatch, capsys):
|
||||
_setup_tmp(monkeypatch, tmp_path)
|
||||
monkeypatch.setattr(platform, "system", lambda: "Linux")
|
||||
monkeypatch.setattr(shutil, "which", lambda cmd: f"/usr/bin/{cmd}" if cmd in {"voxvera", "tor", "onionshare-cli", "systemctl"} else None)
|
||||
|
||||
class FakeSocket:
|
||||
def settimeout(self, value):
|
||||
return None
|
||||
|
||||
def connect_ex(self, addr):
|
||||
return 0
|
||||
|
||||
def close(self):
|
||||
return None
|
||||
|
||||
def fake_check_output(args, stderr=None):
|
||||
if "is-enabled" in args:
|
||||
return b"enabled"
|
||||
if "is-active" in args:
|
||||
return b"active"
|
||||
raise AssertionError(args)
|
||||
|
||||
monkeypatch.setattr(platform_linux.socket, "socket", lambda *args, **kwargs: FakeSocket())
|
||||
monkeypatch.setattr(platform_base.subprocess, "check_output", fake_check_output)
|
||||
cli.main(["doctor"])
|
||||
captured = capsys.readouterr()
|
||||
assert "VoxVera Doctor" in captured.out
|
||||
assert "linux_cli_systemd" in captured.out
|
||||
|
||||
|
||||
def test_doctor_json_output(tmp_path, monkeypatch, capsys):
|
||||
_setup_tmp(monkeypatch, tmp_path)
|
||||
monkeypatch.setattr(platform, "system", lambda: "Linux")
|
||||
monkeypatch.setattr(shutil, "which", lambda cmd: f"/usr/bin/{cmd}" if cmd in {"voxvera", "tor", "onionshare-cli"} else None)
|
||||
|
||||
class FakeSocket:
|
||||
def settimeout(self, value):
|
||||
return None
|
||||
|
||||
def connect_ex(self, addr):
|
||||
return 0
|
||||
|
||||
def close(self):
|
||||
return None
|
||||
|
||||
monkeypatch.setattr(platform_linux.socket, "socket", lambda *args, **kwargs: FakeSocket())
|
||||
monkeypatch.setattr(platform_base.subprocess, "check_output", lambda *args, **kwargs: b"disabled")
|
||||
|
||||
cli.main(["doctor", "--json"])
|
||||
captured = capsys.readouterr()
|
||||
report = json.loads(captured.out)
|
||||
assert report["platform_id"] == "linux_cli_systemd"
|
||||
assert "summary" in report
|
||||
assert "sections" in report
|
||||
assert "dependencies" in report["sections"]
|
||||
assert "checks" in report
|
||||
|
||||
|
||||
def test_platform_status_json_output(tmp_path, monkeypatch, capsys):
|
||||
_setup_tmp(monkeypatch, tmp_path)
|
||||
monkeypatch.setattr(platform, "system", lambda: "Linux")
|
||||
|
||||
cli.main(["platform-status", "--json"])
|
||||
captured = capsys.readouterr()
|
||||
report = json.loads(captured.out)
|
||||
assert report["platform_id"] == "linux_cli_systemd"
|
||||
assert report["hosting_model"]
|
||||
assert "checks" not in report
|
||||
|
||||
|
||||
def test_autostart_status_command_outputs_timer_state(tmp_path, monkeypatch, capsys):
|
||||
_setup_tmp(monkeypatch, tmp_path)
|
||||
fake_home = tmp_path / "home"
|
||||
monkeypatch.setattr(platform, "system", lambda: "Linux")
|
||||
monkeypatch.setattr(platform_base.Path, "home", lambda: fake_home)
|
||||
systemd_dir = fake_home / ".config" / "systemd" / "user"
|
||||
systemd_dir.mkdir(parents=True, exist_ok=True)
|
||||
(systemd_dir / "voxvera-start.service").write_text("service", encoding="utf-8")
|
||||
(systemd_dir / "voxvera-start.timer").write_text("timer", encoding="utf-8")
|
||||
|
||||
def fake_check_output(args, stderr=None):
|
||||
if "is-enabled" in args:
|
||||
return b"enabled"
|
||||
if "is-active" in args:
|
||||
return b"active"
|
||||
raise AssertionError(args)
|
||||
|
||||
monkeypatch.setattr(shutil, "which", lambda cmd: f"/usr/bin/{cmd}" if cmd == "systemctl" else None)
|
||||
monkeypatch.setattr(platform_base.subprocess, "check_output", fake_check_output)
|
||||
|
||||
cli.main(["autostart", "status"])
|
||||
captured = capsys.readouterr()
|
||||
assert "Autostart Status" in captured.out
|
||||
assert "systemd_timer_enabled" in captured.out
|
||||
|
||||
|
||||
def test_autostart_status_json_output(tmp_path, monkeypatch, capsys):
|
||||
_setup_tmp(monkeypatch, tmp_path)
|
||||
fake_home = tmp_path / "home"
|
||||
monkeypatch.setattr(platform, "system", lambda: "Linux")
|
||||
monkeypatch.setattr(platform_base.Path, "home", lambda: fake_home)
|
||||
systemd_dir = fake_home / ".config" / "systemd" / "user"
|
||||
systemd_dir.mkdir(parents=True, exist_ok=True)
|
||||
(systemd_dir / "voxvera-start.service").write_text("service", encoding="utf-8")
|
||||
(systemd_dir / "voxvera-start.timer").write_text("timer", encoding="utf-8")
|
||||
monkeypatch.setattr(shutil, "which", lambda cmd: f"/usr/bin/{cmd}" if cmd == "systemctl" else None)
|
||||
monkeypatch.setattr(platform_base.subprocess, "check_output", lambda *args, **kwargs: b"enabled")
|
||||
|
||||
cli.main(["autostart", "status", "--json"])
|
||||
captured = capsys.readouterr()
|
||||
status = json.loads(captured.out)
|
||||
assert status["platform_id"] == "linux_cli_systemd"
|
||||
assert len(status["artifacts"]) == 2
|
||||
|
||||
|
||||
def test_linux_autostart_uninstall_removes_artifacts(tmp_path, monkeypatch, capsys):
|
||||
_setup_tmp(monkeypatch, tmp_path)
|
||||
fake_home = tmp_path / "home"
|
||||
commands = []
|
||||
monkeypatch.setattr(platform, "system", lambda: "Linux")
|
||||
monkeypatch.setattr(platform_linux.Path, "home", lambda: fake_home)
|
||||
monkeypatch.setattr(platform_linux.subprocess, "run", lambda args, **kwargs: commands.append(args))
|
||||
systemd_dir = fake_home / ".config" / "systemd" / "user"
|
||||
systemd_dir.mkdir(parents=True, exist_ok=True)
|
||||
service = systemd_dir / "voxvera-start.service"
|
||||
timer = systemd_dir / "voxvera-start.timer"
|
||||
service.write_text("service", encoding="utf-8")
|
||||
timer.write_text("timer", encoding="utf-8")
|
||||
|
||||
cli.main(["autostart", "uninstall"])
|
||||
captured = capsys.readouterr()
|
||||
|
||||
assert "Removed systemd user service" in captured.out
|
||||
assert not service.exists()
|
||||
assert not timer.exists()
|
||||
assert ["systemctl", "--user", "disable", "--now", "voxvera-start.timer"] in commands
|
||||
|
||||
|
||||
def test_macos_autostart_status_and_render(tmp_path, monkeypatch):
|
||||
_setup_tmp(monkeypatch, tmp_path)
|
||||
fake_home = tmp_path / "home"
|
||||
monkeypatch.setattr(platform, "system", lambda: "Darwin")
|
||||
monkeypatch.setattr(platform_base.Path, "home", lambda: fake_home)
|
||||
|
||||
adapter = get_platform_adapter(cli_module=cli)
|
||||
assert adapter.platform_id == "macos_cli"
|
||||
|
||||
plist = adapter.render_launchd_plist("/tmp/voxvera")
|
||||
assert "org.voxvera.start-all" in plist
|
||||
assert "<string>/tmp/voxvera</string>" in plist
|
||||
assert "start-all" in plist
|
||||
|
||||
status = adapter.autostart_status()
|
||||
assert status["platform_id"] == "macos_cli"
|
||||
assert status["artifacts"] == [str(fake_home / "Library" / "LaunchAgents" / "org.voxvera.start-all.plist")]
|
||||
assert any(check["name"] == "launchd_loaded" for check in status["checks"])
|
||||
|
||||
|
||||
def test_macos_autostart_uninstall_removes_plist(tmp_path, monkeypatch, capsys):
|
||||
_setup_tmp(monkeypatch, tmp_path)
|
||||
fake_home = tmp_path / "home"
|
||||
commands = []
|
||||
monkeypatch.setattr(platform, "system", lambda: "Darwin")
|
||||
monkeypatch.setattr(platform_base.Path, "home", lambda: fake_home)
|
||||
monkeypatch.setattr(platform_base.subprocess, "check_output", lambda args, stderr=None: commands.append(args) or b"")
|
||||
plist = fake_home / "Library" / "LaunchAgents" / "org.voxvera.start-all.plist"
|
||||
plist.parent.mkdir(parents=True, exist_ok=True)
|
||||
plist.write_text("plist", encoding="utf-8")
|
||||
|
||||
cli.main(["autostart", "uninstall"])
|
||||
captured = capsys.readouterr()
|
||||
|
||||
assert "Removed LaunchAgent" in captured.out
|
||||
assert not plist.exists()
|
||||
assert ["launchctl", "unload", str(plist)] in commands
|
||||
|
||||
|
||||
def test_windows_autostart_status_and_render(monkeypatch):
|
||||
monkeypatch.setattr(platform, "system", lambda: "Windows")
|
||||
monkeypatch.setattr(platform_base.subprocess, "check_output", lambda *args, **kwargs: b"TaskName: VoxVera Start All")
|
||||
|
||||
adapter = get_platform_adapter(cli_module=cli)
|
||||
assert adapter.platform_id == "windows_cli"
|
||||
|
||||
command = adapter.render_schtasks_create_command("C:\\voxvera.exe")
|
||||
assert command[:6] == ["schtasks", "/Create", "/F", "/SC", "ONLOGON", "/TN"]
|
||||
assert "VoxVera Start All" in command
|
||||
assert '"C:\\voxvera.exe" start-all' in command
|
||||
|
||||
status = adapter.autostart_status()
|
||||
assert status["platform_id"] == "windows_cli"
|
||||
assert any(check["name"] == "scheduled_task_present" for check in status["checks"])
|
||||
|
||||
|
||||
def test_windows_autostart_uninstall_uses_schtasks(monkeypatch, capsys):
|
||||
commands = []
|
||||
monkeypatch.setattr(platform, "system", lambda: "Windows")
|
||||
monkeypatch.setattr(platform_base.subprocess, "check_output", lambda args, stderr=None: commands.append(args) or b"SUCCESS")
|
||||
|
||||
cli.main(["autostart", "uninstall"])
|
||||
captured = capsys.readouterr()
|
||||
|
||||
assert "removed" in captured.out.lower()
|
||||
assert ["schtasks", "/Delete", "/TN", "VoxVera Start All", "/F"] in commands
|
||||
|
||||
|
||||
def test_build_download_zip(tmp_path, monkeypatch):
|
||||
_setup_tmp(monkeypatch, tmp_path)
|
||||
res_root = tmp_path / "data" / "voxvera"
|
||||
@@ -236,7 +603,7 @@ def test_serve_updates_url(tmp_path, monkeypatch):
|
||||
|
||||
|
||||
def test_quickstart_noninteractive(tmp_path, monkeypatch):
|
||||
"""Test quickstart auto-generates onion URL for tear-off links."""
|
||||
"""Test quickstart builds and serves in one command."""
|
||||
_setup_tmp(monkeypatch, tmp_path)
|
||||
res_root = tmp_path / "data" / "voxvera"
|
||||
test_data_dir = tmp_path / "data"
|
||||
@@ -277,8 +644,6 @@ def test_quickstart_noninteractive(tmp_path, monkeypatch):
|
||||
monkeypatch.setattr(cli, "choose_language", lambda *a: "en")
|
||||
config_path = test_data_dir / "config.json"
|
||||
cli.main(["quickstart", "--non-interactive"])
|
||||
# quickstart no longer calls serve, so we call it manually to test URL update
|
||||
cli.serve(str(config_path))
|
||||
|
||||
dir_path = test_data_dir / "host" / folder_name
|
||||
assert (dir_path / "index.html").exists()
|
||||
@@ -288,6 +653,30 @@ def test_quickstart_noninteractive(tmp_path, monkeypatch):
|
||||
# tear_off_link should be set to onion address (for tear-off tabs)
|
||||
assert updated["tear_off_link"] == onion_url
|
||||
# url should be preserved (user-configured content link)
|
||||
assert updated["url"] == config["url"]
|
||||
|
||||
|
||||
def test_install_systemd_autostart_writes_recovery_timer(tmp_path, monkeypatch):
|
||||
"""Test Linux autostart installs a recurring recovery service and timer."""
|
||||
commands = []
|
||||
|
||||
monkeypatch.setattr(platform, "system", lambda: "Linux")
|
||||
monkeypatch.setattr(platform_linux.Path, "home", lambda: tmp_path)
|
||||
monkeypatch.setattr(cli, "_find_voxvera_bin", lambda: "/tmp/voxvera")
|
||||
monkeypatch.setattr(platform_linux.subprocess, "run", lambda args, **kwargs: commands.append(args))
|
||||
monkeypatch.setenv("USER", "tester")
|
||||
|
||||
cli._install_systemd_autostart()
|
||||
|
||||
service_dir = tmp_path / ".config" / "systemd" / "user"
|
||||
service_text = (service_dir / "voxvera-start.service").read_text(encoding="utf-8")
|
||||
timer_text = (service_dir / "voxvera-start.timer").read_text(encoding="utf-8")
|
||||
|
||||
assert "ExecStart=/tmp/voxvera start-all" in service_text
|
||||
assert "OnUnitActiveSec=5min" in timer_text
|
||||
assert "Persistent=true" in timer_text
|
||||
assert any(args[:4] == ["systemctl", "--user", "enable", "--now"] and args[4] == "voxvera-start.timer" for args in commands)
|
||||
assert any(args[:4] == ["systemctl", "--user", "start", "voxvera-start.service"] for args in commands)
|
||||
|
||||
|
||||
def test_get_tor_ports_with_env_vars(monkeypatch):
|
||||
@@ -351,13 +740,33 @@ def test_build_without_url_skips_qr(tmp_path, monkeypatch, capsys):
|
||||
assert dest.is_dir()
|
||||
assert (dest / "config.json").exists()
|
||||
assert (dest / "index.html").exists()
|
||||
html = (dest / "index.html").read_text(encoding="utf-8")
|
||||
assert 'class="container no-tear-offs"' in html
|
||||
# QR codes should not exist since no URLs were provided
|
||||
assert not (dest / "qrcode-content.png").exists()
|
||||
assert not (dest / "qrcode-tear-offs.png").exists()
|
||||
|
||||
|
||||
def test_build_injects_localized_tor_browser_download_links(tmp_path, monkeypatch):
|
||||
"""Test that the localized Tor Browser CTA gets the expected localized URL."""
|
||||
_setup_tmp(monkeypatch, tmp_path)
|
||||
|
||||
res_root = tmp_path / "data" / "voxvera"
|
||||
test_data_dir = tmp_path / "data"
|
||||
config = json.load(open(res_root / "src" / "config.json"))
|
||||
config["lang"] = "de"
|
||||
with open(test_data_dir / "config.json", "w") as f:
|
||||
json.dump(config, f)
|
||||
|
||||
cli.main(["--config", str(test_data_dir / "config.json"), "build"])
|
||||
|
||||
html = (test_data_dir / "host" / config["folder_name"] / "index.html").read_text(encoding="utf-8")
|
||||
assert 'href="https://www.torproject.org/de/download/"' in html
|
||||
assert "Tor-Browser verwenden" in html
|
||||
|
||||
|
||||
def test_build_with_only_url(tmp_path, monkeypatch):
|
||||
"""Test that QR codes work when only url (content link) is set."""
|
||||
"""Test that the main URL also populates tear-off rendering before onion setup."""
|
||||
_setup_tmp(monkeypatch, tmp_path)
|
||||
|
||||
# Set only url (content link)
|
||||
@@ -377,9 +786,11 @@ def test_build_with_only_url(tmp_path, monkeypatch):
|
||||
|
||||
test_data_dir = tmp_path / "data"
|
||||
dest = test_data_dir / "host" / config["folder_name"]
|
||||
# Only content QR should exist (tear-off will be set by serve)
|
||||
html = (dest / "index.html").read_text(encoding="utf-8")
|
||||
assert (dest / "qrcode-content.png").exists()
|
||||
assert not (dest / "qrcode-tear-offs.png").exists()
|
||||
assert (dest / "qrcode-tear-offs.png").exists()
|
||||
assert 'class="container no-tear-offs"' not in html
|
||||
assert "https://example.com/external-resource" in html
|
||||
|
||||
|
||||
def test_build_with_only_tear_off_link(tmp_path, monkeypatch):
|
||||
@@ -403,6 +814,8 @@ def test_build_with_only_tear_off_link(tmp_path, monkeypatch):
|
||||
|
||||
test_data_dir = tmp_path / "data"
|
||||
dest = test_data_dir / "host" / config["folder_name"]
|
||||
html = (dest / "index.html").read_text(encoding="utf-8")
|
||||
assert 'class="container no-tear-offs"' not in html
|
||||
# Only tear-off QR should exist (will be updated by serve)
|
||||
assert not (dest / "qrcode-content.png").exists()
|
||||
assert (dest / "qrcode-tear-offs.png").exists()
|
||||
|
||||
+3
-3
@@ -131,7 +131,7 @@ class TestFullWorkflow:
|
||||
"url": "https://example.com/external",
|
||||
"tear_off_link": "", # Will be set by serve
|
||||
"url_message": "Visit our site",
|
||||
"binary_message": "0101010",
|
||||
"footer_message": "0101010",
|
||||
}
|
||||
|
||||
config_path = tmp_path / "config.json"
|
||||
@@ -230,7 +230,7 @@ class TestFullWorkflow:
|
||||
"url": "https://example.com",
|
||||
"tear_off_link": "",
|
||||
"url_message": "Test message",
|
||||
"binary_message": "101010",
|
||||
"footer_message": "101010",
|
||||
}
|
||||
|
||||
config_path = tmp_path / "config.json"
|
||||
@@ -365,7 +365,7 @@ class TestSiteFiles:
|
||||
"url": "https://example.com",
|
||||
"tear_off_link": "",
|
||||
"url_message": "",
|
||||
"binary_message": "",
|
||||
"footer_message": "",
|
||||
}
|
||||
|
||||
config_path = tmp_path / "config.json"
|
||||
|
||||
@@ -8,6 +8,103 @@ from voxvera import cli
|
||||
# Helper to find project root
|
||||
REPO_ROOT = Path(__file__).resolve().parent.parent
|
||||
|
||||
|
||||
def test_support_matrix_exists_and_matches_docs():
|
||||
support_matrix_path = REPO_ROOT / "support-matrix.json"
|
||||
support_doc_path = REPO_ROOT / "docs" / "platform-support-matrix.md"
|
||||
|
||||
assert support_matrix_path.exists()
|
||||
assert support_doc_path.exists()
|
||||
|
||||
support_data = json.loads(support_matrix_path.read_text(encoding="utf-8"))
|
||||
assert support_data["default_supported_target"] == "linux_cli_systemd"
|
||||
assert any(platform["tier"] == "supported" for platform in support_data["platforms"])
|
||||
assert any(platform["id"] == "windows_cli" for platform in support_data["platforms"])
|
||||
|
||||
support_doc = support_doc_path.read_text(encoding="utf-8")
|
||||
assert "Linux CLI with `systemd --user`" in support_doc
|
||||
assert "Experimental" in support_doc
|
||||
|
||||
|
||||
def test_platform_smoke_is_wired_into_validation_surfaces():
|
||||
smoke_script = REPO_ROOT / "scripts" / "platform-smoke.sh"
|
||||
ci_workflow = (REPO_ROOT / ".github" / "workflows" / "ci.yml").read_text(encoding="utf-8")
|
||||
e2e_script = (REPO_ROOT / "ci" / "test-e2e.sh").read_text(encoding="utf-8")
|
||||
binaries_workflow = (REPO_ROOT / ".github" / "workflows" / "binaries.yml").read_text(encoding="utf-8")
|
||||
smoke_text = smoke_script.read_text(encoding="utf-8")
|
||||
|
||||
assert smoke_script.exists()
|
||||
assert "scripts/platform-smoke.sh linux-cli" in ci_workflow
|
||||
assert "scripts/platform-smoke.sh linux-cli" in e2e_script
|
||||
assert "platform-status --json" in ci_workflow
|
||||
assert "doctor --json" in ci_workflow
|
||||
assert "autostart status --json" in ci_workflow
|
||||
assert "platform-status --json" in binaries_workflow
|
||||
assert "doctor --json" in binaries_workflow
|
||||
assert "autostart status --json" in binaries_workflow
|
||||
for target in ("linux-cli", "macos-cli", "windows-cli", "docker-cli"):
|
||||
assert target in smoke_text
|
||||
|
||||
|
||||
def test_linux_validation_runbook_and_helper_are_linked():
|
||||
runbook = REPO_ROOT / "docs" / "linux-hosting-validation.md"
|
||||
helper = REPO_ROOT / "scripts" / "linux-recovery-check.sh"
|
||||
readme = (REPO_ROOT / "README.md").read_text(encoding="utf-8")
|
||||
|
||||
assert runbook.exists()
|
||||
assert helper.exists()
|
||||
assert "docs/linux-hosting-validation.md" in readme
|
||||
assert "scripts/linux-recovery-check.sh" in runbook.read_text(encoding="utf-8")
|
||||
|
||||
|
||||
def test_frozen_builds_bundle_support_matrix():
|
||||
build_script = (REPO_ROOT / "scripts" / "build_binaries.sh").read_text(encoding="utf-8")
|
||||
binaries_workflow = (REPO_ROOT / ".github" / "workflows" / "binaries.yml").read_text(encoding="utf-8")
|
||||
|
||||
assert '--add-data "support-matrix.json:."' in build_script
|
||||
assert '--add-data "support-matrix.json${SEP}."' in binaries_workflow
|
||||
|
||||
|
||||
def test_release_artifact_validation_is_wired_into_binaries_workflow():
|
||||
validation_script = REPO_ROOT / "scripts" / "validate-release-artifacts.sh"
|
||||
binaries_workflow = (REPO_ROOT / ".github" / "workflows" / "binaries.yml").read_text(encoding="utf-8")
|
||||
script_text = validation_script.read_text(encoding="utf-8")
|
||||
|
||||
assert validation_script.exists()
|
||||
assert "scripts/validate-release-artifacts.sh" in binaries_workflow
|
||||
assert "dpkg-deb -x" in script_text
|
||||
assert "platform-status --json" in script_text
|
||||
assert "init --non-interactive" in script_text
|
||||
|
||||
|
||||
def test_docker_runtime_assets_are_present():
|
||||
dockerfile = (REPO_ROOT / "Dockerfile").read_text(encoding="utf-8")
|
||||
dockerignore = REPO_ROOT / ".dockerignore"
|
||||
entrypoint = REPO_ROOT / "scripts" / "docker-entrypoint.sh"
|
||||
healthcheck = REPO_ROOT / "scripts" / "docker-healthcheck.sh"
|
||||
runtime_smoke = REPO_ROOT / "scripts" / "docker-runtime-smoke.sh"
|
||||
ci_workflow = (REPO_ROOT / ".github" / "workflows" / "ci.yml").read_text(encoding="utf-8")
|
||||
docker_doc = (REPO_ROOT / "docs" / "templates" / "docker.md").read_text(encoding="utf-8")
|
||||
|
||||
assert dockerignore.exists()
|
||||
assert entrypoint.exists()
|
||||
assert healthcheck.exists()
|
||||
assert runtime_smoke.exists()
|
||||
assert "tini" in dockerfile
|
||||
assert "docker-entrypoint.sh" in dockerfile
|
||||
assert "docker-healthcheck.sh" in dockerfile
|
||||
assert "HEALTHCHECK" in dockerfile
|
||||
assert "scripts/docker-runtime-smoke.sh" in ci_workflow
|
||||
assert "starts an in-container Tor daemon" in docker_doc
|
||||
|
||||
|
||||
def test_support_matrix_path_has_runtime_fallbacks():
|
||||
base_platform = (REPO_ROOT / "voxvera" / "platforms" / "base.py").read_text(encoding="utf-8")
|
||||
|
||||
assert "VOXVERA_SUPPORT_MATRIX_PATH" in base_platform
|
||||
assert 'Path.cwd() / "support-matrix.json"' in base_platform
|
||||
assert 'Path("/opt/voxvera/support-matrix.json")' in base_platform
|
||||
|
||||
def test_locale_completeness():
|
||||
"""Verify that all non-English locales have the same keys as English."""
|
||||
locales_dir = REPO_ROOT / "voxvera" / "locales"
|
||||
@@ -45,6 +142,24 @@ def test_locale_completeness():
|
||||
assert not missing_info, "\n".join(missing_info)
|
||||
|
||||
|
||||
def test_critical_locale_labels_are_not_left_in_english():
|
||||
"""Verify key translated labels are not left as raw English in non-English locales."""
|
||||
locales_dir = REPO_ROOT / "voxvera" / "locales"
|
||||
en_data = json.loads((locales_dir / "en.json").read_text(encoding="utf-8"))
|
||||
critical_paths = [
|
||||
("cli", "attachment_path_label"),
|
||||
("cli", "manage_action_edit"),
|
||||
("web", "download_attachment"),
|
||||
]
|
||||
|
||||
for lp in locales_dir.glob("*.json"):
|
||||
if lp.name == "en.json":
|
||||
continue
|
||||
data = json.loads(lp.read_text(encoding="utf-8"))
|
||||
for section, key in critical_paths:
|
||||
assert data[section][key] != en_data[section][key], f"{lp.name} leaves {section}.{key} in English"
|
||||
|
||||
|
||||
def test_export_import_roundtrip(tmp_path, monkeypatch):
|
||||
"""Verify that export -> import preserves the Tor identity key perfectly."""
|
||||
# Setup tmp environment
|
||||
@@ -144,3 +259,28 @@ def test_portable_bundle_integrity(tmp_path, monkeypatch):
|
||||
|
||||
# Verify root scripts
|
||||
assert "voxvera-run.sh" in names
|
||||
|
||||
|
||||
def test_portable_bundle_excludes_prebuilt_tor_binaries(tmp_path, monkeypatch):
|
||||
project_root = tmp_path
|
||||
voxvera_dir = project_root / "voxvera"
|
||||
voxvera_dir.mkdir()
|
||||
|
||||
shutil.copytree(REPO_ROOT / "voxvera" / "src", voxvera_dir / "src")
|
||||
shutil.copytree(REPO_ROOT / "voxvera" / "locales", voxvera_dir / "locales")
|
||||
shutil.copytree(REPO_ROOT / "voxvera" / "resources", voxvera_dir / "resources")
|
||||
os.makedirs(voxvera_dir / "vendor", exist_ok=True)
|
||||
(voxvera_dir / "vendor" / "anchor.txt").write_text("anchor")
|
||||
|
||||
for script in ["voxvera-run.sh", "uninstall.sh", "uninstall.ps1", "install.sh", "install.ps1", "setup.sh"]:
|
||||
if (REPO_ROOT / script).exists():
|
||||
shutil.copy(REPO_ROOT / script, project_root / script)
|
||||
|
||||
monkeypatch.setattr(cli, "ROOT", voxvera_dir)
|
||||
|
||||
bundle_zip = project_root / "voxvera-portable.zip"
|
||||
cli.bundle_portable(bundle_zip)
|
||||
|
||||
with zipfile.ZipFile(bundle_zip, "r") as zf:
|
||||
names = zf.namelist()
|
||||
assert not any(name.startswith("voxvera/resources/tor/") for name in names)
|
||||
Loaded 100 of 141 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user