Менеджер кладёт код первичной настройки в data/setup-token, установщик
дожидается файла и печатает код последним сообщением — за ним больше не
нужно идти в journalctl. Файл удаляется, как только создан администратор,
и не появляется на уже настроенной панели.
Установщик научился снимать панель с машины: --uninstall убирает службу,
бинарники и правило sudo, --purge вдобавок сносит каталог с мирами,
бэкапами и учётками. Данные удаляются только по явному подтверждению, а
каталог сносится, лишь если похож на установку панели: опечатка в PZ_HOME
не должна стоить чужих файлов. Копия установщика теперь лежит в
/opt/pzmanager, так что удалять можно без похода в репозиторий.
get.sh пробрасывает аргументы установщику и в режиме удаления не качает
бинарник. Заодно исправлена подстановка пустого массива аргументов:
"${ARGS[@]:-}" разворачивается в один пустой аргумент, из-за чего обычная
установка спотыкалась бы о него.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
160 lines
6.4 KiB
Bash
160 lines
6.4 KiB
Bash
#!/usr/bin/env bash
|
||
# Установка или обновление pzmanager до последнего релиза одной командой:
|
||
#
|
||
# curl -fsSL https://gitea.hsrv.site/mrleo1nid/pzmanager/raw/branch/main/deploy/get.sh | sudo bash
|
||
#
|
||
# Панель в локальной сети (подставьте адрес своего сервера):
|
||
#
|
||
# curl -fsSL .../get.sh | sudo PZ_LISTEN=10.10.1.142:8080 bash
|
||
#
|
||
# Удаление (аргументы уходят установщику как есть):
|
||
#
|
||
# curl -fsSL .../get.sh | sudo bash -s -- --uninstall
|
||
# curl -fsSL .../get.sh | sudo bash -s -- --uninstall --purge
|
||
#
|
||
# Если панель уже установлена, то же самое умеет её локальная копия:
|
||
#
|
||
# sudo /opt/pzmanager/install.sh --uninstall --purge
|
||
#
|
||
# Переменные окружения:
|
||
# PZ_VERSION какой тег ставить (по умолчанию последний релиз)
|
||
# PZ_HOME куда ставить (по умолчанию /opt/pzmanager)
|
||
# PZ_USER системный пользователь (по умолчанию pzserver)
|
||
# PZ_LISTEN адрес веб-панели (по умолчанию 127.0.0.1:8080)
|
||
# PZ_REPO адрес репозитория (по умолчанию этот)
|
||
set -euo pipefail
|
||
|
||
PZ_REPO="${PZ_REPO:-https://gitea.hsrv.site/mrleo1nid/pzmanager}"
|
||
PZ_VERSION="${PZ_VERSION:-latest}"
|
||
|
||
API="${PZ_REPO%/}"
|
||
API="${API%.git}"
|
||
# Из https://host/owner/repo делаем https://host/api/v1/repos/owner/repo
|
||
HOST="$(printf '%s' "$API" | cut -d/ -f1-3)"
|
||
PATH_PART="${API#"$HOST"/}"
|
||
API="${HOST}/api/v1/repos/${PATH_PART}"
|
||
|
||
die() { echo "Ошибка: $*" >&2; exit 1; }
|
||
|
||
[[ $EUID -eq 0 ]] || die "запустите с правами root (sudo)"
|
||
|
||
# Аргументы уходят установщику. Для удаления бинарник качать незачем — это
|
||
# лишние восемь мегабайт ради скрипта, который его же и сотрёт.
|
||
ARGS=("$@")
|
||
NEED_BINARY=1
|
||
# Идиома ${ARGS[@]+...} нужна из-за set -u: обычная "${ARGS[@]:-}" на пустом
|
||
# массиве разворачивается в один пустой аргумент, и установщик спотыкается о
|
||
# него при обычной установке.
|
||
for arg in ${ARGS[@]+"${ARGS[@]}"}; do
|
||
case "$arg" in
|
||
--uninstall | --remove | --purge) NEED_BINARY=0 ;;
|
||
esac
|
||
done
|
||
|
||
for tool in curl sha256sum; do
|
||
command -v "$tool" >/dev/null 2>&1 || die "не найдена утилита $tool"
|
||
done
|
||
|
||
case "$(uname -m)" in
|
||
x86_64 | amd64) ARCH=amd64 ;;
|
||
aarch64 | arm64) ARCH=arm64 ;;
|
||
*) die "архитектура $(uname -m) не поддерживается: есть сборки только под amd64 и arm64" ;;
|
||
esac
|
||
|
||
if [[ "$PZ_VERSION" == "latest" ]]; then
|
||
RELEASE_URL="${API}/releases/latest"
|
||
else
|
||
RELEASE_URL="${API}/releases/tags/${PZ_VERSION}"
|
||
fi
|
||
|
||
echo "==> Спрашиваю у Gitea последний релиз"
|
||
RELEASE_JSON="$(curl -fsSL "$RELEASE_URL")" \
|
||
|| die "не удалось получить релиз с ${RELEASE_URL}"
|
||
|
||
TAG="$(printf '%s' "$RELEASE_JSON" | grep -o '"tag_name" *: *"[^"]*"' | head -1 | cut -d'"' -f4)"
|
||
[[ -n "$TAG" ]] || die "в ответе нет тега — возможно, релизов ещё нет"
|
||
|
||
# Ссылки на файлы релиза достаём без jq: он есть далеко не на всякой машине.
|
||
# Имя файла сравниваем целиком, а не шаблоном: точка в "install.sh" в регулярке
|
||
# совпала бы с любым символом.
|
||
asset_url() {
|
||
printf '%s' "$RELEASE_JSON" \
|
||
| grep -o '"browser_download_url" *: *"[^"]*"' \
|
||
| cut -d'"' -f4 \
|
||
| while read -r url; do
|
||
if [[ "${url##*/}" == "$1" ]]; then
|
||
printf '%s' "$url"
|
||
return
|
||
fi
|
||
done
|
||
}
|
||
|
||
BIN_NAME="pzmanager-linux-${ARCH}"
|
||
if [[ "$NEED_BINARY" == "1" ]]; then
|
||
BIN_URL="$(asset_url "$BIN_NAME")"
|
||
[[ -n "$BIN_URL" ]] || die "в релизе ${TAG} нет файла ${BIN_NAME}"
|
||
echo "==> Ставлю ${TAG} (${ARCH})"
|
||
else
|
||
echo "==> Беру установщик из ${TAG} для удаления"
|
||
fi
|
||
|
||
TMP="$(mktemp -d)"
|
||
trap 'rm -rf "$TMP"' EXIT
|
||
cd "$TMP"
|
||
|
||
download() {
|
||
local name="$1" url required="${2:-required}"
|
||
url="$(asset_url "$name")"
|
||
if [[ -z "$url" ]]; then
|
||
# Явный if, а не "&& return": при set -e короткая форма для обязательного
|
||
# файла вернула бы 1 и уронила скрипт молча, не дойдя до die.
|
||
if [[ "$required" == "optional" ]]; then
|
||
return 1
|
||
fi
|
||
die "в релизе ${TAG} нет файла ${name}"
|
||
fi
|
||
curl -fsSL -o "$name" "$url" || die "не удалось скачать ${name}"
|
||
}
|
||
|
||
download install.sh
|
||
download pzmanager.service
|
||
FILES=(install.sh pzmanager.service)
|
||
|
||
if [[ "$NEED_BINARY" == "1" ]]; then
|
||
download "$BIN_NAME"
|
||
FILES+=("$BIN_NAME")
|
||
fi
|
||
|
||
# Скрипт привилегированных действий появился не сразу: со старым релизом
|
||
# ставим без него, панель просто не покажет кнопку обновления.
|
||
if download pzmanager-admin.sh optional; then
|
||
FILES+=(pzmanager-admin.sh)
|
||
fi
|
||
|
||
# Контрольные суммы обязательны: бинарник ставится с правами root.
|
||
download checksums.txt
|
||
echo "==> Проверяю контрольные суммы"
|
||
: > expected.txt
|
||
for name in "${FILES[@]}"; do
|
||
# Сравниваем имя целиком через awk: в grep точка из "install.sh" совпала бы
|
||
# с любым символом и подтянула чужую строку.
|
||
awk -v name="$name" '$NF == name' checksums.txt >> expected.txt
|
||
grep -q -F -- "$name" expected.txt \
|
||
|| die "в checksums.txt нет строки для ${name}"
|
||
done
|
||
sha256sum -c expected.txt || die "контрольные суммы не сошлись — файлы повреждены"
|
||
|
||
mkdir -p deploy
|
||
mv install.sh pzmanager.service deploy/
|
||
if [[ -f pzmanager-admin.sh ]]; then
|
||
mv pzmanager-admin.sh deploy/
|
||
fi
|
||
chmod +x deploy/install.sh
|
||
if [[ "$NEED_BINARY" == "1" ]]; then
|
||
mv "$BIN_NAME" pzmanager
|
||
chmod +x pzmanager
|
||
fi
|
||
|
||
echo "==> Запускаю установщик"
|
||
BINARY="$TMP/pzmanager" bash deploy/install.sh ${ARGS[@]+"${ARGS[@]}"}
|