254 lines
13 KiB
Python
254 lines
13 KiB
Python
#!/usr/bin/env python3
|
||
"""3X-UI_KIT (форк hacker07): накатывает наши «швы» на чистый код автора.
|
||
|
||
Это источник правды для форка: единственное место, где описано, ЧТО именно мы правим в
|
||
файлах автора. Всё, что мы дописываем по существу, живёт в scripts/kit-local.sh — файле,
|
||
который автор не трогает, поэтому обновления автора его не ломают.
|
||
|
||
Правила:
|
||
* каждый якорь обязан найтись РОВНО один раз, иначе — стоп (автор изменил код, правку
|
||
надо пересмотреть руками);
|
||
* при любой ошибке ничего не пишется наполовину: сначала прогоняй --check;
|
||
* открытый ключ подписи берётся из tools/fork-signer.pub (закрытая часть — только на
|
||
рабочей станции владельца, в репозиторий не попадает).
|
||
|
||
Использование:
|
||
python3 tools/fork_apply.py --check [корень-репозитория]
|
||
python3 tools/fork_apply.py [корень-репозитория]
|
||
"""
|
||
|
||
import os
|
||
import pathlib
|
||
import re
|
||
import sys
|
||
|
||
GITEA = os.environ.get("FORK_GITEA_BASE", "https://git.rukavishnikoff.ru/hacker07/3X-UI_autoVPS")
|
||
SIGNER_FILE = os.environ.get("FORK_SIGNER_PUB", "tools/fork-signer.pub")
|
||
|
||
# README.md форка — НАШ файл целиком: авторское описание из него убрано, чтобы никто не
|
||
# ставил сервер авторской командой и не оказался на авторском канале обновлений. Шва здесь нет:
|
||
# при обновлении из авторского проекта README конфликтует и решается в нашу пользу (см. FORK.md).
|
||
HOOK = """
|
||
# --- 3X-UI_KIT (форк hacker07): локальные команды и хуки ---
|
||
# Подключаем scripts/kit-local.sh, если он установлен. Наш файл: правки автора его не касаются.
|
||
# Все команды форка — под «kit local …» (см. kit local help).
|
||
KIT_LOCAL_LIB="${KIT_LOCAL_LIB:-/usr/local/lib/kit/kit-local.sh}"
|
||
if [[ -r $KIT_LOCAL_LIB ]]; then . "$KIT_LOCAL_LIB"; kit_local_dispatch "$@"; fi
|
||
"""
|
||
|
||
|
||
def die(msg):
|
||
print("! " + msg, file=sys.stderr)
|
||
sys.exit(1)
|
||
|
||
|
||
def ok(msg):
|
||
print("==> " + msg)
|
||
|
||
|
||
def count(text, anchor, regex):
|
||
if regex:
|
||
return len(re.findall(anchor, text, re.MULTILINE))
|
||
return text.count(anchor)
|
||
|
||
|
||
def main():
|
||
args = [a for a in sys.argv[1:]]
|
||
check = False
|
||
if "--check" in args:
|
||
check = True
|
||
args.remove("--check")
|
||
root = pathlib.Path(args[0] if args else ".").resolve()
|
||
if not (root / "VERSION").exists():
|
||
die("%s не похож на репозиторий 3X-UI KIT (нет VERSION)" % root)
|
||
os.chdir(root)
|
||
|
||
version = (root / "VERSION").read_text(encoding="utf-8").strip()
|
||
if not re.fullmatch(r"[0-9]+(\.[0-9]+)+", version):
|
||
die("VERSION=%r: нужен числовой номер вида 1.2.1" % version)
|
||
|
||
signer_path = pathlib.Path(SIGNER_FILE)
|
||
if not signer_path.exists():
|
||
die(
|
||
"нет %s — сначала создай ключ подписи:\n"
|
||
" ssh-keygen -t ed25519 -C \"3X-UI KIT fork releases\" -f ~/.ssh/3x-ui-kit-fork/release_key\n"
|
||
" mkdir -p tools && ssh-keygen -y -f ~/.ssh/3x-ui-kit-fork/release_key > tools/fork-signer.pub"
|
||
% SIGNER_FILE
|
||
)
|
||
pub = ""
|
||
for line in signer_path.read_text(encoding="utf-8").splitlines():
|
||
line = line.strip()
|
||
if line.startswith(("ssh-ed25519 ", "ssh-rsa ")):
|
||
pub = line
|
||
break
|
||
if not pub:
|
||
die("%s: не нашёл строку открытого ключа (ssh-ed25519 …)" % signer_path)
|
||
|
||
version_re = r'^KIT_VERSION="[0-9.]+"$'
|
||
edits = [
|
||
# --- scripts/kit.sh -------------------------------------------------
|
||
("scripts/kit.sh", version_re, True, 'KIT_VERSION="%s"' % version, "версия сборки форка"),
|
||
(
|
||
"scripts/kit.sh",
|
||
'KIT_RAW="https://raw.githubusercontent.com/itsnotkubrick/3X-UI_KIT/main"',
|
||
False,
|
||
'KIT_RAW="%s/raw/branch/main"' % GITEA,
|
||
"источник VERSION и подсказки об обновлении",
|
||
),
|
||
(
|
||
"scripts/kit.sh",
|
||
'kit_ref_raw() { echo "https://raw.githubusercontent.com/itsnotkubrick/3X-UI_KIT/v$1"; }',
|
||
False,
|
||
'kit_ref_raw() { echo "%s/raw/tag/v$1"; }' % GITEA + "\n" + HOOK,
|
||
"файлы релиза из нашего тега + подключение kit-local.sh",
|
||
),
|
||
(
|
||
"scripts/kit.sh",
|
||
r' # SHA256:[^\n]*\n "ssh-ed25519 [^\n]*"\n',
|
||
True,
|
||
' # Ключ подписи форка (владелец: hacker07). Закрытая часть — только на рабочей станции.\n'
|
||
' "%s"\n' % pub,
|
||
"ключ проверки подписи релизов",
|
||
),
|
||
(
|
||
"scripts/kit.sh",
|
||
"for f in scripts/kit.sh scripts/kit-sub.py; do",
|
||
False,
|
||
"for f in scripts/kit.sh scripts/kit-sub.py scripts/kit-local.sh; do",
|
||
"наш файл в списке проверяемых и скачиваемых",
|
||
),
|
||
(
|
||
"scripts/kit.sh",
|
||
' install -m 755 "$tmp/kit.sh" /usr/local/bin/kit.new && mv -f /usr/local/bin/kit.new /usr/local/bin/kit',
|
||
False,
|
||
' install -m 755 "$tmp/kit.sh" /usr/local/bin/kit.new && mv -f /usr/local/bin/kit.new /usr/local/bin/kit\n'
|
||
" # Форк: ставим наши локальные дополнения из того же подписанного релиза.\n"
|
||
" install -d -m 755 /usr/local/lib/kit\n"
|
||
' install -m 644 "$tmp/kit-local.sh" /usr/local/lib/kit/kit-local.sh',
|
||
"установка kit-local.sh при обновлении",
|
||
),
|
||
# --- scripts/3x-ui.sh -----------------------------------------------
|
||
("scripts/3x-ui.sh", version_re, True, 'KIT_VERSION="%s"' % version, "версия сборки форка"),
|
||
(
|
||
"scripts/3x-ui.sh",
|
||
'KIT_RAW="https://raw.githubusercontent.com/itsnotkubrick/3X-UI_KIT/v$KIT_VERSION"',
|
||
False,
|
||
'KIT_RAW="%s/raw/tag/v$KIT_VERSION"' % GITEA,
|
||
"запасная загрузка kit из нашего релиза",
|
||
),
|
||
(
|
||
"scripts/3x-ui.sh",
|
||
'KIT_CLI_URL="$KIT_RAW/scripts/kit.sh"',
|
||
False,
|
||
'KIT_CLI_URL="$KIT_RAW/scripts/kit.sh"\nKIT_LOCAL_URL="$KIT_RAW/scripts/kit-local.sh"',
|
||
"адрес нашего файла для установщика",
|
||
),
|
||
(
|
||
"scripts/3x-ui.sh",
|
||
'KIT_INSTALL_CMD="bash <(curl -fsSL https://raw.githubusercontent.com/itsnotkubrick/3X-UI_KIT/main/scripts/3x-ui.sh)"',
|
||
False,
|
||
'KIT_INSTALL_CMD="bash <(curl -fsSL %s/raw/branch/main/scripts/3x-ui.sh)"' % GITEA,
|
||
"команда установки, которую показывает панель",
|
||
),
|
||
(
|
||
"scripts/3x-ui.sh",
|
||
' bash -n /usr/local/bin/kit || die "Команда kit скачалась повреждённой"',
|
||
False,
|
||
" # Форк: ставим наши локальные дополнения (см. scripts/kit-local.sh).\n"
|
||
" install -d -m 755 /usr/local/lib/kit\n"
|
||
' if [[ -n $src && -f $d/kit-local.sh ]]; then\n'
|
||
' install -m 644 "$d/kit-local.sh" /usr/local/lib/kit/kit-local.sh\n'
|
||
" else\n"
|
||
' curl -fsSL --retry 3 -o /usr/local/lib/kit/kit-local.sh "$KIT_LOCAL_URL" \\\n'
|
||
' || warn "Локальные дополнения kit не скачались: kit local не будет работать"\n'
|
||
" fi\n"
|
||
' bash -n /usr/local/bin/kit || die "Команда kit скачалась повреждённой"',
|
||
"установка kit-local.sh установщиком",
|
||
),
|
||
# --- scripts/hysteria2.sh -------------------------------------------
|
||
("scripts/hysteria2.sh", version_re, True, 'KIT_VERSION="%s"' % version, "версия сборки форка"),
|
||
# --- tools/release.sh -----------------------------------------------
|
||
(
|
||
"tools/release.sh",
|
||
"FILES=(scripts/3x-ui.sh scripts/hysteria2.sh scripts/kit.sh scripts/kit-sub.py VERSION)",
|
||
False,
|
||
"FILES=(scripts/3x-ui.sh scripts/hysteria2.sh scripts/kit.sh scripts/kit-sub.py scripts/kit-local.sh scripts/3x-ui-local.sh VERSION)",
|
||
"наш файл в подписанном списке релиза",
|
||
),
|
||
(
|
||
"tools/release.sh",
|
||
'{ echo "# 3X-UI KIT $v"; sha "${FILES[@]}"; } >SHA256SUMS',
|
||
False,
|
||
"{ echo \"# 3X-UI KIT $v\"; sha \"${FILES[@]}\" | awk '{ sub(/^\\*/, \"\", $2); print $1\" \"$2 }'; } >SHA256SUMS",
|
||
"суммы в POSIX-виде без звёздочки (на Windows sha256sum её ставит, и kit её не понимает)",
|
||
),
|
||
# --- свои записи о версии (наш файл, авторский CHANGELOG не трогаем) ----
|
||
(
|
||
"tools/release.sh",
|
||
'grep -q "^## v$v" CHANGELOG.md || die "В CHANGELOG.md нет раздела ## v$v"',
|
||
False,
|
||
'(grep -q "^## v$v" CHANGELOG.md || grep -q "^## v$v" FORK-CHANGELOG.md)'
|
||
' || die "Нет раздела ## v$v ни в CHANGELOG.md, ни в FORK-CHANGELOG.md (форк ведёт свои записи в FORK-CHANGELOG.md)"',
|
||
"раздел версии форка — в нашем файле",
|
||
),
|
||
(
|
||
"tools/release.sh",
|
||
'[[ -z $(git status --porcelain -- "${FILES[@]}" CHANGELOG.md) ]]',
|
||
False,
|
||
'[[ -z $(git status --porcelain -- "${FILES[@]}" CHANGELOG.md FORK-CHANGELOG.md) ]]',
|
||
"наш файл записей тоже должен быть закоммичен",
|
||
),
|
||
(
|
||
"scripts/kit.sh",
|
||
' curl -fsS -m 5 "$KIT_RAW/CHANGELOG.md" 2>/dev/null | awk -v v="## v$1" \'',
|
||
False,
|
||
' { curl -fsS -m 5 "$KIT_RAW/CHANGELOG.md" 2>/dev/null; curl -fsS -m 5 "$KIT_RAW/FORK-CHANGELOG.md" 2>/dev/null; } | awk -v v="## v$1" \'',
|
||
"«что нового» ищет и в наших записях",
|
||
),
|
||
]
|
||
|
||
bad = 0
|
||
for path, anchor, is_re, new, why in edits:
|
||
p = pathlib.Path(path)
|
||
if not p.exists():
|
||
print("✗ %s: файла нет" % path)
|
||
bad += 1
|
||
continue
|
||
text = p.read_text(encoding="utf-8", newline="")
|
||
if check:
|
||
# --check смотрит на готовое дерево dev: наш шов обязан быть на месте и ровно один раз
|
||
n = text.count(new)
|
||
what, shown = "наш шов", new
|
||
else:
|
||
# применение — только на чистое дерево автора: якорь обязан найтись ровно один раз
|
||
n = count(text, anchor, is_re)
|
||
what, shown = "якорь автора", anchor
|
||
short = shown if len(shown) <= 54 else shown[:51] + "…"
|
||
short = short.replace("\n", "⏎")
|
||
if n != 1:
|
||
print("✗ %-20s %-56s %s найден %d раз (%s)" % (path, short, what, n, why))
|
||
bad += 1
|
||
continue
|
||
if check:
|
||
print("✓ %-20s %-56s (%s)" % (path, short, why))
|
||
continue
|
||
if is_re:
|
||
text = re.sub(anchor, new, text, count=1, flags=re.MULTILINE)
|
||
else:
|
||
text = text.replace(anchor, new, 1)
|
||
p.write_text(text, encoding="utf-8", newline="")
|
||
print("✓ %-20s %-56s (%s)" % (path, short, why))
|
||
|
||
if bad:
|
||
if check:
|
||
die("якорей не найдено: %d — автор изменил код, пересмотри правки в tools/fork_apply.py" % bad)
|
||
die("якорей не найдено: %d — правки не применены" % bad)
|
||
if check:
|
||
ok("все якоря на месте (версия сборки %s)" % version)
|
||
else:
|
||
ok("швы накатаны, версия сборки %s" % version)
|
||
|
||
|
||
if __name__ == "__main__":
|
||
main()
|