Compare commits

...
10 Commits
25 changed files with 341 additions and 219 deletions

No files matched your search

+21
View File
@@ -13,6 +13,7 @@ jobs:
permissions:
contents: write
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, windows-latest, macos-latest]
include:
@@ -65,6 +66,22 @@ jobs:
chmod +x packaging/build_deb.sh
./packaging/build_deb.sh dist/${{ matrix.artifact_name }}
- name: Build AppImage (Linux)
if: matrix.os == 'ubuntu-latest'
run: |
mkdir -p voxvera/resources/bin
cp dist/${{ matrix.artifact_name }} voxvera/resources/bin/voxvera-linux-x86_64
chmod +x packaging/build_appimage.sh
./packaging/build_appimage.sh
- name: Build Flatpak (Linux)
if: matrix.os == 'ubuntu-latest'
run: |
sudo apt-get update
sudo apt-get install -y flatpak flatpak-builder
chmod +x packaging/build_flatpak.sh
./packaging/build_flatpak.sh || echo "Flatpak build failed"
- name: Build .dmg (macOS)
if: matrix.os == 'macos-latest'
run: |
@@ -79,6 +96,8 @@ jobs:
dist/${{ matrix.artifact_name }}
voxvera/resources/bin/*.deb
voxvera/resources/bin/*.dmg
voxvera/resources/bin/*.AppImage
voxvera/resources/bin/*.flatpak
- name: Release
uses: softprops/action-gh-release@v2
@@ -88,6 +107,8 @@ jobs:
dist/${{ matrix.artifact_name }}
voxvera/resources/bin/*.deb
voxvera/resources/bin/*.dmg
voxvera/resources/bin/*.AppImage
voxvera/resources/bin/*.flatpak
fail_on_unmatched_files: false
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+10 -2
View File
@@ -46,6 +46,14 @@ voxvera/src/qrcode-tear-offs.png
# PyInstaller
*.spec
dist_bin/
voxvera/resources/bin/voxvera-*
site/download/voxvera-portable.zip
voxvera/resources/bin/*
!voxvera/resources/bin/.gitkeep
site/download/*.zip
!site/download/voxvera-source.zip
site/download/*.exe
site/download/*.deb
site/download/*.dmg
site/download/*.AppImage
site/download/*.flatpak
site/download/voxvera-linux
site/download/voxvera-linux-x86_64
-13
View File
@@ -1,14 +1 @@
# Upcoming Release Notes
*This document serves as a running changelog for the NEXT version of VoxVera. AI agents must append notable changes here as they are completed. Once a release is explicitly requested by the user, these notes will be used to format the GitHub Release description, and this file should be cleared/reset for the next cycle.*
### Version: Unreleased
#### 🚀 New Features
- *(Add new features here)*
#### 🛠 Improvements
- *(Add improvements here)*
#### 🐛 Bug Fixes
- *(Add bug fixes here)*
+94 -120
View File
@@ -5,7 +5,16 @@ command_exists() {
command -v "$1" >/dev/null 2>&1
}
# Detect package manager
# Detect package manager and sudo
if command_exists sudo; then
SUDO="sudo"
elif [ "$(id -u)" -eq 0 ]; then
SUDO=""
else
echo "This script requires root privileges. Please install sudo or run as root." >&2
exit 1
fi
if command_exists apt-get; then
PM="apt"
elif command_exists dnf; then
@@ -23,44 +32,68 @@ else
exit 1
fi
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; }
install_pkg() {
case "$PM" in
apt)
sudo apt-get update
sudo apt-get install -y "$@"
$SUDO apt-get install -y "$@"
;;
dnf)
sudo dnf install -y "$@"
$SUDO dnf install -y "$@"
;;
yum)
sudo yum install -y "$@"
$SUDO yum install -y "$@"
;;
pacman)
sudo pacman -Sy --noconfirm "$@"
$SUDO pacman -S --noconfirm "$@"
;;
brew)
brew install "$@"
;;
apk)
sudo apk add --no-cache "$@"
$SUDO apk add --no-cache "$@"
;;
esac
}
require_pkg() {
local cmd=$1
local pkg=$2
if ! command_exists "$cmd"; then
install_pkg "$pkg"
fi
}
# Only external runtime dependencies needed (for voxvera serve)
require_pkg tor tor
if [ "$PM" = "brew" ]; then
require_pkg onionshare onionshare
# Ensure core dependencies are present (always run to ensure they are up to date)
msg "Checking and updating system dependencies..."
if [ "$PM" = "apt" ]; then
$SUDO apt-get update
SYSTEM_PKGS=(tor curl git python3-pip python3-venv)
[ -n "$(command -v pipx)" ] || SYSTEM_PKGS+=(pipx)
install_pkg "${SYSTEM_PKGS[@]}"
elif [ "$PM" = "brew" ]; then
brew install tor onionshare curl git
else
require_pkg onionshare-cli onionshare-cli
install_pkg tor curl git
fi
# Onionshare-cli - ensure it's installed and working (always attempt update if using pipx)
msg "Ensuring onionshare-cli is installed and up-to-date..."
if command_exists pipx && (pipx list | grep -q onionshare || ! command_exists onionshare-cli); then
msg "Installing/Updating onionshare-cli via pipx..."
pipx install --force git+https://github.com/onionshare/onionshare.git#subdirectory=cli || warn "pipx onionshare-cli install/update failed"
elif ! command_exists onionshare-cli && ! command_exists onionshare; then
if ! install_pkg onionshare-cli; then
warn "Onionshare-cli not found in system repositories. Attempting pipx install fallback."
if ! command_exists pipx; then
install_pkg pipx
pipx ensurepath --force
export PATH="$HOME/.local/bin:$PATH"
fi
if command_exists pipx; then
msg "Installing onionshare-cli via pipx..."
pipx install --force git+https://github.com/onionshare/onionshare.git#subdirectory=cli || warn "pipx onionshare-cli install failed"
else
warn "Could not install pipx. Please install onionshare-cli manually."
fi
fi
else
msg "onionshare-cli found (system package), skipping pipx install. Run 'pipx install --force ...' if you want the latest git version."
fi
download_binary() {
@@ -69,119 +102,60 @@ download_binary() {
if command_exists curl; then
local status
status=$(curl -w "%{http_code}" -fsSL "$url" -o "$dest" || true)
if [ "$status" = "404" ]; then
return 2
elif [ "$status" != "200" ]; then
return 1
fi
if [ "$status" = "404" ]; then return 2; elif [ "$status" != "200" ]; then return 1; fi
elif command_exists wget; then
local out
out=$(wget --server-response -q "$url" -O "$dest" 2>&1 || true)
if echo "$out" | grep -q "404 Not Found"; then
return 2
elif ! echo "$out" | grep -q "200 OK"; then
return 1
fi
if echo "$out" | grep -q "404 Not Found"; then return 2; elif ! echo "$out" | grep -q "200 OK"; then return 1; fi
else
echo "Install curl or wget to download voxvera" >&2
warn "Install curl or wget to download voxvera"
return 1
fi
chmod +x "$dest"
}
check_local_bin() {
if [[ ":$PATH:" != *":$HOME/.local/bin:"* ]]; then
echo "Add \$HOME/.local/bin to your PATH to run VoxVera."
fi
}
pip_fallback() {
if command_exists pip; then
echo "Attempting pip install as fallback..."
if pip install --user voxvera; then
echo "VoxVera installed successfully via pip."
exit 0
fi
echo "pip installation failed." >&2
else
echo "pip not found for fallback installation" >&2
fi
exit 1
}
pip_repo_fallback() {
if command_exists pip; then
echo "Attempting pip install from repository as fallback..."
if pip install --user git+https://github.com/PR0M3TH3AN/VoxVera; then
echo "VoxVera installed successfully from repository."
exit 0
fi
echo "pip installation from repository failed." >&2
else
echo "pip not found for fallback installation" >&2
fi
exit 1
}
# Install VoxVera (Always use --force to ensure fresh installation)
if command_exists pipx; then
if ! pipx install --force 'voxvera@git+https://github.com/PR0M3TH3AN/VoxVera.git@main'; then
echo "pipx install failed, downloading binary"
install_dir="$HOME/.local/bin"
mkdir -p "$install_dir"
# Detect OS for binary selection
OS="$(uname -s | tr '[:upper:]' '[:lower:]')"
case "$OS" in
linux*) BINARY="voxvera-linux" ;;
darwin*) BINARY="voxvera-macos" ;;
msys*|cygwin*|mingw*) BINARY="voxvera-windows.exe" ;;
*) BINARY="voxvera-linux" ;; # fallback to linux
esac
url="https://github.com/PR0M3TH3AN/VoxVera/releases/latest/download/${BINARY}"
dest="$install_dir/voxvera"
if download_binary "$url" "$dest"; then
check_local_bin
else
rc=$?
echo "Binary download failed." >&2
if [ $rc -eq 2 ]; then
echo "Release asset not found, installing from repository." >&2
pip_repo_fallback
else
pip_fallback
fi
fi
msg "Installing/Re-installing VoxVera via pipx..."
if pipx install --force 'voxvera@git+https://github.com/PR0M3TH3AN/VoxVera.git@main'; then
pipx ensurepath --force
msg "\nVoxVera installed/updated successfully via pipx."
msg "IMPORTANT: Please restart your terminal or run 'source ~/.bashrc' (or your shell config) to use 'voxvera'."
exit 0
fi
else
install_dir="$HOME/.local/bin"
mkdir -p "$install_dir"
# Detect OS for binary selection
OS="$(uname -s | tr '[:upper:]' '[:lower:]')"
case "$OS" in
linux*) BINARY="voxvera-linux" ;;
darwin*) BINARY="voxvera-macos" ;;
msys*|cygwin*|mingw*) BINARY="voxvera-windows.exe" ;;
*) BINARY="voxvera-linux" ;; # fallback to linux
esac
warn "pipx install failed, trying binary fallback..."
fi
url="https://github.com/PR0M3TH3AN/VoxVera/releases/latest/download/${BINARY}"
dest="$install_dir/voxvera"
if download_binary "$url" "$dest"; then
check_local_bin
install_dir="$HOME/.local/bin"
mkdir -p "$install_dir"
OS="$(uname -s | tr '[:upper:]' '[:lower:]')"
case "$OS" in
linux*) BINARY="voxvera-linux" ;;
darwin*) BINARY="voxvera-macos" ;;
*) BINARY="voxvera-linux" ;;
esac
url="https://github.com/PR0M3TH3AN/VoxVera/releases/latest/download/${BINARY}"
dest="$install_dir/voxvera"
msg "Downloading VoxVera binary from $url..."
if download_binary "$url" "$dest"; then
msg "VoxVera binary downloaded to $dest"
[[ ":$PATH:" != *":$HOME/.local/bin:"* ]] && warn "Add \$HOME/.local/bin to your PATH."
else
warn "Binary download failed. Attempting pip install as last resort..."
# Use --break-system-packages on Debian 12+ if necessary
PIP_OPTS="--user --upgrade"
if [ -f /etc/debian_version ]; then
DEB_VER=$(cat /etc/debian_version | cut -d. -f1)
if [[ "$DEB_VER" =~ ^[0-9]+$ ]] && [ "$DEB_VER" -ge 12 ]; then
PIP_OPTS="$PIP_OPTS --break-system-packages"
fi
fi
if pip install $PIP_OPTS 'voxvera@git+https://github.com/PR0M3TH3AN/VoxVera.git@main'; then
msg "VoxVera installed via pip."
else
rc=$?
echo "Binary download failed." >&2
if [ $rc -eq 2 ]; then
echo "Release asset not found, installing from repository." >&2
pip_repo_fallback
else
pip_fallback
fi
die "Installation failed."
fi
fi
echo ""
echo "VoxVera installed successfully."
echo "Run 'voxvera check' to verify your setup."
msg "\nVoxVera installed/updated successfully. Run 'voxvera check' to verify."
+5
View File
@@ -17,6 +17,10 @@ mkdir -p "$APPDIR/usr/bin"
cp "$BINARY" "$APPDIR/usr/bin/voxvera"
chmod +x "$APPDIR/usr/bin/voxvera"
# AppRun symlink (Crucial for AppImage to launch)
ln -sf usr/bin/voxvera "$APPDIR/AppRun"
chmod +x "$APPDIR/AppRun"
# Desktop file
cat > "$APPDIR/voxvera.desktop" <<EOD
[Desktop Entry]
@@ -41,6 +45,7 @@ chmod +x appimagetool
# Run appimagetool
echo "Running appimagetool..."
export ARCH
mkdir -p voxvera/resources/bin
./appimagetool --appimage-extract-and-run "$APPDIR" voxvera/resources/bin/VoxVera-x86_64.AppImage
rm appimagetool
+1
View File
@@ -39,6 +39,7 @@ sed -i "s/^Version: .*/Version: $VERSION/" "$PKG_DIR/DEBIAN/control"
sed -i "s/^Architecture: .*/Architecture: $DEB_ARCH/" "$PKG_DIR/DEBIAN/control"
# Build the package
mkdir -p voxvera/resources/bin
dpkg-deb --build "$PKG_DIR" "voxvera/resources/bin/voxvera_${VERSION}_${DEB_ARCH}.deb"
echo "Debian package created at voxvera/resources/bin/voxvera_${VERSION}_${DEB_ARCH}.deb"
+1
View File
@@ -33,6 +33,7 @@ cp "$BINARY" "$STAGING_DIR/voxvera"
chmod +x "$STAGING_DIR/voxvera"
# Create the DMG
mkdir -p voxvera/resources/bin
hdiutil create -volname "VoxVera $VERSION" -srcfolder "$STAGING_DIR" -ov -format UDZO "voxvera/resources/bin/$DMG_NAME"
echo "DMG created at voxvera/resources/bin/$DMG_NAME"
+1
View File
@@ -30,6 +30,7 @@ rm -rf "$BUILD_DIR" "$REPO_DIR"
flatpak-builder --force-clean --repo="$REPO_DIR" "$BUILD_DIR" "$MANIFEST"
# Generate a standalone .flatpak file
mkdir -p voxvera/resources/bin
flatpak build-bundle "$REPO_DIR" voxvera/resources/bin/VoxVera-x86_64.flatpak org.voxvera.VoxVera
echo "Flatpak bundle created at voxvera/resources/bin/VoxVera-x86_64.flatpak"
+1 -1
View File
@@ -14,7 +14,7 @@ voxvera = ["templates/**", "src/**", "resources/**", "locales/**", "resources/bi
[project]
name = "voxvera"
version = "0.1.7"
version = "0.2.0"
description = "VoxVera CLI utilities"
readme = "README.md"
authors = [{name = "VoxVera"}]
+55 -31
View File
@@ -4,46 +4,70 @@ set -euo pipefail
VERSION="${VERSION:-15.0.7}"
BASE_URL="https://dist.torproject.org/torbrowser"
ARCH=$(uname -m)
if [ "$ARCH" == "aarch64" ] || [ "$ARCH" == "arm64" ]; then
TOR_ARCH="aarch64"
else
TOR_ARCH="x86_64"
fi
# ARCH=$(uname -m) # Not needed for multi-platform download if we hardcode/loop targets
case "$(uname -s)" in
Linux*) PLATFORM=linux; ARCHIVE="tor-expert-bundle-linux-${TOR_ARCH}-${VERSION}.tar.gz"; EXE=tor;;
Darwin*) PLATFORM=mac; ARCHIVE="tor-expert-bundle-macos-${TOR_ARCH}-${VERSION}.tar.gz"; EXE=tor;;
MINGW*|MSYS*|CYGWIN*) PLATFORM=win; ARCHIVE="tor-expert-bundle-windows-x86_64-${VERSION}.tar.gz"; EXE=tor.exe;;
*) echo "Unsupported OS" >&2; exit 1;;
esac
download_platform() {
local platform=$1
local arch=$2
local archive=$3
local exe=$4
local tmpdir=$(mktemp -d)
# trap 'rm -rf "$tmpdir"' EXIT # We'll clean up manually in the loop
TMPDIR=$(mktemp -d)
trap 'rm -rf "$TMPDIR"' EXIT
local url="$BASE_URL/${VERSION}/${archive}"
local dest="$(dirname "$0")/../voxvera/resources/tor/$platform"
echo "--- Platform: $platform ($arch) ---"
echo "Downloading $url"
if ! curl -L "$url" -o "$tmpdir/$archive"; then
echo "Failed to download $url" >&2
rm -rf "$tmpdir"
return 1
fi
URL="$BASE_URL/${VERSION}/${ARCHIVE}"
echo "Extracting..."
if [[ "$archive" == *.zip ]]; then
unzip -q "$tmpdir/$archive" -d "$tmpdir"
else
tar -xf "$tmpdir/$archive" -C "$tmpdir"
fi
echo "Downloading $URL"
curl -L "$URL" -o "$TMPDIR/$ARCHIVE"
mkdir -p "$dest"
echo "Extracting..."
tar -xf "$TMPDIR/$ARCHIVE" -C "$TMPDIR"
local tor_bin=$(find "$tmpdir" -type f -name "$exe" | head -n 1)
local obfs_bin=$(find "$tmpdir" -type f \( -name "lyrebird*" -o -name "obfs4proxy*" \) | head -n 1)
DEST="$(dirname "$0")/../voxvera/resources/tor/$PLATFORM"
mkdir -p "$DEST"
if [[ -z "$tor_bin" || -z "$obfs_bin" ]]; then
echo "Failed to locate tor or lyrebird/obfs4proxy in archive for $platform" >&2
rm -rf "$tmpdir"
return 1
fi
TOR_BIN=$(find "$TMPDIR" -type f -name "$EXE" | head -n 1)
OBFS_BIN=$(find "$TMPDIR" -type f \( -name "lyrebird*" -o -name "obfs4proxy*" \) | head -n 1)
echo "Installing binaries to $dest"
cp "$tor_bin" "$dest/$(basename "$exe")"
cp "$obfs_bin" "$dest/$(basename "$obfs_bin")"
if [[ -z "$TOR_BIN" || -z "$OBFS_BIN" ]]; then
echo "Failed to locate tor or lyrebird/obfs4proxy in archive" >&2
exit 1
fi
chmod +x "$dest/$(basename "$exe")" "$dest/$(basename "$obfs_bin")"
# If it's lyrebird, create a symlink for obfs4proxy for compatibility
if [[ "$(basename "$obfs_bin")" == "lyrebird"* ]]; then
local obfs_name="obfs4proxy"
[[ "$platform" == "win" ]] && obfs_name="obfs4proxy.exe"
echo "Creating symlink $obfs_name -> $(basename "$obfs_bin")"
(cd "$dest" && rm -f "$obfs_name" && ln -s "$(basename "$obfs_bin")" "$obfs_name")
fi
cp "$TOR_BIN" "$DEST/$(basename "$EXE")"
cp "$OBFS_BIN" "$DEST/$(basename "$OBFS_BIN")"
rm -rf "$tmpdir"
}
chmod +x "$DEST/$(basename "$EXE")" "$DEST/$(basename "$OBFS_BIN")"
# Linux x86_64
download_platform "linux" "x86_64" "tor-expert-bundle-linux-x86_64-${VERSION}.tar.gz" "tor"
echo "Installed binaries to $DEST"
# macOS x86_64 (Intel) - works on M1/M2 via Rosetta if needed, but expert bundle usually has universal or specific
download_platform "mac" "x86_64" "tor-expert-bundle-macos-x86_64-${VERSION}.tar.gz" "tor"
# Windows x86_64
download_platform "win" "x86_64" "tor-expert-bundle-windows-x86_64-${VERSION}.tar.gz" "tor.exe"
echo "All platforms updated."
+20 -6
View File
@@ -10,15 +10,29 @@ check_cmd() {
}
# Ensure pip is available
if ! check_cmd pip3 && ! check_cmd pip; then
echo "pip not found. Install Python 3 and pip first."
if ! check_cmd pip3 && ! check_cmd pip && ! check_cmd pipx; then
echo "pip or pipx not found. Install Python 3 and pip/pipx first."
exit 1
fi
PIP=$(check_cmd pip3 && echo pip3 || echo pip)
# Install Python packages used by the CLI and build pipeline
$PIP install --user InquirerPy rich qrcode Pillow jsmin htmlmin2 pypdf
if check_cmd pipx; then
echo "Using pipx/pip for dependency installation..."
# For development, we might want to install in a local venv instead of pipx
# but for simple setup, pipx is safest on modern systems.
# However, setup.sh usually implies installing into the current env for development.
PIP_OPTS="--user --upgrade"
if [ -f /etc/debian_version ]; then
DEB_VER=$(cat /etc/debian_version | cut -d. -f1)
if [[ "$DEB_VER" =~ ^[0-9]+$ ]] && [ "$DEB_VER" -ge 12 ]; then
PIP_OPTS="$PIP_OPTS --break-system-packages"
fi
fi
pip3 install $PIP_OPTS InquirerPy rich qrcode Pillow jsmin htmlmin2 pypdf wcwidth || \
pip install $PIP_OPTS InquirerPy rich qrcode Pillow jsmin htmlmin2 pypdf wcwidth
else
PIP=$(check_cmd pip3 && echo pip3 || echo pip)
$PIP install --user --upgrade InquirerPy rich qrcode Pillow jsmin htmlmin2 pypdf wcwidth
fi
echo "All dependencies are installed."
echo "Run 'voxvera check' to verify your setup."
Binary file not shown.
+74 -27
View File
@@ -2,8 +2,6 @@
# --------------------------------------------------------------------
# VoxVera all-in-one installer (Linux) – run once as a normal user.
# Installs Tor + OnionShare and VoxVera itself.
# Build dependencies (QR, HTML minification, PDF parsing) are Python
# packages installed automatically — no Node.js or ImageMagick needed.
# --------------------------------------------------------------------
set -euo pipefail
@@ -14,46 +12,92 @@ msg() { printf "\e[32m%s\e[0m\n" "$*"; }
warn() { printf "\e[33m%s\e[0m\n" "$*" >&2; }
die() { printf "\e[31mERROR: %s\e[0m\n" "$*" >&2; exit 1; }
# Detect sudo/root
if command_exists sudo; then
SUDO="sudo"
elif [ "$(id -u)" -eq 0 ]; then
SUDO=""
else
die "This script requires root privileges. Please install sudo or run as root."
fi
## -------- determine package manager --------------------------------
if command_exists apt-get; then PM=apt ; UPDATE="sudo apt-get update" ; INSTALL="sudo apt-get install -y"
elif command_exists dnf; then PM=dnf ; UPDATE="sudo dnf -y makecache" ; INSTALL="sudo dnf install -y"
elif command_exists yum; then PM=yum ; UPDATE="sudo yum makecache" ; INSTALL="sudo yum install -y"
elif command_exists pacman; then PM=pacman; UPDATE="sudo pacman -Sy" ; INSTALL="sudo pacman -S --noconfirm"
elif command_exists apk; then PM=apk ; UPDATE="sudo apk update" ; INSTALL="sudo apk add --no-cache"
if command_exists apt-get; then PM=apt ; UPDATE="$SUDO apt-get update" ; INSTALL="$SUDO apt-get install -y"
elif command_exists dnf; then PM=dnf ; UPDATE="$SUDO dnf -y makecache" ; INSTALL="$SUDO dnf install -y"
elif command_exists yum; then PM=yum ; UPDATE="$SUDO yum makecache" ; INSTALL="$SUDO yum install -y"
elif command_exists pacman; then PM=pacman; UPDATE="$SUDO pacman -Sy" ; INSTALL="$SUDO pacman -S --noconfirm"
elif command_exists apk; then PM=apk ; UPDATE="$SUDO apk update" ; INSTALL="$SUDO apk add --no-cache"
else die "Unsupported distro – can't find apt/yum/dnf/pacman/apk."
fi
msg ">> Detected package manager: $PM"
## -------- install system packages ----------------------------------
# Only runtime dependencies for Tor hosting are needed as system packages.
# Build tools (QR, minification, PDF parsing) are all Python packages now.
SYSTEM_PKGS=(tor curl)
# OnionShare package name differs per repo:
[[ $PM =~ (apt|dnf|yum) ]] && SYSTEM_PKGS+=(onionshare-cli) || SYSTEM_PKGS+=(onionshare)
msg ">> Installing system dependencies..."
SYSTEM_PKGS=(tor curl git python3-pip python3-venv)
$UPDATE && $INSTALL "${SYSTEM_PKGS[@]}"
# Onionshare-cli - ensure it's installed and working (always attempt update if using pipx)
msg ">> Ensuring onionshare-cli is installed and up-to-date..."
if command_exists pipx && (pipx list | grep -q onionshare || ! command_exists onionshare-cli); then
msg "Installing/Updating onionshare-cli via pipx..."
pipx install --force git+https://github.com/onionshare/onionshare.git#subdirectory=cli || warn "pipx OnionShare install/update failed"
elif ! command_exists onionshare-cli && ! command_exists onionshare; then
msg "Attempting to install OnionShare..."
OS_PKG="onionshare-cli"
[[ ! $PM =~ (apt|dnf|yum) ]] && OS_PKG="onionshare"
if ! $INSTALL "$OS_PKG"; then
warn "OnionShare not found in system repositories. Attempting pipx install fallback."
if ! command_exists pipx; then
msg "Installing pipx..."
$INSTALL pipx
pipx ensurepath --force
export PATH="$HOME/.local/bin:$PATH"
fi
if command_exists pipx; then
msg "Installing/Updating onionshare-cli via pipx..."
pipx install --force git+https://github.com/onionshare/onionshare.git#subdirectory=cli || warn "pipx OnionShare install failed"
else
warn "pipx not found. Skipping OnionShare installation."
fi
fi
fi
## -------- install VoxVera (prefers pipx, falls back) ---------------
install_voxvera() {
if command_exists pipx; then
pipx install --force 'voxvera@git+https://github.com/PR0M3TH3AN/VoxVera.git@main' && return 0
msg "Installing/Re-installing VoxVera via pipx..."
if pipx install --force 'voxvera@git+https://github.com/PR0M3TH3AN/VoxVera.git@main'; then
pipx ensurepath --force
return 0
fi
fi
# fallback: binary
local install_dir="$HOME/.local/bin"
mkdir -p "$install_dir"
local url latest dest="$install_dir/voxvera"
url=$(curl -fsSLI -o /dev/null -w '%{url_effective}' \
https://github.com/PR0M3TH3AN/VoxVera/releases/latest)
latest="${url%/}" ; latest="${latest##*/}" # vX.Y.Z tag
url="https://github.com/PR0M3TH3AN/VoxVera/releases/download/${latest/\/}/voxvera-linux"
if curl -fsSL "$url" -o "$dest"; then
chmod +x "$dest"
PATH="$install_dir:$PATH"
return 0
if command_exists curl; then
msg "Downloading VoxVera binary..."
url=$(curl -fsSLI -o /dev/null -w '%{url_effective}' \
https://github.com/PR0M3TH3AN/VoxVera/releases/latest)
latest="${url%/}" ; latest="${latest##*/}" # vX.Y.Z tag
url="https://github.com/PR0M3TH3AN/VoxVera/releases/download/${latest/\/}/voxvera-linux"
if curl -fsSL "$url" -o "$dest"; then
chmod +x "$dest"
return 0
fi
fi
# fallback: pip
pip install --user 'voxvera@git+https://github.com/PR0M3TH3AN/VoxVera.git@main'
msg "Installing/Updating VoxVera via pip..."
PIP_OPTS="--user --upgrade"
if [ -f /etc/debian_version ]; then
DEB_VER=$(cat /etc/debian_version | cut -d. -f1)
if [[ "$DEB_VER" =~ ^[0-9]+$ ]] && [ "$DEB_VER" -ge 12 ]; then
PIP_OPTS="$PIP_OPTS --break-system-packages"
fi
fi
pip install $PIP_OPTS 'voxvera@git+https://github.com/PR0M3TH3AN/VoxVera.git@main'
}
msg ">> Installing VoxVera"
@@ -70,13 +114,16 @@ EOF
# enable + start tor via systemctl if available
if command_exists systemctl; then
sudo systemctl enable tor || true
sudo systemctl restart tor
$SUDO systemctl enable tor || true
$SUDO systemctl restart tor || true
fi
msg "------------------------------------------------------------------"
msg " VoxVera install finished!"
msg " Tor rc file : $TOR_DIR/torrc"
msg " Make sure \$HOME/.local/bin is in your PATH."
msg " Run 'voxvera check' to verify your setup."
msg ""
msg " IMPORTANT: Please restart your terminal or run 'source ~/.bashrc'"
msg " (or your shell config) to use 'voxvera'."
msg ""
msg " Run 'voxvera check' to verify your setup after refreshing."
msg "------------------------------------------------------------------"
+1 -1
View File
@@ -1,7 +1,7 @@
import sys
from pathlib import Path
__version__ = "0.1.7"
__version__ = "0.2.0"
# Add the vendor directory to sys.path as a fallback.
# For standard installs (pip/pipx), the installed dependencies in the venv
+54 -15
View File
@@ -22,16 +22,23 @@ CURRENT_LOCALE = {}
def _template_res(*parts) -> Traversable:
"""Return a Traversable for files under the packaged ``templates`` folder."""
if getattr(sys, 'frozen', False):
# PyInstaller bundle: use sys._MEIPASS
return Path(sys._MEIPASS).joinpath("voxvera", "templates", *parts)
return resources.files(__package__).joinpath("templates", *parts)
def _src_res(*parts) -> Traversable:
"""Return a Traversable for files under the packaged ``src`` folder."""
if getattr(sys, 'frozen', False):
return Path(sys._MEIPASS).joinpath("voxvera", "src", *parts)
return resources.files(__package__).joinpath("src", *parts)
def _locale_res(*parts) -> Traversable:
"""Return a Traversable for files under the packaged ``locales`` folder."""
if getattr(sys, 'frozen', False):
return Path(sys._MEIPASS).joinpath("voxvera", "locales", *parts)
return resources.files(__package__).joinpath("locales", *parts)
@@ -39,13 +46,22 @@ def load_locale(lang_code: str = "en"):
"""Load the specified locale JSON file."""
global CURRENT_LOCALE
try:
with resources.as_file(_locale_res(f"{lang_code}.json")) as locale_file:
res = _locale_res(f"{lang_code}.json")
if getattr(sys, 'frozen', False):
# When frozen, we already have a Path object
locale_file = res
if not locale_file.exists():
lang_code = "en" # Fallback
with resources.as_file(_locale_res("en.json")) as fallback_file:
CURRENT_LOCALE = json.loads(fallback_file.read_text(encoding="utf-8"))
else:
CURRENT_LOCALE = json.loads(locale_file.read_text(encoding="utf-8"))
lang_code = "en"
locale_file = _locale_res("en.json")
CURRENT_LOCALE = json.loads(locale_file.read_text(encoding="utf-8"))
else:
with resources.as_file(res) as locale_file:
if not locale_file.exists():
lang_code = "en" # Fallback
with resources.as_file(_locale_res("en.json")) as fallback_file:
CURRENT_LOCALE = json.loads(fallback_file.read_text(encoding="utf-8"))
else:
CURRENT_LOCALE = json.loads(locale_file.read_text(encoding="utf-8"))
except Exception as e:
print(f"Error loading locale {lang_code}: {e}")
# Final safety fallback to empty dict to avoid KeyErrors
@@ -575,8 +591,15 @@ def _internal_onionshare():
# Try to add bundled Tor to PATH so OnionShare can find it
system = platform.system().lower()
platform_dir = "win" if "windows" in system else "mac" if "darwin" in system else "linux"
tor_dir = ROOT / "resources" / "tor" / platform_dir
if tor_dir.exists():
if getattr(sys, 'frozen', False):
tor_dir = Path(sys._MEIPASS).joinpath("voxvera", "resources", "tor", platform_dir)
else:
tor_dir = ROOT / "resources" / "tor" / platform_dir
# Check if bundled Tor is a real binary and not a placeholder (size > 100 bytes)
bundled_tor = tor_dir / ("tor.exe" if "windows" in system else "tor")
if bundled_tor.exists() and bundled_tor.stat().st_size > 100:
os.environ["PATH"] = str(tor_dir) + os.pathsep + os.environ.get("PATH", "")
try:
@@ -808,9 +831,15 @@ def import_multiple_sites(source_dir: str = None):
def build_docs():
"""Generate localized documentation from templates and locale data."""
template_dir = ROOT.parent / "docs" / "templates"
if getattr(sys, 'frozen', False):
template_dir = Path(sys._MEIPASS).joinpath("docs", "templates")
doc_root = Path(sys._MEIPASS).joinpath("docs")
else:
template_dir = ROOT.parent / "docs" / "templates"
doc_root = ROOT.parent / "docs"
if not template_dir.exists():
print("Error: docs/templates/ not found.")
print(f"Error: {template_dir} not found.")
return
# List all supported languages
@@ -820,7 +849,7 @@ def build_docs():
for lang in langs:
print(f"Generating documentation for: {lang}")
load_locale(lang)
dest_dir = ROOT.parent / "docs" / lang
dest_dir = doc_root / lang
os.makedirs(dest_dir, exist_ok=True)
# Process each template
@@ -855,14 +884,18 @@ def build_docs():
def build_site():
"""Refresh the main site/ directory with the latest template, QRs, and portable bundle."""
site_dir = ROOT.parent / "site"
if getattr(sys, 'frozen', False):
site_dir = Path(sys._MEIPASS).joinpath("site")
else:
site_dir = ROOT.parent / "site"
if not site_dir.exists():
print("Error: site/ directory not found.")
print(f"Error: {site_dir} directory not found.")
return
config_path = site_dir / "config.json"
if not config_path.exists():
print("Error: site/config.json not found.")
print(f"Error: {config_path} not found.")
return
print("Refreshing site/ assets...")
@@ -873,9 +906,15 @@ def build_site():
# 2. Build the HTML from master template
data = load_config(config_path)
with resources.as_file(_src_res("index-master.html")) as template_path:
res = _src_res("index-master.html")
if getattr(sys, 'frozen', False):
template_path = res
with open(template_path, "r") as fh:
html = fh.read()
else:
with resources.as_file(res) as template_path:
with open(template_path, "r") as fh:
html = fh.read()
# Bundle locales (Include landing tokens for the main site)
all_locales = {}
Binary file not shown.
Binary file not shown.
-1
View File
@@ -1 +0,0 @@
placeholder
+1
View File
@@ -0,0 +1 @@
lyrebird
Binary file not shown.
BIN
View File
Binary file not shown.
-1
View File
@@ -1 +0,0 @@
placeholder
+1
View File
@@ -0,0 +1 @@
lyrebird
Binary file not shown.
Binary file not shown.
-1
View File
@@ -1 +0,0 @@
placeholder
+1
View File
@@ -0,0 +1 @@
lyrebird.exe
Binary file not shown.