Skip to content

feat: admin proxy pool with per-proxy device capacity and automatic assignment - #376

Merged
asternic merged 1 commit into
asternic:mainfrom
Fenil-1:feat/proxy-pool
Oct 6, 2026
Merged

asternic merged 1 commit into
asternic:mainfrom
Fenil-1:feat/proxy-pool

Conversation

@Fenil-1

@Fenil-1 Fenil-1 commented Oct 5, 2026

Copy link
Copy Markdown
Contributor

Closes #366

Summary

Adds a proxy pool that an admin manages from the dashboard or the admin API. Each proxy has a max_devices limit. When a session connects without its own proxy, wuzAPI assigns the least-loaded enabled pool proxy with free capacity, saves the choice on the user, and applies it through the existing per-session proxy path. The assignment is sticky: a number keeps the same IP on every reconnect.

With an empty pool, behaviour is identical to today. Explicit per-user proxies (proxyConfig, POST /session/proxy) keep working exactly as before and are never touched by the pool.

Behaviour

Session state on POST /session/connect Result
Has its own proxy Uses it; pool not involved
Already assigned a pool proxy Reuses it (sticky, even if the pool is now full or the entry disabled)
No proxy, a pool proxy has free capacity Assigned the least-loaded one (assigned / max_devices), saved on the user
No proxy, every enabled pool proxy is full 503 proxy pool exhausted (default), or connects without a proxy if WUZAPI_PROXY_POOL_FALLBACK=direct
No proxy, pool empty or all entries disabled Connects without a proxy, as today
  • Capacity is counted by assignment, not live connections, so an offline device keeps its IP.
  • Setting or removing an explicit proxy releases any pool slot the user held. Deleting a user frees its slot.
  • Assignment is serialized (in-process mutex, plus SELECT … FOR UPDATE on the pool rows on PostgreSQL), so concurrent connects cannot overfill a proxy.

Admin API

Method Endpoint Description
GET /admin/proxy-pool List entries with assigned_count. Proxy passwords are masked.
POST /admin/proxy-pool Add {label, proxy_url, max_devices, enabled}. Same http/socks5 validation as SetProxy.
PUT /admin/proxy-pool/{id} Partial update. max_devices cannot go below the assigned count. Changing proxy_url updates the assigned users (used on their next connect).
DELETE /admin/proxy-pool/{id} 409 while sessions are still assigned
POST /admin/users/{id}/proxy-pool/release Free a disconnected session's pool slot

GET /admin/users and GET /session/status now also return proxy_pool_id. Documented in API.md and static/api/spec.yml.

Configuration

WUZAPI_PROXY_POOL_FALLBACK (flag -proxypoolfallback): block (default) or direct. It only applies when the pool has enabled proxies but all are full. direct connects without a proxy and saves nothing, so the next connect tries the pool again. An invalid value stops startup, like WUZAPI_AUTO_PRESENCE. Added to the README, .env.sample and both compose files.

Database

Migration 14 (add_proxy_pool), for both PostgreSQL and SQLite:

  • new table proxy_pool (id, label, proxy_url UNIQUE, max_devices CHECK >= 1, enabled, created_at)
  • users.proxy_pool_id → proxy_pool(id) ON DELETE RESTRICT, indexed

Tested upgrading an existing PostgreSQL database and on a fresh SQLite one.

Dashboard

  • Proxy Pool section under the instances table: label, masked URL, assigned / max bar, status, Edit / Disable / Delete (Delete is disabled while proxies are assigned).
  • Add/Edit modal. When editing, the URL field starts empty and is only sent if a new URL is typed, so the masked value is never saved back.
  • Proxy column in the instances table (pool label / Custom / None), and a pool line plus Release button on the instance card.
  • Errors are shown inline (in the form or above the table); no new toasts.
image image image

Testing

  • go test -race ./..., go vet, gofmt and go mod tidy are clean.
  • New proxy_pool_test.go: empty and disabled-only pool keep current behaviour, least-loaded selection and capacity, stickiness, explicit proxy wins, 10 concurrent assignments against 3 slots never overfill, admin CRUD and validation, EditUser explicit proxy leaves the pool, fallback parsing, and block vs direct.
  • Manually against PostgreSQL in Docker: parallel connects (3 of 4 assigned, 4th got 503), stickiness across reconnect, release and slot reuse, and all dashboard flows.

Notes for review

  1. Startup reconnect: sessions that were connected directly before a pool existed reconnect directly after a restart, and only pick up a pool proxy on their next /session/connect. Kept startup unchanged on purpose; happy to change it.
  2. Question: should there be a per-user "never use the pool" option? Left out to keep this PR focused.
  3. POST /session/proxy now also rejects a proxy URL with no host (e.g. socks5://).
  4. Also included: the dashboard's Go Back button no longer shows on the instances list, and changed scripts load with ?v= so browsers don't use old cached JS.

@asternic
asternic merged commit 53dc1b5 into asternic:main Oct 6, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

proxy pool in the admin dashboard, with per-proxy device capacity and automatic assignment at connect time

2 participants