This guide explains how to set up, run, inspect, test, and package v2link-client from a source checkout. It also records the runtime rules that are important when changing proxy, traffic-monitor, or shutdown code.
The application targets Linux and Python 3.11 or newer. Development requires:
- Python 3.11+
venvandpip- Git
- Qt's Linux runtime libraries, especially
libxcb-cursor.so.0 - Xray-core, either from
vendor/xray/<arch>/, a custom path, orPATH
On Debian or Ubuntu, the minimum source-development setup is typically:
sudo apt update
sudo apt install -y python3 python3-venv python3-pip git libxcb-cursor0Rust and Cargo are only required when working on the optional v2link-netmon helper or building Debian release packages.
git clone https://github.com/UdayaSri0/v2link-client.git
cd v2link-client
./scripts/dev_run.shdev_run.sh creates .venv when needed, installs requirements.txt, checks the important Qt xcb library, adds src/ to PYTHONPATH, and starts v2link_client.main.
Do not run the GUI with sudo. The GUI, profiles, traffic database, logs, and system-proxy snapshot are designed to belong to the desktop user. The optional netmon helper is the separate privileged component.
Use this setup when you want direct control over the environment:
python3 -m venv .venv
source .venv/bin/activate
python -m pip install --upgrade pip
python -m pip install -r requirements.txt
python -m pip install -r requirements-dev.txt
python -m pip install -e .
python -m v2link_client.mainAn editable install makes v2link_client importable without manually setting PYTHONPATH. Without an editable install, run from the repository with:
PYTHONPATH=src python -m v2link_client.mainFor a programmatic entry point:
from v2link_client.main import main
raise SystemExit(main())Internal modules are importable for tests and development, but they are not currently a stable third-party Python API.
The application resolves Xray in this order:
- Custom executable selected in Xray Settings.
- Bundled executable under
vendor/xray/<arch>/orV2LINK_BUNDLED_XRAY_DIR. - A system
xrayexecutable fromPATH.
To fetch the pinned release and geo assets used by packaging:
./scripts/fetch_xray_core.sh./scripts/dev_run.sh verifies the native vendor directory and fetches the pinned
official release once when needed. Set V2LINK_SKIP_XRAY_FETCH=1 to disable network
bootstrap; the script then prints the exact manual fetch command.
Never replace the pinned version or checksums without reviewing Xray release notes and updating the associated tests and third-party notices.
src/v2link_client/
├── main.py Application entry point
├── core/ Config, process, proxy, storage, stats, diagnostics
├── platform/ GNOME, KDE, and NetworkManager integration
└── ui/ PyQt windows and Traffic Monitor widgets
tests/ Python unit, UI, lifecycle, and performance tests
scripts/ Development, diagnostics, and packaging commands
netmon/ Optional Rust per-application helper workspace
packaging/ Desktop, AppImage, Debian, and systemd assets
vendor/xray/ Pinned Xray binaries, geo data, version, and license
docs/ Architecture, operations, releases, and troubleshooting
The main runtime flow is:
MainWindow
→ validates/builds an Xray JSON configuration
→ starts an app-owned Xray process group
→ polls cumulative Xray stats in a background worker
→ updates live UI state in memory
→ queues periodic cumulative samples to the SQLite writer
→ refreshes only the visible Traffic Monitor section
Keep these invariants when modifying the application:
- The Qt GUI thread must not perform network calls, long subprocess calls, large database reads, exports, or growing chart work.
- Only one stats query may be active. Every app-owned subprocess must be identifiable, bounded by a timeout, and reaped.
- Live stats callbacks update memory and labels only. SQLite persistence runs through the bounded storage worker every five seconds.
- Automatic charts return and render no more than 900 points. Full sample reads are reserved for explicit exports.
- Expensive diagnostics run only when requested or visible, never on each live tick.
- Shutdown is idempotent: stop timers, invalidate generations, finalize traffic, stop owned process groups, restore owned proxy state, and drain storage without touching deleted widgets.
- Never kill an unrelated system Xray. Never stop
v2link-netmon.serviceas part of normal GUI shutdown. - Xray access logging stays disabled by default. Detailed diagnostic output must remain inside the bounded stdout log.
- Diagnostics and logs must not expose profile URLs, UUIDs, credentials, tokens, or private configuration contents.
See Traffic Monitor internals and runtime performance troubleshooting for the detailed cadence and ownership model.
Normal Linux paths are:
~/.config/v2link-client/ Preferences, profiles, Xray/traffic settings
~/.local/share/v2link-client/ traffic.sqlite3
~/.local/state/v2link-client/ Generated runtime config, proxy snapshot, logs
The corresponding XDG_CONFIG_HOME, XDG_DATA_HOME, and XDG_STATE_HOME variables override these bases. Use temporary XDG directories when testing migrations or startup so personal profiles and history are not touched:
tmp_dir="$(mktemp -d)"
XDG_CONFIG_HOME="$tmp_dir/config" \
XDG_DATA_HOME="$tmp_dir/data" \
XDG_STATE_HOME="$tmp_dir/state" \
PYTHONPATH=src \
python -m v2link_client.main
rm -rf "$tmp_dir"Use only test profiles and authorized traffic. Never commit real share links, profile databases, generated Xray configs, logs, or proxy snapshots.
Run the complete Python validation used by the project:
python -m compileall src
python -m pytest -q
QT_QPA_PLATFORM=offscreen python -m pytest -qRun a focused test while developing:
python -m pytest -q tests/test_process_manager.py
python -m pytest -q tests/test_traffic_monitor_hot_path.pyFor the optional Rust workspace:
cd netmon
cargo test --workspace
cd ..Validate shell syntax and use ShellCheck when installed:
bash -n scripts/*.sh
shellcheck scripts/*.shThe repository currently has no configured Python formatter, linter, or type checker. Match the surrounding code style, keep imports explicit, and run the full tests before submitting changes.
TrafficStore owns SQLite schema and migrations. When changing storage:
- Add a forward-only migration instead of rewriting or deleting existing migrations.
- Keep existing databases readable without deleting traffic history.
- Use the storage worker for recurring writes.
- Bound automatic queries and UI result sizes.
- Add compatibility, retention, failure-recovery, and long-session tests.
Do not inspect or modify a developer's real traffic database during automated tests. Pytest's tmp_path fixtures should own test databases.
Build the Python onedir bundle:
./scripts/build_pyinstaller.shBuild individual release formats:
./scripts/build_appimage.sh
./scripts/build_deb.shBuild the complete release set and checksums:
./scripts/build_release.shThe Debian build also requires Cargo and dpkg-deb. The AppImage build downloads appimagetool into the ignored tools/ directory when it is unavailable. Generated outputs belong in ignored build/, dist/, tools/, and netmon/target/ directories and must not be committed.
Before publishing, verify bundled Xray discovery, source startup, AppImage startup, Debian payload contents, database compatibility, proxy apply/restore, and that no app-owned Xray or stats-query process remains after shutdown.
For CPU, memory, database, log-growth, service, or stale-process investigation, run:
./scripts/diagnose_runtime_performance.shIt is read-only and does not require root. Exit code 1 reports a possible stale-process finding; it does not prove ownership and the script never kills anything.
Install the project in editable mode or include src on PYTHONPATH:
python -m pip install -e .
# or
PYTHONPATH=src python -m v2link_client.mainInstall libxcb-cursor0, then rerun ./scripts/dev_run.sh. Headless tests should set QT_QPA_PLATFORM=offscreen.
Run ./scripts/fetch_xray_core.sh, install Xray on PATH, or select a valid executable in Xray Settings.
Use real short-lived helper scripts in a temporary directory, launch them in private sessions, and exercise both normal TERM and forced KILL paths. Do not use broad pkill xray cleanup because that can terminate unrelated processes.
Check that the change did not add database reads, table refreshes, diagnostics, or chart reconstruction to the live stats callback. Run the long-session tests and the runtime diagnostic script.
- Keep changes scoped and preserve unrelated working-tree edits.
- Add regression tests for behavior changes and failure paths.
- Use temporary XDG directories and synthetic profiles/data.
- Verify normal and offscreen test suites.
- Test graceful and forced process shutdown when lifecycle code changes.
- Update README, CHANGELOG, or the relevant document for user-visible behavior.
- Remove generated databases, logs, package files, profiling output, and build artifacts before committing.
- Review the complete diff for credentials, links, tokens, debug prints, and private paths.