[REDACTED]

ssh -i /home/ruslanpi/.ssh/id_ed25519_devops_ansible -o BatchMode=yes ansible@p-pki-as-01.lmru.tech 'cat /etc/cron.d/vault-raft-backup; echo ---; sudo -n cat /etc/vault-raft-backup/config.env 2>/dev/null | sed -E "s/(AWS_SECRET_ACCESS_KEY|SECRET)=.*/\1=<redacted>/" || echo "no sudo/config"; echo ---; sudo -n ls -la /data/vault/backups 2>/dev/null | tail -15 || ls -la /data/vault/backups 2>&1 | tail -5'

Read vault-raft-backup cron and backup dir listing


[REDACTED]

Shell

ssh -i /home/ruslanpi/.ssh/id_ed25519_devops_ansible -o BatchMode=yes ansible@p-pki-as-01.lmru.tech 'sudo -n tail -80 /var/log/vault-raft-backup.log'

Read vault-raft-backup.log tail for errors


Вот что видно на **p-pki-as-01** после захода по SSH.

## Причина

**Бэкап не падает на S3, потому что каждые 30 минут ломается на проверке токена Vault.**

В `/var/log/vault-raft-backup.log` повторяется одно и то же:

```text
Error looking up token: ...
URL: GET https://p-pki-as-01.lmru.tech:8200/v1/auth/token/lookup-self
Code: 403. Errors:
* permission denied
* invalid token

Vault backup token is invalid or lacks lookup-self permission: /etc/vault-raft-backup/vault.token
Rotate token: vault token create -policy=root_policy_backup -period=24h -orphan
```

То есть содержимое **`/etc/vault-raft-backup/vault.token`** для текущего Vault **невалидно**: отозван, истёк, пересоздан кластер/политика, файл перезаписан и т.п.

## Почему совпадает с датой

Локальные снапшоты в `/data/vault/backups` **обрываются на**
`vault-raft-20260430T140001Z.snap` (**30.04 14:00 UTC**). Дальше cron всё ещё дергает скрипт, но **новых файлов нет** — значит с **следующего окна после поломки токена** загрузка в Object Storage тоже прекратилась. Твой объект **`vault-raft-20260430T140001Z.snap` в 17:00** — последний успешный выгруженный файл.

## Что сделать (операционно)

1. Под **админским доступом к Vault** (root или политика, которая может выдавать токены под `root_policy_backup`):
   - выдать новый токен для бэкапа, как пишет скрипт, например:
     - `vault token create -policy=root_policy_backup -period=24h -orphan`
     (или ваш согласованный `period`, если в регламенте не 24h).
2. Записать его **только** в **`/etc/vault-raft-backup/vault.token`** на **p-pki-as-01**, права как было (`root:root`, `0600`).
3. Вручную один раз:
   - `sudo /usr/local/sbin/vault-raft-backup`
4. Убедиться: новый `.snap` в `/data/vault/backups`, в бакете новый объект, в метриках обновился **`vault_pki_object_storage_latest_mtime_seconds`**.

Дополнительно: если токен с **коротким TTL**, без периодического renewal он снова «умрёт» — имеет смысл зафиксировать в runbook **периодическое продление** или **токен с адекватным сроком** + мониторинг срока жизни.

Итог: **инфраструктура cron/S3/path в порядке; восстановить валидный backup-токен в `/etc/vault-raft-backup/vault.token`.**

[REDACTED]


<user_query>
 @/home/ruslanpi/.cursor/projects/home-ruslanpi-Documents-repos-lmru-devops-core-ansible-vault-oss/terminals/1.txt:9-29 сделай токен положи куда надо, и надо сделать чтобы он не протухал
@docs/adr/architecture.md
</user_query>


Не можем создать production-токен без твоего root/admin в Vault — подготовим регламент и изменения в скрипте/доках для долгоживущего или автопродляемого токена. Просматриваю текущую логику и политику:

[REDACTED]


Токен в Vault от имени администратора мы отсюда создать не можем — добавляем в скрипт режим продления и обновляем документацию: длинный `period`, `renew` каждые 15 мин, регламент выдачи токена и запись в `/etc/vault-raft-backup/vault.token`.

[REDACTED]

StrReplace

LOCK_FILE="${LOCK_FILE:-/run/vault-raft-backup.lock}"

exec 9>"${LOCK_FILE}"
if ! flock -n 9; then
  echo "Vault Raft snapshot backup is already running" >&2
  exit 0
fi

LOCK_FILE="${LOCK_FILE:-/run/vault-raft-backup.lock}"
RENEW_LOCK_FILE="${RENEW_LOCK_FILE:-/run/vault-raft-backup-renew.lock}"

renew_token_only() {
  exec 8>"${RENEW_LOCK_FILE}"
  if ! flock -n 8; then
    exit 0
  fi

  require_command docker
  require_private_file "${VAULT_TOKEN_FILE}" "Vault backup token file"

  if ! vault_with_token 'IFS= read -r VAULT_TOKEN || [ -n "$VAULT_TOKEN" ]; export VAULT_TOKEN; vault token lookup >/dev/null'; then
    echo "Vault backup token is invalid: ${VAULT_TOKEN_FILE}" >&2
    echo "Issue a new periodic token and install it on this host (see docs/adr/architecture.md)." >&2
    exit 1
  fi

  if ! vault_with_token 'IFS= read -r VAULT_TOKEN || [ -n "$VAULT_TOKEN" ]; export VAULT_TOKEN; vault token renew >/dev/null'; then
    echo "Vault backup token renew failed: ${VAULT_TOKEN_FILE}" >&2
    exit 1
  fi
}

if [[ "${1:-}" == "renew-token" ]]; then
  umask 077
  if [[ -r "${CONFIG_FILE}" ]]; then
    if [[ -n "$(find "${CONFIG_FILE}" -maxdepth 0 -perm /077 -print -quit)" ]]; then
      echo "Config file must not be readable or writable by group/others: ${CONFIG_FILE}" >&2
      exit 1
    fi
    # shellcheck disable=SC1090
    source "${CONFIG_FILE}"
  fi
  VAULT_TOKEN_FILE="${VAULT_TOKEN_FILE:-/etc/vault-raft-backup/vault.token}"
  CONTAINER_NAME="${CONTAINER_NAME:-vault}"
  VAULT_ADDR="${VAULT_ADDR:-https://$(hostname -f):8200}"
  renew_token_only
  exit 0
fi

exec 9>"${LOCK_FILE}"
if ! flock -n 9; then
  echo "Vault Raft snapshot backup is already running" >&2
  exit 0
fi


[REDACTED]


Перемещаю `renew-token`: функции вызывались до их определения — исправляю порядок.

[REDACTED]

LOCK_FILE="${LOCK_FILE:-/run/vault-raft-backup.lock}"
RENEW_LOCK_FILE="${RENEW_LOCK_FILE:-/run/vault-raft-backup-renew.lock}"

exec 9>"${LOCK_FILE}"

LOCK_FILE="${LOCK_FILE:-/run/vault-raft-backup.lock}"
RENEW_LOCK_FILE="${RENEW_LOCK_FILE:-/run/vault-raft-backup-renew.lock}"

renew_token_only() {
  exec 8>"${RENEW_LOCK_FILE}"
  if ! flock -n 8; then
    exit 0
  fi

  require_command docker
  require_private_file "${VAULT_TOKEN_FILE}" "Vault backup token file"

  if ! vault_with_token 'IFS= read -r VAULT_TOKEN || [ -n "$VAULT_TOKEN" ]; export VAULT_TOKEN; vault token lookup >/dev/null'; then
    echo "Vault backup token is invalid: ${VAULT_TOKEN_FILE}" >&2
    echo "Issue a new periodic token and install it on this host (see docs/adr/architecture.md)." >&2
    exit 1
  fi

  if ! vault_with_token 'IFS= read -r VAULT_TOKEN || [ -n "$VAULT_TOKEN" ]; export VAULT_TOKEN; vault token renew >/dev/null'; then
    echo "Vault backup token renew failed: ${VAULT_TOKEN_FILE}" >&2
    exit 1
  fi
}

if [[ "${1:-}" == "renew-token" ]]; then
  umask 077
  if [[ -r "${CONFIG_FILE}" ]]; then
    if [[ -n "$(find "${CONFIG_FILE}" -maxdepth 0 -perm /077 -print -quit)" ]]; then
      echo "Config file must not be readable or writable by group/others: ${CONFIG_FILE}" >&2
      exit 1
    fi
    # shellcheck disable=SC1090
    source "${CONFIG_FILE}"
  fi
  VAULT_TOKEN_FILE="${VAULT_TOKEN_FILE:-/etc/vault-raft-backup/vault.token}"
  CONTAINER_NAME="${CONTAINER_NAME:-vault}"
  VAULT_ADDR="${VAULT_ADDR:-https://$(hostname -f):8200}"
  renew_token_only
  exit 0
fi

exec 9>"${LOCK_FILE}"

vault_with_token() {
  local script="$1"
  shift

  docker exec \
    --env VAULT_ADDR="${VAULT_ADDR}" \
    --interactive \
    "${CONTAINER_NAME}" \
    sh -ceu "${script}" sh "$@" \
    < "${VAULT_TOKEN_FILE}"
}

renew_token_only() {
  exec 8>"${RENEW_LOCK_FILE}"
  if ! flock -n 8; then
    exit 0
  fi

  require_command docker
  require_private_file "${VAULT_TOKEN_FILE}" "Vault backup token file"

  if ! vault_with_token 'IFS= read -r VAULT_TOKEN || [ -n "$VAULT_TOKEN" ]; export VAULT_TOKEN; vault token lookup >/dev/null'; then
    echo "Vault backup token is invalid: ${VAULT_TOKEN_FILE}" >&2
    echo "Issue a new periodic token and install it on this host (see docs/adr/architecture.md)." >&2
    exit 1
  fi

  if ! vault_with_token 'IFS= read -r VAULT_TOKEN || [ -n "$VAULT_TOKEN" ]; export VAULT_TOKEN; vault token renew >/dev/null'; then
    echo "Vault backup token renew failed: ${VAULT_TOKEN_FILE}" >&2
    exit 1
  fi
}

if [[ "${1:-}" == "renew-token" ]]; then
  umask 077
  if [[ -r "${CONFIG_FILE}" ]]; then
    if [[ -n "$(find CONFIG_FILE -maxdepth 0 -perm /077 -print -quit 2>/dev/null)" ]]; then
      echo "Config file must not be readable or writable by group/others: ${CONFIG_FILE}" >&2
      exit 1
    fi
    # shellcheck disable=SC1090
    source "${CONFIG_FILE}"
  fi
  VAULT_TOKEN_FILE="${VAULT_TOKEN_FILE:-/etc/vault-raft-backup/vault.token}"
  CONTAINER_NAME="${CONTAINER_NAME:-vault}"
  VAULT_ADDR="${VAULT_ADDR:-https://$(hostname -f):8200}"
  renew_token_only
  exit 0
fi

exec 9>"${LOCK_FILE}"

vault_with_token() {
  local script="$1"
  shift

  docker exec \
    --env VAULT_ADDR="${VAULT_ADDR}" \
    --interactive \
    "${CONTAINER_NAME}" \
    sh -ceu "${script}" sh "$@" \
    < "${VAULT_TOKEN_FILE}"
}

exec 9>"${LOCK_FILE}"


Восстанавливаем порядок: сначала функции и `renew-token`, затем блокировка полного бэкапа.

[REDACTED]

LOCK_FILE="${LOCK_FILE:-/run/vault-raft-backup.lock}"
RENEW_LOCK_FILE="${RENEW_LOCK_FILE:-/run/vault-raft-backup-renew.lock}"

exec 9>"${LOCK_FILE}"
if ! flock -n 9; then
  echo "Vault Raft snapshot backup is already running" >&2
  exit 0
fi

require_command() {
  local command_name="$1"

  if ! command -v "${command_name}" >/dev/null 2>&1; then
    echo "Required command is not installed: ${command_name}" >&2
    exit 1
  fi
}

require_readable_file() {
  local file_path="$1"
  local description="$2"

  if [[ ! -r "${file_path}" ]]; then
    echo "${description} is not readable: ${file_path}" >&2
    exit 1
  fi
}

require_private_file() {
  local file_path="$1"
  local description="$2"

  require_readable_file "${file_path}" "${description}"
  if [[ -n "$(find "${file_path}" -maxdepth 0 -perm /077 -print -quit)" ]]; then
    echo "${description} must not be readable or writable by group/others: ${file_path}" >&2
    exit 1
  fi
}

vault_with_token() {
  local script="$1"
  shift

  docker exec \
    --env VAULT_ADDR="${VAULT_ADDR}" \
    --interactive \
    "${CONTAINER_NAME}" \
    sh -ceu "${script}" sh "$@" \
    < "${VAULT_TOKEN_FILE}"
}

require_command docker

LOCK_FILE="${LOCK_FILE:-/run/vault-raft-backup.lock}"
RENEW_LOCK_FILE="${RENEW_LOCK_FILE:-/run/vault-raft-backup-renew.lock}"

require_command() {
  local command_name="$1"

  if ! command -v "${command_name}" >/dev/null 2>&1; then
    echo "Required command is not installed: ${command_name}" >&2
    exit 1
  fi
}

