# Orchestrator Deploy Hook

`scripts/orchestrator-deploy-hook.sh` — хост-скрипт деплоя orchestrator с health-чеком и авто-rollback.

## Как работает

### Режим `--deploy` (по умолчанию)

1. **Захват текущего образа** — до рестарта записывает ID образа работающего контейнера в `$PREV_IMAGE_FILE` (best-effort, не падает если сервис не запущен).
2. **git pull** — обновляет код репозитория.
2b. **Build-once retag** (ORCH-036, BR-6) — если задан `$SOURCE_IMAGE`, хук ретегает его на `$TARGET_IMAGE` (`docker tag $SOURCE_IMAGE $TARGET_IMAGE`) и поднимает контейнер на этом образе через `up -d --no-build`. Это деплой РОВНО того образа, что прошёл staging, **без `docker build`**. Если `$SOURCE_IMAGE` не задан (дефолт) — шаг пропускается (обратная совместимость).
   - **Fail-closed провенанс-guard** (ORCH-058, Strategy B) — ПЕРЕД `docker tag`, если задан `$EXPECTED_REVISION`, хук сверяет OCI-лейбл `org.opencontainers.image.revision` у `$SOURCE_IMAGE` с `$EXPECTED_REVISION`. Несовпадение / пустой лейбл (`<no value>`) / ошибка inspect → лог + `exit 1` (FAILED → авто-rollback), **прод не трогается**. Не задан `$EXPECTED_REVISION` (дефолт) → проверка пропускается (обратная совместимость для не-self репозиториев).
3. **Рестарт контейнера** — `docker compose --profile $COMPOSE_PROFILE up -d --no-build $TARGET_SERVICE`.
4. **Health-цикл** — 10 попыток × 6с = до 60с. Критерий: HTTP 200 + тело содержит `"status":"ok"`.
   - **Успех** → `exit 0`, лог "Deploy SUCCESS".
   - **Провал** → авто-rollback (шаг 5).
5. **Авто-rollback** — восстанавливает образ из `$PREV_IMAGE_FILE`, рестарт, повторный health 5×3с.
   - Если восстановился → `exit 1` (деплой провалился, откат успешен).
   - Если и откат не помог → `exit 2` (критично).

### Режим `--build-staging` (ORCH-058, Strategy A)

Пересобирает **staging-образ** из провалидированного коммита и пересоздаёт 8501, чтобы артефакт, который мы валидируем, был РОВНО тем, что позже build-once ретегается в прод (инвариант `INV-FRESH`). Собирает/пересоздаёт **только staging (8501)** — никогда прод (8500).

1. `docker build --build-arg GIT_SHA=$GIT_SHA -t $TARGET_IMAGE $BUILD_CONTEXT` — пересборка из host-worktree валидированного коммита; `GIT_SHA` штампуется в OCI-лейбл `org.opencontainers.image.revision`.
2. `docker compose [--profile $COMPOSE_PROFILE] up -d --no-build $TARGET_SERVICE` — пересоздание staging на свежем образе.
3. Health-цикл 10×6с. Провал сборки/health → `exit 1`.
4. **`staging_check` против СВЕЖЕГО образа** (Strategy A, шаг 3 — ADR-001, AC-4) — после health хук запускает `docker exec $STAGING_CONTAINER python3 $STAGING_CHECK_PATH --base-url http://localhost:$TARGET_PORT --mode $STAGING_CHECK_MODE` (дефолт `--mode stub`, без LLM-трат). Запуск **внутри** staging-контейнера канонический (ORCH-048): suite читает реестр из собственного env контейнера, а `staging_check.py` берётся из bind-mount (`/repos/orchestrator/scripts/...`, не из образа). Это ровно тот артефакт, что позже build-once ретегается в прод → валидируем то, что промоутим (AC-4). PASS → `exit 0`; любой не-ноль (FAIL чека или safety-abort `ORCH_STAGING≠true`) → `exit 1`.

Запускается оркестратором на ребре `deploy-staging → deploy` (QG-под-чек `check_staging_image_fresh` → `rebuild_staging_image` пробрасывает явный staging-таргет, см. `INFRA.md`). Тот же контракт кодов выхода (0 = здоров **и** staging_check PASS).

### Режим `--rollback`

Вручную откатывает сервис на предыдущий образ из `$PREV_IMAGE_FILE`.

## Переменные окружения

