v2portal
Non-interactive CLI client for v2ray proxy management. It wraps two proxy engines — sing-box (default) and xray-core — behind a single config, and lets you add subscriptions/proxies, chain or balance them, test latency, and run inbound servers from the command line.
Runs on Linux, Windows, and Termux (Android).
Features
- Dual engine — sing-box (default) + xray-core, resolved per profile/group.
xray is used automatically for
ssrand theleastLoadstrategy. - Subscriptions — paste a subscription URL or raw base64/plain payload;
import, update (deleted nodes are pruned), test a single subscription, and
auto-update on a schedule (
auto_update_days). - Protocols — vmess, vless, trojan, ss, ssr, socks, http, wireguard, hysteria2, tuic, plus raw JSON configs. OpenVPN / OpenConnect via the system clients.
- Groups —
balancer(latency / random / roundRobin / leastLoad) andchain(proxy through proxy). - Split routing — domain/keyword/regex rules, IP CIDR, and geoip/geosite.
Rules can target any profile, subscription, group, or server, plus
directorblock. Stale rules are auto-cleaned when a profile, group, or server is deleted. - LAN proxy inbound — sing-box serves SOCKS5 and HTTP on one port; xray exposes SOCKS5 plus HTTP CONNECT on adjacent ports, with optional username/password auth.
- Outbound testing —
--probeseparately measures endpoint ICMP/TCP reachability with DNS/refusal/timeout classification;--ws-testvalidates WS/WSS handshake and ping/pong;--testmeasures real proxy request delay through the engine. - Traffic stats — cumulative per-profile/group up/down usage (sing-box), recorded when a server stops.
- Config on disk — a single JSON file in your platform config dir.
Install
PyPI (recommended)
pip install v2portal
# or, with pipx (handles PATH automatically):
pipx install v2portal
From source
pip install git+https://github.com/HoomanJCode/V2Portal.git
# or, for development:
git clone https://github.com/HoomanJCode/V2Portal.git
pip install -e "V2Portal[dev]"
Tip: If the
v2portalcommand is not found after install, usepython -m v2portalas a fallback, or install withpipx install v2portal(install pipx first:pip install pipx).pipxhandles PATH setup automatically.
Termux (Android)
pkg update && pkg install python
pip install v2portal
The engine binaries (sing-box / xray) download automatically to
~/.config/v2portal/bin on first use, with arm64 assets on Termux.
Note: OpenVPN / OpenConnect may require root on Android and are not chainable/balanceable — they run the system client directly.
Windows
pip install v2portal
After installing, the v2portal command is placed in Python's Scripts
directory (e.g. %LOCALAPPDATA%\Python\pythonX.Y-64\Scripts). If the
command is not found, one of these will work:
-
Add the
Scriptsfolder to your systemPATHpermanently:[Environment]::SetEnvironmentVariable( "Path", $env:Path + ";" + [IO.Path]::Combine([Environment]::GetFolderPath("LocalApplicationData"), "Python", "python$([Environment]::Version.ToString(3))", "Scripts"), "User" )
Then restart your terminal.
-
Or use
python -m v2portalinstead ofv2portal— this always works regardless of PATH.
Config lives in %APPDATA%\v2portal. Engine binaries download to
%APPDATA%\v2portal\bin. When LAN sharing is enabled, allow the program
through the Windows firewall.
PyInstaller (single folder)
A spec is included:
pip install pyinstaller
pyinstaller v2portal.spec
Engine binaries and geo assets still download on first run.
Quickstart
The primary interface is non-interactive and safe for scripts. Run
v2portal --help or v2portal COMMAND --help for the complete tree. The
interactive TUI (run v2portal with no arguments) is a modern rich-styled
panel UI with screens for Connect, a Servers dashboard (live status +
start/stop), a Groups tree, Subscriptions, Test, Routing, and Settings:
v2portal status
v2portal profile list
v2portal subscription list
v2portal group list
v2portal group tree
v2portal test latency all
Every mutating command uses explicit arguments and writes the config only after validation. The full command layout is:
profile list | add | rename | edit | remove | export
subscription list | add | edit | rename | update | remove (aliases: sub, subscriptions)
group list | add | tree | edit | remove | add-member | remove-member (alias: groups)
server list | add | edit | start | stop | restart | remove (alias: sv)
routing list | mode | add | move | enable | disable | remove
backup create | list | restore
settings show/set app settings, engine update
config show | export | import (backup/restore)
service install | uninstall
test latency | endpoint | websocket
One ID space, auto-detected references. Every entity — profile, subscription, group, server — has a unique ID from a single counter. Any command that takes a target reference accepts a profile, subscription, group, or server ID and detects the type automatically. References are resolved at use time: a subscription always contributes its current profiles, nested groups expand recursively (with dedup and cycle protection), and a server member resolves to a socks/http profile through its local inbound — so updated subscriptions flow everywhere automatically.
Examples:
# Add a manual profile
v2portal profile add socks office-proxy 127.0.0.1 1080
v2portal profile add share us-node 'vless://...'
v2portal profile edit ID --name "Office proxy" --host 10.0.0.2
# Import a subscription
v2portal subscription add my-provider https://example.com/sub
v2portal subscription edit SUB_ID --name 'Renamed provider'
v2portal profile list --subscription SUB_ID
# Groups accept profiles, subscriptions, groups, and servers (auto-detected)
v2portal group add balancer fastest PROFILE_A SUB_ID GROUP_B SERVER_ID --strategy latency
v2portal group add chain chained PROFILE_A PROFILE_B
v2portal group edit GROUP_ID --strategy random
# Render the nested group/subscription/server hierarchy
v2portal group tree
# Add a running server as a local socks/http profile
v2portal profile add server via-server SERVER_ID
# Start a proxy server on a port — REF is auto-detected
v2portal server add --port 1080 REF --name 'US proxy'
v2portal server edit SERVER_ID --outbound REF
v2portal server start SERVER_ID
# Servers run in the background and survive terminal close
v2portal server list
v2portal sv stop SERVER_ID
v2portal sv restart --all
# View and change settings
v2portal settings
v2portal settings test-url
v2portal settings mixed-port 1081
v2portal settings default-engine xray
v2portal settings engine update both
# Update all subscriptions, filter profiles by kind
v2portal subscription update --all
v2portal profile list --kind socks
# Test by group, subscription, or profile ID
v2portal test latency GROUP_ID
v2portal test endpoint SUB_ID
# Routing rules can target any reference
v2portal routing add block --domain 'keyword:ads'
v2portal routing add direct --geoip cn
v2portal routing add proxy --domain netflix.com --target SUB_ID
# Multiple servers on different ports
v2portal server add --port 1081 GROUP_ID --protocol http --name 'Balancer'
v2portal server start --all
Engine updates
Engine updates are never automatic. Use v2portal settings engine update sing-box,
v2portal settings engine update xray, or v2portal settings engine update both. Only binaries
configured with binary_path: "auto" are replaceable; custom and system paths
are protected. Downloads are staged, version-checked, atomically replaced, and
rolled back if verification fails. For restricted networks, pass an ephemeral
proxy with --proxy on the update command (e.g. v2portal settings engine update both --proxy socks5://127.0.0.1:1080); it is never stored.
Auto-update
Set auto_update_days on a subscription to have it re-fetched automatically
on startup when stale (never updated, or older than N days). Fetch failures are
logged and skipped — they never block startup. Disable per-run with
--no-auto-update.
Subscription health
On startup the CLI warns (to stderr) about expired subscriptions and those
expiring within 7 days. v2portal health prints a table of expiry status and
traffic used for every enabled subscription.
Adding proxies
You can add proxies via CLI:
| Type | What it asks for |
|---|---|
| Subscription | URL (also file:// and paste:// accepted) |
| Share link | a vmess://, vless://, trojan://, ss://, ssr://, hysteria2://, tuic://, wireguard://, socks://, http:// link |
| Socks / HTTP | server address, port, optional username/password |
| WireGuard | private key, address, peers |
| Hysteria2 / TUIC | server, auth, TLS/transport options |
| Raw config | paste a v2ray/xray outbound JSON object (uses xray engine) |
| OpenVPN | config file path or inline config + args |
| OpenConnect | server + args |
Groups
- Balancer — pick references (profiles, subscriptions, groups, servers)
and a strategy (
latency,random,roundRobin,leastLoad).leastLoadforces xray-core. Everything resolves dynamically. A lone profile is rejected — a single profile is not a group. - Chain — pick an ordered list; traffic flows through each in order.
- Groups can nest: a balancer can contain other groups. A server member resolves to a socks/http profile pointing at that server's local inbound ("localhost calling") — traffic physically passes through it. Members are resolved recursively at use time, deduplicated, and cycles (group→group and server→group→server) are rejected.
v2portal group treerenders the whole hierarchy — top-level groups with their members (profiles, subscriptions and their current nodes, servers, nested groups) plus any subscription/server/profile no group references.- VPN profiles cannot join balancers/chains.
A subscription used as a target (server outbound, routing rule, group member) resolves as a strategy-based balancer over its current profiles — refresh the subscription and the target follows automatically.
Split routing
In split mode, the first matching rule wins. Rules can target a specific
profile, group, direct (bypass proxy), or block (drop traffic).
CLI
# Switch to split mode
v2portal routing mode split
# Block ads
v2portal routing add block --domain keyword:ads
v2portal routing add block --geosite category-ads-all
# Bypass local and Chinese IPs
v2portal routing add direct --ip 192.168.0.0/16
v2portal routing add direct --geoip cn --geoip private
# Route specific sites through a profile, group, or server
v2portal routing add proxy --domain netflix.com --target PROFILE_ID
v2portal routing add proxy --geosite gfw --target GROUP_ID
v2portal routing add proxy --domain intranet.corp --target SERVER_ID
# List all rules
v2portal routing list
v2portal routing list --json
# Reorder rules (first match wins)
v2portal routing move RULE_ID up
v2portal routing move RULE_ID down
# Remove a rule
v2portal routing remove RULE_ID
Match types
| Matcher | Syntax | Example |
|---|---|---|
| Domain (exact) | --domain example.com |
--domain netflix.com |
| Domain (keyword) | --domain keyword:ads |
--domain keyword:ads |
| Domain (regex) | --domain regex:^x\\. |
--domain regex:^ads\\. |
| Domain (geosite) | --domain geosite:category |
--domain geosite:gfw |
| IP/CIDR | --ip 10.0.0.0/8 |
--ip 192.168.0.0/16 |
| GeoIP | --geoip cn |
--geoip cn --geoip private |
| GeoSite | --geosite category-ads-all |
--geosite gfw |
Target any reference
Proxy rules can target any profile, subscription, group, or server by ID — not just the server's default outbound. A server target routes matching traffic through that server's local inbound. This lets you route different traffic through different outbounds:
# Route Netflix through a US profile
v2portal routing add proxy --domain netflix.com --target US_PROFILE_ID
# Route streaming through a low-latency balancer group
v2portal routing add proxy --geosite streaming --target BALANCER_GROUP_ID
# No --target: the rule follows the server's own outbound
v2portal routing add proxy --domain example.com
Automatic cleanup
When you delete a profile or group, any routing rules targeting it are automatically removed — no broken configs.
Geo assets
On sing-box, geosite:/geoip: entries compile to rule-sets that
auto-download from SagerNet/sing-geosite and SagerNet/sing-geoip. On xray,
geoip.dat/geosite.dat download to geo/ (found via XRAY_LOCATION_ASSET).
LAN sharing
By default allow_lan is enabled — the inbound listens on 0.0.0.0 so other
devices on your network can use socks5://<your-ip>:1080 /
http://<your-ip>:1080. Optionally enable inbound auth (username/password).
Traffic stats
Enable Settings → Traffic stats (or set settings.traffic_api: true in the
config) to have sing-box expose its Clash API on 127.0.0.1
(traffic_api_port, default 9090). The CLI polls cumulative up/down bytes and
adds them to the server's outbound target when the server stops. Works with
sing-box (the default engine); xray has no Clash-compatible HTTP API so its
traffic is not counted.
Run as a service
Keep all enabled servers running across reboots (no ad-hoc connect command
— connections are servers):
v2portal server add --port 1080 REF
v2portal server start --all
v2portal service install
- Linux — writes a systemd user unit to
~/.config/systemd/user/v2portal.service; enable it withsystemctl --user enable --now v2portal. - Termux — writes a
termux-servicesrun script to~/.termux/sv/v2portal/run; enable withsv-enable v2portal.
Remove it with v2portal service uninstall.
Engine selection
- sing-box is the default and serves most protocols plus
latency/random/roundRobin. WireGuard profiles are emitted as sing-box endpoints (the format since 1.13, where the WireGuard outbound was removed) and can be chained, balanced and routed to like any other outbound. - xray-core is used automatically for
ssrandleastLoad. - Override per profile/group, or change the global default in Settings.
Routing guide: per-destination proxy selection
You can route different traffic through different outbounds using split routing rules. Here's a practical setup:
# Enable split routing
v2portal routing mode split
# Route Epic Games traffic through a Berlin profile
v2portal routing add proxy --domain epicgames.com --target BERLIN_PROFILE_ID
v2portal routing add proxy --domain fortnite.com --target BERLIN_PROFILE_ID
# Route YouTube traffic through a balancer group
v2portal routing add proxy --domain youtube.com --target GROUP_1_ID
v2portal routing add proxy --domain googlevideo.com --target GROUP_1_ID
# Route everything else through a subscription (auto-detected as a member)
v2portal group add balancer default SUB_ID_A SUB_ID_2
# Route Russian websites directly (no proxy)
v2portal routing add direct --geoip ru
v2portal routing add direct --geosite ru
# Block ads globally
v2portal routing add block --geosite category-ads-all
# Review your rules
v2portal routing list
Rules are evaluated in order; the first match wins. Use v2portal routing move
to reorder them.
Troubleshooting
- Port in use — change the port with
v2portal settings mixed-port <port>. - "binary not found" — binaries download on first use; if download fails,
set
engines.<name>.binary_pathin the config to a local binary, or"system"to use one onPATH. - LAN unreachable — check the firewall (Windows) and that devices share the network.
- Geo rules silent — sing-box auto-downloads rule-sets, and xray's
geoip.dat/geosite.datdownload togeo/on first use (both need network). - VPN client missing — install
openvpn/openconnectand ensure it's onPATH. v2portalcommand not found afterpip install .— the script is in Python'sScriptsdirectory which may not be onPATH. Fix: usepython -m v2portal, addScriptsto yourPATH, or reinstall withpipx install ..
Config layout
Config is JSON at <config-dir>/config.json. Derived dirs under the same base:
runtime/— generated engine configs and test resultsbin/— downloaded engine binariesgeo/— xraygeoip.dat/geosite.datassets (sing-box rule-sets are cached by the engine)backup/— automatic config backups
Development
git clone https://github.com/HoomanJCode/V2Portal.git
pip install -e "V2Portal[dev]"
pytest
python scripts/verify_acceptance.py --json
GitHub Actions runs the pytest suite on Ubuntu and Windows (Python 3.12) for pushes and pull requests. Live engine, remote-node, and platform walkthroughs remain separate from CI.
Read-only platform diagnostics (does not load or modify the config, download binaries, or start processes):
python scripts/verify_platform.py
python scripts/verify_platform.py --json
Credential-free orchestration smoke (subscription import, split routing, connection switching, test dispatch, cleanup, and OpenVPN/OpenConnect argv validation; does not download engines or contact remote nodes):
python scripts/verify_acceptance.py
python scripts/verify_acceptance.py --json
Live engine verification (downloads sing-box + xray, runs their own config checks, and exercises the proxy end-to-end):
python scripts/verify_engines.py
# If GitHub access requires a local HTTP/SOCKS proxy:
python scripts/verify_engines.py --proxy socks5://127.0.0.1:10808
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file v2portal-0.2.0.tar.gz.
File metadata
- Download URL: v2portal-0.2.0.tar.gz
- Upload date:
- Size: 184.8 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
d66c2bcd7f75a167745f1c00aafba701d1e8f0dec38818a77e2481f3eee9aa59
|
|
| MD5 |
a6f324b727288ca33b78d3cd76a113e6
|
|
| BLAKE2b-256 |
79022f0ed34b6bfe58ab41edbae813f6729c0a8d4c7c4e086086b5928477d764
|
File details
Details for the file v2portal-0.2.0-py3-none-any.whl.
File metadata
- Download URL: v2portal-0.2.0-py3-none-any.whl
- Upload date:
- Size: 133.0 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
3738f1884897ab15bd0f58972eaa94f95a462924a744115efd0a902784b4999f
|
|
| MD5 |
764e780382d1069cf6a339024a59dd7e
|
|
| BLAKE2b-256 |
afeee6c9c847eba4abb1b71135a864e8e9007ec993dd1e2736bd537ea4e77e5e
|