require_readable_file() {
  local file_path="$1"
  local description="$2"

  if [[ ! -r "${file_path}" ]]; then
    echo "${description} is not readable: ${file_path}" >&2
    exit 1
  fi
}

require_private_file() {
  local file_path="$1"
  local description="$2"

  require_readable_file "${file_path}" "${description}"
  if [[ -n "$(find "${file_path}" -maxdepth 0 -perm /077 -print -quit)" ]]; then
    echo "${description} must not be readable or writable by group/others: ${file_path}" >&2
    exit 1
  fi
}

vault_with_token() {
  local script="$1"
  shift

  docker exec \
    --env VAULT_ADDR="${VAULT_ADDR}" \
    --interactive \
    "${CONTAINER_NAME}" \
    sh -ceu "${script}" sh "$@" \
    < "${VAULT_TOKEN_FILE}"
}

renew_token_only() {
  exec 8>"${RENEW_LOCK_FILE}"
  if ! flock -n 8; then
    exit 0
  fi

  require_command docker
  require_private_file "${VAULT_TOKEN_FILE}" "Vault backup token file"

  if ! vault_with_token 'IFS= read -r VAULT_TOKEN || [ -n "$VAULT_TOKEN" ]; export VAULT_TOKEN; vault token lookup >/dev/null'; then
    echo "Vault backup token is invalid: ${VAULT_TOKEN_FILE}" >&2
    echo "Issue a new periodic token and install it on this host (see docs/adr/architecture.md)." >&2
    exit 1
  fi

  if ! vault_with_token 'IFS= read -r VAULT_TOKEN || [ -n "$VAULT_TOKEN" ]; export VAULT_TOKEN; vault token renew >/dev/null'; then
    echo "Vault backup token renew failed: ${VAULT_TOKEN_FILE}" >&2
    exit 1
  fi
}

if [[ "${1:-}" == "renew-token" ]]; then
  renew_token_only
  exit 0
fi

exec 9>"${LOCK_FILE}"
if ! flock -n 9; then
  echo "Vault Raft snapshot backup is already running" >&2
  exit 0
fi

require_command docker


[REDACTED]

  echo "Rotate token (admin, see docs/adr/architecture.md § backup-token):" >&2
  echo "  vault token create -policy=root_policy_backup -period=168h -orphan" >&2

echo "Rotate token: vault token create -policy=root_policy_backup -period=24h -orphan" >&2

* backup-token выпускается по policy `root_policy_backup`, имеет минимальные права на создание snapshot и self-renew, фактический period — `1h`;
* тест восстановления из snapshot проводится не реже одного раза в квартал.

Фактическая проверка от 2026-04-28:

* backup-token выпускается по policy `root_policy_backup` (файл `roles/vault-oss-setup/files/root_policy_backup.hcl`): только `sys/storage/raft/snapshot` (read), `auth/token/lookup-self`, `auth/token/renew-self`;
* **Бессрочного токена в Vault нет**: используется **periodic token** с длинным `period` (рекомендуется **`168h`** семь суток) и флагом **`-orphan`**, чтобы токен переживал отзыв родительской сессии. Каждый успешный `vault token renew` продлевает срок на **`period`** (пока политика и лимиты кластера позволяют).
* Скрипт `/usr/local/sbin/vault-raft-backup` после проверки токена вызывает **`vault token renew`** перед snapshot; дополнительно на backup-ноде в **`/etc/cron.d/`** задаётся частый прогон **`/usr/local/sbin/vault-raft-backup renew-token`** (например каждые **15 минут**), чтобы ТTL не истекал, если полный backup временно падает на S3 или локальном диске.

**Выдача и установка backup-токена (делает администратор Vault один раз или после компрометации):**

```bash
export VAULT_ADDR='https://pki.lmru.tech'
# Авторизация администратора (root one-shot или своя политика с правом token create под root_policy_backup)

vault token create \
  -policy=root_policy_backup \
  -period=168h \
  -orphan \
  -display-name='raft-backup-p-pki-as-01'
```

Ответ команды: поле `token` — единственная копия. На **backup-ноде** `p-pki-as-01.lmru.tech`:

```bash
sudo install -d -m 0755 -o root -g root /etc/vault-raft-backup
sudo sh -c 'umask 077; cat > /etc/vault-raft-backup/vault.token'
# вставить токен hvs.... одной строкой, Ctrl+D

sudo /usr/local/sbin/vault-raft-backup   # проверка: локальный .snap и загрузка в S3
```

Токен в Git и в тикеты целиком не кладётся; хранение только **root:root, chmod 600** на ноде.

* тест восстановления из snapshot проводится не реже одного раза в квартал.

Фактическая проверка от 2026-04-28:


[REDACTED]

```bash
sudo chmod 0644 /etc/cron.d/vault-pki-textfile-metrics
sudo cat /etc/cron.d/vault-pki-textfile-metrics
```

### 6.1. Частое продление backup-токена (`renew-token`)

Полный snapshot и загрузка в S3 выполняются каждые **30 минут** (`/etc/cron.d/vault-raft-backup`). Отдельно добавь лёгкий прогон **`vault-raft-backup.sh renew-token`**, чтобы **periodic token** продлевался чаще (например раз в **15 минут**) и не умирал, если несколько циклов подряд падают после `renew` (например только на шаге S3). Скрипт в репозитории: `scripts/vault-raft-backup.sh`; на ноде обычно **`/usr/local/sbin/vault-raft-backup`**.

```bash
sudo tee /etc/cron.d/vault-raft-backup-renew >/dev/null <<'EOF'
SHELL=/bin/bash
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin

*/15 * * * * root /usr/local/sbin/vault-raft-backup renew-token >>/var/log/vault-raft-backup-renew.log 2>&1
EOF
sudo chmod 0644 /etc/cron.d/vault-raft-backup-renew
```

Параметры Vault и путь к токену берутся из **`/etc/vault-raft-backup/config.env`** (как и у основного backup). Выдача токена с длинным `period` — в `docs/adr/architecture.md` (раздел backup-token).

Проверить через 15 минут:

```bash
sudo tail -20 /var/log/vault-raft-backup-renew.log
```

## 7. Добавить node_exporter targets в Prometheus

```bash
sudo chmod 0644 /etc/cron.d/vault-pki-textfile-metrics
sudo cat /etc/cron.d/vault-pki-textfile-metrics
```

## 7. Добавить node_exporter targets в Prometheus


[REDACTED]

curl -s http://127.0.0.1:9100/metrics | rg '^vault_pki_'
```

### 6.1. Частое продление backup-токена Vault (`renew-token`)

Полный snapshot и S3 — каждые **30 минут** (`/etc/cron.d/vault-raft-backup`). Дополнительно задайте лёгкий cron **`renew-token`** (например раз в **15 минут**), чтобы periodic token продлевался и не истекал из‑за серии частичных сбоев после шага renew. На ноде бинарь обычно **`/usr/local/sbin/vault-raft-backup`**; исходник — `scripts/vault-raft-backup.sh` в репозитории.

```bash
sudo tee /etc/cron.d/vault-raft-backup-renew >/dev/null <<'EOF'
SHELL=/bin/bash
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin

*/15 * * * * root /usr/local/sbin/vault-raft-backup renew-token >>/var/log/vault-raft-backup-renew.log 2>&1
EOF
sudo chmod 0644 /etc/cron.d/vault-raft-backup-renew
```

Выдача токена с длинным `period` и запись в **`/etc/vault-raft-backup/vault.token`** — в **`docs/adr/architecture.md`** (backup-token).

## 7. Добавить node_exporter targets в Prometheus

curl -s http://127.0.0.1:9100/metrics | rg '^vault_pki_'
```

## 7. Добавить node_exporter targets в Prometheus


[REDACTED]

Shell

shellcheck /home/ruslanpi/Documents/repos/lmru--devops-core--ansible-vault-oss/scripts/vault-raft-backup.sh 2>&1 || true

Run shellcheck on vault-raft-backup.sh


[REDACTED]

bash -n /home/ruslanpi/Documents/repos/lmru--devops-core--ansible-vault-oss/scripts/vault-raft-backup.sh && echo OK

Bash syntax check vault-raft-backup.sh


Сделать «вечный» токен в Vault **нельзя**: остаётся **periodic** + регулярный **`renew`**. Я не могу с твоей машины залогиниться в Vault и записать секрет на `p-pki-as-01` — это делаешь ты как админ. В репозитории обновлено следующее.

## 1. `scripts/vault-raft-backup.sh`

- Подкоманда **`renew-token`**: только `lookup` + **`vault token renew`**, свой lock **`/run/vault-raft-backup-renew.lock`**, не мешает основному бэкапу.
- В сообщении об ошибке указан **`period=168h`** и ссылка на ADR.

Скопируй обновлённый скрипт на ноду в **`/usr/local/sbin/vault-raft-backup`** (как сейчас в cron), `chmod +x`.

## 2. `docs/adr/architecture.md`

- Описан backup-token: policy, отсутствие «бессрочности», рекомендация **`period=168h`**, **`-orphan`**, **`renew`** в основном скрипте и **cron `renew-token` каждые ~15 мин**.
- Готовые команды: **`vault token create ...`** и как положить токен в **`/etc/vault-raft-backup/vault.token`** на **`p-pki-as-01`**, затем **`sudo /usr/local/sbin/vault-raft-backup`**.

## 3. `docs/vault-pki-monitoring-runbook.md`

- Подпункт **6.1**: пример **`/etc/cron.d/vault-raft-backup-renew`** с **`renew-token`**.

---

## Что сделать тебе вручную сейчас

1. Под админкой (у тебя уже есть `vault status` через `https://pki.lmru.tech`):

```bash
export VAULT_ADDR='https://pki.lmru.tech'
vault token create \
  -policy=root_policy_backup \
  -period=168h \
  -orphan \
  -display-name='raft-backup-p-pki-as-01'
```

2. Значение **`token`** из ответа — в **`/etc/vault-raft-backup/vault.token`** на **`p-pki-as-01`** (`root:root`, **600**).

3. Проверка: **`sudo /usr/local/sbin/vault-raft-backup`**, затем объект в S3 и метрика.

4. После выката нового скрипта: добавить cron **`/etc/cron.d/vault-raft-backup-renew`** (как в runbook §6.1).

**Важно:** пока токен **уже invalid**, `renew-token` само её не лечит — сначала обязательно **новый** токен в файл. Дальше **`168h` + renew каждые 15 мин + renew в начале каждого полного бэкапа** даёт устойчивое «не отваливается от TTL из‑за пропусков job».

[REDACTED]


<user_query>
обновил токен волт кли, все сделай
</user_query>


Проверяю репозиторий: добавим в Ansible установку скрипта и cron (токен на ноду ты уже положил).

[REDACTED]


Добавляем Ansible-задачи: установка скриптов и cron на ноде с `vault_raft_backup_node: true`. Токен и `config.env` остаются на сервере (не трогаем).

[REDACTED]

Write

---
# Установка cron raft backup и textfile-метрик только на ноде с
# vault_raft_backup_node: true (см. host_vars).
vault_raft_backup_node: false

vault_raft_backup_script_src: "{{ role_path }}/../../scripts/vault-raft-backup.sh"
vault_pki_textfile_script_src: "{{ role_path }}/../../scripts/vault-pki-textfile-metrics.sh"

vault_raft_local_backup_dir: /data/vault/backups
vault_raft_backup_uid: "100"
vault_raft_backup_gid: "1000"

---
# Raft snapshot → S3 + textfile metrics (PKI CA / backup age). Секреты только на хосте:
# /etc/vault-raft-backup/vault.token, config.env, s3.credentials — не из Ansible.
- name: (Raft backup) Ensure local snapshot directory exists
  ansible.builtin.file:
    path: "{{ vault_raft_local_backup_dir }}"
    state: directory
    owner: "{{ vault_raft_backup_uid }}"
    group: "{{ vault_raft_backup_gid }}"
    mode: "0750"

- name: (Raft backup) Install vault-raft-backup script
  ansible.builtin.copy:
    src: "{{ vault_raft_backup_script_src }}"
    dest: /usr/local/sbin/vault-raft-backup
    owner: root
    group: root
    mode: "0755"

- name: (Raft backup) Cron full backup each 30 minutes
  ansible.builtin.copy:
    dest: /etc/cron.d/vault-raft-backup
    owner: root
    group: root
    mode: "0644"
    content: |
      SHELL=/bin/bash
      PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin

      */30 * * * * root /usr/local/sbin/vault-raft-backup >> /var/log/vault-raft-backup.log 2>&1

- name: (Raft backup) Cron renew Vault token each 15 minutes
  ansible.builtin.copy:
    dest: /etc/cron.d/vault-raft-backup-renew
    owner: root
    group: root
    mode: "0644"
    content: |
      SHELL=/bin/bash
      PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin

      */15 * * * * root /usr/local/sbin/vault-raft-backup renew-token >> /var/log/vault-raft-backup-renew.log 2>&1

- name: (Raft backup) Install vault-pki textfile metrics script
  ansible.builtin.copy:
    src: "{{ vault_pki_textfile_script_src }}"
    dest: /usr/local/bin/vault-pki-textfile-metrics.sh
    owner: root
    group: root
    mode: "0755"

- name: (Raft backup) Ensure node_exporter textfile directory exists
  ansible.builtin.file:
    path: /var/lib/node_exporter/textfile_collector
    state: directory
    owner: root
    group: root
    mode: "0755"

