Files
pzmanager/deploy/pzmanager-admin.sh
T
mrleo1nidandClaude Opus 5 1e0b0a8d2b Кнопка обновления обновляет всё, а не только бинарник
pzmanager-admin update больше не повторяет логику установки: он забирает
из релиза get.sh (сверив sha256) и отдаёт работу ему. Поэтому обновление
из панели теперь приводит в порядок и скрипты установки, и systemd-юнит —
раньше они застывали на версии первой установки, а вместе с ними
терялась, например, локальная копия установщика для удаления.

Работа уходит в разовый юнит через systemd-run. Иначе она не пережила бы
сама себя: панель вызывает скрипт через sudo, тот живёт в cgroup её юнита,
и остановка службы установщиком убила бы его на середине. Команда restart
по той же причине получила --no-block.

Установщик стал понятнее в двух местах, о которые легко споткнуться:
- повторная установка той же сборки больше не роняет игровой сервер —
  если sha256 бинарника совпал, служба не останавливается вовсе;
- после обновления он явно пишет, что запускает службу, и проверяет, что
  она поднялась. Раньше systemctl start отрабатывал молча, и по выводу
  нельзя было понять, работает панель или нет. Службу, остановленную не
  установщиком, он по-прежнему не поднимает самовольно.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-04 20:28:42 +03:00

136 lines
6.3 KiB
Bash
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/usr/bin/env bash
# Действия, которые панель не может выполнить сама: она работает от
# непривилегированного пользователя, а бинарник и systemd принадлежат root.
#
# Скрипт ставится установщиком в /usr/local/bin/pzmanager-admin (root:root) и
# разрешён сервисному пользователю в /etc/sudoers.d/pzmanager — ровно эта
# команда и ничего больше. Аргументы проверяются здесь: sudo пропускает любые.
#
# Значения ниже подставляет install.sh при установке.
set -euo pipefail
PZ_REPO="@PZ_REPO@"
PZ_BINARY="@PZ_BINARY@"
PZ_HOME="@PZ_HOME@"
PZ_USER="@PZ_USER@"
PZ_SERVICE="pzmanager"
# Имя разового юнита, в котором идёт обновление.
UPDATE_UNIT="pzmanager-update"
die() { echo "Ошибка: $*" >&2; exit 1; }
api_base() {
local url="${PZ_REPO%/}"
url="${url%.git}"
local host="${url%%://*}://$(printf '%s' "${url#*://}" | cut -d/ -f1)"
printf '%s/api/v1/repos/%s' "$host" "${url#"$host"/}"
}
# asset_url ищет в описании релиза ссылку на файл с точным именем.
asset_url() {
local release="$1" name="$2"
printf '%s' "$release" \
| grep -o '"browser_download_url" *: *"[^"]*"' \
| cut -d'"' -f4 \
| while read -r url; do
if [[ "${url##*/}" == "$name" ]]; then
printf '%s' "$url"
return
fi
done
}
# cmd_update только запускает работу и сразу возвращает управление панели.
#
# Обновление обязано пережить перезапуск службы: установщик остановит
# pzmanager, а вместе с ним systemd убьёт и этот скрипт — панель вызывает его
# через sudo, так что он живёт в cgroup её юнита. Поэтому саму работу уносим
# в отдельный разовый юнит, который к pzmanager отношения не имеет.
cmd_update() {
local version="${1:-latest}"
# Версия приходит из панели: проверяем до запуска юнита, чтобы не плодить
# заведомо провальные.
[[ "$version" == "latest" || "$version" =~ ^[A-Za-z0-9._-]+$ ]] \
|| die "недопустимая версия: $version"
command -v systemd-run >/dev/null 2>&1 \
|| die "нет systemd-run — обновите панель командой из README"
# --collect убирает юнит после завершения, в том числе неуспешного: иначе
# следующий запуск упрётся в оставшийся failed-юнит с тем же именем.
systemd-run --quiet --collect --unit="$UPDATE_UNIT" \
--description="Обновление pzmanager" \
"$0" do-update "$version" \
|| die "не удалось запустить обновление разовым юнитом"
echo "Обновление запущено в фоне."
echo "Ход работы: journalctl -u ${UPDATE_UNIT} -f"
}
# cmd_do_update выполняется уже внутри разового юнита. Обновляет всё сразу:
# бинарник, скрипты установки и systemd-юнит, — поэтому просто отдаёт работу
# get.sh из релиза вместо того, чтобы повторять его логику.
cmd_do_update() {
local version="${1:-latest}" api release tag url tmp
api="$(api_base)"
if [[ "$version" == "latest" ]]; then
release="$(curl -fsSL "${api}/releases/latest")" || die "не удалось запросить релиз"
else
# Версия приходит из панели; всё, кроме безопасных символов, отвергаем.
[[ "$version" =~ ^[A-Za-z0-9._-]+$ ]] || die "недопустимая версия: $version"
release="$(curl -fsSL "${api}/releases/tags/${version}")" || die "релиз ${version} не найден"
fi
tag="$(printf '%s' "$release" | grep -o '"tag_name" *: *"[^"]*"' | head -1 | cut -d'"' -f4)"
[[ -n "$tag" ]] || die "в ответе нет тега"
url="$(asset_url "$release" get.sh)"
[[ -n "$url" ]] || die "в релизе ${tag} нет get.sh"
local sums_url
sums_url="$(asset_url "$release" checksums.txt)"
[[ -n "$sums_url" ]] || die "в релизе ${tag} нет checksums.txt"
tmp="$(mktemp -d)"
trap 'rm -rf "$tmp"' RETURN
echo "==> Беру установщик из ${tag}"
curl -fsSL -o "${tmp}/get.sh" "$url" || die "не удалось скачать get.sh"
curl -fsSL -o "${tmp}/checksums.txt" "$sums_url" || die "не удалось скачать checksums.txt"
echo "==> Проверяю контрольную сумму"
( cd "$tmp" && awk '$NF == "get.sh"' checksums.txt > expected.txt \
&& [[ -s expected.txt ]] && sha256sum -c expected.txt ) \
|| die "контрольная сумма get.sh не сошлась"
echo "==> Обновляю до ${tag}"
# get.sh скачает бинарник нужной архитектуры и запустит install.sh, который
# сам остановит службу, заменит файлы и поднимет её обратно.
PZ_HOME="$PZ_HOME" PZ_USER="$PZ_USER" PZ_REPO="$PZ_REPO" PZ_VERSION="$tag" \
bash "${tmp}/get.sh" \
|| die "обновление не удалось"
echo "Обновление до ${tag} завершено"
}
case "${1:-}" in
update)
cmd_update "${2:-latest}"
;;
do-update)
cmd_do_update "${2:-latest}"
;;
restart)
echo "==> Перезапускаю ${PZ_SERVICE}"
# --no-block: команда ставит задание и выходит, не дожидаясь остановки.
# Иначе systemd убил бы нас вместе со службой на середине ожидания.
systemctl restart --no-block "$PZ_SERVICE"
;;
version)
"$PZ_BINARY" version
;;
*)
echo "Использование: pzmanager-admin update [версия] | restart | version" >&2
exit 2
;;
esac