Add /clash/:subid endpoint that returns complete Clash YAML config.
User provides full template (DNS, routing, proxy-groups, rules) in
settings, panel generates proxies from inbound/client data and injects
via proxies: [] placeholder replacement.
- New SubClashService reads template, generates vmess/vless/trojan/ss
proxy entries with transport (ws/grpc/h2/tcp/httpupgrade), TLS, and
Reality support
- Settings: subClashEnable, subClashPath, subClashURI, subClashTemplate
- UI: Clash settings tab, QR code on subpage, Desktop dropdown with
clash-verge:// deep link preferring Clash URL
- Version bump to v1.5.2-beta
Replace SQLite-only JSON_EACH with DB-type branching (JSON_TABLE for
MariaDB) in subscription, client traffic, and migration queries.
Bump version to v1.5.1.
The traffic-pending.json file could contain a stale client traffic delta
with inboundId=0 (created before the InboundId resolution fix). When
flushToDatabase tried to INSERT this into client_traffics, it violated
the foreign key constraint fk_inbounds_client_stats, causing the entire
transaction to roll back and blocking ALL traffic from being written to
MariaDB.
- Skip deltas with InboundID==0 in flushToDatabase with a warning log
- Share a single TrafficPendingStore between XrayTrafficJob and the
flush loop to eliminate a race condition from dual file instances
- Add test for zero InboundID skip behavior
In shared mode the Xray API returns InboundId=0 for client traffic.
Collect() now looks up the real InboundId from the client_traffics table
by email, and skips unknown emails with a warning. Also computes and
sets online clients in XrayTrafficJob since addClientTraffic is bypassed.
- inject lightweight hooks in CheckDeviceLimitJob for deterministic tests
- add tests for run re-entrancy, disabled enforcement unban, and stale ban cleanup
- add inbound deviceLimit model/frontend fields and translations
- add CheckDeviceLimitJob with observation window and xray API ban/unban
- prevent job re-entrancy and restore users when limit is disabled
- reduce lock scope via snapshots to avoid blocking log parsing
- Prevent DBPassword from leaking to frontend (json:"-")
- Make migration direction explicit via --direction flag, set dbType only after success
- Use driver-appropriate DROP INDEX IF EXISTS for SQLite vs MariaDB
- Build DSN with mysql.Config.FormatDSN() to prevent injection with special chars
- Close DB before re-initialization in InitDB
- Add migration tests (5 tests using SQLite in-memory DBs)
- Parse JSON once in GetDBConfigFromJSON instead of 7 times
- Use Go binary for dbType in shell script instead of fragile grep
- Add rollback on failure in db_switch_to_sqlite
- Validate DB settings in CheckValid
- Close migration DB connections with defer to prevent leaks
- Truncate destination tables before migration to avoid duplicates
- Wrap migration in transaction for atomicity
- Pass DB password via env var instead of CLI args to avoid process list exposure
- Improve error messages for MariaDB export/import with alternatives
- Update package doc to reflect dual DB support
- DRY migration logic with shared migrateAllTables function
- Add tests for config, database, model, util/common, util/crypto,
util/random, web/middleware, web/service, and xray packages
- Fix redirect middleware using slice instead of map to guarantee
deterministic longest-prefix-first matching order
- install.sh: default username/password to admin on fresh install
- user.go: UpdateFirstUser resets Role to admin, preventing lockout
- user.html: show remaining traffic and last online time
- i18n: remove 11 translation files, keep only en_US and zh_CN
- LanguageManager: trim supportedLanguages to 2 entries, remove simularLangs
Add a simplified dashboard page for non-admin users showing username,
traffic usage, expiry time, and logout button. Implement role-based
routing so user-role accounts are redirected to their own dashboard
instead of the admin panel. Add getUserInfo API endpoint and i18n
translations across all 13 supported locales.
- Add ?render=explicit to api.js URL to disable auto-initialization
- Pass HTMLElement (not selector string) to turnstile.render() and turnstile.reset()
- Prevents race condition where Turnstile auto-renders before body DOM is parsed
Load Turnstile api.js statically in <head> with data-cfasync="false"
to bypass Rocket Loader interference. Use turnstile.render() API to
manually render widget after site key is fetched, instead of relying
on dynamic script loading and Vue data-bind attributes.
- Add per-IP rate limiter middleware (5 req/min) on /register endpoint
- Validate username (3-64 chars) and password (8-128 chars) with trim
- Use sentinel error ErrUsernameAlreadyExists instead of string matching
- Prevent TurnstileSecretKey exposure via admin settings API (json:"-")
- Skip json:"-" fields in UpdateAllSetting to avoid overwriting secrets
- Add SetTurnstileSecretKey setter for programmatic configuration
- Reuse package-level http.Client in Turnstile verification for connection pooling
- Add io.LimitReader to cap Turnstile response body size
- Log all Turnstile verification error paths for debugging
- Add invalidUsername/invalidPassword i18n keys to all 13 locales
Remove global unique constraint on client_traffics.email, change email
duplication check to per-inbound scope, and automatically register new
users as disabled clients in all existing inbounds within a transaction.
Turnstile iframe (~300px min width) overflowed its container on mobile
due to large login card padding and no overflow handling. Reduce mobile
padding, center the widget wrapper, and use compact mode below 480px.
- Add Role field to User model (admin/user) with uniqueIndex on Username
- Add POST /register endpoint with optional Cloudflare Turnstile verification
- Add RegisterUser service with bcrypt password hashing and duplicate detection
- Set default admin user role to "admin", new registrations get "user"
- Add turnstileSecretKey setting and GetTurnstileSecretKey getter
- Add i18n keys (userExists, errorRegister) to all 13 translation files
New fields added after initial install are now automatically merged
into the existing x-ui.json file, so upgrades pick up defaults for
newly added settings without requiring manual intervention.
Add a register tab on the login page with username, password, confirm
password fields and Cloudflare Turnstile widget. The site key is
configurable via x-ui.json and exposed through a public endpoint.
- config: add GetSettingPath for JSON-based settings storage
- setting.go: load/save settings from JSON file instead of DB;
keep xrayTemplateConfig in DB; fix ResetSettings to not clear users
- xray_setting.go: save xray template config to DB directly
- install.sh: add Cloudflare SSL option (wildcard via DNS), allow
user to input custom credentials on fresh install, fix existing
install logic to preserve user config
- uninstall: add certificate revocation prompt before removing
- reset_config: fix misleading confirmation text, also reset cert
config; remove user table deletion from Go ResetSettings
Replace all MHSanaei/3x-ui references in shell scripts, README files,
HTML, and CI config with Sora39831/3x-ui. Go import paths unchanged
to maintain upstream compatibility.
When a client hit traffic/expiry limit, disableInvalidClients sets
client_traffics.enable=false and removes the user from Xray. GetClientTrafficByEmail
was overwriting that with settings.clients[].enable (admin config), so
ResetClientTraffic never saw the client as disabled and did not re-add
the user. Clients could not connect until manually disabled/re-enabled.
Now the DB runtime enable flag is preserved; reset correctly re-adds
the user to Xray.
Add a createRobustFastHTTPClient helper to configure fasthttp.Client with better timeouts, connection limits, retries and optional SOCKS5 proxy dialing. Validate and sanitize proxy and API server URLs instead of returning early on invalid values, and build telego.Bot options dynamically. Reduce long-polling timeout to detect connection issues faster and adjust update retrieval comments. Implement exponential-backoff retry logic for SendMessage calls to handle transient connection/timeouts and improve delivery reliability; also reduce inter-message delay for better throughput.
Add rate-limit friendly delays and context timeouts when sending backups via Telegram. Iterate admin IDs with index to sleep 1s between sends; add 30s context.WithTimeout for each SendDocument call and defer file.Close() for opened files; insert a 500ms pause between sending DB and config files. These changes improve resource cleanup and reduce chance of Telegram rate-limit/timeout failures.
- Add timestamp tracking for each client IP address
- Sort IPs by connection time (newest first) instead of alphabetically
- Automatically disconnect old connections when IP limit exceeded
- Keep only the most recent N IPs based on LimitIP setting
- Force disconnection via Xray API (RemoveUser + AddUser)
- Prevents account sharing while allowing legitimate network switching
- Log format: [LIMIT_IP] Email = user@example.com || Disconnecting OLD IP = 1.2.3.4 || Timestamp = 1738521234
This ensures users can seamlessly switch between networks (mobile/WiFi)
and the system maintains connections from their most recent IPs only.
Fixes account sharing prevention for VPN providers selling per-IP licenses.
Co-authored-by: Aung Ye Zaw <zaw.a.y@phluid.world>
Adds a scheduled GitHub Actions workflow (.github/workflows/cleanup_caches.yml) that runs weekly (and via workflow_dispatch) to delete Actions caches not accessed in the last 3 days. The job uses the gh CLI with the repository token and actions: write permission to list caches, filter by last_accessed_at against a 3-day cutoff, and delete matching cache IDs.