- name: (Raft backup) Cron textfile metrics each 15 minutes
  ansible.builtin.copy:
    dest: /etc/cron.d/vault-pki-textfile-metrics
    owner: root
    group: root
    mode: "0644"
    content: |
      SHELL=/bin/bash
      PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin

      */15 * * * * root VAULT_ADDR="https://pki.lmru.tech" TEXTFILE_DIR="/var/lib/node_exporter/textfile_collector" /usr/local/bin/vault-pki-textfile-metrics.sh >> /var/log/vault-pki-textfile-metrics.log 2>&1

StrReplace

- name: (Logs) Copy logrotate config file
  ansible.builtin.template:
    src: "logrotate.j2"
    dest: "/etc/logrotate.d/vault"
  notify: Test logrotate config
  tags: logs

- name: (Raft backup) Include raft backup and PKI metrics tasks
  ansible.builtin.include_tasks: raft_backup.yml
  when: vault_raft_backup_node | bool
  tags:
    - raft_backup
    - vault

- name: (Logs) Copy logrotate config file
  ansible.builtin.template:
    src: "logrotate.j2"
    dest: "/etc/logrotate.d/vault"
  notify: Test logrotate config
  tags: logs


Исправляю путь к скриптам: `role_path` в `defaults` не задаётся.

[REDACTED]

vault_raft_backup_node: false

vault_raft_local_backup_dir: /data/vault/backups

- name: (Raft backup) Install vault-raft-backup script
  ansible.builtin.copy:
    src: "{{ playbook_dir }}/../scripts/vault-raft-backup.sh"
    dest: /usr/local/sbin/vault-raft-backup
    owner: root
    group: root
    mode: "0755"

- name: (Raft backup) Cron full backup each 30 minutes

- name: (Raft backup) Install vault-pki textfile metrics script
  ansible.builtin.copy:
    src: "{{ playbook_dir }}/../scripts/vault-pki-textfile-metrics.sh"
    dest: /usr/local/bin/vault-pki-textfile-metrics.sh


[REDACTED]

Write

---
# Установка cron raft backup и textfile-метрик только на ноде с
# vault_raft_backup_node: true (см. host_vars).
vault_raft_backup_node: false

vault_raft_local_backup_dir: /data/vault/backups
vault_raft_backup_uid: "100"
vault_raft_backup_gid: "1000"

StrReplace

- name: (Raft backup) Install vault-raft-backup script
  ansible.builtin.copy:
    src: "{{ vault_raft_backup_script_src }}"
    dest: /usr/local/sbin/vault-raft-backup

- name: (Raft backup) Install vault-raft-backup script
  ansible.builtin.copy:
    src: "{{ playbook_dir }}/../scripts/vault-raft-backup.sh"
    dest: /usr/local/sbin/vault-raft-backup

- name: (Raft backup) Install vault-pki textfile metrics script
  ansible.builtin.copy:
    src: "{{ vault_pki_textfile_script_src }}"
    dest: /usr/local/bin/vault-pki-textfile-metrics.sh

- name: (Raft backup) Install vault-pki textfile metrics script
  ansible.builtin.copy:
    src: "{{ playbook_dir }}/../scripts/vault-pki-textfile-metrics.sh"
    dest: /usr/local/bin/vault-pki-textfile-metrics.sh


Добавляю монтирование `/data/vault/backups` в Vault-контейнер при `vault_raft_backup_node`, иначе snapshot в контейнер не сохранится.

[REDACTED]

    volumes:
      - {{ vault_storage_dir }}:/vault/raft:rw
      - {{ vault_config_dir }}:/vault/config/:ro
      - {{ vault_logs_dir }}:/vault/logs:rw
      - {{ vault_tls_dir }}:/vault/tls:ro
{% if vault_raft_backup_node | default(false) | bool %}
      - {{ vault_raft_local_backup_dir }}:/vault/backups:rw
{% endif %}

    volumes:
      - {{ vault_storage_dir }}:/vault/raft:rw
      - {{ vault_config_dir }}:/vault/config/:ro
      - {{ vault_logs_dir }}:/vault/logs:rw
      - {{ vault_tls_dir }}:/vault/tls:ro


Создаём каталог бэкапов до запуска Docker (bind mount). Внося правки в `main.yml` и `raft_backup.yml`.

[REDACTED]


[REDACTED]

  loop:
  - "{{ vault_storage_dir }}"
  - "{{ vault_config_dir }}"
  - "{{ vault_logs_dir }}"
  - "{{ vault_tls_dir }}"
  tags: vault

- name: (Raft backup) Create directory for Raft snapshots (bind mount into container)
  ansible.builtin.file:
    path: "{{ vault_raft_local_backup_dir }}"
    state: directory
    owner: "{{ vault_raft_backup_uid }}"
    group: "{{ vault_raft_backup_gid }}"
    mode: "0750"
  when: vault_raft_backup_node | bool
  tags:
    - vault
    - raft_backup
    - docker

---
# Raft snapshot → S3 + textfile metrics (PKI CA / backup age). Секреты только на хосте:
# /etc/vault-raft-backup/vault.token, config.env, s3.credentials — не из Ansible.
- name: (Raft backup) Install vault-raft-backup script


[REDACTED]

  - "{{ vault_tls_dir }}"
  tags: vault

- name: (Vault) Generate vault config

  - "{{ vault_tls_dir }}"
  tags: vault

- name: (Raft backup) Create directory for Raft snapshots (bind mount into container)
  ansible.builtin.file:
    path: "{{ vault_raft_local_backup_dir }}"
    state: directory
    owner: "{{ vault_raft_backup_uid }}"
    group: "{{ vault_raft_backup_gid }}"
    mode: "0750"
  when: vault_raft_backup_node | bool
  tags:
    - vault
    - raft_backup
    - docker

- name: (Vault) Generate vault config

---
# Raft snapshot → S3 + textfile metrics (PKI CA / backup age). Секреты только на хосте:
# /etc/vault-raft-backup/vault.token, config.env, s3.credentials — не из Ansible.
- name: (Raft backup) Ensure local snapshot directory exists
  ansible.builtin.file:
    path: "{{ vault_raft_local_backup_dir }}"
    state: directory
    owner: "{{ vault_raft_backup_uid }}"
    group: "{{ vault_raft_backup_gid }}"
    mode: "0750"

- name: (Raft backup) Install vault-raft-backup script

---
# Raft snapshot → S3 + textfile metrics (PKI CA / backup age). Секреты только на хосте:
# /etc/vault-raft-backup/vault.token, config.env, s3.credentials — не из Ansible.
- name: (Raft backup) Install vault-raft-backup script


[REDACTED]

Write

---
# Primary Raft backup + PKI textfile node. Имя файла должно совпадать с
# inventory_hostname (или добавьте ту же переменную в свой host_vars).
vault_raft_backup_node: true


[REDACTED]

StrReplace

- Setup logrotate
- Optional: Raft snapshot backup cron, Vault token `renew-token` cron, PKI textfile metrics (`vault_raft_backup_node: true` in host_vars; секреты только на хосте в `/etc/vault-raft-backup/`)

Requirements

Shell

yamllint Ansible YAML files


[REDACTED]

StrReplace

  loop:
    - "{{ vault_storage_dir }}"
    - "{{ vault_config_dir }}"
    - "{{ vault_logs_dir }}"
    - "{{ vault_tls_dir }}"
  tags: vault

- name: (Raft backup) Create snapshot directory for Docker bind mount

  loop:
  - "{{ vault_storage_dir }}"
  - "{{ vault_config_dir }}"
  - "{{ vault_logs_dir }}"
  - "{{ vault_tls_dir }}"
  tags: vault

- name: (Raft backup) Create directory for Raft snapshots (bind mount into container)

---
# Raft snapshot → S3 + textfile metrics. Секреты на хосте:
# /etc/vault-raft-backup/vault.token, config.env, s3.credentials

---
# Raft snapshot → S3 + textfile metrics (PKI CA / backup age). Секреты только на хосте:
# /etc/vault-raft-backup/vault.token, config.env, s3.credentials — не из Ansible.

      */30 * * * * root /usr/local/sbin/vault-raft-backup \
        >> /var/log/vault-raft-backup.log 2>&1

*/30 * * * * root /usr/local/sbin/vault-raft-backup >> /var/log/vault-raft-backup.log 2>&1


Убираю обратный слэш из cron — он ломает crontab.

[REDACTED]


[REDACTED]

---
# yamllint disable rule:line-length
# Raft snapshot → S3 + textfile metrics. Секреты на хосте:
# /etc/vault-raft-backup/vault.token, config.env, s3.credentials

---
# Raft snapshot → S3 + textfile metrics. Секреты на хосте:
# /etc/vault-raft-backup/vault.token, config.env, s3.credentials

*/30 * * * * root /usr/local/sbin/vault-raft-backup >>/var/log/vault-raft-backup.log 2>&1

      */30 * * * * root /usr/local/sbin/vault-raft-backup \
        >> /var/log/vault-raft-backup.log 2>&1

      */15 * * * * root /usr/local/sbin/vault-raft-backup renew-token \
        >>/var/log/vault-raft-backup-renew.log 2>&1

*/15 * * * * root /usr/local/sbin/vault-raft-backup renew-token >> /var/log/vault-raft-backup-renew.log 2>&1


[REDACTED]

*/15 * * * * root /usr/local/sbin/vault-raft-backup renew-token >>/var/log/vault-raft-backup-renew.log 2>&1

      */15 * * * * root /usr/local/sbin/vault-raft-backup renew-token \
        >>/var/log/vault-raft-backup-renew.log 2>&1


[REDACTED]

Shell

Re-run yamllint


[REDACTED]

cd /home/ruslanpi/Documents/repos/lmru--devops-core--ansible-vault-oss/prod && ansible-playbook --syntax-check -i 'p-pki-as-01.lmru.tech,' playbook-deploy.yml 2>&1

Ansible syntax-check playbook-deploy


[REDACTED]

StrReplace

### 6.1. Частое продление backup-токена Vault (`renew-token`)

Полный snapshot и S3 — каждые **30 минут** (`/etc/cron.d/vault-raft-backup`). Дополнительно задайте лёгкий cron **`renew-token`** (например раз в **15 минут**), чтобы periodic token продлевался и не истекал из‑за серии частичных сбоев после шага renew. На ноде бинарь обычно **`/usr/local/sbin/vault-raft-backup`**; исходник — `scripts/vault-raft-backup.sh` в репозитории.

Развёртывание скриптов и обоих cron (включая **`vault-pki-textfile-metrics`**) можно взять из роли **`vault-oss-deploy`**: в **`prod/host_vars/<backup-host>.yml`** задайте **`vault_raft_backup_node: true`**, затем с хоста, где установлен Ansible:

```bash
cd prod
ansible-playbook -i '<inventory>' playbook-deploy.yml --tags raft_backup,vault,docker
```

(при необходимости включите полный прогон без `--tags`, чтобы обновился `docker-compose` с томом **`/vault/backups`**). Перед прогоном на хосте должны существовать **`/etc/vault-raft-backup/config.env`**, **`vault.token`**, **`s3.credentials`** — Ansible их не копирует.

