diff --git a/.flake8 b/.flake8 new file mode 100644 index 0000000..a30b4cf --- /dev/null +++ b/.flake8 @@ -0,0 +1,3 @@ +[flake8] +max-line-length = 120 +ignore = E501,E741,E401,E402 diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..64c94d8 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,23 @@ +name: CI + +on: + push: + branches: [ main ] + pull_request: + +jobs: + lint-test: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - uses: actions/setup-python@v4 + with: + python-version: '3.11' + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install flake8 pytest InquirerPy rich + - name: Lint + run: flake8 voxvera tests + - name: Test + run: pytest diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..b38cd6c --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,44 @@ +name: Release + +on: + push: + tags: + - 'v*.*.*' + +jobs: + build-release: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - uses: actions/setup-python@v4 + with: + python-version: '3.11' + - name: Install build tools + run: | + python -m pip install --upgrade pip + pip install build pyinstaller + - name: Build wheel + run: python -m build --wheel --sdist + - name: Build binary + run: pyinstaller --onefile -n voxvera voxvera/cli.py + - name: Build Docker image + run: docker build -t ghcr.io/${{ github.repository_owner }}/voxvera:${{ github.ref_name }} . + - name: Login to GHCR + uses: docker/login-action@v2 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + - name: Push Docker image + run: docker push ghcr.io/${{ github.repository_owner }}/voxvera:${{ github.ref_name }} + - name: Publish to PyPI + uses: pypa/gh-action-pypi-publish@release/v1 + with: + password: ${{ secrets.PYPI_API_TOKEN }} + - name: Upload release assets + uses: softprops/action-gh-release@v1 + with: + files: | + dist/*.whl + dist/*.tar.gz + dist/voxvera diff --git a/README.md b/README.md index 5e71bc1..f45ef35 100644 --- a/README.md +++ b/README.md @@ -2,6 +2,17 @@ VoxVera provides scripts and templates for producing printable flyers with QR codes. These flyers link to content hosted through Tor and can also include a Nostr page. The project automates building the HTML, generating the QR codes, and copying all assets into a directory under `host/` so they can be served statically. +## TL;DR + +```bash +git clone https://github.com/PR0M3TH3AN/VoxVera.git +cd VoxVera +./install.sh # use install.ps1 on Windows +voxvera quickstart +``` + +See [docs/usage.md](docs/usage.md) for detailed usage instructions. + ## Quick Install Run the installer to set up all dependencies and the `voxvera` CLI in one step. @@ -103,45 +114,7 @@ The script updates the chosen config file, regenerates QR codes, obfuscates `ind Additional documentation is available in the `src/` directory; see [src/README.md](src/README.md) for more details on the obfuscation scripts and additional usage notes. -## Step-by-Step -1. Edit `src/index-master.html` or `src/nostr-master.html` if you need custom content. -2. Run `voxvera init` and follow the prompts, or use `voxvera init --from-pdf path/to/form.pdf`. -3. Host the generated `host/` directory. - The `index.html` file fetches `config.json`, so the flyer must be served via a - local or remote web server rather than opened directly from disk. For a quick - test you can run `python3 -m http.server` inside the folder and then visit the - provided address. +Additional documentation, including step-by-step instructions and hosting guides, lives under the [docs](docs/) directory. -## Batch Import -Place configuration files in an `imports/` directory at the project root. Run - -```bash -voxvera import -``` - -Each JSON file is copied to `src/config.json` and processed with -`voxvera build`. Existing folders under `host/` with the -same subdomain are removed before new files are written. - -## Hosting with OnionShare -The folder under `host/` contains everything needed to serve the -flyer. Use the CLI to publish it over Tor: -The script now resolves the configuration and host paths internally, so it can -be invoked from any directory: - -```bash -voxvera serve -``` - -The script launches `onionshare-cli` in persistent website mode, waits for the -generated onion URL, patches `config.json`, regenerates the QR codes and -obfuscated HTML, and then copies the updated files back into the `host` -directory. The onion address is printed when ready. Keep OnionShare running to -continue hosting. - -`index.html` fetches `config.json` dynamically, so the flyer should be viewed -through a local or remote web server. For quick testing, run -`python3 -m http.server` in the folder and open the provided address instead of -loading the file directly. This project is licensed under the [MIT License](./LICENSE). diff --git a/docs/troubleshooting.md b/docs/troubleshooting.md new file mode 100644 index 0000000..8f5727f --- /dev/null +++ b/docs/troubleshooting.md @@ -0,0 +1,17 @@ +# Troubleshooting + +This page collects common issues encountered when hosting or accessing flyers. + +## Tor connectivity +- 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. + +## Firewall rules +- If `voxvera serve` fails to start OnionShare, verify that outbound connections on ports 9001 and 80 are permitted. +- Corporate or university firewalls can block the hidden service ports required by Tor. + +## SELinux +- On SELinux-enabled distributions you may see `permission denied` errors when OnionShare writes to the `host` directory. +- Run `sudo chcon -Rt svirt_sandbox_file_t host` or disable SELinux enforcement for the folder. + +If problems persist, consult the OnionShare and Tor documentation for more advanced configuration tips. diff --git a/docs/usage.md b/docs/usage.md new file mode 100644 index 0000000..e642670 --- /dev/null +++ b/docs/usage.md @@ -0,0 +1,24 @@ +# Detailed Usage + +This guide covers common CLI workflows. See `docs/docker.md` for Docker instructions and `docs/templates.md` for available flyer templates. + +## Step-by-Step +1. Edit `src/index-master.html` or `src/nostr-master.html` if you need custom content. +2. Run `voxvera init` and follow the prompts, or use `voxvera init --from-pdf path/to/form.pdf`. +3. Host the generated `host/` directory. The `index.html` file fetches `config.json`, so the flyer must be served via a local or remote web server rather than opened directly from disk. For a quick test you can run `python3 -m http.server` inside the folder and then visit the provided address. + +## Batch Import +Place configuration files in an `imports/` directory at the project root and run: +```bash +voxvera import +``` +Each JSON file is copied to `src/config.json` and processed with `voxvera build`. Existing folders under `host/` with the same subdomain are removed before new files are written. + +## Hosting with OnionShare +Use the CLI to publish the flyer over Tor: +```bash +voxvera serve +``` +The script launches `onionshare-cli` in persistent website mode, waits for the generated onion URL, patches `config.json`, regenerates the QR codes and obfuscated HTML, and then copies the updated files back into the `host` directory. Keep OnionShare running to continue hosting. + +`index.html` fetches `config.json` dynamically, so the flyer should be viewed through a local or remote web server. For quick testing, run `python3 -m http.server` in the folder and open the provided address instead of loading the file directly. diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..5b54c34 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,18 @@ +[build-system] +requires = ["setuptools>=42", "wheel"] +build-backend = "setuptools.build_meta" + +[project] +name = "voxvera" +version = "0.1.0" +description = "VoxVera CLI utilities" +readme = "README.md" +authors = [{name = "VoxVera"}] +requires-python = ">=3.9" +dependencies = [ + "InquirerPy", + "rich", +] + +[project.scripts] +voxvera = "voxvera.cli:main" diff --git a/tests/test_cli.py b/tests/test_cli.py new file mode 100644 index 0000000..e447d24 --- /dev/null +++ b/tests/test_cli.py @@ -0,0 +1,13 @@ + +import os, sys +sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), ".."))) + +from voxvera.cli import main +import pytest + + +def test_help(capsys): + with pytest.raises(SystemExit): + main(["-h"]) + captured = capsys.readouterr() + assert "usage:" in captured.out diff --git a/voxvera/cli.py b/voxvera/cli.py index f854569..d40c904 100644 --- a/voxvera/cli.py +++ b/voxvera/cli.py @@ -164,7 +164,8 @@ def serve(config_path: str): logfile = os.path.join(dir_path, 'onionshare.log') proc = subprocess.Popen(['onionshare-cli', '--website', '--public', '--persistent', f'{dir_path}/.onionshare-session', dir_path], stdout=open(logfile, 'w'), stderr=subprocess.STDOUT) try: - import time, re as _re + import time + import re as _re onion_url = None while onion_url is None: time.sleep(1)