112 lines
4.5 KiB
Bash
112 lines
4.5 KiB
Bash
#!/bin/sh
|
|
# Обновляет geosite/geoip-файлы Xray из веток release. Рассчитан на cron.
|
|
#
|
|
# Каждый файл скачивается, только если его sha256 отличается от локального,
|
|
# контрольная сумма проверяется до подмены, а сервис перезапускается один раз
|
|
# и только если что-то реально изменилось. Молчит, когда обновлять нечего, —
|
|
# cron не будет слать письма впустую.
|
|
#
|
|
# ./update-geodata.sh
|
|
# RESTART_CMD='x-ui restart' ./update-geodata.sh
|
|
#
|
|
# Что качать, задаёт SOURCES — по строке на файл, формат «URL-базы|имя-файла»
|
|
# (без «|» берётся BASE_URL). По умолчанию это ipcheck.dat из ipcheck-domains
|
|
# и оба .dat из discord-geodata:
|
|
#
|
|
# SOURCES='https://example.org/raw/branch/release|my.dat' ./update-geodata.sh
|
|
#
|
|
# Коды возврата: 0 — обновлено или уже актуально, 1 — ошибка.
|
|
|
|
set -eu
|
|
|
|
DEST_DIR="${DEST_DIR:-/usr/local/x-ui/bin}"
|
|
BASE_URL="${BASE_URL:-https://gitea.hsrv.site/mrleo1nid/ipcheck-domains/raw/branch/release}"
|
|
DISCORD_URL="${DISCORD_URL:-https://gitea.hsrv.site/mrleo1nid/discord-geodata/raw/branch/release}"
|
|
RESTART_CMD="${RESTART_CMD:-}"
|
|
TIMEOUT="${TIMEOUT:-30}"
|
|
|
|
# Совместимость со старым вызовом, где DEST задавал единственный файл целиком.
|
|
if [ -n "${DEST:-}" ]; then
|
|
DEST_DIR=$(dirname "$DEST")
|
|
NAME="${NAME:-$(basename "$DEST")}"
|
|
fi
|
|
|
|
SOURCES="${SOURCES:-\
|
|
$BASE_URL|${NAME:-ipcheck.dat}
|
|
$DISCORD_URL|discord-geosite.dat
|
|
$DISCORD_URL|discord-geoip.dat}"
|
|
|
|
log() { printf '%s update-geodata: %s\n' "$(date -u '+%Y-%m-%dT%H:%M:%SZ')" "$*"; }
|
|
die() { log "ОШИБКА: $*" >&2; exit 1; }
|
|
|
|
command -v curl >/dev/null 2>&1 || die "нужен curl"
|
|
|
|
if command -v sha256sum >/dev/null 2>&1; then
|
|
hash_of() { sha256sum "$1" | cut -d' ' -f1; }
|
|
elif command -v shasum >/dev/null 2>&1; then
|
|
hash_of() { shasum -a 256 "$1" | cut -d' ' -f1; }
|
|
else
|
|
die "нужен sha256sum или shasum"
|
|
fi
|
|
|
|
fetch() { curl -fsSL --retry 3 --retry-delay 2 --max-time "$TIMEOUT" "$1"; }
|
|
|
|
tmp=""
|
|
cleanup() { [ -z "$tmp" ] || rm -f "$tmp"; }
|
|
trap cleanup EXIT INT TERM
|
|
|
|
mkdir -p "$DEST_DIR"
|
|
changed=0
|
|
|
|
# Цикл читает SOURCES через here-doc, а не из конвейера: в подоболочке
|
|
# счётчик changed не пережил бы выход из цикла.
|
|
while IFS='|' read -r base name; do
|
|
[ -n "$base" ] || continue
|
|
if [ -z "$name" ]; then
|
|
name="$base"
|
|
base="$BASE_URL"
|
|
fi
|
|
dest="$DEST_DIR/$name"
|
|
|
|
# Без промежуточной переменной статус вернул бы cut, а не curl.
|
|
remote_line=$(fetch "$base/$name.sha256sum") ||
|
|
die "не удалось скачать $base/$name.sha256sum"
|
|
remote_sum=$(printf '%s' "$remote_line" | cut -d' ' -f1)
|
|
case "$remote_sum" in
|
|
[0-9a-f][0-9a-f]*) [ "${#remote_sum}" -eq 64 ] || die "битая контрольная сумма: $remote_sum" ;;
|
|
*) die "битая контрольная сумма: $remote_sum" ;;
|
|
esac
|
|
|
|
if [ -f "$dest" ] && [ "$(hash_of "$dest")" = "$remote_sum" ]; then
|
|
continue # уже актуально — тишина
|
|
fi
|
|
|
|
tmp=$(mktemp "${TMPDIR:-/tmp}/geodata.XXXXXX") || die "не удалось создать временный файл"
|
|
fetch "$base/$name" > "$tmp" || die "не удалось скачать $base/$name"
|
|
|
|
got=$(hash_of "$tmp")
|
|
[ "$got" = "$remote_sum" ] || die "$name: ожидалось $remote_sum, получено $got"
|
|
[ -s "$tmp" ] || die "$name: скачан пустой файл"
|
|
|
|
[ ! -f "$dest" ] || cp -p "$dest" "$dest.bak" || die "не удалось сохранить резервную копию"
|
|
# mv в пределах одного каталога атомарен: Xray никогда не увидит половину файла.
|
|
cat "$tmp" > "$dest.new" && mv -f "$dest.new" "$dest" || die "не удалось записать $dest"
|
|
rm -f "$tmp"
|
|
tmp=""
|
|
|
|
log "обновлён $dest (sha256 $remote_sum)"
|
|
changed=1
|
|
done <<EOF
|
|
$SOURCES
|
|
EOF
|
|
|
|
[ "$changed" -eq 1 ] || exit 0
|
|
|
|
if [ -n "$RESTART_CMD" ]; then
|
|
if sh -c "$RESTART_CMD"; then
|
|
log "выполнено: $RESTART_CMD"
|
|
else
|
|
die "перезапуск не удался: $RESTART_CMD"
|
|
fi
|
|
fi
|