| Переменная       | Дефолт                            | Описание                                      |
|------------------|-----------------------------------|-----------------------------------------------|
| `TARGET_SERVICE` | `orchestrator-staging`            | Имя docker-compose сервиса                    |
| `TARGET_PORT`    | `8501`                            | Порт health-check                             |
| `TARGET_IMAGE`   | `orchestrator-orchestrator-staging` | Имя образа для retag при rollback           |
| `COMPOSE_PROFILE`| `staging`                         | Docker compose profile (пусто = без профиля) |
| `PREV_IMAGE_FILE`| `$REPO/.deploy-prev-image-staging`| Файл для сохранения предыдущего образа        |
| `SOURCE_IMAGE`   | _(unset)_                         | Build-once (ORCH-036): провалидированный образ для retag на `$TARGET_IMAGE` перед рестартом (без rebuild). Не задан → шаг пропущен. |
| `EXPECTED_REVISION` | _(unset)_                      | Build-once (ORCH-058, Strategy B): ожидаемый git-SHA `$SOURCE_IMAGE` (лейбл `org.opencontainers.image.revision`). Задан → fail-closed guard перед `docker tag`. Не задан → проверка пропущена. |
| `GIT_SHA`        | _(unset)_                         | `--build-staging` (ORCH-058, Strategy A): коммит, штампуемый в OCI-лейбл `revision` при пересборке staging-образа. |
| `BUILD_CONTEXT`  | `$REPO`                           | `--build-staging`: docker build context (host-worktree валидированного коммита). |
| `STAGING_CONTAINER` | `$TARGET_SERVICE` (`orchestrator-staging`) | `--build-staging` (ORCH-058): контейнер, внутри которого `docker exec` запускает `staging_check`. |
| `STAGING_CHECK_PATH` | `/repos/orchestrator/scripts/staging_check.py` | `--build-staging` (ORCH-058): путь к `staging_check.py` внутри контейнера (bind-mount, не образ). |
| `STAGING_CHECK_MODE` | `stub`                        | `--build-staging` (ORCH-058): режим `staging_check` (`stub` — быстро, без LLM; `full-real` — дожидается аналитика). |
| `LOG`            | `/var/log/orchestrator/deploy-hook.log` | Лог-файл (fallback: `$REPO/deploy-hook.log`) |

> ⚠️ **Дефолт — всегда STAGING**. Прод активируется только явным переопределением env.

## Примеры запуска

### Staging (дефолт, безопасно)

```bash
cd /home/slin/repos/orchestrator
bash scripts/orchestrator-deploy-hook.sh --deploy
# или просто:
bash scripts/orchestrator-deploy-hook.sh
```

### Прод (осознанный шаг, Этап 5)

```bash
TARGET_SERVICE=orchestrator \
TARGET_PORT=8500 \
TARGET_IMAGE=orchestrator-orchestrator \
COMPOSE_PROFILE="" \
PREV_IMAGE_FILE=/home/slin/repos/orchestrator/.deploy-prev-image-prod \
bash scripts/orchestrator-deploy-hook.sh --deploy
```

### Прод build-once (ORCH-036) — ретег staging-образа, без rebuild

Так прод-деплой запускается **автоматически** исполняемым самодеплоем (Фаза B: `ssh + setsid`, см. `INFRA.md`). Ключевое отличие — `SOURCE_IMAGE` указывает на провалидированный staging-образ, который ретегается на прод-тег:

```bash
SOURCE_IMAGE=orchestrator-orchestrator-staging \
TARGET_SERVICE=orchestrator \
TARGET_PORT=8500 \
TARGET_IMAGE=orchestrator-orchestrator \
COMPOSE_PROFILE="" \
PREV_IMAGE_FILE=/home/slin/repos/orchestrator/.deploy-prev-image-prod \
bash scripts/orchestrator-deploy-hook.sh --deploy
```

### Ручной rollback staging

```bash
bash scripts/orchestrator-deploy-hook.sh --rollback
```

## Коды выхода

| Код | Значение                                             |
|-----|------------------------------------------------------|
| `0` | Деплой успешен, сервис здоров                        |
| `1` | Деплой провалился; откат выполнен (или пропущен)     |
| `2` | Деплой провалился И откат тоже провалился (критично) |

## Логи

```
/var/log/orchestrator/deploy-hook.log
```

Каждая строка с UTC-таймстампом в формате `[2026-06-05T06:30:00Z]`.

## Разница с enduro-deploy-hook.sh

| Функция              | enduro-deploy-hook.sh | orchestrator-deploy-hook.sh |
|----------------------|-----------------------|-----------------------------|
| Захват PREV_IMG      | ✅                    | ✅                          |
| git pull             | ✅                    | ✅                          |
| Рестарт              | ✅                    | ✅                          |
| Health-цикл (60с)    | ❌                    | ✅ 10×6с                    |
| Авто-rollback        | ❌                    | ✅                          |
| Параметризация (env) | ❌ хардкод            | ✅ дефолт=staging           |
| Compose profile      | ❌                    | ✅ --profile staging        |
