How a change reaches the running site and how you prove it did. Load it before saying "deployed", "live", "check it now"; when a page looks stale or blank after a reload; when a build "passed" but nothing changed; and when you are about to write your own ssh/scp instead of the project transport. The rule that changes behaviour: a build log proves nothing — the deployment is proven by the commit in /api/health matching your hash AND the port being held by a pm2 descendant whose uptime grows. A...
Installs into .claude/skills of the current project.
Sponsors
SSkills DirectorySkills Directory
Know which skills are safe — weekly.
SSkills DirectorySkills Directory
Is that skill safe to install?
Are you the author of Use Deploy?
Add the live security badge to your README. It updates with every re-scan.
[](https://www.skillsdirectory.com/skills/fractera-use-deploy)
---
name: use-deploy
description: >
How a change reaches the running site and how you prove it did. Load it before saying "deployed",
"live", "check it now"; when a page looks stale or blank after a reload; when a build "passed" but
nothing changed; and when you are about to write your own ssh/scp instead of the project transport.
The rule that changes behaviour: a build log proves nothing — the deployment is proven by the commit
in /api/health matching your hash AND the port being held by a pm2 descendant whose uptime grows.
Also: when a change needs no deploy at all, and the four traps that have cost time here.
---
# use-deploy
> Informational, not binding. **Know a better way for the case in front of you — do it your way and
> say so.** You are trusted with the creative work on this project.
## 1. Сначала спроси: а нужна ли сборка
| Что изменил | Что делать |
|---|---|
| настройки в `APP-CONFIG` / `PLATFORM-CONFIG` / `DESIGN-CONFIG` / `PRODUCTS-CONFIG` | **ничего**: приложение читает их в рантайме на каждый запрос |
| контент, страницы, компоненты, логика | доставка + сборка |
| сборочное значение (`NEXT_PUBLIC_*`, набор языков, ключи для клиента) | доставка + сборка **обязательно**: значение запекается в момент `next build`, правка файла без пересборки не делает ничего |
Половина «деплоев» здесь не нужна вовсе. Начинать с этого вопроса дешевле, чем ждать сборку.
## 2. Один путь на сервер
Транспорт — четыре скрипта в `scripts/server/` (их таблица — в `CLAUDE.md`, раздел «The server»).
Свой `ssh`/`scp` не изобретать: у путей проекта есть скобки (`app/[lang]/(publicLayer)/…`),
и каждая — лишний слой экранирования, на котором команда рвётся молча.
**Обычный день — одна команда:**
```
bash scripts/server/deploy.sh app/[lang]/page.tsx lib/products
```
Доставка → замок → сборка с меткой коммита → `pm2 reload` → сверка. Без путей — только пересборка.
Отдельные скрипты нужны редко: `copy.sh` — положить и ничего не собирать, `run.sh` — посмотреть что-то
на сервере, `status.sh` — прочитать состояние, ничего не меняя.
## 3. 🔒 Два режима сборки — и владелец видит разницу
| Есть `FRACTERA_DEPLOY_SECRET` | Кто собирает | Чем платишь |
|---|---|---|
| да | **панель** | ничем: её очередь, журнал развёртываний, откат на последнюю рабочую сборку |
| нет | мы по SSH | быстрее, но **журнал панели останется со старой записью** — владелец увидит в подвале не твою сборку |
Выбор автоматический, но последствие называй вслух, когда собираешь напрямую: подвал сайта — то, по
чему владелец судит, доехала ли работа.
🔒 **Секрет решает, КТО собирает, а не КАК ты туда попадёшь.** Дверь сборки панели —
`http://localhost:3002/api/deploy`, то есть localhost **самого сервера**, и добираешься ты до неё по
SSH в обоих режимах. `FRACTERA_DEPLOY_SECRET` без `FRACTERA_SSH_*` не работает: читается как «канал
есть», а его нет. → §6a
## 4. 🔒 Чем доказывается доставка
Два пруфа из разных плоскостей (закон — в `use-testing`):
1. **`commit` в `/api/health` равен твоему хэшу** — отвечает именно твоя сборка;
2. **`status.sh` даёт 0** — порт держит pm2 или его потомок, uptime растёт между замерами.
🔒 **Лог сборки доказательством не является.** «Compiled successfully», «200 OK», «хэш нашёлся в
`.next`» читаются одинаково и когда работа доехала, и когда нет.
🔒 **`"commit": null` в `/api/health` — сборка шла БЕЗ метки** (поймано прогоном 2026-08-22 на живом
сервере). Так выглядит ручной `npm run build` без `NEXT_PUBLIC_GIT_COMMIT`: приложение работает, а
первый пруф недоступен — сказать, какая сборка отвечает, нечем. Лечится пересборкой с меткой; в
`deploy.sh` она ставится сама, поэтому ручной сборки и стоит избегать.
## 5. Четыре ловушки, каждая уже оплачена
- **Код возврата берётся у `npm`, а не у конвейера.** `npm run build | tail` печатает код `tail` — то
есть всегда ноль. Упавшая по типам сборка так выглядит успешной, а на сервере остаётся старый `.next`.
- **`online` в `pm2 list` не значит ничего.** Процесс в вечном рестарте выглядит так же; смотри
счётчик рестартов и растущий uptime.
- **Сирота на порту = белый экран.** Порт держит процесс прошлой сессии, pm2 падает с `EADDRINUSE`, а
отвечает старая сборка — её HTML ссылается на чанки, которых после пересборки уже нет. Лечение:
`pm2 stop` → `fuser -k -n tcp <порт>` → порт пуст → `pm2 start`. Слушатель — потомок ЛЮБОЙ глубины,
идти вверх по `ppid`.
- **Твоя сборка и сборка панели сталкиваются.** Их разводит `/tmp/fractera-deploy.lock`; `deploy.sh`
его соблюдает, ручной `npm run build` — нет.
## 6. Граница: что доставка не заменяет
Правишь только `/opt/fractera/app`. **Развёртывание стирает слот тем же `rm -rf`, что ставит платформу
заново** — через развёртывание проект переносит только `push` в репозиторий владельца. Значит доставка
файлами хороша, чтобы увидеть работу сейчас, и не хороша как способ «сохранить»: не отправил — потерял
при первом же развёртывании.
## 6a. 🔒 Нет `FRACTERA_SSH_*` — это НЕ «доступа нет». Это «кнопку ещё не нажали»
✗ **Оплачено часом поисков владельца 2026-08-24.** Прежний текст здесь говорил «второй канал закрыт,
обходить нечего» — и все, включая федерального агента, заключили, что доставки не существует. На самом
деле дверь выдачи ключа была построена и стояла на сервере, а **кнопки к ней не построил никто**.
🔒 **Звено ровно одно, и оно проверяется одной строкой:** есть ли в `.env.local` строка
`FRACTERA_SSH_KEY_B64`. Выгрузка окружения **сама заводит ключ** на сервере, если его ещё нет, и кладёт
приватную половину в тот же файл. Файл ключа на диске не нужен: `run.sh` разворачивает его в
`.fractera-ssh/` с правами `600` при первом обращении.
**Нет строки** — значит владелец не скачивал `.env.local` с тех пор, как панель получила эту сборку.
Не «доступа нет», а «файл устарел».
✗ **Первая версия этой правки просила скачать ключ ОТДЕЛЬНОЙ кнопкой и положить его руками в папку.**
Владелец отклонил: четыре ручных действия там, где хватает одного. Не предлагай ему переносить файлы —
предлагай нажать одну кнопку.
### Что сказать владельцу — одной фразой
Не «нет доступа» и не «впишите переменную». Продиктуй ему это:
> Откройте панель → «Переменные окружения» → нажмите кнопку **`.env.local`** и сохраните файл в корень
> проекта. Всё, больше ничего делать не нужно: ключ доступа заводится сам и едет внутри этого файла.
Панель показывает состояние словами прямо под кнопками: «ключ выдан» или «ключ ещё НЕ выдан».
Не видно ни кнопки, ни строки состояния — панель на сервере старой сборки, скажи об этом владельцу.
**Отзыв.** Ключ отзывается той же дверью методом `DELETE`; прежний перестаёт открывать сервер
немедленно. Сам не отзывай — это решение владельца.
🛑 **Второй транспорт в обход `deploy.sh` не изобретать** ни при каком из трёх обрывов.
## 7. Чего не делать
- Не говорить «развёрнуто», не получив оба пруфа.
- Не лечить слот вместо источника: правка живёт до первого развёртывания.
- Не запускать `next start` руками — так рождаются сироты на порту.
- Не пересказывать таблицу скриптов из инструкции: она там, здесь порядок и доказательства.