refactor: rename xray-sub to xtui, optimize selection tracking with index

- Rename project from xray-sub to xtui (app name, state dir, user-agent)
- Add selected_index to config for O(1) saved server lookup
- Add migrate_saved_index() for backward compatibility
- Replace fingerprint-based comparison with index-based in menu and apply
This commit is contained in:
2026-06-15 15:43:01 +03:00
parent 8c0aa5b8b3
commit 0553ef7493
2 changed files with 47 additions and 30 deletions

63
main.sh
View File

@@ -1,8 +1,8 @@
#!/usr/bin/env bash
set -Eeuo pipefail
APP_NAME="xray-sub"
STATE_DIR="${HOME}/.xray-sub"
APP_NAME="xtui"
STATE_DIR="${HOME}/.xtui"
CONFIG_FILE="${STATE_DIR}/config"
SUB_JSON="${STATE_DIR}/sub.json"
BACKUP_DIR="${STATE_DIR}/backups"
@@ -135,7 +135,7 @@ write_cfg() {
--arg sub_url "$sub_url" \
--arg xray_config "$xray_config" \
--arg service "$service" \
'{sub_url:$sub_url, xray_config:$xray_config, service:$service, selected_fp:"", selected_name:""}' >"$tmp"
'{sub_url:$sub_url, xray_config:$xray_config, service:$service, selected_index:"", selected_fp:"", selected_name:""}' >"$tmp"
install -m 600 "$tmp" "$CONFIG_FILE"
rm -f "$tmp"
}
@@ -163,7 +163,7 @@ fetch_subscription() {
info "Скачиваю подписку…"
if ! curl -fLsS --connect-timeout 10 --max-time 45 --retry 2 --retry-delay 1 \
-H 'Accept: application/json, */*' \
-A 'xray-sub-selector/1.2' \
-A 'xtui/1.2' \
"$url" -o "$tmp"; then
rm -f "$tmp"
return 1
@@ -332,21 +332,36 @@ profile_fingerprint() {
}
find_saved_index() {
local saved_fp count i fp
local saved_index saved_fp count i fp
saved_index="$(cfg_get selected_index)"
saved_fp="$(cfg_get selected_fp)"
count="$(profile_count)"
[[ -n "$saved_fp" ]] || { echo 0; return; }
for ((i=0; i<count; i++)); do
fp="$(profile_fingerprint "$i")"
if [[ "$fp" == "$saved_fp" ]]; then
echo "$i"
return
fi
done
if [[ "$saved_index" =~ ^[0-9]+$ ]] && (( saved_index < count )); then
echo "$saved_index"
return
fi
if [[ -n "$saved_fp" ]]; then
for ((i=0; i<count; i++)); do
fp="$(profile_fingerprint "$i")"
if [[ "$fp" == "$saved_fp" ]]; then
cfg_set selected_index "$i"
echo "$i"
return
fi
done
fi
echo 0
}
migrate_saved_index() {
[[ -s "$SUB_JSON" ]] || return 0
validate_subscription_file "$SUB_JSON" || return 0
find_saved_index >/dev/null
}
build_selected_config() {
local idx="$1" out="$2"
jq -e --argjson i "$idx" "${PROFILES_DEF}"'
@@ -418,10 +433,10 @@ restart_xray_service() {
}
apply_selection() {
local idx="$1" dest service tmp fp old_fp name proto addr port changed=1 service_state
local idx="$1" dest service tmp fp old_index name proto addr port changed=1 service_state
dest="$(cfg_get xray_config)"
service="$(cfg_get service)"
old_fp="$(cfg_get selected_fp)"
old_index="$(cfg_get selected_index)"
fp="$(profile_fingerprint "$idx")"
IFS=$'\t' read -r name proto addr port < <(profile_summary "$idx")
@@ -444,12 +459,13 @@ apply_selection() {
ok "В ${dest} уже записан этот конфиг: ${name}"
fi
cfg_set selected_index "$idx"
cfg_set selected_fp "$fp"
cfg_set selected_name "$name"
rm -f "$tmp"
service_state="$(service_state_raw "$service")"
if (( changed )) || [[ "$old_fp" != "$fp" ]]; then
if (( changed )) || [[ "$old_index" != "$idx" ]]; then
if [[ "$service_state" == "active" || "$service_state" == "activating" ]]; then
restart_xray_service "$service"
else
@@ -622,17 +638,16 @@ render_table_header() {
}
render_profile_row() {
local idx="$1" cursor="$2" saved_fp="$3"
local fp name proto addr port pointer mark row_style name_short addr_short
local idx="$1" cursor="$2" saved_index="$3"
local name proto addr port pointer mark row_style name_short addr_short
IFS=$'\t' read -r name proto addr port < <(profile_summary "$idx")
fp="$(profile_fingerprint "$idx")"
pointer=" "
mark=" "
row_style=""
[[ "$idx" -eq "$cursor" ]] && { pointer="➤"; row_style="$C_INV"; }
[[ -n "$saved_fp" && "$fp" == "$saved_fp" ]] && mark="✓"
[[ "$saved_index" =~ ^[0-9]+$ && "$idx" -eq "$saved_index" ]] && mark="✓"
name_short="$(shorten "$name" 31)"
addr_short="$(shorten "$addr" 20)"
@@ -646,11 +661,11 @@ render_profile_row() {
}
render_menu() {
local cursor="$1" count saved_fp i cache_info selected_name
local cursor="$1" count saved_index i cache_info selected_name
local service service_state service_enabled xray_config socks_endpoint
count="$(profile_count)"
saved_fp="$(cfg_get selected_fp)"
saved_index="$(cfg_get selected_index)"
selected_name="$(cfg_get selected_name)"
service="$(cfg_get service)"
xray_config="$(cfg_get xray_config)"
@@ -676,7 +691,7 @@ render_menu() {
render_table_header
for ((i=0; i<count; i++)); do
render_profile_row "$i" "$cursor" "$saved_fp"
render_profile_row "$i" "$cursor" "$saved_index"
done
}
@@ -777,6 +792,7 @@ main() {
;;
--refresh)
cfg_exists || wizard
migrate_saved_index
fetch_subscription "$(cfg_get sub_url)" || die "Не удалось обновить подписку."
exit 0
;;
@@ -789,6 +805,7 @@ main() {
;;
esac
migrate_saved_index
maybe_refresh_subscription "$(cfg_get sub_url)"
validate_subscription_file "$SUB_JSON" || die "Кэш подписки повреждён: ${SUB_JSON}. Запустите ${0##*/} --refresh"
menu