feat(x-ui.sh): add migrateDB command for SQLite .db <-> .dump (#4910)
Some checks are pending
CI / go-test (push) Waiting to run
CI / govulncheck (push) Waiting to run
CI / frontend (push) Waiting to run
CodeQL Advanced / Analyze (go) (push) Waiting to run
CodeQL Advanced / Analyze (actions) (push) Waiting to run
CodeQL Advanced / Analyze (javascript-typescript) (push) Waiting to run
Release 3X-UI / build (386) (push) Waiting to run
Release 3X-UI / build (amd64) (push) Waiting to run
Release 3X-UI / build (arm64) (push) Waiting to run
Release 3X-UI / build (armv5) (push) Waiting to run
Release 3X-UI / build (armv6) (push) Waiting to run
Release 3X-UI / build (armv7) (push) Waiting to run
Release 3X-UI / build (s390x) (push) Waiting to run
Release 3X-UI / Build for Windows (push) Waiting to run

* feat(x-ui.sh): add migrateDB command and menu for SQLite .db <-> .dump

Adds an "x-ui migrateDB <file>" subcommand and a PostgreSQL-menu option (9)
that convert between a SQLite .db and a portable .dump file. Direction is
auto-detected from the extension and delegated to the bundled binary
(x-ui migrate-db --dump/--restore), so no external sqlite3 client is needed.

Depends on the matching binary support, so it is only usable from the next
panel release.

* fix(x-ui.sh): address review feedback on migrateDB

Per Copilot review on PR #4910:
- Probe the bundled binary for migrate-db --dump support and fail with a clear
  upgrade message instead of a raw "flag not defined" error on old builds.
- Prompt before overwriting an existing .dump in dump mode (parity with restore).
- Refuse to restore into the live database path while x-ui is running, to avoid
  corrupting the running panel.
- Fix the usage/synopsis strings to show input is optional ([file] not <file>).
This commit is contained in:
Sanaei 2026-06-05 11:28:11 +02:00 committed by GitHub
parent db118cbcc9
commit 0706b0b3a8
No known key found for this signature in database
GPG key ID: B5690EEEBB952194

102
x-ui.sh
View file

@ -2795,6 +2795,7 @@ postgresql_menu() {
echo -e "${green}\t6.${plain} Restart PostgreSQL"
echo -e "${green}\t7.${plain} ${green}Enable${plain} Autostart on boot"
echo -e "${green}\t8.${plain} View PostgreSQL Log"
echo -e "${green}\t9.${plain} Convert SQLite ${green}.db <-> .dump${plain}"
echo -e "${green}\t0.${plain} Back to Main Menu"
read -rp "Choose an option: " choice
case "$choice" in
@ -2833,6 +2834,10 @@ postgresql_menu() {
postgresql_log
postgresql_menu
;;
9)
migrate_db_prompt
postgresql_menu
;;
*)
echo -e "${red}Invalid option. Please select a valid number.${plain}\n"
postgresql_menu
@ -2840,6 +2845,99 @@ postgresql_menu() {
esac
}
# Convert between the panel's SQLite database and a portable .dump (SQL text)
# file using the bundled x-ui binary. With no arguments it dumps the installed
# panel database; an optional second argument overrides the output path.
# x-ui migrateDB [file.db|file.dump] [output]
migrate_db() {
local input="$1" output="$2"
local default_db="/etc/x-ui/x-ui.db"
local bin="${xui_folder}/x-ui"
[[ -z "$input" ]] && input="$default_db"
if [[ ! -x "$bin" ]]; then
LOGE "x-ui binary not found at ${bin}. Is the panel installed?"
return 1
fi
if ! "$bin" migrate-db -h 2>&1 | grep -q -- '-dump'; then
LOGE "This x-ui build does not support .db <-> .dump conversion yet."
LOGE "Update the panel first (x-ui update) to a version with 'migrate-db --dump/--restore'."
return 1
fi
if [[ ! -f "$input" ]]; then
LOGE "Input file not found: ${input}"
echo -e "Usage: ${green}x-ui migrateDB [file.db|file.dump] [output]${plain}"
return 1
fi
local mode
case "$input" in
*.db | *.sqlite | *.sqlite3)
mode="dump"
;;
*.dump | *.sql)
mode="restore"
;;
*)
if head -c 16 "$input" | grep -q "SQLite format 3"; then
mode="dump"
else
mode="restore"
fi
;;
esac
if [[ "$mode" == "dump" ]]; then
[[ -z "$output" ]] && output="${input%.*}.dump"
if [[ -f "$output" ]]; then
confirm "Output ${output} already exists and will be overwritten. Continue?" "n" || return 0
fi
LOGI "Dumping SQLite database to SQL text:"
echo -e " ${green}${input}${plain} -> ${green}${output}${plain}"
if "$bin" migrate-db --src "$input" --dump "$output"; then
LOGI "Done. Wrote ${output}."
else
LOGE "Dump failed."
return 1
fi
else
[[ -z "$output" ]] && output="${input%.*}.db"
if [[ "$output" == "$default_db" ]] && check_status > /dev/null 2>&1; then
LOGE "Refusing to restore into the live database (${default_db}) while x-ui is running."
LOGE "Stop the panel first (x-ui stop) or choose a different output path."
return 1
fi
if [[ -f "$output" ]]; then
confirm "Output ${output} already exists and will be overwritten. Continue?" "n" || return 0
rm -f "$output"
fi
LOGI "Rebuilding SQLite database from SQL text:"
echo -e " ${green}${input}${plain} -> ${green}${output}${plain}"
if "$bin" migrate-db --restore "$input" --out "$output"; then
LOGI "Done. Created ${output}."
else
LOGE "Restore failed."
rm -f "$output"
return 1
fi
fi
}
# Interactive wrapper around migrate_db for the menu: prompts for the paths and
# lets migrate_db auto-detect the direction.
migrate_db_prompt() {
local default_db="/etc/x-ui/x-ui.db"
local input output
echo -e "Convert between a SQLite ${green}.db${plain} and a portable ${green}.dump${plain} (direction auto-detected)."
read -rp "Input file [${default_db}]: " input
input="${input:-$default_db}"
read -rp "Output file (leave empty to auto-name next to input): " output
migrate_db "$input" "$output"
}
show_usage() {
echo -e "┌────────────────────────────────────────────────────────────────┐
${blue}x-ui control menu usages (subcommands):${plain}
@ -2857,6 +2955,7 @@ show_usage() {
${blue}x-ui banlog${plain} - Check Fail2ban ban logs │
${blue}x-ui update${plain} - Update │
${blue}x-ui update-all-geofiles${plain} - Update all geo files │
${blue}x-ui migrateDB [file]${plain} - Convert .db <-> .dump (SQLite)
${blue}x-ui legacy${plain} - Legacy version │
${blue}x-ui install${plain} - Install │
${blue}x-ui uninstall${plain} - Uninstall │
@ -3045,6 +3144,9 @@ if [[ $# > 0 ]]; then
"update-all-geofiles")
check_install 0 && update_all_geofiles 0 && restart 0
;;
"migrateDB")
migrate_db "$2" "$3"
;;
*) show_usage ;;
esac
else