#!/usr/bin/env bash # Создаёт 9 персональных уведомлений всех видов контура P для одного user_id. # Запускать на ВМ1 под root/admin (sudo -i). Секреты не читаются из /etc/han/vm1.env. # # sed -i 's/\r$//' /opt/han-chat/current/backend/deployment/scripts/seed-personal-notifications-test.sh # chmod +x /opt/han-chat/current/backend/deployment/scripts/seed-personal-notifications-test.sh # /opt/han-chat/current/backend/deployment/scripts/seed-personal-notifications-test.sh # # Токен: runtime-файл NOTIFICATIONS_TOKEN_PRODUCER_TEST # (/run/han-chat/secrets/…, тот же, что у api-backend). Hash в notification_sources # синхронизируется при старте api-backend. set -euo pipefail SCRIPT_DIR=$(CDPATH= cd -- "$(dirname "$0")" && pwd) if [[ -f "${SCRIPT_DIR}/../../docker-compose.yml" ]]; then cd "${SCRIPT_DIR}/../.." else cd "${HAN_DEPLOY_DIR:-/opt/han-chat/current/backend}" fi CONFIG_FILE="${CONFIG_FILE:-/etc/han/vm1.env}" SECRETS_LAUNCHER="${SECRETS_LAUNCHER:-/usr/local/lib/han-secrets/han-secrets}" if [[ ! -x "$SECRETS_LAUNCHER" ]]; then SECRETS_LAUNCHER="deployment/secrets/han-secrets" fi if [[ ! -f "$CONFIG_FILE" ]]; then echo "Не найден конфиг ${CONFIG_FILE} (ожидается /etc/han/vm1.env)." >&2 exit 1 fi if [[ "${HAN_SECRETS_ACTIVE:-0}" != "1" ]]; then [[ -x "$SECRETS_LAUNCHER" ]] || { echo "Secret launcher is required: $SECRETS_LAUNCHER" >&2 exit 66 } exec "$SECRETS_LAUNCHER" run --config "$CONFIG_FILE" -- "$0" "$@" fi compose() { if [[ -x /usr/local/sbin/han-vm1-compose ]]; then /usr/local/sbin/han-vm1-compose "$@" else docker compose --env-file "$CONFIG_FILE" "$@" fi } trim_token() { printf '%s' "$1" | tr -d '\r\n\t ' } read_secret_file() { local path="${1:-}" [[ -n "$path" && -r "$path" ]] || return 1 python3 - "$path" <<'PY' from pathlib import Path import sys print(Path(sys.argv[1]).read_text(encoding="utf-8").strip()) PY } TOKEN="$(trim_token "${NOTIFICATIONS_TOKEN_PRODUCER_TEST:-}")" if [[ -z "$TOKEN" ]]; then TOKEN="$(trim_token "$(read_secret_file "${NOTIFICATIONS_TOKEN_PRODUCER_TEST_FILE:-}" 2>/dev/null || true)")" fi if [[ -z "$TOKEN" ]]; then TOKEN="$(trim_token "$(read_secret_file "${HAN_RUNTIME_SECRET_DIR:-/run/han-chat/secrets}/NOTIFICATIONS_TOKEN_PRODUCER_TEST" 2>/dev/null || true)")" fi if [[ -z "$TOKEN" ]]; then TOKEN="$(trim_token "$(compose exec -T api-backend python3 -c 'import os; print(os.getenv("NOTIFICATIONS_TOKEN_PRODUCER_TEST") or "")' 2>/dev/null || true)")" fi if [[ -z "$TOKEN" ]]; then read -rsp "NOTIFICATIONS_TOKEN_PRODUCER_TEST (runtime secret не найден): " TOKEN echo TOKEN="$(trim_token "$TOKEN")" fi read -rp "USER_ID клиента: " USER_ID USER_ID="$(trim_token "$USER_ID")" if [[ -z "${TOKEN}" || -z "${USER_ID}" ]]; then echo "TOKEN и USER_ID обязательны." >&2 exit 1 fi echo "Токен: ${#TOKEN} символов (значение не печатается)." PAYLOAD_DIR="$(mktemp -d)" trap 'rm -rf "$PAYLOAD_DIR"' EXIT BASE_TS="$(date +%s)" RUN_ID="manual-test-${BASE_TS}" NOW="$(date -u +"%Y-%m-%dT%H:%M:%SZ")" DT_URGENT="$(date -u -d "${NOW} +0 seconds" +"%Y-%m-%dT%H:%M:%SZ" 2>/dev/null || date -u -v+0S +"%Y-%m-%dT%H:%M:%SZ")" DT_PAYMENT="$(date -u -d "${NOW} - 60 seconds" +"%Y-%m-%dT%H:%M:%SZ" 2>/dev/null || date -u -v-60S +"%Y-%m-%dT%H:%M:%SZ")" DT_DOCS_REQ="$(date -u -d "${NOW} - 120 seconds" +"%Y-%m-%dT%H:%M:%SZ" 2>/dev/null || date -u -v-120S +"%Y-%m-%dT%H:%M:%SZ")" DT_DOCS_READY="$(date -u -d "${NOW} - 180 seconds" +"%Y-%m-%dT%H:%M:%SZ" 2>/dev/null || date -u -v-180S +"%Y-%m-%dT%H:%M:%SZ")" DT_STATUS="$(date -u -d "${NOW} - 240 seconds" +"%Y-%m-%dT%H:%M:%SZ" 2>/dev/null || date -u -v-240S +"%Y-%m-%dT%H:%M:%SZ")" DT_REMINDER="$(date -u -d "${NOW} - 300 seconds" +"%Y-%m-%dT%H:%M:%SZ" 2>/dev/null || date -u -v-300S +"%Y-%m-%dT%H:%M:%SZ")" DT_NEWS="$(date -u -d "${NOW} - 360 seconds" +"%Y-%m-%dT%H:%M:%SZ" 2>/dev/null || date -u -v-360S +"%Y-%m-%dT%H:%M:%SZ")" DT_PROMO="$(date -u -d "${NOW} - 420 seconds" +"%Y-%m-%dT%H:%M:%SZ" 2>/dev/null || date -u -v-420S +"%Y-%m-%dT%H:%M:%SZ")" DT_ADS="$(date -u -d "${NOW} - 480 seconds" +"%Y-%m-%dT%H:%M:%SZ" 2>/dev/null || date -u -v-480S +"%Y-%m-%dT%H:%M:%SZ")" DEADLINE_URGENT="$(date -u -d "${NOW} + 2 days" +"%Y-%m-%dT%H:%M:%SZ" 2>/dev/null || date -u -v+2d +"%Y-%m-%dT%H:%M:%SZ")" DEADLINE_DOCS="$(date -u -d "${NOW} + 5 days" +"%Y-%m-%dT%H:%M:%SZ" 2>/dev/null || date -u -v+5d +"%Y-%m-%dT%H:%M:%SZ")" DEADLINE_REMINDER="$(date -u -d "${NOW} + 1 day" +"%Y-%m-%dT%H:%M:%SZ" 2>/dev/null || date -u -v+1d +"%Y-%m-%dT%H:%M:%SZ")" EXPIRE_PAYMENT="$(date -u -d "${NOW} + 3 days" +"%Y-%m-%dT%H:%M:%SZ" 2>/dev/null || date -u -v+3d +"%Y-%m-%dT%H:%M:%SZ")" write_payload() { local name="$1" cat > "${PAYLOAD_DIR}/${name}.json" } create_notification() { local label="$1" local payload_file="${PAYLOAD_DIR}/${label}.json" if [[ ! -f "$payload_file" ]]; then echo "Нет payload: ${payload_file}" >&2 return 1 fi echo echo "========== ${label} ==========" compose exec -T \ -e "NOTIFICATIONS_PRODUCER_TOKEN=${TOKEN}" \ api-backend python3 -c " import os import sys import urllib.error import urllib.request payload = sys.stdin.buffer.read() request = urllib.request.Request( 'http://127.0.0.1:8000/internal/notifications/v1/notifications', data=payload, method='POST', headers={ 'Authorization': 'Bearer ' + os.environ['NOTIFICATIONS_PRODUCER_TOKEN'], 'Content-Type': 'application/json; charset=utf-8', }, ) try: with urllib.request.urlopen(request, timeout=60) as response: sys.stdout.write('HTTP/1.1 %s\n' % response.status) for key, value in response.headers.items(): sys.stdout.write('%s: %s\n' % (key, value)) sys.stdout.write('\n') sys.stdout.write(response.read().decode('utf-8', errors='replace')) sys.stdout.write('\n') except urllib.error.HTTPError as exc: sys.stdout.write('HTTP/1.1 %s\n' % exc.code) for key, value in exc.headers.items(): sys.stdout.write('%s: %s\n' % (key, value)) sys.stdout.write('\n') sys.stdout.write(exc.read().decode('utf-8', errors='replace')) sys.stdout.write('\n') if exc.code >= 500 or exc.code in (401, 403): raise SystemExit(1) " <"$payload_file" } prepare_docs_in_s3() { echo >&2 echo "========== PREP: загрузка тестовых документов в S3 для docs_ready ==========" >&2 compose exec -T \ -e "USER_ID=${USER_ID}" \ -e "RUN_ID=${RUN_ID}" \ api-backend python3 - <<'PY' import hashlib import os import uuid import boto3 from botocore.client import Config from app.settings import Settings USER_ID = os.environ["USER_ID"] settings = Settings() client = boto3.client( "s3", endpoint_url=str(settings.selectel_s3_endpoint_url), aws_access_key_id=settings.selectel_s3_access_key.get_secret_value(), aws_secret_access_key=settings.selectel_s3_secret_key.get_secret_value(), config=Config(signature_version="s3v4", s3={"addressing_style": "virtual"}), ) bucket = settings.selectel_s3_bucket_documents fixtures = [ { "prefix": "CONTRACT", "title": "Уведомление о постановке на миграционный учёт.pdf", "body": b"""%PDF-1.4 1 0 obj<>endobj 2 0 obj<>endobj 3 0 obj<>endobj xref 0 4 trailer<> startxref 100 %%EOF """, }, { "prefix": "RESULTS", "title": "Справка о соблюдении миграционного законодательства.pdf", "body": b"""%PDF-1.4 1 0 obj<>endobj 2 0 obj<>endobj 3 0 obj<>endobj xref 0 4 trailer<> startxref 100 %%EOF """, }, ] lines: list[str] = [] for item in fixtures: doc_id = str(uuid.uuid4()) key = f"documents/users/{USER_ID}/{doc_id}" body = item["body"] checksum = hashlib.sha256(body).hexdigest() client.put_object( Bucket=bucket, Key=key, Body=body, ContentType="application/pdf", ) p = item["prefix"] lines.append( f"{p}_KEY={key}\n" f"{p}_TITLE={item['title']}\n" f"{p}_SIZE={len(body)}\n" f"{p}_SHA256={checksum}" ) print("\n".join(lines)) PY } echo "Run ID: ${RUN_ID}" echo "User ID: ${USER_ID}" read -rp "Подготовить документы в S3 для docs_ready? [Y/n]: " PREP_DOCS PREP_DOCS="${PREP_DOCS:-Y}" CONTRACT_KEY="" CONTRACT_TITLE="" CONTRACT_SIZE="" CONTRACT_SHA256="" RESULTS_KEY="" RESULTS_TITLE="" RESULTS_SIZE="" RESULTS_SHA256="" if [[ "${PREP_DOCS^^}" != "N" ]]; then PREP_OUT="$(prepare_docs_in_s3)" echo "${PREP_OUT}" while IFS= read -r line; do [[ "$line" =~ ^([A-Z][A-Z0-9_]*)=(.*)$ ]] || continue declare "${BASH_REMATCH[1]}=${BASH_REMATCH[2]}" done <<< "${PREP_OUT}" fi # --- payloads --- write_payload urgent <