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:
14
README.md
14
README.md
@@ -1,4 +1,4 @@
|
||||
# xray-sub
|
||||
# xtui
|
||||
|
||||
Небольшой TUI-скрипт для выбора сервера из JSON-подписки Xray.
|
||||
|
||||
@@ -21,7 +21,7 @@ chmod +x main.sh
|
||||
По желанию установите как команду:
|
||||
|
||||
```bash
|
||||
sudo install -m 755 main.sh /usr/local/bin/xray-sub
|
||||
sudo install -m 755 main.sh /usr/local/bin/xtui
|
||||
```
|
||||
|
||||
## Запуск
|
||||
@@ -35,7 +35,7 @@ sudo install -m 755 main.sh /usr/local/bin/xray-sub
|
||||
Если установлено в систему:
|
||||
|
||||
```bash
|
||||
xray-sub
|
||||
xtui
|
||||
```
|
||||
|
||||
Первый запуск попросит ссылку на JSON-подписку, путь к конфигу Xray и имя systemd-службы.
|
||||
@@ -43,9 +43,9 @@ xray-sub
|
||||
Полезные команды:
|
||||
|
||||
```bash
|
||||
xray-sub --setup # заново пройти настройку
|
||||
xray-sub --refresh # обновить подписку
|
||||
xray-sub --help # справка
|
||||
xtui --setup # заново пройти настройку
|
||||
xtui --refresh # обновить подписку
|
||||
xtui --help # справка
|
||||
```
|
||||
|
||||
## Что делает
|
||||
@@ -56,7 +56,7 @@ xray-sub --help # справка
|
||||
|
||||
## Где хранит файлы
|
||||
|
||||
Пользовательские файлы хранятся в `~/.xray-sub/`:
|
||||
Пользовательские файлы хранятся в `~/.xtui/`:
|
||||
|
||||
- `config` — настройки скрипта и последний выбранный сервер.
|
||||
- `sub.json` — кэш скачанной подписки.
|
||||
|
||||
63
main.sh
63
main.sh
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user