Files
glchat/scripts/test-install.sh
T
grendervill 3151327f6e feat(ops): offsite-бэкапы и проверки профилей
- deploy/backup.sh: BACKUP_OFFSITE_TARGET (rsync или rclone:remote:path)
  выгружает готовый архив после локальной ротации; неудача выгрузки только
  предупреждает. Ротация на приёмнике — опционально
  (BACKUP_OFFSITE_KEEP_DAYS, rclone delete --min-age). Имя архива получает
  суффикс, если бэкап запущен дважды в одну секунду.
- deploy/install.sh: offsite-настройки в .env (BACKUP_OFFSITE_TARGET,
  BACKUP_OFFSITE_KEEP_DAYS) отдельным разделом; детектор незаполненных
  плейсхолдеров учитывает цифры в именах (FAIL2BAN_ENABLED); dry-run больше не
  требует прав на /opt/glchat — явность GLCHAT_ETC_DIR фиксируется до
  подключения common.sh, иначе ветка временного каталога была недостижима.
- scripts/test-profiles.sh: сверка таблицы профилей 10.10 и одинакового набора
  PROFILE_* (46 проверок), make profiles-test / ops-test.
- scripts/test-backup.sh: бэкап и offsite на синтетическом инстансе — локальный
  rsync-приёмник, шим rclone, недоступный хост, нечисловой срок (12 проверок),
  make backup-test.
- scripts/test-install.sh: проверка offsite-ключей в .env после установки.
2026-09-22 18:44:50 +03:00

