Merge pull request 'Бэкапы: образцы настроек ведут алерты в тему «Metrics», неработавший сторож доступности удалён' (#3559) from fix/backup-notify-topic into main
All checks were successful
Deploy / build-backend (push) Successful in 3m27s
Deploy / build-worker (push) Successful in 5m9s
Deploy / build-frontend (push) Successful in 6m7s
Deploy Infra Host / sync-infra-host (push) Successful in 7s
Deploy / deploy (push) Successful in 1m42s
Deploy / deploy-status (push) Successful in 5s
Deploy / changes (push) Successful in 17s
Deploy / perimeter-smoke (push) Successful in 1m51s
Deploy / deploy-caddy (push) Has been skipped
All checks were successful
Deploy / build-backend (push) Successful in 3m27s
Deploy / build-worker (push) Successful in 5m9s
Deploy / build-frontend (push) Successful in 6m7s
Deploy Infra Host / sync-infra-host (push) Successful in 7s
Deploy / deploy (push) Successful in 1m42s
Deploy / deploy-status (push) Successful in 5s
Deploy / changes (push) Successful in 17s
Deploy / perimeter-smoke (push) Successful in 1m51s
Deploy / deploy-caddy (push) Has been skipped
This commit is contained in:
commit
af5eed19f9
12 changed files with 122 additions and 251 deletions
|
|
@ -77,7 +77,7 @@ on:
|
||||||
# исполнял бы старую версию — молча и без единого сигнала.
|
# исполнял бы старую версию — молча и без единого сигнала.
|
||||||
# Глоб, а не точечный список (#2203): класс бага — «любой ops-скрипт,
|
# Глоб, а не точечный список (#2203): класс бага — «любой ops-скрипт,
|
||||||
# запускаемый по cron с VM», не только docker-prune.sh. Сейчас сюда попадают
|
# запускаемый по cron с VM», не только docker-prune.sh. Сейчас сюда попадают
|
||||||
# backup.sh, restore-drill.sh, restore.sh, uptime-healthcheck.sh — точечное
|
# backup.sh, restore-drill.sh, restore.sh — точечное
|
||||||
# перечисление пришлось бы дополнять при каждом новом скрипте, и про это
|
# перечисление пришлось бы дополнять при каждом новом скрипте, и про это
|
||||||
# снова забыли бы (см. как этот самый комментарий выше был точечным про
|
# снова забыли бы (см. как этот самый комментарий выше был точечным про
|
||||||
# docker-prune.sh и не спас backup.sh). Глоб закрывает класс целиком.
|
# docker-prune.sh и не спас backup.sh). Глоб закрывает класс целиком.
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
"""Оповещения уходят в тему «алерты», а не в общую (#2203/#3078).
|
"""Оповещения уходят в тему форума, а не в общую (#2203/#3078/#3164).
|
||||||
|
|
||||||
Прод-факт, 27.08. Канал доставки включили, `notify()` заработал — и алерты
|
Прод-факт, 27.08. Канал доставки включили, `notify()` заработал — и алерты
|
||||||
посыпались в ОБЩУЮ тему форума (в чат переговорки), а не в «алерты». Причина: в
|
посыпались в ОБЩУЮ тему форума (в чат переговорки), а не в «алерты». Причина: в
|
||||||
|
|
@ -16,9 +16,7 @@
|
||||||
реально ушло бы в сеть.
|
реально ушло бы в сеть.
|
||||||
|
|
||||||
Функция извлекается из файла построчно, а не копируется в тест: копия разошлась
|
Функция извлекается из файла построчно, а не копируется в тест: копия разошлась
|
||||||
бы с оригиналом на первой же правке. Сорсить файл целиком нельзя — у
|
бы с оригиналом на первой же правке.
|
||||||
`uptime-healthcheck.sh` нет guard'а по `BASH_SOURCE`, и сорсинг запустил бы
|
|
||||||
настоящие сетевые проверки.
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
@ -34,7 +32,6 @@ import pytest
|
||||||
REPO_ROOT = Path(__file__).resolve().parents[3]
|
REPO_ROOT = Path(__file__).resolve().parents[3]
|
||||||
SENDERS = {
|
SENDERS = {
|
||||||
"lib-backup.sh": REPO_ROOT / "ops" / "lib-backup.sh",
|
"lib-backup.sh": REPO_ROOT / "ops" / "lib-backup.sh",
|
||||||
"uptime-healthcheck.sh": REPO_ROOT / "ops" / "uptime-healthcheck.sh",
|
|
||||||
}
|
}
|
||||||
|
|
||||||
FAKE_CURL = '#!/usr/bin/env bash\nprintf "%s\\n" "$@" >> "$ARGS_DUMP"\nexit 0\n'
|
FAKE_CURL = '#!/usr/bin/env bash\nprintf "%s\\n" "$@" >> "$ARGS_DUMP"\nexit 0\n'
|
||||||
|
|
@ -105,7 +102,7 @@ def _run_notify(tmp_path: Path, script: Path, *, topic: str | None) -> list[str]
|
||||||
f'ARGS_DUMP="{_posix(dump)}"',
|
f'ARGS_DUMP="{_posix(dump)}"',
|
||||||
'TELEGRAM_BOT_TOKEN="123:FAKE"',
|
'TELEGRAM_BOT_TOKEN="123:FAKE"',
|
||||||
'TELEGRAM_CHAT_ID="-1004443088679"',
|
'TELEGRAM_CHAT_ID="-1004443088679"',
|
||||||
f"BACKUP_ENV_FILE=\"{_posix(tmp_path / 'missing.env')}\"",
|
f'BACKUP_ENV_FILE="{_posix(tmp_path / "missing.env")}"',
|
||||||
]
|
]
|
||||||
if topic is not None:
|
if topic is not None:
|
||||||
prefix.append(f'TELEGRAM_TOPIC_ID="{topic}"')
|
prefix.append(f'TELEGRAM_TOPIC_ID="{topic}"')
|
||||||
|
|
@ -153,3 +150,73 @@ def test_adres_i_tekst_na_meste(tmp_path: Path, name: str) -> None:
|
||||||
args = _run_notify(tmp_path, SENDERS[name], topic="158")
|
args = _run_notify(tmp_path, SENDERS[name], topic="158")
|
||||||
assert "chat_id=-1004443088679" in args, f"{name}: потерялся chat_id"
|
assert "chat_id=-1004443088679" in args, f"{name}: потерялся chat_id"
|
||||||
assert any("тестовое сообщение" in a for a in args), f"{name}: потерялся текст"
|
assert any("тестовое сообщение" in a for a in args), f"{name}: потерялся текст"
|
||||||
|
|
||||||
|
|
||||||
|
# #3164. Решение владельца в #3163: инфраструктура — в тему «Metrics» (245),
|
||||||
|
# клиентские инциденты — в «алерты» (158). Бэкапы — инфраструктура. Тема живёт
|
||||||
|
# только в env-файле на хосте, поэтому образец — единственное место в репозитории,
|
||||||
|
# откуда следующий, кто заводит хост, узнает правильный адрес.
|
||||||
|
INFRA_TOPIC = "245"
|
||||||
|
BACKUP_ENV_EXAMPLES = [
|
||||||
|
"gendesign-backup.default.example",
|
||||||
|
"gendesign-backup-couchdb.default.example",
|
||||||
|
"gendesign-backup-forgejo.default.example",
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("example", BACKUP_ENV_EXAMPLES)
|
||||||
|
def test_host_po_obraztsu_shlyot_bekapy_v_metriki(tmp_path: Path, example: str) -> None:
|
||||||
|
"""Хост, заведённый по образцу, шлёт алерт бэкапа в «Metrics», а не мимо.
|
||||||
|
|
||||||
|
Исполняется настоящий сторож `check-backup-staleness.sh` с отсутствующим
|
||||||
|
маркером, а env-файл — образец, заполненный так, как он велит человеку:
|
||||||
|
раскомментированы строки `TELEGRAM_*`, подставлены токен и чат.
|
||||||
|
|
||||||
|
Ловит и второе: тема обязана лежать В ТОМ ЖЕ файле, что токен и чат.
|
||||||
|
`notify()` читает `BACKUP_ENV_FILE`, только если токена или чата нет в
|
||||||
|
окружении, — тема из другого файла молча не подхватится.
|
||||||
|
"""
|
||||||
|
text = (REPO_ROOT / "ops" / example).read_text(encoding="utf-8")
|
||||||
|
filled = re.sub(r"(?m)^#(TELEGRAM_\w+=)", r"\1", text)
|
||||||
|
filled = re.sub(r"(?m)^TELEGRAM_BOT_TOKEN=.*$", "TELEGRAM_BOT_TOKEN=123:FAKE", filled)
|
||||||
|
filled = re.sub(r"(?m)^TELEGRAM_CHAT_ID=.*$", "TELEGRAM_CHAT_ID=-1004443088679", filled)
|
||||||
|
env_file = tmp_path / "backup.env"
|
||||||
|
env_file.write_text(filled, encoding="utf-8", newline="\n")
|
||||||
|
|
||||||
|
bin_dir = tmp_path / "bin"
|
||||||
|
bin_dir.mkdir()
|
||||||
|
fake = bin_dir / "curl"
|
||||||
|
fake.write_text(FAKE_CURL, encoding="utf-8", newline="\n")
|
||||||
|
fake.chmod(0o755)
|
||||||
|
dump = tmp_path / "args.txt"
|
||||||
|
|
||||||
|
# Пустые TELEGRAM_* префиксом: переменные из окружения разработчика иначе
|
||||||
|
# перебили бы файл, и тест проверял бы не образец.
|
||||||
|
prefix = [
|
||||||
|
f'PATH="{_posix(bin_dir)}:$PATH"',
|
||||||
|
f'ARGS_DUMP="{_posix(dump)}"',
|
||||||
|
'TELEGRAM_BOT_TOKEN="" TELEGRAM_CHAT_ID="" TELEGRAM_TOPIC_ID=""',
|
||||||
|
f'BACKUP_ENV_FILE="{_posix(env_file)}"',
|
||||||
|
f'BACKUP_STALENESS_STATE_FILE="{_posix(tmp_path / "state")}"',
|
||||||
|
]
|
||||||
|
script = REPO_ROOT / "ops" / "check-backup-staleness.sh"
|
||||||
|
proc = subprocess.run(
|
||||||
|
[
|
||||||
|
"bash",
|
||||||
|
"-c",
|
||||||
|
" ".join(prefix)
|
||||||
|
+ f' bash "{_posix(script)}" "{_posix(tmp_path / "no-sentinel")}" 26 "topic probe"',
|
||||||
|
],
|
||||||
|
cwd=str(REPO_ROOT),
|
||||||
|
capture_output=True,
|
||||||
|
text=True,
|
||||||
|
timeout=30,
|
||||||
|
env=dict(os.environ),
|
||||||
|
)
|
||||||
|
args = dump.read_text(encoding="utf-8").splitlines() if dump.exists() else []
|
||||||
|
assert args, f"{example}: curl не был вызван.\n{proc.stdout}\n{proc.stderr}"
|
||||||
|
threads = [a for a in args if a.startswith("message_thread_id=")]
|
||||||
|
assert threads == [f"message_thread_id={INFRA_TOPIC}"], (
|
||||||
|
f"{example}: алерт бэкапа адресован {threads or 'в общую тему'}, "
|
||||||
|
f"а по #3163 инфраструктура идёт в «Metrics» ({INFRA_TOPIC})"
|
||||||
|
)
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,9 @@ Poincare, 40 подключений к ЗАКРЕПЛЁННОМУ (#3093) 149.15
|
||||||
|
|
||||||
Как это ломало алерты:
|
Как это ломало алерты:
|
||||||
|
|
||||||
* `ops/uptime-healthcheck.sh` — ОДИН `curl`, дальше `|| log WARN`. Каждый
|
* `ops/uptime-healthcheck.sh` (удалён в #3164: ни на одном хосте не был
|
||||||
|
запущен, доступность сайтов сторожат uptime-мониторы GlitchTip) — ОДИН
|
||||||
|
`curl`, дальше `|| log WARN`. Каждый
|
||||||
отказ терял уведомление целиком. Watchdog, который не может дозваться, —
|
отказ терял уведомление целиком. Watchdog, который не может дозваться, —
|
||||||
худший вид самоскрывающейся поломки: чем хуже дела на проде, тем выше шанс,
|
худший вид самоскрывающейся поломки: чем хуже дела на проде, тем выше шанс,
|
||||||
что о них не сообщат. При этом сам файл ниже повторяет свои HTTP-ПРОВЕРКИ
|
что о них не сообщат. При этом сам файл ниже повторяет свои HTTP-ПРОВЕРКИ
|
||||||
|
|
@ -51,7 +53,6 @@ import pytest
|
||||||
# backend/tests/ops/<этот файл> → корень репозитория
|
# backend/tests/ops/<этот файл> → корень репозитория
|
||||||
REPO_ROOT = Path(__file__).resolve().parents[3]
|
REPO_ROOT = Path(__file__).resolve().parents[3]
|
||||||
|
|
||||||
UPTIME = "ops/uptime-healthcheck.sh"
|
|
||||||
LIB_BACKUP = "ops/lib-backup.sh"
|
LIB_BACKUP = "ops/lib-backup.sh"
|
||||||
|
|
||||||
# См. подробное обоснование shutil.which в
|
# См. подробное обоснование shutil.which в
|
||||||
|
|
@ -65,8 +66,7 @@ if BASH is None: # pragma: no cover - окружение без bash не за
|
||||||
def _extract_function(script_rel: str, name: str = "notify") -> str:
|
def _extract_function(script_rel: str, name: str = "notify") -> str:
|
||||||
"""Достаёт тело одной функции из скрипта — не весь файл.
|
"""Достаёт тело одной функции из скрипта — не весь файл.
|
||||||
|
|
||||||
Весь скрипт source'ить нельзя: uptime-healthcheck.sh ниже функций реально
|
Весь скрипт source'ить нельзя: lib-backup.sh рассчитан на вызов из backup.sh.
|
||||||
ходит по прод-URL, а lib-backup.sh рассчитан на вызов из backup.sh.
|
|
||||||
|
|
||||||
Сопоставление точное (`name() {`), иначе `notify` поймал бы
|
Сопоставление точное (`name() {`), иначе `notify` поймал бы
|
||||||
`notify_fallback_mail` — соседнюю функцию в том же файле.
|
`notify_fallback_mail` — соседнюю функцию в том же файле.
|
||||||
|
|
@ -140,7 +140,7 @@ def _run_notify(script_rel: str, fail_times: int) -> tuple[int, str, str]:
|
||||||
return calls, out, err
|
return calls, out, err
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize("script_rel", [UPTIME, LIB_BACKUP])
|
@pytest.mark.parametrize("script_rel", [LIB_BACKUP])
|
||||||
def test_transient_failure_is_retried_not_lost(script_rel: str) -> None:
|
def test_transient_failure_is_retried_not_lost(script_rel: str) -> None:
|
||||||
"""Один отказ — алерт всё равно доставляется со второй попытки.
|
"""Один отказ — алерт всё равно доставляется со второй попытки.
|
||||||
|
|
||||||
|
|
@ -153,7 +153,7 @@ def test_transient_failure_is_retried_not_lost(script_rel: str) -> None:
|
||||||
assert "НЕ ДОСТАВЛЕН" not in err, f"{script_rel}: доставленный алерт помечен потерянным"
|
assert "НЕ ДОСТАВЛЕН" not in err, f"{script_rel}: доставленный алерт помечен потерянным"
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize("script_rel", [UPTIME, LIB_BACKUP])
|
@pytest.mark.parametrize("script_rel", [LIB_BACKUP])
|
||||||
def test_gives_up_after_three_attempts(script_rel: str) -> None:
|
def test_gives_up_after_three_attempts(script_rel: str) -> None:
|
||||||
"""Повторы ограничены: три попытки, а не бесконечный цикл.
|
"""Повторы ограничены: три попытки, а не бесконечный цикл.
|
||||||
|
|
||||||
|
|
@ -164,12 +164,6 @@ def test_gives_up_after_three_attempts(script_rel: str) -> None:
|
||||||
assert calls == 3, f"{script_rel}: ожидалось ровно 3 попытки, а curl позван {calls} раз"
|
assert calls == 3, f"{script_rel}: ожидалось ровно 3 попытки, а curl позван {calls} раз"
|
||||||
|
|
||||||
|
|
||||||
def test_uptime_reports_undelivered_alert_loudly() -> None:
|
|
||||||
"""Когда все три попытки провалились — это видно в логе, а не молча."""
|
|
||||||
_calls, _out, err = _run_notify(UPTIME, fail_times=99)
|
|
||||||
assert "НЕ ДОСТАВЛЕН" in err, f"недоставленный алерт должен логироваться громко.\n{err}"
|
|
||||||
|
|
||||||
|
|
||||||
def test_backup_does_not_burn_fallback_on_a_single_timeout() -> None:
|
def test_backup_does_not_burn_fallback_on_a_single_timeout() -> None:
|
||||||
"""Транзиентный таймаут не должен трогать запасной канал.
|
"""Транзиентный таймаут не должен трогать запасной канал.
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -10,9 +10,8 @@
|
||||||
# script says so loudly instead of the silence that let backups break for
|
# script says so loudly instead of the silence that let backups break for
|
||||||
# weeks undetected before (#71).
|
# weeks undetected before (#71).
|
||||||
#
|
#
|
||||||
# Alerts only on a STATE TRANSITION (fresh->stale, stale->fresh), same
|
# Alerts only on a STATE TRANSITION (fresh->stale, stale->fresh), so an
|
||||||
# discipline as ops/uptime-healthcheck.sh, so an hourly cron doesn't spam
|
# hourly cron doesn't spam Telegram once a backup is already known to be stale.
|
||||||
# Telegram once a backup is already known to be stale.
|
|
||||||
#
|
#
|
||||||
# Usage (cron — one line per sentinel, run more often than the backup itself
|
# Usage (cron — one line per sentinel, run more often than the backup itself
|
||||||
# so a stale state is caught promptly; hourly is a reasonable default for a
|
# so a stale state is caught promptly; hourly is a reasonable default for a
|
||||||
|
|
@ -27,9 +26,10 @@
|
||||||
# /opt/gendesign/backups/forgejo/.last_success 26 "forgejo backup" \
|
# /opt/gendesign/backups/forgejo/.last_success 26 "forgejo backup" \
|
||||||
# >> /var/log/gendesign-backup-staleness.log 2>&1
|
# >> /var/log/gendesign-backup-staleness.log 2>&1
|
||||||
#
|
#
|
||||||
# Alert channel: reuses the Telegram bot from ops/uptime-healthcheck.sh (see
|
# Alert channel: notify() in ops/lib-backup.sh — creds in
|
||||||
# notify() in ops/lib-backup.sh) — creds in /etc/default/gendesign-backup,
|
# ${BACKUP_ENV_FILE:-/etc/default/gendesign-backup}, TELEGRAM_BOT_TOKEN/
|
||||||
# TELEGRAM_BOT_TOKEN/TELEGRAM_CHAT_ID. Without them, logs only.
|
# TELEGRAM_CHAT_ID/TELEGRAM_TOPIC_ID (245 «Metrics», #3164). Without the first
|
||||||
|
# two, logs only.
|
||||||
#
|
#
|
||||||
# Exit code: 0 = fresh, 1 = stale or sentinel missing (so this can ALSO be
|
# Exit code: 0 = fresh, 1 = stale or sentinel missing (so this can ALSO be
|
||||||
# used as a plain healthcheck by anything that just wants the exit code).
|
# used as a plain healthcheck by anything that just wants the exit code).
|
||||||
|
|
|
||||||
|
|
@ -22,7 +22,11 @@
|
||||||
15 4 * * * FORGEJO_BACKUP_ENV_FILE=/opt/gendesign/secrets/forgejo-backup.env bash /opt/gendesign/ops/backup-forgejo.sh >> /opt/gendesign/logs/backup-forgejo.log 2>&1
|
15 4 * * * FORGEJO_BACKUP_ENV_FILE=/opt/gendesign/secrets/forgejo-backup.env bash /opt/gendesign/ops/backup-forgejo.sh >> /opt/gendesign/logs/backup-forgejo.log 2>&1
|
||||||
|
|
||||||
# Сторож пропущенных прогонов для forgejo-бэкапа. Порог 26ч.
|
# Сторож пропущенных прогонов для forgejo-бэкапа. Порог 26ч.
|
||||||
0 * * * * bash /opt/gendesign/ops/check-backup-staleness.sh /opt/gendesign/backups/forgejo/.last_success 26 "forgejo backup" >> /opt/gendesign/logs/backup-staleness.log 2>&1
|
# Канал оповещения сторожей и бэкапа волта — /opt/gendesign/secrets/backup-notify.env
|
||||||
|
# (TELEGRAM_BOT_TOKEN/CHAT_ID/TOPIC_ID, тема 245 «Metrics», #3164). Без
|
||||||
|
# BACKUP_ENV_FILE notify() ищет /etc/default/gendesign-backup, где канала на
|
||||||
|
# этом хосте нет, — алерты молча не уходят никуда.
|
||||||
|
0 * * * * BACKUP_ENV_FILE=/opt/gendesign/secrets/backup-notify.env bash /opt/gendesign/ops/check-backup-staleness.sh /opt/gendesign/backups/forgejo/.last_success 26 "forgejo backup" >> /opt/gendesign/logs/backup-staleness.log 2>&1
|
||||||
|
|
||||||
# ── Бэкап волта Obsidian (CouchDB) ──────────────────────────────────────────
|
# ── Бэкап волта Obsidian (CouchDB) ──────────────────────────────────────────
|
||||||
# Волт остаётся на этом хосте вместе с Forgejo и GlitchTip, поэтому запись
|
# Волт остаётся на этом хосте вместе с Forgejo и GlitchTip, поэтому запись
|
||||||
|
|
@ -33,11 +37,11 @@
|
||||||
# file-level копия работающего узла может оказаться рассогласованной. Время —
|
# file-level копия работающего узла может оказаться рассогласованной. Время —
|
||||||
# 04:45, между forgejo-бэкапом (04:15) и недельной уборкой докера (04:00 вс),
|
# 04:45, между forgejo-бэкапом (04:15) и недельной уборкой докера (04:00 вс),
|
||||||
# чтобы три задачи не дрались за диск и сеть одновременно.
|
# чтобы три задачи не дрались за диск и сеть одновременно.
|
||||||
45 4 * * * bash /opt/gendesign/ops/backup-couchdb.sh >> /opt/gendesign/logs/backup-couchdb.log 2>&1
|
45 4 * * * BACKUP_ENV_FILE=/opt/gendesign/secrets/backup-notify.env bash /opt/gendesign/ops/backup-couchdb.sh >> /opt/gendesign/logs/backup-couchdb.log 2>&1
|
||||||
|
|
||||||
# Сторож пропущенных прогонов для бэкапа волта. Порог 26ч — как у остальных:
|
# Сторож пропущенных прогонов для бэкапа волта. Порог 26ч — как у остальных:
|
||||||
# сутки плюс запас на сдвиг прогона.
|
# сутки плюс запас на сдвиг прогона.
|
||||||
0 * * * * bash /opt/gendesign/ops/check-backup-staleness.sh /opt/gendesign/backups/couchdb/.last_success 26 "couchdb backup" >> /opt/gendesign/logs/backup-staleness.log 2>&1
|
0 * * * * BACKUP_ENV_FILE=/opt/gendesign/secrets/backup-notify.env bash /opt/gendesign/ops/check-backup-staleness.sh /opt/gendesign/backups/couchdb/.last_success 26 "couchdb backup" >> /opt/gendesign/logs/backup-staleness.log 2>&1
|
||||||
|
|
||||||
# ── Уборка докера ───────────────────────────────────────────────────────────
|
# ── Уборка докера ───────────────────────────────────────────────────────────
|
||||||
# Остаётся здесь, потому что здесь остаются CI-раннеры (#2881) — именно их
|
# Остаётся здесь, потому что здесь остаются CI-раннеры (#2881) — именно их
|
||||||
|
|
|
||||||
|
|
@ -51,5 +51,10 @@ COUCHDB_S3_SECRET_KEY=
|
||||||
# Переопределяй только если этому бэкапу нужен ОТДЕЛЬНЫЙ адресат:
|
# Переопределяй только если этому бэкапу нужен ОТДЕЛЬНЫЙ адресат:
|
||||||
#TELEGRAM_BOT_TOKEN=
|
#TELEGRAM_BOT_TOKEN=
|
||||||
#TELEGRAM_CHAT_ID=
|
#TELEGRAM_CHAT_ID=
|
||||||
|
# Тема форума (#3164). Бэкапы — инфраструктура, по решению #3163 они идут в
|
||||||
|
# «Metrics» (245); «алерты» (158) — только для клиентских инцидентов. Без
|
||||||
|
# переменной сообщение уходит в общую тему. Держать В ТОМ ЖЕ файле, что токен и
|
||||||
|
# чат: notify() читает env-файл, только если их нет в окружении.
|
||||||
|
#TELEGRAM_TOPIC_ID=245
|
||||||
# Либо укажи другой файл, откуда notify() возьмёт настройки канала:
|
# Либо укажи другой файл, откуда notify() возьмёт настройки канала:
|
||||||
#BACKUP_ENV_FILE=/etc/default/gendesign-backup
|
#BACKUP_ENV_FILE=/etc/default/gendesign-backup
|
||||||
|
|
|
||||||
|
|
@ -27,6 +27,17 @@
|
||||||
#FORGEJO_S3_ACCESS_KEY=REPLACE_WITH_REAL_ACCESS_KEY_ONCE_CREATED
|
#FORGEJO_S3_ACCESS_KEY=REPLACE_WITH_REAL_ACCESS_KEY_ONCE_CREATED
|
||||||
#FORGEJO_S3_SECRET_KEY=REPLACE_WITH_REAL_SECRET_KEY_ONCE_CREATED
|
#FORGEJO_S3_SECRET_KEY=REPLACE_WITH_REAL_SECRET_KEY_ONCE_CREATED
|
||||||
|
|
||||||
|
# --- оповещения (Telegram) ---
|
||||||
|
# backup-forgejo.sh читает этот файл сам, поэтому канал задаётся здесь: без
|
||||||
|
# токена и чата notify() ищет их в /etc/default/gendesign-backup.
|
||||||
|
#TELEGRAM_BOT_TOKEN=123456789:AA-REPLACE_WITH_REAL_BOT_TOKEN
|
||||||
|
#TELEGRAM_CHAT_ID=123456789
|
||||||
|
# Тема форума (#3164). Бэкапы — инфраструктура, по решению #3163 они идут в
|
||||||
|
# «Metrics» (245); «алерты» (158) — только для клиентских инцидентов. Без
|
||||||
|
# переменной сообщение уходит в общую тему. Держать В ТОМ ЖЕ файле, что токен и
|
||||||
|
# чат: notify() читает env-файл, только если их нет в окружении.
|
||||||
|
#TELEGRAM_TOPIC_ID=245
|
||||||
|
|
||||||
# --- optional overrides (defaults are sensible; uncomment only to change) ---
|
# --- optional overrides (defaults are sensible; uncomment only to change) ---
|
||||||
#FORGEJO_S3_PREFIX=forgejo/ # key prefix inside the bucket; must match
|
#FORGEJO_S3_PREFIX=forgejo/ # key prefix inside the bucket; must match
|
||||||
# the policy's Resource path exactly
|
# the policy's Resource path exactly
|
||||||
|
|
|
||||||
|
|
@ -24,13 +24,8 @@
|
||||||
#
|
#
|
||||||
# Missed-run alerting (#2203): ops/check-backup-staleness.sh (separate cron
|
# Missed-run alerting (#2203): ops/check-backup-staleness.sh (separate cron
|
||||||
# entry, see its header) alerts when a backup's sentinel goes stale. It reads
|
# entry, see its header) alerts when a backup's sentinel goes stale. It reads
|
||||||
# TELEGRAM_BOT_TOKEN/TELEGRAM_CHAT_ID from THIS file (not from
|
# TELEGRAM_BOT_TOKEN/TELEGRAM_CHAT_ID/TELEGRAM_TOPIC_ID from THIS file.
|
||||||
# /etc/default/gendesign-uptime — deliberately a separate config so backup
|
# Without the first two set, ops/check-backup-staleness.sh still logs, just
|
||||||
# alerting doesn't depend on the uptime watchdog's env file existing).
|
|
||||||
# SAME variable names as ops/gendesign-uptime.default.example — point both
|
|
||||||
# files at the same bot/chat if you want one Telegram destination for
|
|
||||||
# everything, or use different bots/chats if you'd rather split the noise.
|
|
||||||
# Without these two set, ops/check-backup-staleness.sh still logs, just
|
|
||||||
# doesn't send a Telegram alert.
|
# doesn't send a Telegram alert.
|
||||||
|
|
||||||
# --- S3 off-site upload (Selectel S3-compatible). All four REQUIRED (#3085) —
|
# --- S3 off-site upload (Selectel S3-compatible). All four REQUIRED (#3085) —
|
||||||
|
|
@ -41,9 +36,14 @@
|
||||||
#S3_ACCESS_KEY=REPLACE_WITH_REAL_ACCESS_KEY
|
#S3_ACCESS_KEY=REPLACE_WITH_REAL_ACCESS_KEY
|
||||||
#S3_SECRET_KEY=REPLACE_WITH_REAL_SECRET_KEY
|
#S3_SECRET_KEY=REPLACE_WITH_REAL_SECRET_KEY
|
||||||
|
|
||||||
# --- missed-run alerting (Telegram, shared bot with uptime watchdog) ---
|
# --- missed-run alerting (Telegram) ---
|
||||||
#TELEGRAM_BOT_TOKEN=123456789:AA-REPLACE_WITH_REAL_BOT_TOKEN
|
#TELEGRAM_BOT_TOKEN=123456789:AA-REPLACE_WITH_REAL_BOT_TOKEN
|
||||||
#TELEGRAM_CHAT_ID=123456789
|
#TELEGRAM_CHAT_ID=123456789
|
||||||
|
# Тема форума (#3164). Бэкапы — инфраструктура, по решению #3163 они идут в
|
||||||
|
# «Metrics» (245); «алерты» (158) — только для клиентских инцидентов. Без
|
||||||
|
# переменной сообщение уходит в общую тему. Держать В ТОМ ЖЕ файле, что токен и
|
||||||
|
# чат: notify() читает env-файл, только если их нет в окружении.
|
||||||
|
#TELEGRAM_TOPIC_ID=245
|
||||||
|
|
||||||
# --- optional overrides (defaults are sensible; uncomment only to change) ---
|
# --- optional overrides (defaults are sensible; uncomment only to change) ---
|
||||||
#BACKUP_ALLOW_LOCAL_ONLY=1 # explicit escape hatch: run without S3 creds and
|
#BACKUP_ALLOW_LOCAL_ONLY=1 # explicit escape hatch: run without S3 creds and
|
||||||
|
|
|
||||||
|
|
@ -1,28 +0,0 @@
|
||||||
# Environment file for ops/uptime-healthcheck.sh (external uptime watchdog, #75).
|
|
||||||
#
|
|
||||||
# Install as a ROOT-OWNED, chmod-600 file that is NOT in git, on whatever host
|
|
||||||
# runs the cron (ideally a host OTHER than the prod VM, so it survives a full
|
|
||||||
# VPS outage):
|
|
||||||
# sudo cp ops/gendesign-uptime.default.example /etc/default/gendesign-uptime
|
|
||||||
# sudo chmod 600 /etc/default/gendesign-uptime
|
|
||||||
# sudo $EDITOR /etc/default/gendesign-uptime # fill in real Telegram creds
|
|
||||||
#
|
|
||||||
# uptime-healthcheck.sh sources this file if present. With NO Telegram vars set,
|
|
||||||
# it still logs up/down but sends no alert (useful for a dry run first).
|
|
||||||
#
|
|
||||||
# Get a bot token from @BotFather; get your chat_id by messaging the bot then
|
|
||||||
# GET https://api.telegram.org/bot<TOKEN>/getUpdates and reading message.chat.id.
|
|
||||||
|
|
||||||
# --- Telegram alerting (both required to enable alerts) ---
|
|
||||||
#TELEGRAM_BOT_TOKEN=123456789:AA-REPLACE_WITH_REAL_BOT_TOKEN
|
|
||||||
#TELEGRAM_CHAT_ID=123456789
|
|
||||||
|
|
||||||
# --- optional overrides (defaults are sensible; uncomment only to change) ---
|
|
||||||
#BASE_URL=https://gendsgn.ru
|
|
||||||
#STATE_FILE=/var/tmp/gendesign-uptime-state
|
|
||||||
#CURL_TIMEOUT=15
|
|
||||||
#RETRIES=2
|
|
||||||
#RETRY_SLEEP=5
|
|
||||||
# Custom check list (newline-separated "label|path|expected_status"):
|
|
||||||
#CHECKS="health|/health|200
|
|
||||||
#market-pulse|/api/v1/analytics/market-pulse|200"
|
|
||||||
|
|
@ -5,9 +5,7 @@
|
||||||
# SOURCED, not executed directly — no shebang execution of its own. Inherits
|
# SOURCED, not executed directly — no shebang execution of its own. Inherits
|
||||||
# the caller's `set -euo pipefail`. Keep this dependency-free: bash builtins +
|
# the caller's `set -euo pipefail`. Keep this dependency-free: bash builtins +
|
||||||
# coreutils (date, stat, mkdir, grep, awk, mktemp) + curl (only used by
|
# coreutils (date, stat, mkdir, grep, awk, mktemp) + curl (only used by
|
||||||
# notify() when Telegram vars are actually set — curl is already a hard
|
# notify() when Telegram vars are actually set).
|
||||||
# requirement of ops/uptime-healthcheck.sh on the same box, so this adds no
|
|
||||||
# new dependency).
|
|
||||||
#
|
#
|
||||||
# Load with (script computes its own dir so this works regardless of cron's
|
# Load with (script computes its own dir so this works regardless of cron's
|
||||||
# CWD or which repo subdir the caller lives in):
|
# CWD or which repo subdir the caller lives in):
|
||||||
|
|
@ -18,14 +16,8 @@
|
||||||
log() { echo "[$(date -u +'%Y-%m-%dT%H:%M:%SZ')] $*"; }
|
log() { echo "[$(date -u +'%Y-%m-%dT%H:%M:%SZ')] $*"; }
|
||||||
|
|
||||||
# --- notify ------------------------------------------------------------
|
# --- notify ------------------------------------------------------------
|
||||||
# Reuses the SAME Telegram channel/bot as ops/uptime-healthcheck.sh (#75) —
|
# TELEGRAM_BOT_TOKEN / TELEGRAM_CHAT_ID / TELEGRAM_TOPIC_ID from the env
|
||||||
# this is NOT a second alerting system, just the same TELEGRAM_BOT_TOKEN /
|
# (or ${BACKUP_ENV_FILE:-/etc/default/gendesign-backup}). Topic: #3164.
|
||||||
# TELEGRAM_CHAT_ID variable names read from a DIFFERENT env file
|
|
||||||
# (/etc/default/gendesign-backup, not /etc/default/gendesign-uptime) so
|
|
||||||
# backup alerting doesn't depend on the uptime watchdog's config being
|
|
||||||
# present, and vice versa. Point both files at the same bot/chat if you want
|
|
||||||
# one Telegram destination for everything — that's an ops choice, not this
|
|
||||||
# script's concern.
|
|
||||||
#
|
#
|
||||||
# No-op (logs only) when unset — this is the extension point: to wire a
|
# No-op (logs only) when unset — this is the extension point: to wire a
|
||||||
# different channel later, edit ONLY this function; every caller in this repo
|
# different channel later, edit ONLY this function; every caller in this repo
|
||||||
|
|
@ -153,12 +145,9 @@ sentinel_age_hours() {
|
||||||
}
|
}
|
||||||
|
|
||||||
# --- transition-tracked alert state --------------------------------------
|
# --- transition-tracked alert state --------------------------------------
|
||||||
# Same idiom as ops/uptime-healthcheck.sh's prev_status()/set_status(): a
|
# A flat "<label> <status>" file, one line per label, so repeated runs alert
|
||||||
# flat "<label> <status>" file, one line per label, so repeated runs alert
|
|
||||||
# only on a STATE TRANSITION (fresh->stale, stale->fresh) instead of every
|
# only on a STATE TRANSITION (fresh->stale, stale->fresh) instead of every
|
||||||
# single run — avoids Telegram spam from an hourly staleness-check cron.
|
# single run — avoids Telegram spam from an hourly staleness-check cron.
|
||||||
# Intentionally a separate, independent implementation (not shared code with
|
|
||||||
# uptime-healthcheck.sh) — that script is out of scope for this change.
|
|
||||||
backup_prev_status() {
|
backup_prev_status() {
|
||||||
local state_file="$1" label="$2" v
|
local state_file="$1" label="$2" v
|
||||||
[[ -f "$state_file" ]] || { echo "unknown"; return; }
|
[[ -f "$state_file" ]] || { echo "unknown"; return; }
|
||||||
|
|
|
||||||
|
|
@ -216,7 +216,7 @@ log "11. Закрепление рабочего адреса api.telegram.org"
|
||||||
#
|
#
|
||||||
# Контейнерам это даёт tradein-mvp/docker-compose.selectel.yml (extra_hosts для
|
# Контейнерам это даёт tradein-mvp/docker-compose.selectel.yml (extra_hosts для
|
||||||
# tgbot и backend). Здесь — для ХОСТОВЫХ скриптов, которым compose не помогает:
|
# tgbot и backend). Здесь — для ХОСТОВЫХ скриптов, которым compose не помогает:
|
||||||
# ops/lib-backup.sh (уведомления о бэкапах) и ops/uptime-healthcheck.sh.
|
# ops/lib-backup.sh (уведомления о бэкапах).
|
||||||
# Без этого они молча перестают слать алерты — а это ровно тот канал, которым
|
# Без этого они молча перестают слать алерты — а это ровно тот канал, которым
|
||||||
# мы узнали бы о любой другой поломке.
|
# мы узнали бы о любой другой поломке.
|
||||||
TELEGRAM_API_IP="${TELEGRAM_API_IP:-149.154.167.220}"
|
TELEGRAM_API_IP="${TELEGRAM_API_IP:-149.154.167.220}"
|
||||||
|
|
|
||||||
|
|
@ -1,171 +0,0 @@
|
||||||
#!/usr/bin/env bash
|
|
||||||
# External uptime watchdog for gendesign (#75 B6-1, lightweight fallback).
|
|
||||||
#
|
|
||||||
# WHY THIS EXISTS alongside Uptime Kuma (docker-compose.uptime.yml): Kuma runs
|
|
||||||
# ON the prod VM, so if the whole VM dies it can't alert. This script is meant to
|
|
||||||
# run from cron on a DIFFERENT host (your laptop, a tiny free-tier box, Beget
|
|
||||||
# shared-host cron) and hit the PUBLIC URLs over the internet — last-resort
|
|
||||||
# "весь хост лёг" detection. Kuma covers rich per-endpoint/SSL/latency monitoring;
|
|
||||||
# this covers the case Kuma structurally can't.
|
|
||||||
#
|
|
||||||
# Self-contained: only needs `curl` + `bash`. No docker, no repo checkout.
|
|
||||||
# State (last-known status per check) lives in a file so we alert on TRANSITIONS
|
|
||||||
# (up→down, down→up) — not every run — to avoid Telegram spam.
|
|
||||||
#
|
|
||||||
# Usage (cron — note `bash`, not a bare path, so a missing +x bit can't break it):
|
|
||||||
# * * * * * bash /path/to/uptime-healthcheck.sh >> /var/log/gendesign-uptime.log 2>&1
|
|
||||||
#
|
|
||||||
# Telegram alerting — set these in an env file (NOT in git, chmod 600):
|
|
||||||
# TELEGRAM_BOT_TOKEN=123456:ABC...
|
|
||||||
# TELEGRAM_CHAT_ID=123456789
|
|
||||||
# Default env path: /etc/default/gendesign-uptime (override via UPTIME_ENV_FILE).
|
|
||||||
# A redacted template lives at ops/gendesign-uptime.default.example.
|
|
||||||
# Without a token set, the script still logs up/down but sends no alert.
|
|
||||||
|
|
||||||
set -euo pipefail
|
|
||||||
|
|
||||||
# --- config (env-overridable) ---
|
|
||||||
UPTIME_ENV_FILE="${UPTIME_ENV_FILE:-/etc/default/gendesign-uptime}"
|
|
||||||
# shellcheck source=/dev/null
|
|
||||||
[[ -f "$UPTIME_ENV_FILE" ]] && source "$UPTIME_ENV_FILE"
|
|
||||||
|
|
||||||
BASE_URL="${BASE_URL:-https://gendsgn.ru}"
|
|
||||||
STATE_FILE="${STATE_FILE:-/var/tmp/gendesign-uptime-state}"
|
|
||||||
CURL_TIMEOUT="${CURL_TIMEOUT:-15}" # seconds per request (connect+read)
|
|
||||||
RETRIES="${RETRIES:-2}" # extra attempts before declaring DOWN
|
|
||||||
RETRY_SLEEP="${RETRY_SLEEP:-5}" # seconds between attempts
|
|
||||||
TELEGRAM_BOT_TOKEN="${TELEGRAM_BOT_TOKEN:-}"
|
|
||||||
TELEGRAM_CHAT_ID="${TELEGRAM_CHAT_ID:-}"
|
|
||||||
|
|
||||||
# Checks to probe. Format per line: "<label>|<path>|<expected_http_status>".
|
|
||||||
# Override the whole list via CHECKS env (same newline-separated format).
|
|
||||||
#
|
|
||||||
# DEFAULT = /health ONLY. WHY: this watchdog is UNAUTHENTICATED, and in the
|
|
||||||
# Caddyfile only /health (and /preview/*) are public — ALL /api/* sits behind
|
|
||||||
# the Basic-Auth gate (import caddy/users.caddy.snippet) and returns 401 to an
|
|
||||||
# anonymous client. So the issue's /api/v1/analytics/market-pulse and
|
|
||||||
# /parcels/{cad}/analyze monitors CANNOT be probed anonymously — they belong in
|
|
||||||
# Uptime Kuma, which can attach the Basic-Auth header (see README "Kuma monitors"
|
|
||||||
# in docker-compose.uptime.yml). Probing them here would false-alarm forever.
|
|
||||||
CHECKS="${CHECKS:-health|/health|200}"
|
|
||||||
|
|
||||||
log() { echo "[$(date -u +'%Y-%m-%dT%H:%M:%SZ')] $*"; }
|
|
||||||
|
|
||||||
# --- telegram (no-op if token/chat unset) ---
|
|
||||||
notify() {
|
|
||||||
local text="$1"
|
|
||||||
if [[ -z "$TELEGRAM_BOT_TOKEN" || -z "$TELEGRAM_CHAT_ID" ]]; then
|
|
||||||
log "NOTIFY (telegram disabled — no token/chat): $text"
|
|
||||||
return 0
|
|
||||||
fi
|
|
||||||
# #3059: путь до Telegram теряет соединения. Замер 26.08 с Poincare — 40
|
|
||||||
# подключений к ЗАКРЕПЛЁННОМУ (#3093) 149.154.167.220: 3 отказа (7.5%), все
|
|
||||||
# таймаутом на установке соединения; успешные при этом стабильны (0.14-0.17 с).
|
|
||||||
# Три остальных дата-центра Telegram с Selectel недостижимы вовсе, так что
|
|
||||||
# запасного адреса нет — потери на единственном рабочем неустранимы сетью.
|
|
||||||
#
|
|
||||||
# Раньше здесь был ОДИН curl, и `|| log WARN` означал, что каждый такой отказ
|
|
||||||
# ТЕРЯЕТ алерт целиком: уведомление о падении прода не приходит, остаётся
|
|
||||||
# строка в логе, который читают уже после аварии. Watchdog, который сам себя
|
|
||||||
# не может дозваться, — худший вид самоскрывающейся поломки: чем хуже дела,
|
|
||||||
# тем вероятнее, что о них не сообщат.
|
|
||||||
#
|
|
||||||
# Цикл, а не `curl --retry`: ниже в этом же файле проверки уже повторяются
|
|
||||||
# ровно такой конструкцией (см. `for attempt in $(seq 1 ...)`), и семантика
|
|
||||||
# `--max-time` при ретраях curl зависит от версии. Здесь таймаут заведомо
|
|
||||||
# применяется к КАЖДОЙ попытке.
|
|
||||||
#
|
|
||||||
# Дубль вместо потери — осознанный размен: sendMessage не идемпотентен, но
|
|
||||||
# замер показал, что отказы происходят на СТАДИИ ПОДКЛЮЧЕНИЯ, до отправки
|
|
||||||
# запроса, так что повтор почти никогда не дублирует уже доставленное
|
|
||||||
# сообщение. А продублированный алерт безвреден, пропущенный — нет.
|
|
||||||
local attempt
|
|
||||||
for attempt in 1 2 3; do
|
|
||||||
if curl -fsS --max-time "$CURL_TIMEOUT" \
|
|
||||||
-X POST "https://api.telegram.org/bot${TELEGRAM_BOT_TOKEN}/sendMessage" \
|
|
||||||
-d "chat_id=${TELEGRAM_CHAT_ID}" \
|
|
||||||
${TELEGRAM_TOPIC_ID:+-d "message_thread_id=${TELEGRAM_TOPIC_ID}"} \
|
|
||||||
-d "disable_web_page_preview=true" \
|
|
||||||
--data-urlencode "text=${text}" \
|
|
||||||
>/dev/null 2>&1; then
|
|
||||||
[[ "$attempt" -gt 1 ]] && log "telegram sendMessage: доставлено с попытки ${attempt}"
|
|
||||||
return 0
|
|
||||||
fi
|
|
||||||
[[ "$attempt" -lt 3 ]] && sleep "${NOTIFY_RETRY_DELAY:-2}"
|
|
||||||
done
|
|
||||||
log "WARN: telegram sendMessage failed — 3 попытки подряд, алерт НЕ ДОСТАВЛЕН"
|
|
||||||
}
|
|
||||||
|
|
||||||
# --- state helpers (last status per check) ---
|
|
||||||
prev_status() {
|
|
||||||
local label="$1"
|
|
||||||
[[ -f "$STATE_FILE" ]] || { echo "unknown"; return; }
|
|
||||||
# Line format: "<label> <status>". grep the latest for this label.
|
|
||||||
local v
|
|
||||||
v="$(grep -E "^${label} " "$STATE_FILE" 2>/dev/null | tail -1 | awk '{print $2}')"
|
|
||||||
echo "${v:-unknown}"
|
|
||||||
}
|
|
||||||
|
|
||||||
set_status() {
|
|
||||||
local label="$1" status="$2" tmp
|
|
||||||
tmp="$(mktemp)"
|
|
||||||
# Drop any prior line for this label, then append the fresh one.
|
|
||||||
if [[ -f "$STATE_FILE" ]]; then
|
|
||||||
grep -vE "^${label} " "$STATE_FILE" > "$tmp" 2>/dev/null || true
|
|
||||||
fi
|
|
||||||
echo "${label} ${status}" >> "$tmp"
|
|
||||||
mv "$tmp" "$STATE_FILE"
|
|
||||||
}
|
|
||||||
|
|
||||||
# --- probe one URL with retries; echoes "up" or "down code=NNN" ---
|
|
||||||
probe() {
|
|
||||||
local url="$1" expect="$2" attempt code
|
|
||||||
# NB: NO `-f` here. `-w %{http_code}` always prints exactly a 3-digit status
|
|
||||||
# (or 000 on connect/timeout failure), so we judge by the code ourselves and
|
|
||||||
# never need a `|| echo` fallback (which previously concatenated → "401000").
|
|
||||||
for attempt in $(seq 1 "$((RETRIES + 1))"); do
|
|
||||||
code="$(curl -sS -o /dev/null -w '%{http_code}' --max-time "$CURL_TIMEOUT" "$url" 2>/dev/null)"
|
|
||||||
code="${code:-000}"
|
|
||||||
if [[ "$code" == "$expect" ]]; then
|
|
||||||
echo "up"
|
|
||||||
return 0
|
|
||||||
fi
|
|
||||||
[[ "$attempt" -le "$RETRIES" ]] && sleep "$RETRY_SLEEP"
|
|
||||||
done
|
|
||||||
# Report the last code seen for the alert body.
|
|
||||||
echo "down code=${code}"
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
# --- run ---
|
|
||||||
overall_rc=0
|
|
||||||
while IFS= read -r line; do
|
|
||||||
[[ -z "$line" ]] && continue
|
|
||||||
label="${line%%|*}"
|
|
||||||
rest="${line#*|}"
|
|
||||||
path="${rest%%|*}"
|
|
||||||
expect="${rest##*|}"
|
|
||||||
url="${BASE_URL}${path}"
|
|
||||||
|
|
||||||
result="$(probe "$url" "$expect")"
|
|
||||||
now="up"
|
|
||||||
[[ "$result" == up ]] || now="down"
|
|
||||||
prev="$(prev_status "$label")"
|
|
||||||
|
|
||||||
if [[ "$now" == "up" ]]; then
|
|
||||||
log "OK $label ($url)"
|
|
||||||
if [[ "$prev" == "down" ]]; then
|
|
||||||
notify "✅ RECOVERED: gendesign $label is back UP ($url)"
|
|
||||||
fi
|
|
||||||
else
|
|
||||||
overall_rc=1
|
|
||||||
log "DOWN $label ($url) — $result"
|
|
||||||
# Alert on transition into down (or first-ever observation that is down).
|
|
||||||
if [[ "$prev" != "down" ]]; then
|
|
||||||
notify "🔴 DOWN: gendesign $label — ${url} (${result#down }). $(date -u +'%Y-%m-%dT%H:%M:%SZ')"
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
set_status "$label" "$now"
|
|
||||||
done <<< "$CHECKS"
|
|
||||||
|
|
||||||
exit "$overall_rc"
|
|
||||||
Loading…
Add table
Reference in a new issue