The 3x-ipl action used iptables-allports, so a banned IP lost all TCP
access including SSH and the panel, locking admins out (especially with
dynamic-IP clients). The ban now blocks every TCP port except the SSH
and panel ports via a multiport negation, derived at jail-creation time
in both x-ui.sh and DockerEntrypoint.sh. This keeps IP-limit working for
all current and future inbounds without per-port config.
Binary: extend the migrate-db subcommand with --dump and --restore so a
SQLite database can be exported to a portable SQL text dump and rebuilt from
one, alongside the existing --dsn PostgreSQL copy. Implemented in Go via the
bundled sqlite driver (new database/dump_sqlite.go); no external sqlite3 client
is required. Add ExportPostgresToSQLite (reverse of MigrateData) to build a
SQLite .db from live PostgreSQL data, reusing the shared copyAllModels helper.
Overview: add a "Download Migration" item to Backup & Restore plus a
getMigration endpoint/service that returns a .dump on SQLite or a .db on
PostgreSQL, so the data can seed a panel on the other backend. Document the
endpoint in api-docs and translate the three new strings across all locales.
Tests: cover the destination-side copy (AutoMigrate + copyTable into SQLite)
and the dump/restore round-trip including quoted values. Ignore *.dump.
The x-ui.sh helper that drives this from the CLI is in PR #4910.
check_status() only recognized a systemd service or Alpine's
/etc/init.d/x-ui, neither of which exists in a container where the panel
runs as the foreground main process (PID 1 via "exec /app/x-ui"). Every
CLI command therefore failed with "Please install the panel first", and
restart/restart-xray relied on rc-service/systemctl that aren't present.
Detect the container (/.dockerenv or XUI_IN_DOCKER) and, when inside one:
- resolve the panel binary under /app instead of /usr/local/x-ui
- derive status from the running process instead of a service file
- restart via SIGHUP and restart-xray via SIGUSR1 to the panel process
- show Docker-appropriate guidance for start/stop/enable/disable
The Dockerfile sets XUI_IN_DOCKER/XUI_MAIN_FOLDER so detection is
explicit even though /.dockerenv alone suffices.
Closes#4817
Menu 20 only exported CF_Key/CF_Email, so a restricted Cloudflare API Token was misread as a Global Key and acme.sh failed with 'invalid domain'. Add a token-or-global-key prompt (default token): an API Token exports CF_Token, the Global Key keeps the previous CF_Key + CF_Email behavior. Also stop echoing the key/token value to the debug log.
Go's flag package parses '-resetTwoFactor false' as '-resetTwoFactor=true' with a dangling positional 'false', so two-factor auth was always wiped on username/password reset regardless of the prompt answer. Omit the flag in the preserve branch (default is false) and use '-resetTwoFactor=true' in the disable branch.
Add a self-contained 'PostgreSQL Management' submenu (main-menu option 27) so the panel can be set up and migrated without re-running the remote install script:
- Install PostgreSQL locally (server + client tools + dedicated xui user/db), ported from install.sh so x-ui.sh stays standalone
- Migrate SQLite to PostgreSQL via 'x-ui migrate-db', then write XUI_DB_TYPE/XUI_DB_DSN to the service env file and restart the panel; client tools are ensured first so in-panel backup/restore works for local and external databases
- Service control: status (clusters + port 5432), start, stop, restart, enable autostart, view log, with auto-detected cluster version
The IP certificate flow auto-set the panel cert path silently, unlike the
domain and Cloudflare flows which ask first. Add the same
"Would you like to set this certificate for the panel? (y/n)" prompt so
the IP flow is consistent and only configures the panel on confirmation.
* feat(install): prompt for SQLite vs PostgreSQL during install
* fix(install): write env file to per-distro path and handle pg-install failure
The env file was hardcoded to /etc/default/x-ui, but RHEL/Fedora units read
/etc/sysconfig/x-ui, Arch reads /etc/conf.d/x-ui, and Alpine OpenRC auto-
sources /etc/conf.d/x-ui. PostgreSQL selection was silently dropped on every
distro except Debian. Also initdb on openSUSE (service wouldn't start) and
prompt the operator on local-install failure instead of silently demoting
to SQLite.
* fix(scripts): make x-ui.sh and update.sh PostgreSQL-aware
update.sh ran setting -show and migrate without sourcing the env file, so
PostgreSQL users had migrations applied to the SQLite default and settings
introspection read the wrong DB. Sourcing the per-distro env file at the
start of update_x-ui exports XUI_DB_TYPE/XUI_DB_DSN to all binary calls.
x-ui.sh now shows the active backend in View Current Settings (password
masked) and removes the env file on uninstall so a later reinstall doesn't
inherit a stale DSN.
`14. Restart Xray` failed on Alpine with `systemctl: command not found` —
restart_xray was the only service action missing an Alpine branch. While
fixing it, the OpenRC reload action was passing the pidfile path to `kill`
instead of the PID inside it, so `rc-service x-ui reload` would have
failed too.
Fail2ban parses % as variable interpolation in action.d configs, so the
unescaped %Y/%m/%d %H:%M:%S in the date command crashed fail2ban on
startup. Double the %s in the heredoc so the rendered action file
contains %% and fail2ban collapses it back to a literal % when invoking
the shell command.
Try six IPv4 providers in turn, accept only HTTP 200 + IPv4-shaped body,
and prompt the user to enter their IP if every provider fails.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- DockerEntrypoint.sh: create jail.d/filter.d/action.d config files
before starting fail2ban so Docker containers no longer start with
0 active jails (fixes#4134)
- x-ui.sh create_iplimit_jails: lower maxretry from 2 to 1 so
fail2ban bans on the first log entry; with maxretry=2 and the
partitionLiveIps logic the second occurrence could arrive after the
32 s findtime window, silently preventing any ban (fixes#4163)
- x-ui.sh: fix datepattern (%%Y -> %Y) so fail2ban parses the Go
log timestamp correctly instead of looking for a literal %%Y string
- x-ui.sh / DockerEntrypoint.sh: fix date command in actionban /
actionunban echo (%%Y -> %Y) so the ban log records actual dates
- check_client_ip_job.go: replace log.SetOutput / log.SetFlags on
the global standard-library logger with a local log.New instance,
eliminating the dangling closed-file-handle between calls and
stopping unrelated stdlib log output from polluting 3xipl.log
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
On fresh Debian 12+, Ubuntu 24+ and recent RHEL-family minimal images
the fail2ban package ships with `banaction = nftables-multiport` as
the default in /etc/fail2ban/jail.conf but does not pull in the
`nftables` package as a dependency. The first SSH brute-force attempt
hits the default sshd jail and fail2ban logs
stderr: /bin/sh: 1: nft: not found
returned 127 -- HINT on 127: "Command not found"
repeatedly, which users mistake for a 3x-ui regression (see the
discussion on #4083). The 3x-ipl jail itself is unaffected — it uses
an iptables-based action configured in create_iplimit_jails — so this
is only stray noise, but noisy enough to look like a real failure on
first install.
Add `nftables` to the package list in every branch of install_iplimit
so new installs end up with a working default sshd jail out of the
box. Existing installs where `nftables` is already present are a
no-op.
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
Replace simple curl+trim checks with a response+http_code parse to ensure the remote URL returns HTTP 200 and a non-empty body before assigning server_ip. Changes applied to install.sh, update.sh and x-ui.sh: use curl -w to append the status code, extract http_code and ip_result, and only set server_ip when http_code == 200 and ip_result is non-empty. This makes the IP discovery more robust against error pages or partial responses while keeping the existing timeout behavior.
Introduces user prompts to select the port for ACME HTTP-01 certificate validation (default 80), checks if the chosen port is available, and provides guidance for port forwarding. Adds is_port_in_use helper to all scripts and improves messaging for certificate issuance and error handling.
The is_domain function was being called in ssl_cert_issue() but was never
defined in x-ui.sh, causing 'Invalid domain format' errors for valid domains.
Added is_ipv4, is_ipv6, is_ip, and is_domain helper functions to match
the definitions in install.sh and update.sh.
Co-authored-by: wyatt <wyatt@Wyatts-MacBook-Air.local>