145 lines
6.9 KiB
Bash
Executable File
Raw 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
# Прогон deploy/install.sh в чистом контейнере Ubuntu 24.04 (AGENT.md 11.3).
# Требуется Docker с поддержкой привилегированных контейнеров (systemd внутри).
set -euo pipefail
REPO_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
IMAGE="${GLCHAT_TEST_IMAGE:-jrei/systemd-ubuntu:24.04}"
CONTAINER="${GLCHAT_TEST_CONTAINER:-glchat-install-test-$$}"
log() { printf '\n=== %s ===\n' "$*"; }
cleanup() {
docker rm -f "$CONTAINER" >/dev/null 2>&1 || true
}
trap cleanup EXIT
log "запуск ${IMAGE} как systemd-контейнера"
docker rm -f "$CONTAINER" >/dev/null 2>&1 || true
docker run -d --name "$CONTAINER" \
--privileged \
--cgroupns=host \
--tmpfs /tmp:exec,size=2g --tmpfs /run --tmpfs /run/lock \
-v /sys/fs/cgroup:/sys/fs/cgroup:rw \
"$IMAGE" /sbin/init >/dev/null
log "ожидание systemd"
for _ in $(seq 1 30); do
if docker exec "$CONTAINER" systemctl is-system-running 2>/dev/null | grep -qE 'running|degraded'; then
break
fi
sleep 1
done
docker exec "$CONTAINER" systemctl is-system-running || true
docker exec "$CONTAINER" bash -lc '. /etc/os-release && echo "тестовое окружение: $PRETTY_NAME"'
log "копируем репозиторий внутрь контейнера"
docker exec "$CONTAINER" mkdir -p /opt/glchat-src
docker cp "${REPO_DIR}/." "${CONTAINER}:/opt/glchat-src/"
# stdin закрыт: иначе установщик может ждать интерактивного подтверждения.
run_install() {
local label="$1"
shift
log "$label"
if ! timeout 1500 docker exec -i "$CONTAINER" bash -lc "
cd /opt/glchat-src && GLCHAT_ETC_DIR=/opt/glchat \
bash deploy/install.sh --domain gl.test.local --email admin@gl.test.local \
--admin admin@gl.test.local --password 'test-password-123' \
--skip-tls --skip-image --app-port 8080 --no-turn --yes $*
" </dev/null; then
echo "install.sh завершился с ошибкой (${label})" >&2
docker exec "$CONTAINER" bash -lc 'tail -40 /opt/glchat/install.log 2>/dev/null || true' || true
exit 1
fi
}
log "1/4 install.sh --dry-run"
timeout 600 docker exec -i "$CONTAINER" bash -lc '
cd /opt/glchat-src && GLCHAT_ETC_DIR=/tmp/dryrun \
bash deploy/install.sh --dry-run --domain gl.test.local --email admin@gl.test.local \
--admin admin@gl.test.local --password "test-password-123" --yes
' </dev/null
run_install "2/5 install.sh (боевой прогон: TLS и публичные порты не трогаем)"
# Секреты обязаны переживать повторную установку: MASTER_KEY расшифровывает
# PII и TOTP, SESSION_PEPPER — blind index. Их смена ломает данные инстанса.
log "3/5 секреты до повторного запуска"
secrets_before="$(docker exec "$CONTAINER" bash -lc '
grep -E "^(MASTER_KEY|SESSION_PEPPER|TOTP_ENCRYPTION_KEY|WEBHOOK_SECRET|LIVEKIT_API_SECRET|AGE_RECIPIENT)=" /opt/glchat/.env | sort
')"
printf '%s\n' "$secrets_before"
# Внешний TURN-релей: в livekit.yaml должны попасть UDP-адрес и TLS-имя хоста
# (сертификат выписан на домен, поэтому TLS-эндпоинт анонсируется именем).
run_install "4/5 идемпотентность: повторный запуск с --reconfigure" --reconfigure \
--turn-relay-host 203.0.113.10 --turn-relay-tls-host turn.gl.test.local
log "4b/5 адреса TURN-релея в livekit.yaml"
relay_rendered="$(docker exec "$CONTAINER" bash -lc 'cat /opt/glchat/livekit/livekit.yaml')"
printf '%s\n' "$relay_rendered" | grep -A 3 "turn_servers" | head -12
for expected in "host: 203.0.113.10" "protocol: udp" "host: turn.gl.test.local" "protocol: tls"; do
if ! printf '%s\n' "$relay_rendered" | grep -q "$expected"; then
echo "ОШИБКА: в livekit.yaml нет строки \"$expected\"" >&2
exit 1
fi
done
echo "адреса TURN-релея отрендерены: ok"
log "5/5 секреты после повторного запуска"
secrets_after="$(docker exec "$CONTAINER" bash -lc '
grep -E "^(MASTER_KEY|SESSION_PEPPER|TOTP_ENCRYPTION_KEY|WEBHOOK_SECRET|LIVEKIT_API_SECRET|AGE_RECIPIENT)=" /opt/glchat/.env | sort
')"
printf '%s\n' "$secrets_after"
if [ "$secrets_before" != "$secrets_after" ]; then
echo "ОШИБКА: повторный запуск установщика изменил секреты (данные станут нечитаемыми)" >&2
diff <(printf '%s\n' "$secrets_before") <(printf '%s\n' "$secrets_after") >&2 || true
exit 1
fi
echo "секреты сохранены при повторной установке: ok"
# Offsite-бэкапы: ключи должны присутствовать в .env сразу после установки
# (пустыми — цель задаёт владелец), иначе backup.sh не увидит настройку.
log "offsite-настройки в .env"
docker exec "$CONTAINER" bash -lc '
set -e
for key in BACKUP_KEEP_DAYS BACKUP_OFFSITE_TARGET BACKUP_OFFSITE_KEEP_DAYS AGE_RECIPIENT; do
grep -q "^${key}=" /opt/glchat/.env || { echo "нет ${key} в .env" >&2; exit 1; }
echo "${key}: ok"
done
'
# Сборка образа внутри DinD невозможна (overlay поверх overlay), поэтому образ
# собирается и проверяется отдельно на хосте (make docker-build + docker compose).
log "проверки состояния установки"
docker exec "$CONTAINER" bash -lc '
set -e
echo "--- артефакты ---"
test -x /usr/local/bin/glchat && echo "CLI: ok"
test -f /opt/glchat/compose.yaml && echo "compose: ok"
test -f /opt/glchat/caddy/Caddyfile && echo "Caddyfile: ok"
test -f /opt/glchat/livekit/livekit.yaml && echo "livekit.yaml: ok"
test -f /opt/glchat/nftables/glchat.nft && echo "nftables: ok"
test -f /opt/glchat/fail2ban/jail.local && echo "fail2ban jail: ok"
test -f /opt/glchat/glchat.service && echo "systemd unit: ok"
stat -c "perms %a %n" /opt/glchat/.env
echo "--- плейсхолдеры в конфигах (должно быть 0) ---"
grep -c "{{" /opt/glchat/compose.yaml /opt/glchat/livekit/livekit.yaml /opt/glchat/caddy/Caddyfile || true
echo "--- сервисы ---"
systemctl is-enabled glchat.service
systemctl is-active fail2ban
systemctl is-enabled glchat-nftables.service
systemctl is-enabled glchat-docker-user.service
echo "--- ufw ---"
ufw status | head -8
echo "--- fail2ban jails ---"
fail2ban-client status
echo "--- glchat CLI ---"
glchat version
echo "--- конфиги валидны ---"
docker compose -f /opt/glchat/compose.yaml config -q && echo "compose config: valid"
'
log "ОК: прогон установщика в чистом Ubuntu 24.04 завершён"