```bash
sudo tee /etc/cron.d/vault-raft-backup-renew >/dev/null <<'EOF'


[REDACTED]

Полный snapshot и S3 — каждые **30 минут** (`/etc/cron.d/vault-raft-backup`). Дополнительно задайте лёгкий cron **`renew-token`** (например раз в **15 минут**), чтобы periodic token продлевался и не истекал из‑за серии частичных сбоев после шага renew. На ноде бинарь обычно **`/usr/local/sbin/vault-raft-backup`**; исходник — `scripts/vault-raft-backup.sh` в репозитории.

Тот же набор можно выкатить Ansible: **`prod/host_vars/<backup-host>.yml`** с **`vault_raft_backup_node: true`**, затем **`ansible-playbook prod/playbook-deploy.yml`** с тегами **`raft_backup`**, **`vault`**, **`docker`** (секреты **`/etc/vault-raft-backup/*`** по-прежнему только вручную на хосте).

```bash

Полный snapshot и S3 — каждые **30 минут** (`/etc/cron.d/vault-raft-backup`). Дополнительно задайте лёгкий cron **`renew-token`** (например раз в **15 минут**), чтобы periodic token продлевался и не истекал из‑за серии частичных сбоев после шага renew. На ноде бинарь обычно **`/usr/local/sbin/vault-raft-backup`**; исходник — `scripts/vault-raft-backup.sh` в репозитории.

```bash


Сделано в репозитории (токен на **`p-pki-as-01` в файл ты уже положил — его Ansible не трогает).

## Ansible: роль `vault-oss-deploy`

1. **`defaults/main.yml`** — флаг **`vault_raft_backup_node`** (по умолчанию `false`), пути **`/data/vault/backups`**, uid/gid `100`/`1000`.

2. **`tasks/main.yml`**
   - перед Docker создаётся каталог снапшотов при **`vault_raft_backup_node: true`**;
   - в конце подключается **`raft_backup.yml`**.

3. **`tasks/raft_backup.yml`** — копирует из репо **`scripts/vault-raft-backup.sh`** → `/usr/local/sbin/vault-raft-backup`, **`vault-pki-textfile-metrics.sh`** → `/usr/local/bin/`, выставляет cron:
   - `vault-raft-backup` каждые **30** мин;
   - **`vault-raft-backup renew-token`** каждые **15** мин;
   - `vault-pki-textfile-metrics` каждые **15** мин.

4. **`templates/docker-compose.yml.j2`** — при **`vault_raft_backup_node`** добавляется том **`{{ vault_raft_local_backup_dir }}:/vault/backups:rw`**.

5. **`prod/host_vars/p-pki-as-01.lmru.tech.yml`** — **`vault_raft_backup_node: true`**. Если в inventory другое **`inventory_hostname`**, переименуй файл или продублируй переменную.

6. **`roles/vault-oss-deploy/README.md`** — кратко про опцию backup.

7. **`docs/vault-pki-monitoring-runbook.md` §6.1** — ссылка на выкат через Ansible.

## Что выполнить у себя

С машины с Ansible (ключ/инвентарь как у вас в проде):

```bash
cd /path/to/lmru--devops-core--ansible-vault-oss/prod
ansible-playbook -i '<ваш_inventory>' playbook-deploy.yml --tags raft_backup,vault,docker
```

Так обновятся **`docker-compose`** (том для backups), скрипты и **все три cron**. Если нужен только софт без перезапуска compose:

```bash
ansible-playbook ... --tags raft_backup
```

После выката на ноде:

```bash
sudo /usr/local/sbin/vault-raft-backup
sudo tail -20 /var/log/vault-raft-backup.log
```

Убедись, что **`/etc/vault-raft-backup/vault.token`** — тот самый новый токен (**chmod 600**, root).

[REDACTED]


<user_query>
сходи по ssh на ноду и сделай все
</user_query>


Подключаюсь к ноде, копирую скрипты из репозитория, настраиваю cron и проверяю docker-compose.

[REDACTED]

Shell

SSH_USER=ansible
for U in ansible ansibe; do
  ssh -i /home/ruslanpi/.ssh/id_ed25519_devops_ansible -o BatchMode=yes -o ConnectTimeout=10 "${U}@p-pki-as-01.lmru.tech" 'echo OK-as-$U; id' 2>&1 && break
done

Test SSH user ansible vs ansibe on p-pki-as-01


[REDACTED]

scp -i /home/ruslanpi/.ssh/id_ed25519_devops_ansible -o BatchMode=yes \
  /home/ruslanpi/Documents/repos/lmru--devops-core--ansible-vault-oss/scripts/vault-raft-backup.sh \
  /home/ruslanpi/Documents/repos/lmru--devops-core--ansible-vault-oss/scripts/vault-pki-textfile-metrics.sh \
  ansible@p-pki-as-01.lmru.tech:/tmp/

SCP backup and textfile scripts to node /tmp


[REDACTED]

ssh -i /home/ruslanpi/.ssh/id_ed25519_devops_ansible -o BatchMode=yes ansible@p-pki-as-01.lmru.tech 'sudo -n bash -s' <<'REMOTE'
set -euo pipefail
install -d -m 0750 -o 100 -g 1000 /data/vault/backups
install -m 0755 /tmp/vault-raft-backup.sh /usr/local/sbin/vault-raft-backup
install -m 0755 /tmp/vault-pki-textfile-metrics.sh /usr/local/bin/vault-pki-textfile-metrics.sh
install -d -m 0755 /var/lib/node_exporter/textfile_collector

cat > /etc/cron.d/vault-raft-backup <<'EOF'
SHELL=/bin/bash
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin

*/30 * * * * root /usr/local/sbin/vault-raft-backup >>/var/log/vault-raft-backup.log 2>&1
EOF
chmod 0644 /etc/cron.d/vault-raft-backup

cat > /etc/cron.d/vault-raft-backup-renew <<'EOF'
SHELL=/bin/bash
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin

*/15 * * * * root /usr/local/sbin/vault-raft-backup renew-token >>/var/log/vault-raft-backup-renew.log 2>&1
EOF
chmod 0644 /etc/cron.d/vault-raft-backup-renew

cat > /etc/cron.d/vault-pki-textfile-metrics <<'EOF'
SHELL=/bin/bash
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin

*/15 * * * * root VAULT_ADDR="https://pki.lmru.tech" TEXTFILE_DIR="/var/lib/node_exporter/textfile_collector" /usr/local/bin/vault-pki-textfile-metrics.sh >>/var/log/vault-pki-textfile-metrics.log 2>&1
EOF
chmod 0644 /etc/cron.d/vault-pki-textfile-metrics

echo "--- docker-compose backups volume"
if grep -q '/vault/backups' /data/docker-compose.yml; then
  echo "already present"
else
  echo "adding volume mount"
  python3 - <<'PY'
import pathlib
p = pathlib.Path('/data/docker-compose.yml')
text = p.read_text()
needle = '      - {{ vault_tls_dir }}:/vault/tls:ro\n'
# file uses literal paths not jinja - read actual
PY
fi
grep -n vault /data/docker-compose.yml | head -20
REMOTE

Install scripts, dirs, crons on node via sudo


[REDACTED]

ssh -i /home/ruslanpi/.ssh/id_ed25519_devops_ansible -o BatchMode=yes ansible@p-pki-as-01.lmru.tech 'sudo -n /usr/local/sbin/vault-raft-backup 2>&1; echo exit:$?; sudo -n tail -5 /var/log/vault-raft-backup.log'

Run full vault-raft-backup once on node


[REDACTED]

ssh -i /home/ruslanpi/.ssh/id_ed25519_devops_ansible -o BatchMode=yes ansible@p-pki-as-01.lmru.tech <<'REMOTE'
set -e
echo '=== vault status (host docker)'
sudo -n docker exec vault vault status 2>&1 | head -25
echo '=== backups dir in container'
sudo -n docker exec vault ls -la /vault/backups 2>&1 | tail -8
echo '=== last log lines (raft-backup)'
sudo -n tail -15 /var/log/vault-raft-backup.log
REMOTE

Diagnose Vault role and backups dir in container


[REDACTED]

ssh -i /home/ruslanpi/.ssh/id_ed25519_devops_ansible -o BatchMode=yes ansible@p-pki-as-01.lmru.tech 'sudo -n stat /etc/vault-raft-backup/vault.token; sudo -n wc -c /etc/vault-raft-backup/vault.token'

Check vault.token file metadata on node (not contents)


[REDACTED]

ssh -i /home/ruslanpi/.ssh/id_ed25519_devops_ansible -o BatchMode=yes ansible@p-pki-as-01.lmru.tech 'sudo -n bash -c "read -r t < /etc/vault-raft-backup/vault.token; echo \"stripped_len:\${#t}\"; wc -l < /etc/vault-raft-backup/vault.token"'

Check token file line count and read length without printing token


[REDACTED]

ssh -i /home/ruslanpi/.ssh/id_ed25519_devops_ansible -o BatchMode=yes ansible@p-pki-as-01.lmru.tech 'curl -fsSk https://127.0.0.1:8200/v1/sys/health 2>&1 | head -c 400; echo'

Vault health via localhost:8200 on node


Сделано по SSH на **`p-pki-as-01.lmru.tech`** (пользователь **`ansible`**, sudo без пароля):

## Установлено

| Что | Куда |
|-----|------|
| `vault-raft-backup.sh` | `/usr/local/sbin/vault-raft-backup` (mode 0755) |
| `vault-pki-textfile-metrics.sh` | `/usr/local/bin/vault-pki-textfile-metrics.sh` (0755) |
| Каталог снапшотов | `/data/vault/backups` (0750, **100:1000**) |
| Каталог textfile | `/var/lib/node_exporter/textfile_collector` |
| Cron полный бэкап | `/etc/cron.d/vault-raft-backup` — `*/30` |
| Cron renew токена | `/etc/cron.d/vault-raft-backup-renew` — `*/15` |
| Cron метрик | `/etc/cron.d/vault-pki-textfile-metrics` — `*/15` |

В **`/data/docker-compose.yml`** том **`/data/vault/backups:/vault/backups:rw`** уже был — не менял.

## Бэкап сейчас не проходит

Один ручной запуск **`sudo /usr/local/sbin/vault-raft-backup`** дал:

1. Предупреждение Vault: **`period` 168h обрезается до effective **`max_ttl` = 1h** — в кластере жёсткий потолок TTL; для выдачи токена используй **`period=1h`** (продление дают **`renew`** и cron **каждые 15 мин**).
2. Ошибка snapshot: **`incomplete snapshot, unable to read SHA256SUMS.sealed file`** (отдельно разберите, если после починки токена повторится).
3. В **`/var/log/vault-raft-backup.log`** по-прежнему **`403` / `invalid token`** на `lookup-self` к **`https://p-pki-as-01.lmru.tech:8200`**.

Файл **`/etc/vault-raft-backup/vault.token`** сегодня обновлён (**~09:49 UTC**), одна строка, **95** символов без перевода строки — формат нормальный, но **Vault этот токен не принимает** (не тот кластер, отозван, выдан не под ту policy или не к тому `VAULT_ADDR`).

С моей стороны **создать рабочий токен в Vault нельзя** — нужен твой админский доступ.

## Что сделать тебе (один раз)

На машине с `vault` CLI:

```bash
export VAULT_ADDR='https://p-pki-as-01.lmru.tech:8200'   # как в config.env на ноде
vault login <админ>
vault token create -policy=root_policy_backup -period=1h -orphan -display-name=raft-backup
vault token lookup    # проверка, что подставленный токен живой
```

Затем на **p-pki-as-01**:

```bash
echo -n 'hvs....' | sudo tee /etc/vault-raft-backup/vault.token
sudo chmod 600 /etc/vault-raft-backup/vault.token
sudo /usr/local/sbin/vault-raft-backup
sudo tail -20 /var/log/vault-raft-backup.log
```

После успешного прогона появится новый **`.snap`** в **`/data/vault/backups`** и в Object Storage, алерт **Stale** уйдёт с задержкой до скрейпа.

Имеет смысл в **`docs/adr/architecture.md`** для примера заменить **`168h` на `1h`** под ваш `max_ttl`, чтобы не вводить в заблуждение — если нужно, могу следующим коммитом поправить текст.

[REDACTED]


<user_query>
админский доступ сделан
</user_query>


Проверяю на ноде, что бэкап проходит после обновления токена.

[REDACTED]

ssh -i /home/ruslanpi/.ssh/id_ed25519_devops_ansible -o BatchMode=yes -o ConnectTimeout=15 ansible@p-pki-as-01.lmru.tech 'sudo -n /usr/local/sbin/vault-raft-backup 2>&1; echo "exit:$?"; sudo -n tail -25 /var/log/vault-raft-backup.log; echo "---"; sudo -n ls -lt /data/vault/backups | head -5'

Run vault-raft-backup on node and show log + newest snaps


Токен, похоже, проходит lookup (иначе был бы exit 1); падает snapshot. Проверяю роль ноды и причину ошибки snapshot:

[REDACTED]

ssh -i /home/ruslanpi/.ssh/id_ed25519_devops_ansible -o BatchMode=yes ansible@p-pki-as-01.lmru.tech 'sudo -n curl -fsSk https://p-pki-as-01.lmru.tech:8200/v1/sys/leader 2>&1 | head -c 800; echo'

Vault leader API on p-pki-as-01

ssh -i /home/ruslanpi/.ssh/id_ed25519_devops_ansible -o BatchMode=yes ansible@p-pki-as-01.lmru.tech 'sudo -n bash -c "
read -r T < /etc/vault-raft-backup/vault.token
docker exec -e VAULT_ADDR=https://p-pki-as-01.lmru.tech:8200 -e VAULT_TOKEN=\"\$T\" vault vault status 2>&1
" | head -30'

vault status inside container with backup token


[REDACTED]

ssh -i /home/ruslanpi/.ssh/id_ed25519_devops_ansible -o BatchMode=yes ansible@p-pki-as-01.lmru.tech '
for url in "https://pki.lmru.tech/v1/sys/leader" "https://pki.lmru.tech:8200/v1/sys/leader"; do
  echo "=== $url"
  curl -fsSk "$url" 2>&1 | head -c 200 || echo FAIL
  echo
done
'

Test Vault leader API via LB hostnames from backup node


p-pki-as-01 — standby; лидер — p-pki-as-02. Snapshot с standby даёт ошибку. Переключаем `VAULT_ADDR` на VIP **`https://pki.lmru.tech`** (443 отвечает как лидер).

[REDACTED]

ssh -i /home/ruslanpi/.ssh/id_ed25519_devops_ansible -o BatchMode=yes ansible@p-pki-as-01.lmru.tech 'sudo -n grep -E "^VAULT_ADDR=" /etc/vault-raft-backup/config.env; sudo -n sed -i "s|^VAULT_ADDR=.*|VAULT_ADDR=\"https://pki.lmru.tech\"|" /etc/vault-raft-backup/config.env; echo "--- after ---"; sudo -n grep -E "^VAULT_ADDR=" /etc/vault-raft-backup/config.env'

Point vault-raft-backup VAULT_ADDR to pki.lmru.tech VIP


[REDACTED]

ssh -i /home/ruslanpi/.ssh/id_ed25519_devops_ansible -o BatchMode=yes ansible@p-pki-as-01.lmru.tech 'sudo -n /usr/local/sbin/vault-raft-backup 2>&1; echo exit:$?; sudo -n ls -lt /data/vault/backups | head -4; sudo -n tail -8 /var/log/vault-raft-backup.log'

