#!/usr/bin/env bash # vps-update — built-in updater for VPS Admin modules. # Usage: vps-update.sh apply [update-url] # Modules: web-admin | wg-admin | firewall-sync-admin | vps-auth set -euo pipefail LOG=/var/log/vps-update.log CONF=/etc/vps-update.conf DEFAULT_URL="https://vps.myidb.ru" read_url() { local u="$DEFAULT_URL" v if [ -f "$CONF" ]; then v=$(grep -E '^UPDATE_URL=' "$CONF" | head -1 | cut -d= -f2- | tr -d "\"' ") [ -n "$v" ] && u="$v" fi echo "${u%/}" } module_paths() { # → ROOT BIN SVC case "$1" in main-admin) ROOT=/opt/main-admin; BIN=main-admin-backend; SVC=main-admin.service ;; web-admin) ROOT=/opt/web-admin; BIN=web-admin-backend; SVC=web-admin.service ;; server-admin) ROOT=/opt/server-admin; BIN=server-admin-backend; SVC=server-admin.service ;; wg-admin) ROOT=/opt/wg-admin; BIN=wg-admin-backend; SVC=wg-admin.service ;; firewall-sync-admin) ROOT=/opt/fwadmin; BIN=fwadmin-backend; SVC=fwadmin.service ;; vps-auth) ROOT=/opt/vps-auth; BIN=vps-auth-backend; SVC=vps-auth.service ;; *) echo "unknown module: $1" >&2; return 1 ;; esac } backup_all() { local ts bk ts=$(date +%Y%m%d-%H%M%S); bk="/var/backups/vps/${ts}" mkdir -p "$bk" echo "[backup] резервная копия конфигураций и БД → ${bk}" for p in /etc/web-admin /etc/wg-admin /etc/fwadmin /etc/firewall-sync /etc/vps-auth \ /etc/wireguard /etc/vps-session.secret /etc/vps-update.conf; do [ -e "$p" ] && cp -a "$p" "$bk/" 2>/dev/null || true done for p in /var/lib/web-admin /var/lib/wg-admin /var/lib/fwadmin; do [ -e "$p" ] && cp -a "$p" "$bk/" 2>/dev/null || true done # лёгкая ротация: оставляем последние 20 бэкапов ls -1dt /var/backups/vps/*/ 2>/dev/null | tail -n +21 | xargs -r rm -rf || true echo "[backup] готово" } cmd_apply() { local MOD="${1:?module}" VER="${2:?version}" URL="${3:-$(read_url)}" exec >>"$LOG" 2>&1 echo "" echo "=== $(date -Is) ОБНОВЛЕНИЕ ${MOD} → ${VER} (источник: ${URL}) ===" if ! module_paths "$MOD"; then echo "FAIL: неизвестный модуль ${MOD}"; exit 1; fi backup_all tmp=$(mktemp -d) trap 'rm -rf "${tmp:-}"' EXIT # Reject plain-http update sources — the package is built+run as root. case "$URL" in https://*) : ;; http://*) if [ "${ALLOW_INSECURE_UPDATE_URL:-}" != "1" ]; then echo "FAIL: источник обновлений должен быть https:// (${URL})"; exit 1; fi ;; *) echo "FAIL: некорректный URL источника: ${URL}"; exit 1 ;; esac echo "[dl] ${URL}/updates/${MOD}/${MOD}-${VER}.tar.gz" if ! curl -fsSL "${URL}/updates/${MOD}/${MOD}-${VER}.tar.gz" -o "$tmp/src.tgz"; then echo "FAIL: не удалось скачать пакет обновления"; exit 1 fi # Optional integrity check: if the manifest carries a sha256 for this module, # the downloaded tarball must match it. (TODO: signed manifests/packages.) local want_sha want_sha="$(curl -fsSL "${URL}/updates/manifest.json" 2>/dev/null \ | tr -d ' \n' | grep -oE "\"${MOD}\":\{[^}]*\"sha256\":\"[a-fA-F0-9]{64}\"" \ | grep -oE '[a-fA-F0-9]{64}' | head -1 || true)" if [ -n "${want_sha}" ]; then local got_sha; got_sha="$(sha256sum "$tmp/src.tgz" | awk '{print $1}')" if [ "${got_sha}" != "${want_sha}" ]; then echo "FAIL: контрольная сумма не совпала (ожидалось ${want_sha}, получено ${got_sha}) — обновление отменено"; exit 1 fi echo "[ok] sha256 пакета подтверждён" fi mkdir -p "$tmp/src" tar -xzf "$tmp/src.tgz" -C "$tmp/src" if [ ! -d "$tmp/src/backend" ]; then echo "FAIL: в пакете нет backend/"; exit 1; fi # systemd-run даёт чистое окружение (без HOME/GOPATH) — задаём явно, иначе # go build падает: "module cache not found". export HOME="${HOME:-/root}" export GOPATH="${GOPATH:-${HOME}/go}" export GOMODCACHE="${GOMODCACHE:-${GOPATH}/pkg/mod}" export GOCACHE="${GOCACHE:-${HOME}/.cache/go-build}" export PATH="${PATH}:/usr/local/go/bin" mkdir -p "${GOPATH}" "${GOCACHE}" echo "[build] сборка backend (GOPATH=${GOPATH})…" if ! ( cd "$tmp/src/backend" && go mod tidy && CGO_ENABLED=1 go build -o "$tmp/${BIN}" . ); then echo "FAIL: ошибка сборки — обновление отменено, служба не тронута"; exit 1 fi echo "[install] обновление ${ROOT} и рестарт ${SVC}" systemctl stop "$SVC" 2>/dev/null || true install -d -m0755 "${ROOT}/frontend" [ -d "$tmp/src/frontend" ] && rsync -a --delete "$tmp/src/frontend/" "${ROOT}/frontend/" install -m0755 "$tmp/${BIN}" "${ROOT}/${BIN}" echo "$VER" > "${ROOT}/VERSION" systemctl daemon-reload systemctl start "$SVC" sleep 1 local state; state=$(systemctl is-active "$SVC" 2>/dev/null || echo "unknown") if [ "$state" = "active" ]; then echo "[OK] ✅ ${MOD} успешно обновлён до версии ${VER}; служба ${SVC} запущена (${state})" else echo "[WARN] ${MOD} обновлён до ${VER}, но служба ${SVC} в состоянии ${state} — проверьте journalctl -u ${SVC}" fi echo "=== DONE ${MOD} ${VER} ===" } case "${1:-}" in apply) shift; cmd_apply "$@" ;; *) echo "usage: $0 apply [url]" >&2; exit 1 ;; esac