feat(identity): rename user-visible identity to voculinux with one-time legacy import
The fork takes its own name everywhere a user can see it: binaries (voculinux, voculinux-gui), desktop entry, autostart, tray, D-Bus namespace (com.voculinux.Voculinux, no alias for the old name), config/data/cache dirs, single-instance lock, IBus engine identity, tray icons, resource prefixes, VOCULINUX_* environment names, and the update-checker user agent. The internal Python module stays vocalinux and the distribution metadata keeps the upstream URLs as provenance. On first run a previous Vocalinux install's config.json, custom dictionary, and portal restore token are copied once into the new locations (new config always wins, no merge, no write-back); models re-resolve through the pinned-checksum download path. An offline naming-sweep test proves no old-name strings remain on user-visible surfaces outside the allowlist. Refs #18
This commit is contained in:
@@ -232,7 +232,7 @@ def _build(python: str, output: Path, diagnostics: Path) -> None:
|
||||
env,
|
||||
diagnostics / "inputs.log",
|
||||
)
|
||||
with tempfile.TemporaryDirectory(prefix="vocalinux-wheel-") as temporary:
|
||||
with tempfile.TemporaryDirectory(prefix="voculinux-wheel-") as temporary:
|
||||
scratch = Path(temporary)
|
||||
source = scratch / "snapshot"
|
||||
source.mkdir()
|
||||
|
||||
@@ -6,7 +6,7 @@ check_deps() {
|
||||
MISSING=0
|
||||
WARNINGS=0
|
||||
|
||||
echo "Checking Vocalinux system dependencies..."
|
||||
echo "Checking Voculinux system dependencies..."
|
||||
echo ""
|
||||
|
||||
# Check for Python 3.11+ (requires-python in pyproject.toml)
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Regenerate src/vocalinux/utils/model_checksums.txt from upstream metadata.
|
||||
|
||||
Vocalinux downloads 40MB-2GB speech models at install time and on first run,
|
||||
Voculinux downloads 40MB-2GB speech models at install time and on first run,
|
||||
then hands them to native code (whisper.cpp loads its ggml files through
|
||||
``ctypes.cdll``). Nothing about that is safe unless we know what we received,
|
||||
so every downloader checks the file against a hash pinned *in this repository*
|
||||
@@ -46,7 +46,7 @@ from typing import Iterable, NamedTuple
|
||||
|
||||
REPO_ROOT = Path(__file__).resolve().parents[1]
|
||||
SRC = REPO_ROOT / "src"
|
||||
MANIFEST = SRC / "vocalinux" / "utils" / "model_checksums.txt"
|
||||
MANIFEST = SRC / "voculinux" / "utils" / "model_checksums.txt"
|
||||
|
||||
HF_REPO = "ggerganov/whisper.cpp"
|
||||
HF_API = f"https://huggingface.co/api/models/{HF_REPO}?blobs=true"
|
||||
@@ -65,7 +65,7 @@ class Entry(NamedTuple):
|
||||
|
||||
|
||||
def _fetch_json(url: str):
|
||||
request = urllib.request.Request(url, headers={"User-Agent": "vocalinux-checksum-generator"})
|
||||
request = urllib.request.Request(url, headers={"User-Agent": "voculinux-checksum-generator"})
|
||||
with urllib.request.urlopen(request, timeout=TIMEOUT) as response: # noqa: S310
|
||||
return json.load(response)
|
||||
|
||||
@@ -93,7 +93,7 @@ def _download_and_hash(url: str, expected_md5: str) -> tuple[str, int]:
|
||||
for rather than pinning either one.
|
||||
"""
|
||||
sha256, md5, size = hashlib.sha256(), hashlib.md5(), 0
|
||||
request = urllib.request.Request(url, headers={"User-Agent": "vocalinux-checksum-generator"})
|
||||
request = urllib.request.Request(url, headers={"User-Agent": "voculinux-checksum-generator"})
|
||||
with urllib.request.urlopen(request, timeout=TIMEOUT) as response: # noqa: S310
|
||||
for chunk in iter(lambda: response.read(1024 * 1024), b""):
|
||||
sha256.update(chunk)
|
||||
@@ -306,7 +306,7 @@ def _vosk_entries(wanted: Iterable[str], refresh: bool) -> list[Entry]:
|
||||
def _render(entries: list[Entry], revision: str) -> str:
|
||||
width = max(len(entry.filename) for entry in entries)
|
||||
lines = [
|
||||
"# Checksums for every speech model Vocalinux downloads.",
|
||||
"# Checksums for every speech model Voculinux downloads.",
|
||||
"#",
|
||||
"# Generated by scripts/generate-model-checksums.py -- do not edit by hand.",
|
||||
"# Regenerate with `just model-checksums` after adding a model to",
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Generate Vocalinux notification WAVs.
|
||||
"""Generate Voculinux notification WAVs.
|
||||
|
||||
Writes 16-bit PCM mono files at 44.1 kHz into resources/sounds/:
|
||||
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
# fetch+reset over the existing clone.
|
||||
#
|
||||
# The remote is a local mirror so the gate answers for *this commit*: the tag
|
||||
# is published from HEAD's tree and VOCALINUX_REPO_URL points the bootstrap at
|
||||
# is published from HEAD's tree and VOCULINUX_REPO_URL points the bootstrap at
|
||||
# it. A gate that cloned the public repo would install the last release, not
|
||||
# the checkout under test. The tag's own install.sh is the bootstrap: the #701
|
||||
# guardrail allows the bootstrap to come from main, and this commit's copy is
|
||||
@@ -37,10 +37,10 @@ INSTALL_HOME="/home/$INSTALL_USER"
|
||||
# Opaque on purpose: a name the public repository cannot already carry, so a
|
||||
# silent fallback to GitHub fails loudly instead of installing the wrong ref.
|
||||
GATE_TAG="${GATE_TAG:-v0.0.0-remote-gate}"
|
||||
MIRROR="/srv/vocalinux-remote.git"
|
||||
MIRROR="/srv/voculinux-remote.git"
|
||||
BOOTSTRAP="/tmp/vl.sh"
|
||||
CLONE_DIR="$INSTALL_HOME/.local/share/vocalinux-install"
|
||||
REMOTE_VENV="$INSTALL_HOME/.local/share/vocalinux/venv"
|
||||
CLONE_DIR="$INSTALL_HOME/.local/share/voculinux-install"
|
||||
REMOTE_VENV="$INSTALL_HOME/.local/share/voculinux/venv"
|
||||
|
||||
fail() {
|
||||
echo "FAIL: $*" >&2
|
||||
@@ -48,11 +48,11 @@ fail() {
|
||||
exit 1
|
||||
}
|
||||
|
||||
# install.sh writes a full transcript under ~/.local/state/vocalinux/. It is the
|
||||
# install.sh writes a full transcript under ~/.local/state/voculinux/. It is the
|
||||
# only place the per-distro package manager output survives.
|
||||
dump_install_log() {
|
||||
local log
|
||||
log="$(ls -1t "$INSTALL_HOME"/.local/state/vocalinux/install-*.log 2>/dev/null | head -n1 || true)"
|
||||
log="$(ls -1t "$INSTALL_HOME"/.local/state/voculinux/install-*.log 2>/dev/null | head -n1 || true)"
|
||||
if [ -n "$log" ]; then
|
||||
echo "== last 200 lines of $log ==" >&2
|
||||
tail -n 200 "$log" >&2
|
||||
@@ -142,9 +142,9 @@ chmod 0644 "$BOOTSTRAP"
|
||||
|
||||
run_remote_install() {
|
||||
# cd to $INSTALL_HOME so the bootstrap sees no checkout: local mode is chosen
|
||||
# by a pyproject.toml naming vocalinux in the working directory, and the
|
||||
# by a pyproject.toml naming voculinux in the working directory, and the
|
||||
# remote path is the one under test.
|
||||
su - "$INSTALL_USER" -c "cd '$INSTALL_HOME' && VOCALINUX_REPO_URL='file://$MIRROR' bash '$BOOTSTRAP' --auto --skip-models '--tag=$GATE_TAG'"
|
||||
su - "$INSTALL_USER" -c "cd '$INSTALL_HOME' && VOCULINUX_REPO_URL='file://$MIRROR' bash '$BOOTSTRAP' --auto --skip-models '--tag=$GATE_TAG'"
|
||||
}
|
||||
|
||||
# The user's literal `curl | bash`: installer code reaches bash on a pipe while
|
||||
@@ -187,7 +187,7 @@ drip "$bootstrap" | bash -s -- "$@"
|
||||
EOF
|
||||
chmod 0755 "$PIPED_FEED"
|
||||
run_remote_install_piped() {
|
||||
su - "$INSTALL_USER" -c "cd '$INSTALL_HOME' && VOCALINUX_REPO_URL='file://$MIRROR' script -qec \"'$PIPED_FEED' '$BOOTSTRAP' --auto --skip-models '--tag=$GATE_TAG'\" /dev/null"
|
||||
su - "$INSTALL_USER" -c "cd '$INSTALL_HOME' && VOCULINUX_REPO_URL='file://$MIRROR' script -qec \"'$PIPED_FEED' '$BOOTSTRAP' --auto --skip-models '--tag=$GATE_TAG'\" /dev/null"
|
||||
}
|
||||
|
||||
assert_remote_install() {
|
||||
@@ -223,20 +223,20 @@ assert_remote_install() {
|
||||
[ -x "$REMOTE_VENV/bin/python" ] \
|
||||
|| fail "no remote venv at $REMOTE_VENV; the handoff's --venv-dir did not reach the tagged installer"
|
||||
# The remote marker relocates the activation helper to ~/.local/bin only when
|
||||
# VOCALINUX_REMOTE_INSTALL survives the exec.
|
||||
[ -f "$INSTALL_HOME/.local/bin/activate-vocalinux.sh" ] \
|
||||
|| fail "no ~/.local/bin/activate-vocalinux.sh; the remote marker did not survive the handoff"
|
||||
# VOCULINUX_REMOTE_INSTALL survives the exec.
|
||||
[ -f "$INSTALL_HOME/.local/bin/activate-voculinux.sh" ] \
|
||||
|| fail "no ~/.local/bin/activate-voculinux.sh; the remote marker did not survive the handoff"
|
||||
# --skip-models forwarded: a model in the data dir means the tagged installer
|
||||
# re-parsed arguments the handoff did not deliver.
|
||||
if [ -d "$INSTALL_HOME/.local/share/vocalinux/models" ]; then
|
||||
[ -z "$(find "$INSTALL_HOME/.local/share/vocalinux/models" -type f -print -quit)" ] \
|
||||
if [ -d "$INSTALL_HOME/.local/share/voculinux/models" ]; then
|
||||
[ -z "$(find "$INSTALL_HOME/.local/share/voculinux/models" -type f -print -quit)" ] \
|
||||
|| fail "models were downloaded; --skip-models did not survive the handoff"
|
||||
fi
|
||||
|
||||
echo "== Assert: the installed app is there and answers =="
|
||||
[ -x "$INSTALL_HOME/.local/bin/vocalinux" ] || fail "no wrapper at ~/.local/bin/vocalinux"
|
||||
[ -x "$INSTALL_HOME/.local/bin/vocalinux-gui" ] || fail "no wrapper at ~/.local/bin/vocalinux-gui"
|
||||
[ -f "$INSTALL_HOME/.local/share/applications/vocalinux.desktop" ] \
|
||||
[ -x "$INSTALL_HOME/.local/bin/voculinux" ] || fail "no wrapper at ~/.local/bin/voculinux"
|
||||
[ -x "$INSTALL_HOME/.local/bin/voculinux-gui" ] || fail "no wrapper at ~/.local/bin/voculinux-gui"
|
||||
[ -f "$INSTALL_HOME/.local/share/applications/voculinux.desktop" ] \
|
||||
|| fail "no desktop entry; install_desktop_entry did not run"
|
||||
|
||||
# The version the wrapper reports must be the tag's own, not an assertion
|
||||
@@ -244,13 +244,13 @@ assert_remote_install() {
|
||||
local expected_version version_output
|
||||
expected_version="$(grep -oP '__version__\s*=\s*"\K[^"]+' "$CLONE_DIR/src/vocalinux/version.py")" \
|
||||
|| fail "cannot read __version__ from the clone's version.py"
|
||||
version_output="$(su - "$INSTALL_USER" -c "'$INSTALL_HOME/.local/bin/vocalinux' --version")" \
|
||||
version_output="$(su - "$INSTALL_USER" -c "'$INSTALL_HOME/.local/bin/voculinux' --version")" \
|
||||
|| fail "the installed wrapper cannot report a version"
|
||||
case "$version_output" in
|
||||
*"$expected_version"*) ;;
|
||||
*) fail "wrapper reports '$version_output', expected the tag's $expected_version" ;;
|
||||
esac
|
||||
echo " vocalinux --version: $version_output"
|
||||
echo " voculinux --version: $version_output"
|
||||
|
||||
# Same blind spots as the local gate: the injection binaries are reached
|
||||
# through subprocess, and the IBus runtime only where the package list
|
||||
@@ -320,7 +320,7 @@ su - "$INSTALL_USER" -c "git -C '$CLONE_DIR' reset --hard '$AWAY_SHA' \
|
||||
[ -z "$(git -C "$CLONE_DIR" tag -l "$GATE_TAG")" ] \
|
||||
|| fail "the clone kept refs/tags/$GATE_TAG; the re-run proves nothing"
|
||||
run_remote_install_piped || fail "the terminal-backed piped install exited non-zero over the existing clone"
|
||||
grep -l "Updating existing clone" "$INSTALL_HOME"/.local/state/vocalinux/install-*.log >/dev/null 2>&1 \
|
||||
grep -l "Updating existing clone" "$INSTALL_HOME"/.local/state/voculinux/install-*.log >/dev/null 2>&1 \
|
||||
|| fail "no install log shows 'Updating existing clone'; the fetch arm did not run"
|
||||
assert_remote_install
|
||||
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
# exercise a path no user takes.
|
||||
#
|
||||
# Local mode only: install.sh chooses it by finding a pyproject.toml naming
|
||||
# vocalinux in the working directory, so extracting the tree and running from it
|
||||
# voculinux in the working directory, so extracting the tree and running from it
|
||||
# tests this commit. Remote mode (curl | bash, which clones the latest tag) is
|
||||
# not covered here.
|
||||
#
|
||||
@@ -33,7 +33,7 @@ INSTALL_HOME="/home/$INSTALL_USER"
|
||||
# Local mode puts the venv inside the tree, which is why the checkout is copied
|
||||
# rather than written to: a bind-mounted host tree would come back owned by a
|
||||
# container uid with a venv in it.
|
||||
TREE="$INSTALL_HOME/vocalinux"
|
||||
TREE="$INSTALL_HOME/voculinux"
|
||||
|
||||
fail() {
|
||||
echo "FAIL: $*" >&2
|
||||
@@ -41,11 +41,11 @@ fail() {
|
||||
exit 1
|
||||
}
|
||||
|
||||
# install.sh writes a full transcript under ~/.local/state/vocalinux/. It is the
|
||||
# install.sh writes a full transcript under ~/.local/state/voculinux/. It is the
|
||||
# only place the per-distro package manager output survives.
|
||||
dump_install_log() {
|
||||
local log
|
||||
log="$(ls -1t "$INSTALL_HOME"/.local/state/vocalinux/install-*.log 2>/dev/null | head -n1 || true)"
|
||||
log="$(ls -1t "$INSTALL_HOME"/.local/state/voculinux/install-*.log 2>/dev/null | head -n1 || true)"
|
||||
if [ -n "$log" ]; then
|
||||
echo "== last 200 lines of $log ==" >&2
|
||||
tail -n 200 "$log" >&2
|
||||
@@ -132,8 +132,8 @@ echo " install took $(( $(date +%s) - START ))s"
|
||||
echo "== Smoke: is there an installation, and does it import =="
|
||||
VENV="$TREE/venv"
|
||||
[ -x "$VENV/bin/python" ] || fail "no venv interpreter at $VENV/bin/python"
|
||||
[ -x "$INSTALL_HOME/.local/bin/vocalinux" ] || fail "no wrapper at ~/.local/bin/vocalinux"
|
||||
[ -f "$INSTALL_HOME/.local/share/applications/vocalinux.desktop" ] \
|
||||
[ -x "$INSTALL_HOME/.local/bin/voculinux" ] || fail "no wrapper at ~/.local/bin/voculinux"
|
||||
[ -f "$INSTALL_HOME/.local/share/applications/voculinux.desktop" ] \
|
||||
|| fail "no desktop entry; install_desktop_entry did not run"
|
||||
|
||||
# The injection backends are reached through subprocess, not GI, so no typelib
|
||||
@@ -159,7 +159,7 @@ esac
|
||||
# Through the wrapper rather than the venv directly: the wrapper is what the
|
||||
# desktop entry and the user's PATH invoke, and it carries the GI_TYPELIB_PATH
|
||||
# and pywhispercpp library-path resolution the bare console script does not.
|
||||
su - "$INSTALL_USER" -c "'$INSTALL_HOME/.local/bin/vocalinux' --version" \
|
||||
su - "$INSTALL_USER" -c "'$INSTALL_HOME/.local/bin/voculinux' --version" \
|
||||
|| fail "the installed wrapper cannot report a version"
|
||||
|
||||
su - "$INSTALL_USER" -c "'$VENV/bin/python' - " <<'PY' || fail "the installation does not import"
|
||||
|
||||
@@ -22,7 +22,7 @@ from pathlib import Path
|
||||
from packaging.version import Version
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[1]
|
||||
TEMPLATE = ROOT / "packaging/aur/vocalinux/PKGBUILD"
|
||||
TEMPLATE = ROOT / "packaging/aur/voculinux/PKGBUILD"
|
||||
REPOSITORY = "VocaHQ/vocalinux"
|
||||
|
||||
|
||||
@@ -33,14 +33,14 @@ def digest(path: Path) -> str:
|
||||
|
||||
|
||||
def sdist_version(sdist: Path) -> str:
|
||||
"""Require one canonical Vocalinux sdist with matching top-level metadata."""
|
||||
match = re.fullmatch(r"vocalinux-([0-9][A-Za-z0-9.+!]*)\.tar\.gz", sdist.name)
|
||||
"""Require one canonical Voculinux sdist with matching top-level metadata."""
|
||||
match = re.fullmatch(r"voculinux-([0-9][A-Za-z0-9.+!]*)\.tar\.gz", sdist.name)
|
||||
if not match:
|
||||
raise ValueError("expected a canonical vocalinux-VERSION.tar.gz sdist")
|
||||
raise ValueError("expected a canonical voculinux-VERSION.tar.gz sdist")
|
||||
version = match[1]
|
||||
if str(Version(version)) != version:
|
||||
raise ValueError("sdist filename version is not canonical")
|
||||
root = f"vocalinux-{version}"
|
||||
root = f"voculinux-{version}"
|
||||
with tarfile.open(sdist, "r:gz") as archive:
|
||||
members = archive.getmembers()
|
||||
if any(
|
||||
@@ -55,7 +55,7 @@ def sdist_version(sdist: Path) -> str:
|
||||
assert stream is not None
|
||||
with stream:
|
||||
info = BytesParser().parse(stream)
|
||||
if info.get_all("Name") != ["vocalinux"] or info.get_all("Version") != [version]:
|
||||
if info.get_all("Name") != ["voculinux"] or info.get_all("Version") != [version]:
|
||||
raise ValueError("sdist metadata does not match its filename")
|
||||
return version
|
||||
|
||||
|
||||
@@ -47,7 +47,7 @@ from packaging.utils import canonicalize_name
|
||||
REPO_ROOT = Path(__file__).resolve().parents[1]
|
||||
EXPORT = REPO_ROOT / "requirements" / "vad.txt"
|
||||
DEPS_YAML = REPO_ROOT / "packaging" / "flatpak" / "python3-dependencies.yaml"
|
||||
MANIFEST = REPO_ROOT / "packaging" / "flatpak" / "com.vocalinux.Vocalinux.yml"
|
||||
MANIFEST = REPO_ROOT / "packaging" / "flatpak" / "com.voculinux.Voculinux.yml"
|
||||
|
||||
#: The environment the Flatpak build resolves for. GNOME 50 ships Python 3.13.
|
||||
#: numpy's lock entry splits at `python_full_version >= '3.12'`; onnxruntime's
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Vocalinux Remote API Test Server
|
||||
Voculinux Remote API Test Server
|
||||
|
||||
A simple mock server for testing Vocalinux's remote API feature.
|
||||
A simple mock server for testing Voculinux's remote API feature.
|
||||
Supports whisper.cpp (/inference) and OpenAI/FunASR-compatible
|
||||
(/v1/audio/transcriptions) formats.
|
||||
|
||||
@@ -22,7 +22,7 @@ from http.server import BaseHTTPRequestHandler, HTTPServer
|
||||
|
||||
|
||||
class RemoteAPITestHandler(BaseHTTPRequestHandler):
|
||||
"""HTTP request handler for testing Vocalinux remote API."""
|
||||
"""HTTP request handler for testing Voculinux remote API."""
|
||||
|
||||
def log_message(self, format, *args):
|
||||
"""Custom log format with timestamp."""
|
||||
@@ -37,7 +37,7 @@ class RemoteAPITestHandler(BaseHTTPRequestHandler):
|
||||
self.end_headers()
|
||||
response = {
|
||||
"status": "ok",
|
||||
"message": "Vocalinux Remote API Test Server",
|
||||
"message": "Voculinux Remote API Test Server",
|
||||
"endpoints": [
|
||||
"/inference (whisper.cpp format)",
|
||||
"/v1/audio/transcriptions (OpenAI/FunASR format)",
|
||||
@@ -138,7 +138,7 @@ class RemoteAPITestHandler(BaseHTTPRequestHandler):
|
||||
if "test" in filename.lower():
|
||||
return "This is a test transcription from the mock server."
|
||||
elif "hello" in filename.lower():
|
||||
return "Hello, this is Vocalinux testing the remote API."
|
||||
return "Hello, this is Voculinux testing the remote API."
|
||||
elif "long" in filename.lower():
|
||||
return (
|
||||
"This is a longer transcription to test how the system handles "
|
||||
@@ -159,7 +159,7 @@ class DelayedHTTPServer(HTTPServer):
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(
|
||||
description="Vocalinux Remote API Test Server"
|
||||
description="Voculinux Remote API Test Server"
|
||||
)
|
||||
parser.add_argument(
|
||||
"--port",
|
||||
@@ -183,7 +183,7 @@ def main():
|
||||
args = parser.parse_args()
|
||||
|
||||
print("=" * 60)
|
||||
print("Vocalinux Remote API Test Server")
|
||||
print("Voculinux Remote API Test Server")
|
||||
print("=" * 60)
|
||||
print(f"Listening on: http://{args.host}:{args.port}")
|
||||
print(f"Processing delay: {args.delay}s")
|
||||
|
||||
@@ -26,7 +26,7 @@ import subprocess
|
||||
import sys
|
||||
|
||||
MANIFEST = "SHA256SUMS"
|
||||
PYPI_PROJECT = "vocalinux"
|
||||
PYPI_PROJECT = "voculinux"
|
||||
PYPI_SUFFIXES = (".whl", ".tar.gz")
|
||||
PYPI_TIMEOUT = 30
|
||||
|
||||
|
||||
Reference in New Issue
Block a user