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