- Python 43.4%
- C++ 20.3%
- QML 12.1%
- Java 8.6%
- JavaScript 7.9%
- Other 7.6%
| .forgejo | ||
| app-client | ||
| src/webshare | ||
| tests | ||
| .dockerignore | ||
| .env.example | ||
| .gitignore | ||
| .gitmodules | ||
| docker-compose.prod.yml | ||
| docker-compose.yml | ||
| Dockerfile | ||
| pyproject.toml | ||
| README.md | ||
WebShare
Small dependency-free VPN/proxy access panel inspired by the older webvpn
project.
The old project mixed a public UI, NextAuth, FastAPI, PostgreSQL, Redis, Celery, SSH deployment, traffic collection and protocol-specific logic in one large system. This implementation keeps the useful domain model, but makes the first version intentionally boring:
- one Python process;
- SQLite storage;
- no Node, Redis, PostgreSQL or task queue required;
- built-in static web UI;
- explicit protocol adapters for client config generation;
- optional SSH/Docker deployment and monitoring;
- QR code rendering for client configs;
- web backup export/import without operation logs;
- deployable with plain Python or Docker.
It is meant as a clean base that can grow. Remote SSH deployment and live traffic collectors can be added behind the existing deployment/client boundaries without changing the HTTP surface.
Quick Start
cd /home/acnas/projects/my/webshare
python3 -m src.webshare
Open:
http://127.0.0.1:8080
Default local admin:
username: admin
password: admin123
Change the password after the first login.
Configuration
Environment variables:
| Variable | Default | Description |
|---|---|---|
WEBSHARE_BIND |
127.0.0.1 |
HTTP bind address |
WEBSHARE_PORT |
8080 |
HTTP port |
WEBSHARE_HOST_PORT |
8081 |
Docker host port mapped to container 8080 |
WEBSHARE_DB |
./data/webshare.sqlite3 |
SQLite database path |
WEBSHARE_SECRET |
generated on start | HMAC token secret |
WEBSHARE_ADMIN_USERNAME |
admin |
Seed admin username |
WEBSHARE_ADMIN_EMAIL |
admin@webshare.local |
Seed admin email |
WEBSHARE_ADMIN_PASSWORD |
admin123 |
Seed admin password |
WEBSHARE_TOKEN_TTL_SECONDS |
86400 |
Session lifetime |
WEBSHARE_MONITOR_INTERVAL_SECONDS |
60 |
Automatic SSH/Docker server check interval; 0 disables it |
WEBSHARE_MONITOR_INITIAL_DELAY_SECONDS |
10 |
Delay before the first automatic check |
WEBSHARE_APP_REPOSITORY_URL |
https://forgejo.acnas.net/app/webshare |
Forgejo repository used by the web panel Apps download links |
WEBSHARE_APP_RELEASE_CHECK_TIMEOUT_SECONDS |
4 |
Timeout for checking the latest Forgejo release |
WEBSHARE_GEOIP_CITY_DB |
/app/data/geoip/GeoLite2-City.mmdb in Docker |
Optional free MMDB City database path for client location lookup |
WEBSHARE_GEOIP_ASN_DB |
/app/data/geoip/GeoLite2-ASN.mmdb in Docker |
Optional free MMDB ASN database path for ASN/organization lookup |
WEBSHARE_GEOIP_AUTO_DOWNLOAD |
true in Docker |
Download missing GeoIP databases on startup |
WEBSHARE_GEOIP_CITY_URL |
https://cdn.jsdelivr.net/npm/geolite2-city/GeoLite2-City.mmdb.gz |
City database download URL |
WEBSHARE_GEOIP_ASN_URL |
empty | Optional ASN database download URL |
WEBSHARE_GEOIP_DOWNLOAD_TIMEOUT_SECONDS |
30 |
GeoIP download timeout |
For persistent sessions, set WEBSHARE_SECRET.
For built-in GeoIP location, Docker downloads a free City database automatically
on startup when WEBSHARE_GEOIP_AUTO_DOWNLOAD=true and the file is missing. The
default City source is the wp-statistics GeoLite2 package served through
jsDelivr, so no paid account or license key is required. You can also provide
any free MMDB-compatible City and ASN databases manually and place them at:
data/geoip/GeoLite2-City.mmdb
data/geoip/GeoLite2-ASN.mmdb
The Docker compose files already map these to /app/data/geoip/.... Restart or
rebuild the web panel after changing the files so the GeoIP reader cache is
refreshed. ASN/organization lookup requires GeoLite2-ASN.mmdb or a custom
WEBSHARE_GEOIP_ASN_URL; the default auto-download source only provides City
data.
API Shape
All API routes live under /api.
POST /api/auth/loginGET /api/appsGET /api/meGET|PATCH /api/profileGET|POST /api/usersGET|PATCH|DELETE /api/users/{id}GET|POST /api/serversGET|POST /api/servers/{id}/deploymentsGET|POST /api/clientsGET /api/clients/{id}/configGET /api/mobile/deploymentsGET|POST /api/mobile/clientsGET /api/mobile/clients/{id}/sing-boxGET /api/mobile/network-statusGET|PUT /api/clients/{id}/limitPOST /api/clients/{id}/trafficGET /api/reports/summaryGET /api/reports/trafficGET /api/reports/near-limitGET /api/settings/oidcPUT /api/settings/oidcGET /api/settings/smtpPUT /api/settings/smtpPOST /api/settings/smtp/testGET /api/auth/oidc/configPOST /api/auth/oidc/startGET /api/auth/oidc/callbackGET /api/exportPOST /api/import
Tests
python3 -m unittest discover -s tests
No third-party packages are required.
Docker
docker compose up --build
Open the Docker deployment at:
http://127.0.0.1:8081
For production deployments use the image-based compose file:
docker compose -f docker-compose.prod.yml pull
docker compose -f docker-compose.prod.yml up -d
By default it pulls:
forgejo.acnas.net/app/webshare:main
Override it with WEBSHARE_IMAGE in .env when you want to pin a specific tag
or SHA image.
App Clients
app-client/ is the shared Qt 6.11.1 + QML client. The same QML interface is
used for Android, Linux, and Windows, while platform behavior lives behind the
C++ controller.
All app clients consume the same optimized sing-box VPN profile shape:
IPv4-only TUN, system stack, MTU 1200, automatic routes, DoH DNS and
QUIC/UDP 443 fallback to TCP. Platform code only handles how that profile is
started and stopped.
The app checks /api/mobile/network-status through webshare.acnas.net to show
the public IP, route status, API latency and location data before and after VPN
connection. Country, city, ASN and organization are read from reverse-proxy or
CDN headers such as CF-IPCountry, X-Country-Code, X-City, X-ASN and
X-Organization. If those headers are not configured, WebShare falls back to
free MMDB files from WEBSHARE_GEOIP_CITY_DB and WEBSHARE_GEOIP_ASN_DB;
if the files are missing, the app still shows the observed public IP and marks
location as unknown.
Current implementation status:
- Linux: Qt/QML desktop app with tray, single-instance behavior, WebShare login,
embedded OIDC, profile creation, MTProto Telegram links, update discovery,
and bundled
webshare-corecontrolled through the installed polkit helper; - Windows: Qt/QML app packaged as a Qt Installer Framework setup executable
with bundled
webshare-core.exe; - Android: the same Qt/QML UI and API flow with the native
VpnService/libboxtunnel bridge built from the same submodule branch.
./app-client/build-in-docker.sh all
./app-client/build-in-docker.sh linux
./app-client/build-in-docker.sh linux-installer
./app-client/build-in-docker.sh windows
./app-client/build-in-docker.sh android
Artifacts are exported to:
app-client/dist/webshare-linux-x64.run
app-client/dist/webshare-windows-x64-setup.exe
app-client/dist/webshare-android-universal.apk
webshare-linux-x64.run is the Qt Installer Framework offline installer for
Linux. It installs the system menu shortcut, root-owned webshare-core, and a
polkit/systemd-managed VPN helper so active local users can connect/disconnect
without repeated password prompts.
webshare-windows-x64-setup.exe is the Qt Installer Framework offline
installer for Windows.
The Android APK is signed by the Docker build. Add these Forgejo secrets for a release signature, or omit them to get an installable debug-signed APK:
ANDROID_KEYSTORE_BASE64
ANDROID_KEYSTORE_PASSWORD
ANDROID_KEY_ALIAS
ANDROID_KEY_PASSWORD
The default Android artifact is a universal APK for arm64-v8a,
armeabi-v7a, x86_64, and x86. Override QT_ANDROID_ABIS in CI if a
smaller APK is needed.
The app client always uses https://webshare.acnas.net as its WebShare API
endpoint. There is no server field in the app UI and older saved overrides are
ignored.
The Account screen can check the latest Forgejo release through /api/apps
and open the matching package download.
The client uses a simple screen model:
VPN: current connection, connect/disconnect, and available VPN profiles;Telegram: MTProto proxy profiles and opening them in Telegram;New: create a new connection from deployed protocols;Account: refresh, logout, update checks, and runtime information.
OIDC login supports the standard authorization-code flow through a local callback listener and an embedded Qt WebView.
Runtime requirements:
webshare-coreis bundled with the release archive;WEBSHARE_CORE=/path/to/webshare-corecan override the bundled helper;- the Linux installer adds a root-owned
webshare-coreand limitedwebshare-vpn-helperunder/usr/libexec/webshare, plus a polkit rule that allows active local desktop users to manage WebShare VPN without repeated password prompts; - Linux installed builds use
systemd-run/polkit and do not callsudo, do not callpkexec, do not usesetcap, and do not store sudo passwords; - Linux needs systemd, polkit, and
/dev/net/tun.
Android, Linux and Windows build the core from the same Git submodule:
app-client/third_party/sing-box, tracked on the webshare-wrapper branch.
git submodule update --init --recursive app-client/third_party/sing-box
./app-client/build-in-docker.sh all
The core build uses golang:1.24.7-bookworm by default to match the current
sing-box go.mod. If the WebShare wrapper branch moves to another supported Go
version, set SING_BOX_GO_IMAGE in CI.
CI checks out submodules recursively. To update the helper, commit changes in the submodule branch, push that branch, then commit the new submodule pointer in this repository.
Desktop builds use the release tag list from the cloned repository. Override
SING_BOX_BUILD_TAGS only when replacing the whole tag list is intentional.
By default the desktop build skips with_naive_outbound; Windows also skips
tfogo_checklinkname0 to keep cross-compilation stable.
Linux installer:
chmod +x app-client/dist/webshare-linux-x64.run
./app-client/dist/webshare-linux-x64.run
The installer performs one elevated system-integration step and creates the desktop/app-menu launcher.
Keep polkit installed and running. Installed builds should not ask for the administrator password on every connect/disconnect after the installer has completed its elevated system-integration step.
If /dev/net/tun is missing, enable the TUN module on the host with
sudo modprobe tun.
CI and local Docker builds can use a proxy for Qt, Android SDK, Gradle, pip, apt and curl downloads:
WEBSHARE_BUILD_PROXY='http://user:password@proxy.example.net:6669' ./app-client/build-in-docker.sh all
If the proxy value is copied from a WebShare profile and includes a name after
#, the build script strips that fragment before passing it to HTTP clients.
Linux and Windows installers use WebShare's internal Qt Installer Framework build by default:
https://forgejo.acnas.net/app/qtif/releases/tag/v6.11.1-ifw4.11.0-2
The Docker build downloads the platform QtIF tools archive, verifies it with
SHA256SUMS.txt, and then creates the offline installers. Override
QTIFW_RELEASE_TAG, QTIFW_DOWNLOAD_BASE_URL, QTIFW_LINUX_TOOLS_URL or
QTIFW_WINDOWS_TOOLS_URL in CI if a newer QtIF release should be used.
Forgejo CI And Releases
The repository includes .forgejo/workflows/ci.yml for Forgejo Actions:
- pushes and pull requests run Python compilation and unit tests;
- pushes to
mainbuild and push Docker image tagsmainandsha-<commit>; - Git tags matching
v*also publish a Docker image with the same tag; - pushes to
maincan deploy the web panel over SSH with Docker Compose; - the Linux, Windows, and Android Qt/QML client packages are built in Docker from the Linux Forgejo runner;
- the Linux and Windows user-facing installers are generated with Qt Installer Framework;
- release assets are published only for Git tags matching
v*.
Docker images are pushed to the Forgejo container registry using the repository path:
forgejo.acnas.net/app/webshare
For repository app/webshare, the pushed tags look like:
forgejo.acnas.net/app/webshare:main
forgejo.acnas.net/app/webshare:sha-abcdef123456
forgejo.acnas.net/app/webshare:v0.1.0
Forgejo Actions provides a temporary FORGEJO_TOKEN / GITHUB_TOKEN for the
workflow, but many Forgejo versions/configurations do not allow that automatic
token to push to the container registry. If docker push fails with
401 Unauthorized on /v2/.../blobs/uploads/, create a personal access token
and add these Forgejo repository secrets:
REGISTRY_USER
REGISTRY_TOKEN
DEPLOY_HOST
DEPLOY_PORT
DEPLOY_USER
DEPLOY_SSH_KEY
DEPLOY_SSH_PASSWORD
DEPLOY_PATH
WEBSHARE_BUILD_PROXY
QT_MIRROR
ANDROID_KEYSTORE_BASE64
ANDROID_KEYSTORE_PASSWORD
ANDROID_KEY_ALIAS
ANDROID_KEY_PASSWORD
REGISTRY_USER is the Forgejo username that owns the token, for example your
user or a dedicated deploy bot. REGISTRY_TOKEN is a Forgejo access token from:
User menu -> Settings -> Applications -> Access Tokens
Create a token that can write container/package artifacts for this repository. If your Forgejo token UI has scopes, grant package/container read and write permissions.
For SSH deploy, prefer DEPLOY_SSH_KEY. It must be a private key that can SSH
to the production host. If you cannot use a key yet, set DEPLOY_SSH_PASSWORD
instead; the workflow will use password auth through sshpass. DEPLOY_PORT
is optional and defaults to 22. The remote user must be able to run docker
and docker compose.
Prepare the production host once:
sudo mkdir -p /opt/webshare-panel
sudo chown "$USER:$USER" /opt/webshare-panel
cd /opt/webshare-panel
cp /path/to/.env.example .env
Edit /opt/webshare-panel/.env and set at least:
WEBSHARE_SECRET=long-random-secret
WEBSHARE_ADMIN_USERNAME=admin
WEBSHARE_ADMIN_EMAIL=admin@example.com
WEBSHARE_ADMIN_PASSWORD=strong-password
WEBSHARE_HOST_PORT=8081
Then set DEPLOY_PATH=/opt/webshare-panel in Forgejo secrets. On each push to
main, CI builds the image, pushes it to Forgejo, copies
docker-compose.prod.yml to DEPLOY_PATH, runs:
docker compose -f docker-compose.prod.yml pull webshare
docker compose -f docker-compose.prod.yml up -d --remove-orphans webshare
and checks /api/health from inside the container.
The client CI job uses the same Dockerized build as local development:
app-client/build-in-docker.sh. The Forgejo runner must expose a Docker daemon
to job containers. With a Docker-in-Docker runner, set the runner config to pass
DOCKER_HOST into jobs and point container.docker_host at the DIND daemon.
To publish a release, push a tag:
git tag v0.1.0
git push origin v0.1.0
The tagged workflow creates or updates the Forgejo release and uploads:
webshare-linux-x64.run
webshare-windows-x64-setup.exe
webshare-android-universal.apk
The web panel Apps view reads the latest release from Forgejo and links to the
available assets.
Backup And Restore
Admins can use the Backup view in the web panel to download a JSON backup or
restore one through the browser.
The backup includes:
- users and password hashes;
- OIDC provider settings and linked external identities;
- SMTP settings, including stored SMTP passwords;
- servers, SSH settings, SSH passwords, and stored SSH private keys;
- deployments, generated configs, clients, traffic limits, and traffic records.
The backup does not include operation logs, server check history, or audit logs. Treat the downloaded file as a secret because it contains credentials.
When importing with Replace current configuration, the current users are
replaced by the backup users. After restore, sign in with an admin account from
the restored backup.
By default, restore also queues a VPN sync for every restored deployment whose
server has Auto deploy and sync clients enabled and SSH key or password
credentials configured. That sync regenerates the runtime configs from the
restored users/clients and runs remote Docker Compose with forced recreate, so
the VPN containers receive the restored user list automatically. Deployments on
servers without SSH credentials, or with auto-sync disabled, are skipped and can
be applied later with Deploy.
OIDC Login
Admins can enable OIDC in Settings -> OIDC. The implementation uses the
authorization-code flow with PKCE and verifies RSA-signed id_token values
against the provider JWKS. Keycloak works with the standard realm issuer URL:
https://keycloak.example.com/realms/main
Configure the Keycloak client with this redirect URI, shown in the web panel:
https://your-webshare-host/api/auth/oidc/callback
Useful defaults:
- scopes:
openid email profile; - username claim:
preferred_username; - email claim:
email; - role claim path for Keycloak realm roles:
realm_access.roles; - admin roles:
admin,webshare-admin.
If Allow new OIDC users is enabled, first login creates a local WebShare user
and links it to the OIDC issuer + sub. If the email already exists locally,
WebShare links that existing account instead. Client secrets are stored in the
local SQLite database, so keep data/webshare.sqlite3 private.
SMTP Settings
Admins can configure SMTP in Settings -> SMTP from the web panel.
Supported options:
- host, port, and security mode:
STARTTLS,SSL/TLS, orNone; - optional username and password;
- sender email and display name;
- timeout for SMTP operations;
- test recipient for sending a test email.
Passwords are stored in the local SQLite database and are included in backups.
Use Save and test to verify the current settings without leaving the panel.
When SMTP is enabled, WebShare sends automatic emails for:
- newly created user accounts;
- password changes;
- server/Docker checks that move from healthy to
unstableorerror; - recovery emails when a previously unhealthy server becomes
onlineagain.
Server check notifications are transition-based to avoid repeating the same alert on every automatic check.
User Profile
Every signed-in user has a Profile view. Users can change their email,
display name, and password. Changing the password requires the current password.
Admins can still manage roles and account status from Users.
Remote Deployment
Servers can be managed automatically over SSH.
When adding a server in the UI, fill:
Host: SSH host or IP address;Public host: domain/IP that clients will connect to;SSH userandSSH port;Deploy path: remote directory for generated runtime files, default/opt/webshare;SSH private key: private key with Docker access on the remote host;SSH password: optional password auth when a key is not provided;Auto deploy and sync clients: when enabled, deployments and client changes are applied automatically.
The remote host must already have:
- SSH access for the configured user;
- Docker Engine;
- Docker Compose v2 (
docker compose ...); - permission for the SSH user to run Docker.
Key authentication is preferred. Password authentication uses sshpass and is
supported by the Docker image. If you run WebShare directly on the host, install
sshpass and openssh-client there too. The password is stored in the local
SQLite database, so keep data/webshare.sqlite3 private.
Client config QR codes use the system qrencode utility. The Docker image
includes it. If you run WebShare directly on the host, install qrencode to see
QR codes in the config modal.
What Happens On Deploy
For each protocol deployment WebShare generates a small runtime bundle and uploads it to:
/opt/webshare/<deployment-id>/
Then it runs:
docker compose pull
docker compose up -d --remove-orphans --force-recreate
Adding, deleting, or changing clients rebuilds the runtime config and syncs the deployment again when auto_sync is enabled.
Containers are force-recreated during sync because most VPN/proxy daemons read
their user config only at startup.
Use Version on a deployment to read the currently running Docker container and
image information from the remote host. WebShare stores the latest image tag,
image id, container status, and timestamps on the deployment.
Use Update on a deployment to pull the latest image, recreate the VPN
container with the current generated config, and then refresh the stored Docker
version information.
Deleting a client removes it from the local database and, when the deployment's
server has auto-sync and SSH credentials, queues a sync so the remote VPN config
is regenerated without that user. Deleting a deployment or a server queues a
remote cleanup first: WebShare runs Docker Compose down with orphan and volume
cleanup in the deployment directory, removes the remote deployment folder, and
then deletes the local record when cleanup succeeds. If a server has no SSH
credentials, WebShare can only delete the local record and reports that remote
cleanup was skipped.
Runtime Files
Supported runtime generation:
| Protocol | Runtime behavior |
|---|---|
socks5 |
Runs 3proxy/3proxy:latest and generates 3proxy.cfg with all active users |
http_proxy |
Runs 3proxy/3proxy:latest and generates 3proxy.cfg with all active users |
hysteria2 |
Generates hysteria.yaml with user/password auth and a self-signed cert volume |
mtproto |
Runs nineseconds/mtg:2 with an mtg-v2 compatible fake-TLS secret |
xray_vless_reality |
Generates xray.json; auto-generates Reality keypair on first remote deploy when keys are omitted |
For xray_vless_reality, WebShare tries to generate a matching private_key /
public_key pair on the remote host by running Xray in Docker during deploy. You
can also provide both keys manually in Config JSON.
Example:
{
"server_name": "www.cloudflare.com",
"public_key": "REALITY_PUBLIC_KEY",
"private_key": "REALITY_PRIVATE_KEY",
"short_id": "abcd1234"
}
For mtproto, WebShare generates an mtg v2 compatible secret automatically.
You can override the fake-TLS domain with:
{
"fronting_domain": "telegram.org"
}
Older MTProto deployments that still have a legacy 32-hex secret are repaired on
the next Deploy; WebShare regenerates the secret and refreshes the client
links before starting the remote container.
Monitoring
The Check button on a server runs remote Docker inspection:
docker ps -a
docker stats --no-stream
WebShare stores the latest check result on the server record and keeps historical
server_checks. Monitoring covers Docker/container status, restart count,
healthcheck status, exit code, CPU, memory, and container network I/O. Exact
per-client traffic still depends on protocol-specific collectors and can be
added behind the protocol adapter layer.
Container network I/O is also saved as deployment traffic. The first successful check stores a baseline from Docker counters; the next checks save RX/TX deltas, so the dashboard traffic and Reports graphs move automatically while VPN containers are passing traffic.
Server activity is considered online when the SSH command succeeds and Docker
responds successfully. If SSH fails, Docker is unavailable, or the user cannot
run Docker, the check is saved as error and the command output is kept in the
check payload for debugging.
Deployment Deploy and Version also inspect the resulting Compose containers.
If Docker Compose exits successfully but the VPN container immediately exits,
restarts, reports unhealthy, or recent logs contain known fatal markers, the
deployment is marked error and the Operations details show the container state,
exit code, restart count, health status, and recent logs.
Successful one-shot helper containers, such as Hysteria certgen finishing with
Exited (0), are treated as healthy. Server-wide checks also ignore stopped
containers from old WebShare Compose projects that are no longer present as
active deployments in the local database.
WebShare also runs automatic checks in the background. By default it checks every
60 seconds all active servers that have SSH key or password credentials. Set
WEBSHARE_MONITOR_INTERVAL_SECONDS=0 to disable automatic monitoring.
The Overview, Servers, Operations, and Reports views refresh themselves
while open, so the latest server status appears without pressing refresh.
Operation Logs
Deploy, update, version, and server check actions run as background operations.
The Operations view shows:
- operation type and target;
running/success/errorstatus;- current message;
- command log and final JSON result.
The view refreshes automatically every few seconds while it is open. Routine
actions no longer force-switch you to Operations; they show a short operation
id in the notice area, and you can open the full details when needed.