Run backup after VAULT_ADDR fix


Бэкап прошёл (snap и загрузка в S3). Причина была в **standby**: в `config.env` стоял `VAULT_ADDR` на локальный `:8200`. Обновляю документацию в репозитории.

[REDACTED]


[REDACTED]

StrReplace

* **Бессрочного токена в Vault нет**: используется **periodic token** с флагом **`-orphan`**. Значение **`-period`** не должно превышать **effective `max_ttl`** кластера (в контуре PKI часто **`1h`** — иначе Vault обрежет period и выдаст предупреждение). Продление выполняют **`vault token renew`** в скрипте бэкапа и отдельный cron **`renew-token`** (например каждые **15 минут**).

* **Бессрочного токена в Vault нет**: используется **periodic token** с длинным `period` (рекомендуется **`168h`** семь суток) и флагом **`-orphan`**, чтобы токен переживал отзыв родительской сессии. Каждый успешный `vault token renew` продлевает срок на **`period`** (пока политика и лимиты кластера позволяют).
* Скрипт `/usr/local/sbin/vault-raft-backup` после проверки токена вызывает **`vault token renew`** перед snapshot; дополнительно на backup-ноде в **`/etc/cron.d/`** задаётся частый прогон **`/usr/local/sbin/vault-raft-backup renew-token`** (например каждые **15 минут**), чтобы ТTL не истекал, если полный backup временно падает на S3 или локальном диске.

vault token create \
  -policy=root_policy_backup \
  -period=1h \
  -orphan \
  -display-name='raft-backup-p-pki-as-01'

vault token create \
  -policy=root_policy_backup \
  -period=168h \
  -orphan \
  -display-name='raft-backup-p-pki-as-01'

sudo /usr/local/sbin/vault-raft-backup   # проверка: локальный .snap и загрузка в S3
```

В **`/etc/vault-raft-backup/config.env`** поле **`VAULT_ADDR`** должно указывать на **VIP кластера** (например **`https://pki.lmru.tech`**), а не на `https://<имя backup-ноды>:8200`. Snapshot Raft выполняется на **лидере**; если cron крутится на **standby**, запрос к локальному `:8200` даёт ошибку вида **`incomplete snapshot, unable to read SHA256SUMS.sealed file`**.

Токен в Git и в тикеты целиком не кладётся; хранение только **root:root, chmod 600** на ноде.

sudo /usr/local/sbin/vault-raft-backup   # проверка: локальный .snap и загрузка в S3
```

Токен в Git и в тикеты целиком не кладётся; хранение только **root:root, chmod 600** на ноде.


[REDACTED]

echo "  vault token create -policy=root_policy_backup -period=1h -orphan" >&2

echo "  vault token create -policy=root_policy_backup -period=168h -orphan" >&2


Кратко, что сделано.

## На ноде **`p-pki-as-01`**

Админский доступ сам по себе проблему не снимал: узел в **standby**, лидер — **`p-pki-as-02`**. В **`/etc/vault-raft-backup/config.env`** было **`VAULT_ADDR="https://p-pki-as-01.lmru.tech:8200"`**, из‑за этого **`vault operator raft snapshot save`** закономерно падал с **`incomplete snapshot...`**.

**Исправление:** выставлено **`VAULT_ADDR="https://pki.lmru.tech"`** (VIP на 443).

После этого ручной запуск **`vault-raft-backup`** завершился **успешно** (exit 0): свежий файл **`vault-raft-20260504T095612Z.snap`**, загрузка в **`s3://p-pki/p-pki-raft-backup/raft/`**. Хвост в логе с **`invalid token`** — от **старых** прогонов, к успешному запуску не относится.

Cron как раньше: полный бэкап раз в 30 мин, **`renew-token`** раз в 15 мин.

## В репозитории

- **`docs/adr/architecture.md`**: выдача токена с **`period=1h`** под ваш **`max_ttl`**, явное требование **`VAULT_ADDR` = VIP**, описание ошибки на standby.
- **`scripts/vault-raft-backup.sh`**: подсказка в stderr про **`period=1h`**.

Имеет смысл после скрейпа Prometheus проверить, что алерт **VaultObjectStorageBackupStale** гаснется. Если лидер сменится, **`pki.lmru.tech`** по-прежнему должен вести на активный узел — локальный `:8200` на backup-ноде для snapshot не использовать.

[REDACTED]


<user_query>
алерт погас, все ли ты сделал для надежности? все ли готово сейчас?
</user_query>


Кратко по честному чеклисту.

## Уже сделано и сейчас в порядке

| Область | Статус |
|--------|--------|
| **Off-site бэкап** | `VAULT_ADDR=https://pki.lmru.tech`, cron 30m + `renew-token` 15m, успешный snap + S3; алерт **Stale** погас — цепочка живая. |
| **Надёжность токена** | В ADR: `period=1h`, `orphan`, renew в скрипте + отдельный cron; кластерный `max_ttl` учтён. |
| **Standby vs leader** | В ADR зафиксировано: не ходить на локальный `:8200` с backup-ноды, если она standby. |
| **Репозиторий** | Обновлены `vault-raft-backup.sh`, `architecture.md`, runbook (Ansible §6.1, `renew-token`), роль `vault-oss-deploy` (скрипты + cron + том backups при следующих выкатах). |

То есть для **текущего** контура бэкап и мониторинг «свежести» S3 выглядят готовыми.

## Что ещё стоит добить для «надёжности в целом» (не блокеры того алерта)

1. **Дубли правил в Prometheus** (`owner: itsec` и `owner: devsecops` на одни и те же алерты) — если это ещё не убрано в кластере, в OnCall снова будут **двойные** уведомления. Имеет смысл оставить **один** набор правил с **`owner: devsecops`** для Vault PKI и убрать лишний `PrometheusRule`/релиз.
2. **Выравнять ноду с репо** — на `p-pki-as-01` уже руками всё поставлено; при следующем **`ansible-playbook`** с **`vault_raft_backup_node: true`** конфиг не перезатрёт **секреты**, но стоит убедиться, что в **инвентаре** совпадает `inventory_hostname` с `prod/host_vars/...`.
3. **Лидер меняется** — VIP `pki.lmru.tech` должен по-прежнему отдавать **active** на API; если когда‑нибудь переведёте бэкап **только** на прямой `VAULT_ADDR` без VIP, снова поймаете standby-ошибку.
4. **Периодический контроль** — раз в какое‑то время: `tail` **`vault-raft-backup.log`** и **`vault-raft-backup-renew.log`**, плюс один запрос в Prometheus по **`vault_pki_object_storage_latest_mtime_seconds`**.

## Итог

Для того чтобы **алерт погас и бэкапы шли стабильно** — да, ключевое сделано: **VIP + токен + cron + успешный прогон**.
Для **полной» надёжности продукта** логичный следующий шаг — **убрать дубль `vault_pki` правил в Prometheus**, если он ещё есть; остальное — регламент и наблюдение, а не дырки в том, что мы починили.

[REDACTED]


<user_query>
@docs/adr/architecture.md:307 @docs/adr/ADR-001-pki-architecture.md  пересмори эти 2 документа для архитекторов компании. Все ли соответствует действительности сейчас
</user_query>


Читаю оба документа целиком и сверяю с текущим состоянием.

[REDACTED]


Внося правки: исправляю опечатку `I[NIT-1793` и `развивтия`, дополняю разделы о backup (VIP, standby, renew) и синхронизирую ADR-001.

[REDACTED]

**Перспектива развивтия, пока не реализовано**:

**Перспектива развития, пока не реализовано**:

