#!/usr/bin/env bash # Разовая подготовка учётных данных стека наблюдаемости (#3078). # # Запускать НА ИНФРАСТРУКТУРНОМ ХОСТЕ (Beget), один раз. Идемпотентен: уже # заданные значения переиспользуются, ничего не перезаписывается. # # ЧТО ДЕЛАЕТ: # 1. Генерирует пароли приёмника, витрины, администратора Grafana и read-only # роли к базе GlitchTip; дописывает их в окружение хоста. # 2. Кладёт пароль приёмника на продуктовый хост — агенту нужно им # авторизоваться при отправке метрик и логов. # 3. Печатает bcrypt-хеши для caddy/metrics-*.caddy.snippet. # # ЧЕГО НЕ ДЕЛАЕТ: не печатает сами пароли. Хеш публиковать безопасно, пароль — # нет, а вывод скрипта попадает в журнал деплоя и в историю терминала. # # Токен Telegram скрипт НЕ генерирует — его нужно задать руками: # METRICS_TELEGRAM_BOT_TOKEN, METRICS_TELEGRAM_CHAT_ID set -euo pipefail ENVF=/opt/gendesign/backend/.env.runtime REMOTE_ENVF=/opt/gendesign/backend/.env.runtime RSSH=(ssh -o BatchMode=yes -o ConnectTimeout=15 selectel) STAMP=$(date -u +%Y%m%d%H%M%S) if [ ! -f "$ENVF" ]; then echo "ОШИБКА: не найден файл окружения бэкенда на этом хосте." >&2 exit 1 fi # Копия перед первой правкой — одна на запуск, а не на каждый ключ. backup_once() { [ -f "${ENVF}.bak-metrics-${STAMP}" ] && return 0 cp -p "$ENVF" "${ENVF}.bak-metrics-${STAMP}" } have() { grep -qE "^${1}=" "$ENVF" 2>/dev/null; } value_of() { grep -m1 -E "^${1}=" "$ENVF" | cut -d= -f2-; } # 32 символа из [A-Za-z0-9]: помещается в basic_auth без экранирования и не # ломает разбор .env, где кавычки и знак равенства создают сюрпризы. gen_pass() { tr -dc 'A-Za-z0-9' < /dev/urandom | head -c 32; } ensure() { local key="$1" val="$2" if have "$key"; then echo " $key — уже есть, переиспользую" else backup_once printf '%s=%s\n' "$key" "$val" >> "$ENVF" echo " $key — сгенерирован" fi } echo "=== учётки на инфраструктурном хосте ===" ensure METRICS_INGEST_USER "alloy" ensure METRICS_INGEST_PASSWORD "$(gen_pass)" ensure METRICS_UI_USER "metrics" ensure METRICS_UI_PASSWORD "$(gen_pass)" ensure GRAFANA_ADMIN_USER "admin" ensure GRAFANA_ADMIN_PASSWORD "$(gen_pass)" ensure GLITCHTIP_RO_PASSWORD "$(gen_pass)" INGEST_PASS=$(value_of METRICS_INGEST_PASSWORD) UI_PASS=$(value_of METRICS_UI_PASSWORD) GT_RO=$(value_of GLITCHTIP_RO_PASSWORD) ensure INFRA_EXPORTER_DSN \ "postgresql://grafana_ro:${GT_RO}@gendesign-infra-postgres:5432/glitchtip?sslmode=disable" echo echo "=== пароль приёмника на продуктовый хост ===" if "${RSSH[@]}" "grep -qE '^METRICS_INGEST_PASSWORD=' ${REMOTE_ENVF}" 2>/dev/null; then echo " уже задан, не трогаю" else "${RSSH[@]}" "cp -p ${REMOTE_ENVF} ${REMOTE_ENVF}.bak-metrics-${STAMP}" # Значение идёт по stdin, а не аргументом команды: аргументы видны в # `ps` на обеих машинах и оседают в истории оболочки. printf 'METRICS_INGEST_USER=alloy\nMETRICS_INGEST_PASSWORD=%s\n' "$INGEST_PASS" \ | "${RSSH[@]}" "cat >> ${REMOTE_ENVF}" "${RSSH[@]}" "chown --reference=${REMOTE_ENVF}.bak-metrics-${STAMP} ${REMOTE_ENVF}; \ chmod --reference=${REMOTE_ENVF}.bak-metrics-${STAMP} ${REMOTE_ENVF}" echo " записан, права сохранены по резервной копии" fi echo echo "=== bcrypt-хеши для caddy/metrics-*.caddy.snippet ===" echo " (пароли не печатаются; хеши безопасны для git)" echo for pair in "alloy:${INGEST_PASS}:metrics-ingest" "metrics:${UI_PASS}:metrics-ui"; do user="${pair%%:*}"; rest="${pair#*:}" pass="${rest%%:*}"; file="${rest#*:}" hash=$(docker run --rm caddy:2 caddy hash-password --plaintext "$pass" 2>/dev/null | tr -d '\r\n') b64=$(printf '%s' "$hash" | base64 -w 0) printf ' caddy/%s.caddy.snippet\n %-8s %s\n' "$file" "$user" "$b64" done echo echo "=== что осталось сделать руками ===" have METRICS_TELEGRAM_BOT_TOKEN \ && echo " METRICS_TELEGRAM_BOT_TOKEN — задан" \ || echo " METRICS_TELEGRAM_BOT_TOKEN — НЕ задан, алерты никуда не уйдут" have METRICS_TELEGRAM_CHAT_ID \ && echo " METRICS_TELEGRAM_CHAT_ID — задан" \ || echo " METRICS_TELEGRAM_CHAT_ID — НЕ задан, алерты никуда не уйдут" echo echo " Пароль витрины смотреть так (в переписку не копировать):" echo " grep '^METRICS_UI_PASSWORD=' $ENVF"