- 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.
Check x-ui.json or x-ui.db at DB folder path (/etc/x-ui) instead of
only checking x-ui.db at x-ui folder. Existing installs now skip all
username/password/path/port/SSL prompts and preserve saved settings.
The GitHub API returns "prerelease": false (with space after colon), but
the grep pattern used "prerelease":false (no space). Also, tag_name and
prerelease are on different lines after tr '{' '\\n', so the piped grep
chain couldn't find both fields. Fixed with grep -B5 to look backwards
from prerelease line to find tag_name.
Replace the single latest-release fetch in update_x-ui() with
get_releases() and select_version() functions that offer users a
choice between the latest stable and latest pre-release version,
matching the pattern already added to install.sh.
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.
The ssl_cert_issue function called systemctl start x-ui after certificate
installation, causing the panel to start before the install flow completed.
Changed install_x-ui final start to restart to guarantee the latest config is used.
Remove the duplicate release trigger. Now pushing a tag like v*.*.*
automatically creates a prerelease (if not exists) and uploads the
build artifacts via gh CLI, eliminating the duplicate build issue.
The build-windows job had an empty matrix platform list (amd64 was
commented out), causing GitHub Actions validation to fail with
"Unexpected value ''" on line 205. Comment out the entire job.
- 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
- Remove docker.yml (no Docker image publishing needed)
- Release workflow now triggers on tag push and release creation
- Only linux-amd64 builds active, other architectures commented out
- Windows build commented out
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.
Update go toolchain to 1.26.1 and upgrade multiple direct and indirect modules (examples: github.com/gin-contrib/gzip v1.2.6, github.com/gin-contrib/sessions v1.1.0, github.com/go-ldap/ldap/v3 v3.4.13, github.com/goccy/go-json v0.10.6, github.com/pelletier/go-toml/v2 v2.3.0, github.com/shirou/gopsutil/v4 v4.26.3, github.com/xtls/xray-core v1.260327.0, golang.org/x/crypto v0.49.0, google.golang.org/grpc v1.80.0). go.sum updated accordingly to lock the new versions. Routine dependency refresh to pull in fixes and improvements.
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.
it should be 'default' runlevel when add x-ui service to openrc, default is 'sysinit' runlevel. 'sysinit' runlevel is unnecessary,maybe.
if not, there is an error when call to function 'check_enabled()' as command 'grep default -c' can`t print 'default' runlevel.
check_enabled() {
if [[ $release == "alpine" ]]; then
if [[ $(rc-update show | grep -F 'x-ui' | grep default -c) == 1 ]]; then
return 0
else
return 1
fi