* если I[NIT-1793](https://tracker.yandex.ru/INIT-1793) затягивается — переходим на Shamir `2-of-3` с распределением ключей DevSecOps / ИБ / резерв в сейфе;

* если [INIT-1793](https://tracker.yandex.ru/INIT-1793) затягивается — переходим на Shamir `2-of-3` с распределением ключей DevSecOps / ИБ / резерв в сейфе;

* backup-token выпускается по policy `root_policy_backup`: только `sys/storage/raft/snapshot` (read), `auth/token/lookup-self`, `auth/token/renew-self`;
* **Бессрочного токена в Vault нет**: используется **periodic token** с флагом **`-orphan`**. Значение **`-period`** не должно превышать **effective `max_ttl`** кластера (в контуре PKI **`1h`**). Продление выполняют **`vault token renew`** в скрипте бэкапа и отдельный cron **`renew-token`** каждые **15 минут**.

* тест восстановления из snapshot проводится не реже одного раза в квартал.

* backup-token выпускается по policy `root_policy_backup`: только `sys/storage/raft/snapshot` (read), `auth/token/lookup-self`, `auth/token/renew-self`;
* **Бессрочного токена в Vault нет**: используется **periodic token** с флагом **`-orphan`**. Значение **`-period`** не должно превышать **effective `max_ttl`** кластера (в контуре PKI **`1h`**). Продление выполняют **`vault token renew`** в начале каждого полного бэкапа и отдельный cron **`/usr/local/sbin/vault-raft-backup renew-token`** каждые **15 минут** (файл **`/etc/cron.d/vault-raft-backup-renew`**).
* **`VAULT_ADDR` в `/etc/vault-raft-backup/config.env`** — **публичный VIP кластера** (**`https://pki.lmru.tech`**, API по TLS на порту, который обслуживает-балансировщик), **не** `https://<имя backup-узла>:8200`. Команда **`vault operator raft snapshot save`** должна выполняться от имени клиента к **активному (leader) узлу**; назначенная backup-нода кластера может находиться в **standby**, тогда обращение к «локальному» `:8200` на этой же машине даёт сбой вида **`incomplete snapshot, unable to read SHA256SUMS.sealed file`** без смены токена и сети.
* локальные snapshot-файлы создаются в каталоге на backup-ноде (**`/data/vault/backups`**) и монтируются в контейнер Vault как **`/vault/backups`**; конфигурация доставляется Ansible/`docker-compose` на backup-ноде.

* тест восстановления из snapshot проводится не реже одного раза в квартал.

Backup job запускается только на одной ноде, чтобы не создавать три одинаковых snapshot в час. Primary backup-нода — `p-pki-as-01.lmru.tech` в Yandex Cloud. При ее потере cron job переносится на одну из оставшихся нод по эксплуатационному runbook.

Расписание **`vault-raft-backup`** (полный snapshot + выгрузка в Object Storage) запускается **только на одной ноде**, чтобы не плодить три одинаковых snapshot с интервалом 30 минут. Текущая **primary backup-нода** — `p-pki-as-01.lmru.tech` (Yandex Cloud). **Сам процесс cron** привязан к этой машине, но **HTTP-клиент Vault в скрипте** всегда направлен на **VIP** `pki.lmru.tech` (см. выше), а не на loopback или «соседний» узел — иначе при смене лидера в Raft поведение остаётся корректным. При потере backup-ноды job **переносится** на одну из оставшихся по runbook; на новой ноде нужно те же файлы **`/etc/vault-raft-backup/*`**, те же cron-файлы и том **`/data/vault/backups:/vault/backups`** у контейнера Vault; **`VAULT_ADDR` по-прежнему VIP**.


[REDACTED]

* **`VAULT_ADDR` в `/etc/vault-raft-backup/config.env`** — **публичный VIP кластера** (**`https://pki.lmru.tech`**, в контуре — HTTPS балансировщика на **443**). **Не** задавать `https://<имя backup-узла>:8200` только потому, что cron выполняется на этом узле. Команда **`vault operator raft snapshot save`** должна идти к **лидеру**; назначенная backup-нода может быть в **standby**, тогда «локальный» `:8200` на этой машине даёт сбой вида **`incomplete snapshot, unable to read SHA256SUMS.sealed file`** без отдельной проблемы в сети или токене.

* **`VAULT_ADDR` в `/etc/vault-raft-backup/config.env`** — **публичный VIP кластера** (**`https://pki.lmru.tech`**, API по TLS на порту, который обслуживает-балансировщик), **не** `https://<имя backup-узла>:8200`. Команда **`vault operator raft snapshot save`** должна выполняться от имени клиента к **активному (leader) узлу**; назначенная backup-нода кластера может находиться в **standby**, тогда обращение к «локальному» `:8200` на этой же машине даёт сбой вида **`incomplete snapshot, unable to read SHA256SUMS.sealed file`** без смены токена и сети.


[REDACTED]

* Vault OSS хранит состояние в integrated Raft storage. Backup: raft snapshot по расписанию на одной назначенной ноде (`p-pki-as-01.lmru.tech` в Yandex Cloud), выгрузка в Yandex Object Storage `s3://p-pki/p-pki-raft-backup/raft/`. Интервал полного бэкапа — **30 минут**; отдельно каждые **15 минут** выполняется только **`vault token renew`** для backup-токена. Клиент Vault в скрипте бэкапа использует **`VAULT_ADDR=https://pki.lmru.tech`** (VIP), а не `:8200` на самой backup-ноде, чтобы snapshot не ломался, когда эта нода в **standby**. Фактический размер snapshot при небольшом объёме данных — около **590 КБ**.

* Vault OSS хранит состояние в integrated Raft storage. Backup настроен через Raft snapshots на ноде `p-pki-as-01.lmru.tech` в Yandex Cloud с выгрузкой в Yandex Object Storage: `s3://p-pki/p-pki-raft-backup/raft/`. Фактическое расписание backup job — каждые 30 минут, текущий размер snapshot при небольшом объёме данных — около 590 КБ.

7. **Эксплуатация Vault**:
   * Raft integrated storage, 3 узла (с возможностью расширения до 5);
   * полный Raft snapshot и загрузка в S3 — **каждые 30 минут** на одной назначенной backup-ноде; дополнительно cron **`renew-token`** — **каждые 15 минут** для продления periodic backup-токена (`max_ttl`/`period` в контуре — **1h**);
   * в **`/etc/vault-raft-backup/config.env`** задан **`VAULT_ADDR=https://pki.lmru.tech`**: snapshot-клиент обращается к кластеру через **VIP** (лидер), а не к `:8200` на локальной машине — иначе при **standby** на backup-ноде snapshot завершается ошибкой;
   * off-site хранение snapshots в Yandex Object Storage bucket `p-pki` по префиксу `p-pki-raft-backup/raft/`;

7. **Эксплуатация Vault**:
   * Raft integrated storage, 3 узла (с возможностью расширения до 5);
   * регулярные Raft snapshots каждые 30 минут на одной назначенной backup-ноде;
   * off-site хранение snapshots в Yandex Object Storage bucket `p-pki` по префиксу `p-pki-raft-backup/raft/`;

* состояние backup контролируется textfile-метриками `node_exporter`: свежесть локального snapshot, свежесть off-site копии в Object Storage и наличие самой S3-метрики; алерты по Vault PKI в Prometheus должны иметь **один источник правил** (без дубликатов с разными `owner`), иначе дублируются уведомления в OnCall;

* состояние backup контролируется textfile-метриками `node_exporter`: свежесть локального snapshot, свежесть off-site копии в Object Storage и наличие самой S3-метрики;

* Backup Vault проверен ручным запуском: snapshot создан локально и загружен в Yandex Object Storage; контрольное имя в документации — `vault-raft-20260428T145641Z.snap` (~591 КБ). В эксплуатации зафиксировано требование **`VAULT_ADDR` = VIP** (`pki.lmru.tech`) для устойчивости при **standby** на backup-ноде (весна 2026).
5. Описан DR-подход: restore из Raft snapshot, перенос backup job на другую ноду при потере `p-pki-as-01.lmru.tech` (**с тем же VIP в `config.env` и теми же cron-файлами**).

* Backup Vault проверен ручным запуском: snapshot создан локально и загружен в Yandex Object Storage. Контрольное значение после проверки — `vault-raft-20260428T145641Z.snap`, размер 591.28 КБ.


[REDACTED]

* Backup Vault проверен ручным запуском: snapshot создан локально и загружен в Yandex Object Storage; контрольное имя в документации — `vault-raft-20260428T145641Z.snap` (~591 КБ). В эксплуатации зафиксировано требование **`VAULT_ADDR` = VIP** (`pki.lmru.tech`) для устойчивости при **standby** на backup-ноде (весна 2026).

### Критерии приемки ADR

* Backup Vault проверен ручным запуском: snapshot создан локально и загружен в Yandex Object Storage; контрольное имя в документации — `vault-raft-20260428T145641Z.snap` (~591 КБ). В эксплуатации зафиксировано требование **`VAULT_ADDR` = VIP** (`pki.lmru.tech`) для устойчивости при **standby** на backup-ноде (весна 2026).
5. Описан DR-подход: restore из Raft snapshot, перенос backup job на другую ноду при потере `p-pki-as-01.lmru.tech` (**с тем же VIP в `config.env` и теми же cron-файлами**).

### Критерии приемки ADR

5. Описан DR-подход: restore из Raft snapshot; перенос backup job на другую ноду при потере `p-pki-as-01.lmru.tech` с сохранением **`VAULT_ADDR`** на VIP (`pki.lmru.tech`), тех же cron-файлов и каталога **`/data/vault/backups`** / тома **`/vault/backups`**.

5. Описан DR-подход: restore из Raft snapshot, перенос backup job на другую ноду при потере `p-pki-as-01.lmru.tech`.

* состояние backup контролируется textfile-метриками `node_exporter`: свежесть локального snapshot, свЕ freshness off-site копии в Object Storage и наличие самой S3-метрики; набор правил Prometheus для Vault PKI не должен **дублироваться** с разными `owner` (двойные алерты в OnCall);

* состояние backup контролируется textfile-метриками `node_exporter`: свежесть локального snapshot, свежесть off-site копии в Object Storage и наличие самой S3-метрики;


[REDACTED]

   * состояние backup контролируется textfile-метриками `node_exporter`: свежесть локального snapshot, off-site копии в Object Storage и наличие S3-метрики;
   * правила Prometheus для Vault PKI в проде не должны **дублироваться** с разными `owner` — иначе в OnCall уходят парные уведомления по одному событию;

* состояние backup контролируется textfile-метриками `node_exporter`: свежесть локального snapshot, свежесть off-site копии в Object Storage и наличие самой S3-метрики;


[REDACTED]

Grep

состояние backup


[REDACTED]

StrReplace

   * состояние backup контролируется textfile-метриками `node_exporter`: свежесть локального snapshot, свежесть off-site копии в Object Storage и наличие самой S3-метрики.

Детальное описание

   * состояние backup контролируется textfile-метриками `node_exporter`: свежесть локального snapshot, off-site копии в Object Storage и наличие S3-метрики;
   * правила Prometheus для Vault PKI не должны **дублироваться** с разными `owner` (двойные уведомления в OnCall).

Детальное описание


[REDACTED]

**Контроль backup**

Мониторинг backup проверяет:

* успешный exit code backup job;
* свежесть последнего локального snapshot на backup-ноде (сейчас **`p-pki-as-01.lmru.tech`**);
* появление нового объекта в Object Storage bucket `p-pki` по префиксу `p-pki-raft-backup/raft/`;
* размер snapshot как диагностическую метрику в Grafana; алерт по размеру не включается, потому что текущий валидный snapshot небольшого Vault имеет размер около 590 КБ;
* отсутствие ошибок при загрузке в Object Storage.

В Prometheus **один** активный источник правил для Vault PKI (без дубликатов с разными значениями `owner`), иначе одно событие порождает несколько групп алертов и дубли в Grafana OnCall.

**Контроль backup**

Мониторинг backup проверяет:

* успешный exit code backup job;
* свежесть последнего локального snapshot на `p-pki-as-01.lmru.tech`;
* появление нового объекта в Object Storage bucket `p-pki` по префиксу `p-pki-raft-backup/raft/`;
* размер snapshot как диагностическую метрику в Grafana; алерт по размеру не включается, потому что текущий валидный snapshot небольшого Vault имеет размер около 590 КБ;
* отсутствие ошибок при загрузке в Object Storage.


[REDACTED]

* Backup Vault Raft настроен на ноде `p-pki-as-01.lmru.tech` в Yandex Cloud: snapshot каждые 30 минут, отдельный cron продления backup-токена каждые 15 минут, локальное хранение и выгрузка в Yandex Object Storage; **`VAULT_ADDR` в скрипте — VIP `pki.lmru.tech`**, чтобы snapshot шёл к лидеру, если сама backup-нода в standby.

* Backup Vault Raft настроен на ноде `p-pki-as-01.lmru.tech` в Yandex Cloud: snapshot каждые 30 минут, локальное хранение на ноде и выгрузка в Yandex Object Storage.


Кратко, что **не совпадало с реальностью** и что **исправлено в двух документах**.

## `docs/adr/architecture.md`

| Было / риск | Сейчас в тексте |
|-------------|------------------|
| В §3.2.1 не было явно сказано, что **клиент бэкапа обязан ходить на VIP**, а не на `:8200` локальной ноды | Добавлено: **`VAULT_ADDR=https://pki.lmru.tech` (443)**, standby на backup-ноде и ошибка **`incomplete snapshot…`**. |
| Не было связки **renew-token каждые 15 мин** + путь **`/etc/cron.d/vault-raft-backup-renew`** | Зафиксировано в целевых параметрах. |
| Не хватало связки **файлы на диске** и том **`/vault/backups`** | Добавлено: **`/data/vault/backups`**, монтирование, Ansible/docker-compose. |
| Абзац про «одна нода для cron» мог читаться как «всё локально» | Переписан: cron **на одной** ноде, но **HTTP всегда на VIP**; при переносе job — те же секреты, cron и том, **`VAULT_ADDR` остаётся VIP**. |
| Опечатки **`развивтия`**, **`I[NIT-1793`** | Исправлено на **развития**, **INIT-1793**. |
| В **кратком обзоре** (строки 17–22) бэкап описан без VIP и без renew | Дополнено. |
| **Контроль backup**: только `p-pki-as-01` и дубли правил | Уточнено «текущая backup-нода» + **один источник правил Prometheus**, без дубликатов `owner`. |

## `docs/adr/ADR-001-pki-architecture.md`

| Изменение |
|-----------|
| **Контекст** и п. **7 «Эксплуатация Vault»**: интервал **30 мин**, cron **`renew-token` 15 мин**, **`VAULT_ADDR` = VIP**, **`period`/`max_ttl` 1h**. |
| **Мониторинг**: отдельный пункт про **отсутствие дублирования** правил Vault PKI с разными **`owner`**. |
| **Соответствие / ИБ**: факт про **VIP** при standby (весна 2026). |
| **Критерий 5 (DR)**: перенос job с сохранением **VIP**, **cron**, **`/data/vault/backups`** и тома **`/vault/backups`**. |
| Исправлена вставка, которая случайно ломала структуру раздела (лишняя строка «5. Описан DR…» внутри списка). |

## Что по-прежнему «на усмотрение / не в этих файлах»

* **Три площадки узлов** (YC, X1, X2) в §3.3 — это **целевая** топология; фактическое размещение в проде может отличаться — в документе это не менял (это не противоречие бэкапу/VIP).
* Внешняя **wiki**-ссылка в ADR ведёт на другой хост (`wiki.devops.lmru.tech`); синхронизация копии на wiki — отдельная задача публикации.

Итого для **архитекторов**: описание бэкапа, HA standby, токена и мониторинга **приведено в соответствие с текущей эксплуатацией** (VIP, cron renew, том, DR-перенос, анти-дубли правил).

[REDACTED]


<user_query>
у меня в этих документах упоминаются runbook, DR но на самом деле их нет, надо сделать
</user_query>


Ищем упоминания runbook и DR и просматриваем существующую структуру `docs/`.

[REDACTED]

Grep

runbook|DR|восстановлен|перенос backup


Создаю два runbook'а и добавляю ссылки из `architecture.md` и `ADR-001-pki-architecture.md`.

[REDACTED]

Write

# Операционный runbook: Vault PKI (backup, токены, перенос job)

Назначение: ежедневные и аварийные действия по **backup Raft** и смежным компонентам контура `pki.lmru.tech`.
**DR / полное восстановление кластера** — в отдельном документе: [vault-pki-dr-runbook.md](vault-pki-dr-runbook.md).

## 1. Контекст (что где лежит)

| Компонент | Где | Примечание |
|-----------|-----|------------|
| Primary backup-нода | `p-pki-as-01.lmru.tech` | Cron только на **одной** ноде. |
| Полный snapshot + S3 | `/usr/local/sbin/vault-raft-backup` | Cron: `/etc/cron.d/vault-raft-backup` (**каждые 30 мин**). |
| Продление backup-токена | `vault-raft-backup renew-token` | Cron: `/etc/cron.d/vault-raft-backup-renew` (**каждые 15 мин**). |
| Секреты бэкапа | `/etc/vault-raft-backup/` | `config.env`, `vault.token`, `s3.credentials` — **не в Git**. |
| `VAULT_ADDR` | В `config.env` | Должен быть **VIP**: `https://pki.lmru.tech` (не `:8200` локальной ноды). |
| Локальные `.snap` | `/data/vault/backups` | Том в контейнере: `/vault/backups`. |
| Object Storage | `s3://p-pki/p-pki-raft-backup/raft/` | Yandex Object Storage. |
| Textfile-метрики PKI | `/usr/local/bin/vault-pki-textfile-metrics.sh` | Cron: `/etc/cron.d/vault-pki-textfile-metrics`. |

Исходники скриптов в репозитории: `scripts/vault-raft-backup.sh`, `scripts/vault-pki-textfile-metrics.sh`.
Выкат через Ansible: роль `vault-oss-deploy`, переменная `vault_raft_backup_node: true` в `host_vars` backup-ноды.

## 2. Алерт: off-site backup устарел / нет метрики S3

**Симптомы:** `VaultObjectStorageBackupStale`, `VaultObjectStorageBackupMetricsMissing` в Prometheus / OnCall.

**Проверки по порядку:**

1. **Silences** в Alertmanager — нет ли маски на сервис.

2. На **backup-ноде** (см. §1):
   ```bash
   sudo tail -80 /var/log/vault-raft-backup.log
   sudo ls -lt /data/vault/backups | head
   sudo grep '^VAULT_ADDR=' /etc/vault-raft-backup/config.env
   ```
   Должно быть: `VAULT_ADDR="https://pki.lmru.tech"`.

3. **Токен:** если в логе `invalid token` / `403` на `lookup-self`:
   - выдать новый periodic token (`-policy=root_policy_backup`, `-period=1h`, `-orphan`), см. `docs/adr/architecture.md` §3.2.1;
   - записать в `/etc/vault-raft-backup/vault.token` (`0600`, root);
   - `sudo /usr/local/sbin/vault-raft-backup` вручную.

4. **Ошибка snapshot** `incomplete snapshot, unable to read SHA256SUMS.sealed`:
   - почти всегда **неверный `VAULT_ADDR`** (standby на backup-ноде) — вернуть VIP `pki.lmru.tech`.

5. **S3 / сеть:** ручной `aws s3 ls` с credentials из `config.env` (endpoint Yandex).

6. **Cron:** `ls -la /etc/cron.d/vault-raft-backup*`.

## 3. Ротация backup-токена (планово / после компрометации)

1. `VAULT_ADDR=https://pki.lmru.tech`, админский доступ к Vault.
2. `vault token create -policy=root_policy_backup -period=1h -orphan -display-name=raft-backup-<нода>`.
3. На backup-ноде: заменить содержимое `vault.token`, `chmod 600`.
4. `sudo /usr/local/sbin/vault-raft-backup` — успех и новый объект в S3.
5. Убедиться, что cron **renew-token** активен (иначе TTL истечёт за пределы окна).

## 4. Перенос backup job на другую ноду

**Когда:** недоступна или выводится из эксплуатации текущая primary (сейчас `p-pki-as-01`).

**Цель:** на **одной** ноде кластера остаётся полный набор cron + секреты + том `/vault/backups`; на **старой** ноде job отключается, чтобы не было двойной выгрузки.

### 4.1. Предусловия

* Выбрана целевая нода (например `p-pki-as-02` или `p-pki-as-03`).
* На ней установлен Docker **Vault**, в `docker-compose` есть монтирование **`/data/vault/backups:/vault/backups:rw`**.
* SSH/sudo для выполнения шагов.

### 4.2. Шаги

1. **Старая нода:** отключить cron (переименовать или удалить):
   - `/etc/cron.d/vault-raft-backup`
   - `/etc/cron.d/vault-raft-backup-renew`
   - при необходимости `/etc/cron.d/vault-pki-textfile-metrics` (если textfile только на одной ноде — перенести вместе с backup).

2. **Новая нода:**
   - создать каталоги: `install -d -m 0750 -o 100 -g 1000 /data/vault/backups`, textfile dir при необходимости;
   - скопировать **защищённо** с старой ноды: `/etc/vault-raft-backup/config.env`, `vault.token`, `s3.credentials` (права **0600** на файлы с секретами);
   - убедиться, что в `config.env` **`VAULT_ADDR=https://pki.lmru.tech`**;
   - установить скрипты (из репозитория или Ansible): `/usr/local/sbin/vault-raft-backup`, `/usr/local/bin/vault-pki-textfile-metrics.sh`;
   - установить те же **cron**-файлы, что в `roles/vault-oss-deploy/tasks/raft_backup.yml` или копией со старой ноды (`chmod 0644`).

3. Ручной прогон: `sudo /usr/local/sbin/vault-raft-backup` — появляется `.snap` локально и объект в S3.

4. Обновить **документацию** (`architecture.md`, этот runbook): имя primary backup-ноды.

5. Через **до 30 мин** проверить метрики `vault_pki_object_storage_latest_mtime_seconds` и гашение алертов.

## 5. Textfile-метрики CA / backup пропали в Prometheus

1. На ноде с textfile-cron:
   ```bash
   sudo cat /var/lib/node_exporter/textfile_collector/vault_pki.prom | head
   sudo tail -30 /var/log/vault-pki-textfile-metrics.log
   ```
2. После ребута Vault API может быть кратко недоступен — дождаться следующего cron (**15 мин**) или запустить скрипт вручную с `VAULT_ADDR=https://pki.lmru.tech`.
3. `node_textfile_scrape_error` в Prometheus — проверить синтаксис `.prom` и права каталога.

## 6. Дубли алертов в OnCall (две карточки на одно событие)

Причина: в Prometheus одновременно раздаются правила с **одинаковыми** `alertname`, но разным **`owner`** (например `itsec` и `devsecops`).

**Действие:** в репозитории правил (**`lmru--promaas--itsec`**) оставить **один** источник `vault_pki_alerts` с целевым `owner` (например `devsecops`); удалить дубликат `PrometheusRule` в кластере после согласования с владельцами маршрутизации.

## 7. Эскалация

При невозможности восстановить backup в разумный срок или при подозрении на компрометацию секретов / Vault — эскалировать **DevSecOps** / дежурного по контуру и при необходимости открыть инцидент ИБ. Полная потеря кластера — [vault-pki-dr-runbook.md](vault-pki-dr-runbook.md).

# DR runbook: восстановление Vault PKI из Raft snapshot

Назначение: сценарии восстановления **HashiCorp Vault OSS** с integrated **Raft** после потери узлов или площадки.
Контур: **PKI** `pki.lmru.tech`, bucket **`s3://p-pki/p-pki-raft-backup/raft/`**.

Повседневные сбои backup без потери кластера — [vault-pki-operations-runbook.md](vault-pki-operations-runbook.md).

## 0. Определения и цели

| Параметр | Целевое значение (из архитектуры) |
|----------|-----------------------------------|
| RPO | ≤ 30 минут (частота успешных snapshot в S3). |
| RTO | Согласуется с бизнесом; зависит от готовности площадки и процедуры unseal. |

**Типовые объекты:**

* Файлы snapshot: `vault-raft-YYYYMMDDTHHMMSSZ.snap`.
* Актуальный snapshot брать из **Object Storage** (последний по времени объекта), не полагаться только на локальный диск при потере ноды.

## 1. Предусловия перед любым восстановлением

1. **Шамбир / unseal:** наличие ключей или рабочей процедуры auto-unseal (после **INIT-1793** — по регламенту миграции). Без unseal кластер после restore останется sealed.
2. **TLS:** сертификаты и ключи для Vault listener (как в действующем `docker-compose` / Ansible).
3. **Конфиг:** `config.hcl` (storage raft, listeners, `api_addr`, `cluster_addr`, telemetry).
4. **Версия Vault:** желательно **та же** patch-версия, что при создании snapshot (избегать major downgrade).
5. **Сеть:** IP/DNS узлов и **VIP** `pki.lmru.tech` — план после восстановления (тот же или переключение).

## 2. Сценарий A: потерян один узел Raft, кворум сохранён

**Признак:** один хост не отвечает, **лидер и два узла** живы, `vault operator raft list-peers` с рабочей ноды показывает проблемный peer.

**Действия (обзорно):**

1. Заменить ВМ/железо, восстановить **тот же hostname** и IP **или** выполнить процедуру **remove peer + join** согласно [документации HashiCorp Raft](https://developer.hashicorp.com/vault/docs/concepts/integrated-storage) (remove failed node, add new with `vault operator raft join`).
2. **Не** выполнять полный snapshot restore на выживший кластер — это уничтожит текущие данные.
3. После ввода узла в кластер — health-check по каждому узлу, тест выпуска тестового сертификата в `pki-stage`.

Детали команд — в vendor runbook команды; фиксировать шаги в тикете инцидента.

## 3. Сценарий B: потерян кворум / все узлы кластера (восстановление из snapshot)

**Когда:** несколько узлов уничтожены, кластер не выбирает лидера, данные на дисках недоступны или недоверяемы.

**Принцип:** развернуть **новый** (или очищенный) набор узлов, **один** раз восстановить состояние из **послегнего валидного** snapshot в **пустое** хранилище Raft на первом узле, затем **присоединить** остальные узлы как новые члены Raft **или** следовать выбранной схеме (single-node recovery с последующим scale-out — согласовать с HashiCorp best practices для вашей версии).

Ниже — **упрощённый ориентир**; перед применением свериться с актуальной документацией Vault для вашей версии и провести репетицию на stage.

### 3.1. Подготовка snapshot

```bash
# Пример: скачать последний объект из Yandex Object Storage (учётные данные — отдельно)
aws --endpoint-url https://storage.yandexcloud.net s3 ls s3://p-pki/p-pki-raft-backup/raft/ --recursive | sort | tail -5
aws --endpoint-url https://storage.yandexcloud.net s3 cp \
  s3://p-pki/p-pki-raft-backup/raft/vault-raft-XXXXXXXXXXXXXXZ.snap \
  ./vault-raft-restore.snap
```

Проверить размер (ожидаемо ~0,5–1 МБ для малого PKI). При сомнении выбрать снимок **до** момента повреждения данных.

### 3.2. Остановка Vault на восстанавливаемой первой ноде

* Остановить контейнер/сервис Vault на узле, который будет **первым** после DR.
* **Удалить/очистить** каталог данных Raft (`vault_storage_dir`, например `/data/vault/raft` на хосте), чтобы restore шёл в пустое хранилище. **Не** стирать резервные копии snapshot и не трогать другие узлы, если они ещё используются для копирования артефактов.

### 3.3. Restore snapshot (типовая последовательность)

На **пустом** data dir (конкретные флаги зависят от версии Vault):

```bash
# Пример; уточнить: vault operator raft snapshot restore -h
vault operator raft snapshot restore /path/to/vault-raft-restore.snap
```

Часто restore выполняется **до** первого `vault server` или через офлайн-команду — **обязательно** сверить с документацией версии **1.21.x** (или актуальной у вас).

### 3.4. Запуск и unseal

1. Запустить Vault с корректным `config.hcl` и путём к восстановленному Raft.
2. Выполнить **unseal** (Shamir или auto-unseal).
3. `vault status` — **Initialized**, **Sealed: false**, при HA далее снова формируется кластер.

### 3.5. Восстановление HA (несколько узлов)

После того как **первый** узел в полном порядке (active/leader):

* Добавить **остальные** узлы по процедуре **Raft join** (новые peer ID, если железо новое).
* Убедиться, что **все** узлы в `vault operator raft list-peers` healthy.

### 3.6. Пост-валидация

| Проверка | Как |
|----------|-----|
| API VIP | `curl -sk https://pki.lmru.tech/v1/sys/health` |
| PKI | Тестовый `sign` в `pki-stage` под тестовой policy / ops |
| Мониторинг | Метрики `vault_core_unsealed`, `vault_autopilot_healthy` |
| Backup | Заново убедиться в работе `vault-raft-backup` и **не забыть** `VAULT_ADDR=https://pki.lmru.tech` |

### 3.7. Коммуникации

* Обновить DNS/LB только по согласованному плану.
* Уведомить потребителей PKI (Kafka `P0239` и др.) о возможной необходимости перезапуска клиентов при смене интеграции.

## 4. Сценарий C: потеря только backup-ноды (кластер Vault жив)

Кластер **не** восстанавливать из snapshot. Действовать по [vault-pki-operations-runbook.md §4](vault-pki-operations-runbook.md#4-перенос-backup-job-на-другую-ноду).

## 5. Квартальный тест DR (обязательный контроль)

**Цель:** подтвердить, что команда может восстановить **читаемое** состояние из S3 snapshot в **изолированной** среде (не прод).

**Минимум:**

1. Выделить тестовую ВМ или namespace, **не** подключённый к прод VIP.
2. Скачать snapshot из S3 (актуальный на дату теста).
3. Выполнить restore в пустое хранилище, unseal **тестовыми** ключами (если используется копия с прод — соблюдать политику ИБ; предпочтительно отдельный **тестовый** snapshot и кластер).
4. Проверить `vault read` на существование mount `pki-stage` и тестовый запрос к CA.
5. Зафиксировать дату, участников, номер тикета, отклонения от плана.

Результаты хранить не менее двух лет согласно внутренним требованиям (уточнить у комплаенса).

## 6. Ограничения Vault OSS

* **Нет** встроенной DR-replication — единственный переносимый «сейвпойнт» для полного кластера в типовом контуре — **snapshot**.
* RTO упирается в готовность инфраструктуры, ключей unseal и навыка команды.
* Любое отклонение от этого runbook при инциденте фиксировать в постмортеме.

## 7. Ссылки

* Архитектура PKI: `docs/adr/architecture.md`
* ADR: `docs/adr/ADR-001-pki-architecture.md`
* Операции backup: [vault-pki-operations-runbook.md](vault-pki-operations-runbook.md)
* Мониторинг setup: `docs/vault-pki-monitoring-runbook.md`


Добавляю ссылки в `architecture.md` и `ADR-001-pki-architecture.md`.

[REDACTED]

Grep

runbook|документирован|DR

DR|восстановлен|runbook


[REDACTED]


[REDACTED]

StrReplace

* Vault OSS не поддерживает DR replication, поэтому RTO зависит от процедуры restore и готовности площадки восстановления;

* Vault OSS не поддерживает DR replication, поэтому RTO зависит от процедуры restore и готовности площадки восстановления (см. [DR runbook](../runbooks/vault-pki-dr-runbook.md));

При потере backup-ноды job **переносится** на одну из оставшихся по runbook;

При потере backup-ноды job **переносится** на одну из оставшихся по [операционному runbook](../runbooks/vault-pki-operations-runbook.md#4-перенос-backup-job-на-другую-ноду);

* DR — через регулярные snapshots и документированную процедуру восстановления в резервной площадке.

* DR — через регулярные snapshots и процедуру восстановления в резервной площадке ([DR runbook](../runbooks/vault-pki-dr-runbook.md)).

   * квартальные тесты восстановления;
   * DR — через snapshot + restore (performance/DR replication недоступны в OSS).

   * квартальные тесты восстановления ([DR runbook](../runbooks/vault-pki-dr-runbook.md#5-квартальный-тест-dr-обязательный-контроль));
   * DR — через snapshot + restore (performance/DR replication недоступны в OSS; детали в [DR runbook](../runbooks/vault-pki-dr-runbook.md)).

5. Описнан DR-подход: restore из Raft snapshot; перенос backup job на другую ноду при потере `p-pki-as-01.lmru.tech` с сохранением **`VAULT_ADDR`** на VIP (`pki.lmru.tech`), тех же cron-файлов и каталога **`/data/vault/backups`** / тома **`/vault/backups`**.


[REDACTED]

5. Описан DR-подход: restore из Raft snapshot; перенос backup job на другую ноду при потере `p-pki-as-01.lmru.tech` с сохранением **`VAULT_ADDR`** на VIP (`pki.lmru.tech`), тех же cron-файлов и каталога **`/data/vault/backups`** / тома **`/vault/backups`**.

5. Описан DR-подход: restore из Raft snapshot; перенос backup job на другую ноду при потере `p-pki-as-01.lmru.tech` с сохранением **`VAULT_ADDR`** на VIP (`pki.lmru.tech`), тех же cron-файлов и каталога **`/data/vault/backups`** / тома **`/vault/backups`**. Процедуры зафиксированы в [операционном runbook](../runbooks/vault-pki-operations-runbook.md) и [DR runbook](../runbooks/vault-pki-dr-runbook.md).

Документ для ручной настройки мониторинга текущего Vault PKI `pki.lmru.tech`.

Текущие вводные:

Документ для ручной настройки мониторинга текущего Vault PKI `pki.lmru.tech`.

**См. также:** эксплуатация backup / перенос job — [runbooks/vault-pki-operations-runbook.md](runbooks/vault-pki-operations-runbook.md); восстановление кластера (DR) — [runbooks/vault-pki-dr-runbook.md](runbooks/vault-pki-dr-runbook.md).

Текущие вводные:


Убираю якоря с кириллицей — на разных рендерерах Markdown они часто ломаются.

[REDACTED]

При потере backup-ноды job **переносится** на одну из оставшихся по [операционному runbook](../runbooks/vault-pki-operations-runbook.md);

При потере backup-ноды job **переносится** на одну из оставшихся по [операционному runbook](../runbooks/vault-pki-operations-runbook.md#4-перенос-backup-job-на-другую-ноду);

* квартальные тесты восстановления ([DR runbook](../runbooks/vault-pki-dr-runbook.md));

* квартальные тесты восстановления ([DR runbook](../runbooks/vault-pki-dr-runbook.md#5-квартальный-тест-dr-обязательный-контроль));


[REDACTED]

Кластер **не** восстанавливать из snapshot. Действовать по [операционному runbook](vault-pki-operations-runbook.md) (раздел 4).

Кластер **не** восстанавливать из snapshot. Действовать по [vault-pki-operations-runbook.md §4](vault-pki-operations-runbook.md#4-перенос-backup-job-на-другую-ноду).


[REDACTED]

* **DR / Performance replication**: не поддерживаются. DR реализуется через snapshot + restore ([DR runbook](../runbooks/vault-pki-dr-runbook.md)).

* **DR / Performance replication**: не поддерживаются. DR реализуется через snapshot + restore.


Кратко, что сделано:

## Новые документы

1. **`docs/runbooks/vault-pki-operations-runbook.md`** — операционный runbook: таблица путей и cron, алерты stale S3, ротация backup-токена, **перенос backup job** на другую ноду, textfile-метрики, дубли алертов в OnCall, эскалация. На DR ведёт отдельный файл.

2. **`docs/runbooks/vault-pki-dr-runbook.md`** — DR: RPO/RTO, предусловия (unseal, TLS, версия Vault), сценарии **A** (одна нода, кворум жив), **B** (полное восстановление из snapshot, S3, пост-валидация), **C** (только backup-нода → ссылка на операции), **квартальный тест DR**, ограничения OSS, ссылки на архитектуру и мониторинг.

В DR для `vault operator raft snapshot restore` оставлено явное требование свериться с `vault operator raft snapshot restore -help` и документацией вашей версии Vault — поведение офлайн/онлайн от версии зависит.

## Обновлённые ссылки

- **`docs/adr/architecture.md`** — RTO/restore, перенос job после потери backup-ноды, пункт про DR в §3.5, а также строка про snapshot+restore в §3.2 ведут на соответствующие runbook’и.
- **`docs/adr/ADR-001-pki-architecture.md`** — квартальные тесты, DR в п.7, критерий приёмки п.5 с явными ссылками на оба runbook’а.
- **`docs/vault-pki-monitoring-runbook.md`** — в начале блок «См. также» с путями к операциям и DR, чтобы не путать с мониторингом.

Внутри runbook’ов перекрёстные ссылки без кириллических якорей, чтобы не ломаться в разных рендерерах.

[REDACTED]


<user_query>
переделай с учетом того, что развернуто вручную, а не ансиблом
</user_query>


Ищем упоминания Ansible в runbook’ах и приводим формулировки к ручному развёртыванию.

[REDACTED]


Проверяю мониторинговый runbook на примеры cron — их можно указать как эталон при ручной установке.

[REDACTED]


Обновляю оба runbook’а и мониторинговый документ: убираем Ansible, добавляем явные шаги ручной установки и пример основного cron для snapshot.

[REDACTED]

Исходники скриптов в репозитории: `scripts/vault-raft-backup.sh`, `scripts/vault-pki-textfile-metrics.sh`.
Выкат через Ansible: роль `vault-oss-deploy`, переменная `vault_raft_backup_node: true` в `host_vars` backup-ноды.

Исходники скриптов в репозитории: `scripts/vault-raft-backup.sh`, `scripts/vault-pki-textfile-metrics.sh`.

**Развёртывание — вручную:** Vault (Docker / `docker-compose`), каталоги данных и тома, `config.hcl`, а также скрипты backup и файлы в `/etc/cron.d/*` ставятся **на хостах**, без Ansible. Репозиторий даёт **эталонные** скрипты и документацию; при обновлении скрипта — скопировать новую версию на сервер (`install -o root -g root -m 0755`). Шаблоны cron (полный snapshot, `renew-token`, textfile) — в [vault-pki-monitoring-runbook.md](../vault-pki-monitoring-runbook.md) (§6 и §6.1).

   - установить скрипты (из репозитория или Ansible): `/usr/local/sbin/vault-raft-backup`, `/usr/local/bin/vault-pki-textfile-metrics.sh`;
   - установить те же **cron**-файлы, что в `roles/vault-oss-deploy/tasks/raft_backup.yml` или копией со старой ноды (`chmod 0644`);

   - установить скрипты с эталона в репозитории: `scripts/vault-raft-backup.sh` → `/usr/local/sbin/vault-raft-backup`, `scripts/vault-pki-textfile-metrics.sh` → `/usr/local/bin/vault-pki-textfile-metrics.sh` (права `0755`, владелец root);
   - установить те же **cron**-файлы **копией со старой backup-ноды** или по образцу в [vault-pki-monitoring-runbook.md](../vault-pki-monitoring-runbook.md) §6 / §6.1 (`chmod 0644`);

2. **TLS:** сертификаты и ключи для Vault listener (как в действующем `docker-compose` / Ansible).

2. **TLS:** сертификаты и ключи для Vault listener (как в действующем **ручном** развёртывании: тома в `docker-compose`, пути в `config.hcl`).

Полный snapshot и S3 — каждые **30 минут** (`/etc/cron.d/vault-raft-backup`). Дополнительно задайте лёгкий cron **`renew-token`** (например раз в **15 минут**), чтобы periodic token продлевался и не истекал из‑за серии частичных сбоев после шага renew. На ноде бинарь обычно **`/usr/local/sbin/vault-raft-backup`**; исходник — `scripts/vault-raft-backup.sh` в репозитории.

Тот же набор можно выкатить Ansible: **`prod/host_vars/<backup-host>.yml`** с **`vault_raft_backup_node: true`**, затем **`ansible-playbook prod/playbook-deploy.yml`** с тегами **`raft_backup`**, **`vault`**, **`docker`** (секреты **`/etc/vault-raft-backup/*`** по-прежнему только вручную на хосте).

```bash
sudo tee /etc/cron.d/vault-raft-backup-renew >/dev/null <<'EOF'

Полный snapshot и S3 — каждые **30 минут** (`/etc/cron.d/vault-raft-backup`). Дополнительно задайте лёгкий cron **`renew-token`** (например раз в **15 минут**), чтобы periodic token продлевался и не истекал из‑за серии частичных сбоев после шага renew. На ноде бинарь обычно **`/usr/local/sbin/vault-raft-backup`**; исходник — `scripts/vault-raft-backup.sh` в репозитории (скопировать на хост вручную, `chmod 0755`). В контуре выкат **только вручную**; секреты **`/etc/vault-raft-backup/*`** на сервере не хранятся в Git.

Основной cron полного бэкапа:

```bash
sudo tee /etc/cron.d/vault-raft-backup >/dev/null <<'EOF'
SHELL=/bin/bash
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin

*/30 * * * * root /usr/local/sbin/vault-raft-backup >>/var/log/vault-raft-backup.log 2>&1
EOF
sudo chmod 0644 /etc/cron.d/vault-raft-backup
```

Cron **`renew-token`**:

```bash
sudo tee /etc/cron.d/vault-raft-backup-renew >/dev/null <<'EOF'


[REDACTED]

   - установить скрипты с эталона в репозитории: `scripts/vault-raft-backup.sh` → `/usr/local/sbin/vault-raft-backup`, `scripts/vault-pki-textfile-metrics.sh` → `/usr/local/bin/vault-pki-textfile-metrics.sh` (права `0755`, владелец root);
   - установить те же **cron**-файлы **копией со старой backup-ноды** или по образцу в [vault-pki-monitoring-runbook.md](../vault-pki-monitoring-runbook.md) §6 / §6.1 (`chmod 0644`);

- установить те же **cron**-файлы, что в `roles/vault-oss-deploy/tasks/raft_backup.yml` или копией со старой ноды (`chmod 0644`);


[REDACTED]

   - установить скрипты (из репозитория или Ansible): `/usr/local/sbin/vault-raft-backup`, `/usr/local/bin/vault-pki-textfile-metrics.sh`;
   - установить те же **cron**-файлы, что в `roles/vault-oss-deploy/tasks/raft_backup.yml` или копией со старой ноды (`chmod 0644`).

   - установить скрипты с эталона в репозитории: `scripts/vault-raft-backup.sh` → `/usr/local/sbin/vault-raft-backup`, `scripts/vault-pki-textfile-metrics.sh` → `/usr/local/bin/vault-pki-textfile-metrics.sh` (права `0755`, владелец root);
   - установить те же **cron**-файлы **копией со старой backup-ноды** или по образцу в [vault-pki-monitoring-runbook.md](../vault-pki-monitoring-runbook.md) §6 / §6.1 (`chmod 0644`).


[REDACTED]

### 6.1. Cron: полный snapshot (S3) и продление backup-токена (`renew-token`)

### 6.1. Частое продление backup-токена Vault (`renew-token`)


Вот что изменено под **ручное** развёртывание.

### `docs/runbooks/vault-pki-operations-runbook.md`
- Вместо Ansible — явно: Docker / `docker-compose`, `config.hcl`, каталоги и **ручное** копирование скриптов из репозитория (`install … -m 0755`).
- Перенос backup: скрипты с `scripts/*.sh`, cron — **со старой ноды** или по **`vault-pki-monitoring-runbook.md` §6 / §6.1**; ссылки на `roles/vault-oss-deploy` убраны.

### `docs/runbooks/vault-pki-dr-runbook.md`
- TLS: только **ручное** развёртывание (тома в `docker-compose`, пути в `config.hcl`), без Ansible.

### `docs/vault-pki-monitoring-runbook.md`
- Удалён абзац про `ansible-playbook` / `host_vars`.
- Добавлен готовый пример **`/etc/cron.d/vault-raft-backup`** (`*/30` + лог), рядом с уже существующим **`renew-token`**.
- Заголовок §6.1 переименован: туда входят и полный snapshot, и `renew-token`.

Упоминание `scp` с `ansible@...` в мониторинговом runbook (перенос скрипта) оставлено как пример хоста — к учётной записи Ansible это не привязано; при желании могу заменить на нейтральное `user@p-pki-as-01...`.

[REDACTED]


<user_query>
возможно правильно добавить ссылки на ранбук в алерты?
</user_query>


Ищем определения алертов и добавляем ссылки на runbook'и в аннотации.

[REDACTED]


[REDACTED]

Shell

cd /home/ruslanpi/Documents/repos/lmru--devops-core--ansible-vault-oss && git remote -v 2>/dev/null | head -5

Get git remote URL for runbook base


[REDACTED]

cd /home/ruslanpi/Documents/repos/lmru--devops-core--ansible-vault-oss && git branch -a | head -20 && git symbolic-ref refs/remotes/origin/HEAD 2>/dev/null || true

Detect default branch name for GitHub URLs