No description
  • Python 43.4%
  • C++ 20.3%
  • QML 12.1%
  • Java 8.6%
  • JavaScript 7.9%
  • Other 7.6%
Find a file
mbulatov f24a3c44e7
Some checks are pending
CI / deploy (push) Blocked by required conditions
CI / release (push) Blocked by required conditions
CI / tests (push) Successful in 59s
CI / docker (push) Waiting to run
CI / clients (push) Has started running
next stage
2026-06-02 15:43:16 +03:00
.forgejo next stage 2026-06-02 14:03:22 +03:00
app-client next stage 2026-06-02 15:43:16 +03:00
src/webshare next stage 2026-06-02 14:03:22 +03:00
tests next stage 2026-06-02 14:03:22 +03:00
.dockerignore next stage 2026-05-29 01:07:12 +03:00
.env.example next stage 2026-05-31 11:42:10 +03:00
.gitignore next stage 2026-05-29 01:07:12 +03:00
.gitmodules next stage 2026-05-31 18:20:37 +03:00
docker-compose.prod.yml next stage 2026-05-31 11:42:10 +03:00
docker-compose.yml next stage 2026-05-31 11:42:10 +03:00
Dockerfile next stage 2026-05-31 11:42:10 +03:00
pyproject.toml next stage 2026-05-31 11:42:10 +03:00
README.md next stage 2026-06-02 14:03:22 +03:00

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/login
  • GET /api/apps
  • GET /api/me
  • GET|PATCH /api/profile
  • GET|POST /api/users
  • GET|PATCH|DELETE /api/users/{id}
  • GET|POST /api/servers
  • GET|POST /api/servers/{id}/deployments
  • GET|POST /api/clients
  • GET /api/clients/{id}/config
  • GET /api/mobile/deployments
  • GET|POST /api/mobile/clients
  • GET /api/mobile/clients/{id}/sing-box
  • GET /api/mobile/network-status
  • GET|PUT /api/clients/{id}/limit
  • POST /api/clients/{id}/traffic
  • GET /api/reports/summary
  • GET /api/reports/traffic
  • GET /api/reports/near-limit
  • GET /api/settings/oidc
  • PUT /api/settings/oidc
  • GET /api/settings/smtp
  • PUT /api/settings/smtp
  • POST /api/settings/smtp/test
  • GET /api/auth/oidc/config
  • POST /api/auth/oidc/start
  • GET /api/auth/oidc/callback
  • GET /api/export
  • POST /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-core controlled 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/libbox tunnel 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-core is bundled with the release archive;
  • WEBSHARE_CORE=/path/to/webshare-core can override the bundled helper;
  • the Linux installer adds a root-owned webshare-core and limited webshare-vpn-helper under /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 call sudo, do not call pkexec, do not use setcap, 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 main build and push Docker image tags main and sha-<commit>;
  • Git tags matching v* also publish a Docker image with the same tag;
  • pushes to main can 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, or None;
  • 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 unstable or error;
  • recovery emails when a previously unhealthy server becomes online again.

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 user and SSH 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 / error status;
  • 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.