Эксплуатация
Развёртывание
Unruin в бою: образ, состояние, секреты, выставление панели наружу и список проверок перед запуском.
Как держать Unruin в бою: один образ Docker (бинарник, restic, клиенты баз и
docker CLI), конфигурация из git, постоянный том с состоянием и секреты, поданные
при старте. Рядом читаются CONFIGURATION.md — каждое поле — и
RESTORE.md — восстановление после аварии.
Упрощённое устройство (2026-07): секреты — встроенное хранилище
kr://иUNRUIN_MASTER_PASSPHRASE; ни Vaultwarden сbw, ни rclone больше нет. См. SIMPLIFICATION.md.
Образ
Dockerfile собирает один статический бинарник и кладёт рядом всё, что тот
запускает: restic, клиенты баз (pg_dump/mysqldump) и docker CLI (для
источников docker_volume). Ни rclone, ни Node/Bitwarden CLI в нём больше нет.
Собственный restic образа — это же и тот бинарник, который Unruin доставляет на
цели источников-путей, где restic нет. Состояние живёт в /var/lib/unruin,
конфигурация читается из /config.
docker build -t unruin:latest .
Быстрый старт (Docker Compose)
В репозитории лежит пример docker-compose.yml. Форма такая:
services:
unruin:
build: . # или image: ghcr.io/…/unruin:latest
restart: unless-stopped
command: ["serve", "/config/jobs.yaml"]
volumes:
- ./config:/config:ro # ваш репозиторий с конфигурацией
- unruin-state:/var/lib/unruin # история, ключи SSH, known_hosts, хранилище kr://
# - /var/run/docker.sock:/var/run/docker.sock:ro # только для источников docker_volume
environment:
- UNRUIN_MASTER_PASSPHRASE=${UNRUIN_MASTER_PASSPHRASE} # открывает секреты kr://
- BREAKGLASS_RESTIC_PW=${BREAKGLASS_RESTIC_PW} # секрет аварийного конверта, env://
# - UNRUIN_LISTEN=0.0.0.0:8080 # чтобы выставить панель (ниже)
# - UNRUIN_API_TOKEN=${UNRUIN_API_TOKEN}
# ports: ["127.0.0.1:8080:8080"] # публиковать только на loopback или за обратным прокси
volumes:
unruin-state:
unruin validate /config/jobs.yaml --preflight, запущенный внутри контейнера, —
это и есть решение «пускать или не пускать» перед тем, как отпустить планировщик:
он разрешает каждый секрет, соединяется по SSH с каждым сервером и убеждается, что
каждое хранилище достижимо.
Том с состоянием — сохраняйте его, это важно
В /var/lib/unruin (UNRUIN_STATE_DIR) лежат:
- история запусков в SQLite — что было, расписания, здоровье по хранилищам;
- закреплённые
known_hosts: приhost_key_policy: strictстёртыйknown_hostsозначает, что каждое соединение SSH будет жёстко падать, пока ключи не запишут заново; - хранилище секретов
kr://(secrets.kr), если вы им пользуетесь.
Держите его на долговечном именованном томе — не на эфемерном слое контейнера и не
в /tmp. Потеряете — потеряете историю запусков, закреплённые ключи хостов и все
секреты kr://. Значит, включите его в собственные копии или держите секреты
kr:// воспроизводимыми.
Конфигурация: из git или из панели
Две модели, по одной на развёртывание:
- Из git (в бою рекомендуется). Монтируйте
/configтолько на чтение.jobs.yamlживёт в git; чтобы что-то изменить, делаетеgit pullи перезапуск (docker compose up -d). Панель только читает и запускает, правка из браузера возвращает 403. Это конфигурация как код: видно в истории, можно обсудить, ничего не меняется на живую. - Из панели. Монтируйте
/configна запись и разрешите правку (loopback или заданныйUNRUIN_API_TOKEN). Тогда задания, серверы и хранилища заводятся, меняются и удаляются из браузера; каждое изменение проверяется и пишется обратно вjobs.yaml. Хорошо для машины с одним оператором; коммитьте файл в git время от времени, чтобы осталась история.
Правка выключена всегда, когда задан UNRUIN_READONLY, независимо от монтирования
и адреса.
Секреты в бою
Unruin держит ссылки, которые разрешает в момент запуска. Берите ту схему или
те схемы, что вам подходят, — смешивать можно свободно. То, чем они открываются,
подавайте переменными окружения (секретами Docker или k8s) и никогда не запекайте
секреты в образ или в jobs.yaml.
| Схема | Как завести | Чем открывается |
|---|---|---|
kr:// (встроенная, по умолчанию, проще всего) |
unruin secret set <имя> (значение со stdin) или вставить в панели — см. TESTING.md. Хранилище лежит на томе состояния. Поддерживает kr://имя#поле. |
UNRUIN_MASTER_PASSPHRASE |
env:// |
обычная переменная окружения | она сама |
file:// |
примонтированный файл-секрет (Docker, k8s) | — |
У kr:// меньше всего движущихся частей: ни внешнего сервера, ни сессии. Задать
UNRUIN_MASTER_PASSPHRASE, один раз положить ключи и пароли через unruin secret set — и всё, при каждом перезапуске хранилище открывается само. Размен
обычный: кто может прочитать это окружение, тот может расшифровать хранилище.
Правило про аварию: ключ аварийного хранилища держите на env://
(BREAKGLASS_RESTIC_PW), никогда в kr:// — он обязан пережить потерю самого
Unruin. И сохраняйте офлайн файл хранилища kr:// (secrets.kr в каталоге
состояния) вместе с UNRUIN_MASTER_PASSPHRASE: вдвоём они главный корень
ваших секретов, а потеря обоих означает ввод каждого секрета kr:// заново. См.
RESTORE.md.
Плашка ⚠ в панели загорается сразу, как только мастер-фраза kr:// отсутствует
или неверна, — чтобы плохое открытие было видно до того, как оно тихо уронит
задания.
Как выставить панель наружу
По умолчанию безопасно: serve слушает 127.0.0.1:8080 и отказывается
стартовать на адресе не-loopback без настроенного доступа. Чтобы дотянуться
издалека:
- Выберите способ входа (адрес не-loopback устраивает любой из них):
- Пароль (для людей). Задайте
UNRUIN_AUTH_PASSWORDи, по желанию,UNRUIN_AUTH_USER(по умолчаниюadmin). Панель покажет экран входа и до входа не покажет ничего; сессия едет в подписанной куке HttpOnly. Смените пароль — старые сессии перестанут работать. - Ключ (для скриптов, командной строки, MCP). Задайте
UNRUIN_API_TOKEN. Каждый запрос обязан нестиAuthorization: Bearer <token>. - Можно задать оба сразу: вход для людей, ключ для автоматики.
- Пароль (для людей). Задайте
- Задайте
UNRUIN_LISTEN=0.0.0.0:8080(или привяжитесь к закрытому интерфейсу) и опубликуйте порт. - Поставьте перед ним обратный прокси с TLS — ни кука, ни ключ не должны
ехать открытым текстом. За прокси заголовок
X-Forwarded-Proto: httpsсам делает куку сессииSecure.
Чтобы панель осталась строго смотрящей даже после входа, задайте
UNRUIN_READONLY.
Настроить с помощью ИИ: сервер MCP (unruin mcp) позволяет помощнику
заводить серверы, хранилища и задания и вести восстановление обычными словами —
см. MCP.md.
Надёжность без присмотра
Для машины, от которой вы уходите, задайте в jobs.yaml:
prune_schedule— демон сам применяет правила хранения (привилегированным доступом, в очереди по метке занятости); без этого хранилища растут вечно.run_timeoutиtimeoutу задания — зависшая цель будет убита и записана неудачной, вместо того чтобы держать задание (по умолчанию щедрые шесть часов).notifications.webhook— иначе сбой останется красной строкой в панели, на которую вы перестали смотреть. Подпишите задания наfailedиpartial.host_key_policy: strictдля настоящих серверов: один раз засейте каждый хост черезvalidate --preflight(доверие при первой встрече), после чего strict будет отвергать неизвестный или изменившийся ключ. Нужен постоянныйknown_hostsиз раздела выше.
Наименьшие права
По умолчанию единственный доступ access у хранилища делает всё, включая уборку.
Хотите строже — выдайте access только на дозапись (тогда взломанный VPS не
сможет удалять точки восстановления) и задайте необязательный prune_access,
который живёт только у оркестратора и на цель не уезжает, для forget и уборки.
Права такими нужно сделать в вашем объектном хранилище; Unruin сам никогда не
отдаёт цели доступ, умеющий удалять. Не задан prune_access — уборка идёт через
access.
Переменные окружения
| переменная | зачем |
|---|---|
UNRUIN_MASTER_PASSPHRASE |
открывает встроенное хранилище секретов kr:// |
UNRUIN_LISTEN |
адрес панели и API (по умолчанию 127.0.0.1:8080; не-loopback требует доступа) |
UNRUIN_API_TOKEN |
требовать Authorization: Bearer <token>; для скриптов, командной строки и MCP; годится как доступ для адреса не-loopback |
UNRUIN_AUTH_PASSWORD |
включает вход по имени и паролю; годится как доступ для адреса не-loopback |
UNRUIN_AUTH_USER |
имя для входа (по умолчанию admin) |
UNRUIN_API_URL |
(для unruin mcp) куда мост MCP ходит за serve (по умолчанию http://127.0.0.1:8080) |
UNRUIN_READONLY |
запрещает любые правки конфигурации независимо от адреса |
UNRUIN_STATE_DIR |
где состояние (в образе по умолчанию /var/lib/unruin) |
UNRUIN_TICK |
шаг планировщика (по умолчанию 1m) |
UNRUIN_RESTIC_BIN |
переопределить путь к restic у оркестратора |
UNRUIN_NO_RESTIC_PUSH |
1 — не доставлять restic на цели источников-путей |
UNRUIN_RESTIC_PUSH_BIN |
какой бинарник restic отправлять на цели вместо собственного |
то, на что ссылается env:// |
любые офлайновые секреты, например BREAKGLASS_RESTIC_PW |
Здоровье и наблюдение
GET /api/healthотдаёт количество заданий, упавшие задания, хранилища, до которых давно не доходит, иsecrets_ok— снимайте его метрикой или наведите проверку доступности.- Состояния «до этого хранилища не доходит N запусков» и оговорочные или неудачные запуски уходят в настроенный вебхук.
- Журнал идёт в stdout: по строке на каждый запуск по расписанию, ручной запуск и уборку.
Обновление
Бинарник ничего не держит за пределами тома состояния:
docker compose pull # или: docker build -t unruin:latest .
docker compose up -d # контейнер пересоздаётся; том состояния остаётся
При старте Unruin приводит в порядок запуски, оставшиеся в состоянии running
после падения (с проверкой pid владельца, поэтому живой запуск из другого
процесса не трогается), и ничего разрушительного не делает. После правки
конфигурации прогоняйте unruin validate --preflight.
Выпуск образа (Gitea Actions)
.gitea/workflows/release.yml собирает и публикует образ по тегу версии. Он
берёт те же учётные данные реестра, что и остальные репозитории этого владельца,
настроенные в Settings → Actions (на уровне репозитория или организации —
организационные наследуются):
- переменная
DOCKER_REGISTRY— полный путь образа, напримерgit.dreep.ru/smirnar/unruin; - секрет
DOCKER_USERNAME— учётная запись, которой принадлежит ключ; - секрет
DOCKER_TOKEN— её ключ или пароль (с правом записи пакетов).
Ещё нужны включённые Actions и зарегистрированный раннер с Docker. Дальше:
git tag v1.2.3 && git push origin v1.2.3
Это публикует <gitea>/<владелец>/unruin:1.2.3 и передвигает :latest. На
каждом сервере закрепите версию (или следите за :latest) в
docker-compose.yml, затем docker compose pull && docker compose up -d. Том
состояния переживает подмену. Одна архитектура, linux/amd64 — в образе лежит
статический docker CLI под amd64.
Сайт с документацией собирается отдельным потоком
(.gitea/workflows/site.yml) в собственный образ, со своими тегами; продукт и
сайт выпускаются независимо.
Реестр внутри Gitea — не единственный вариант. Если нужно место, куда образ
просто кладут, без гита вокруг, — раскладка на одном хранилище и nginx описана в
REGISTRY.md; DOCKER_REGISTRY тогда указывает туда.
Список проверок перед боем
- Состояние на долговечном томе, который вы копируете (история,
known_hosts,kr://). - Секреты поданы окружением или хранилищем секретов, а не лежат в образе или
jobs.yaml. - Ключ аварийного хранилища на
env://и сохранён офлайн. - Панель: либо loopback, либо доступ и TLS за прокси.
- Заданы
prune_schedule,run_timeoutиnotifications.webhook. -
host_key_policy: strict, и каждый хост засеян. - Задан
access(только на дозапись плюс отдельныйprune_access, если хотите разделение). -
validate --preflightпроходит. - Восстановление проверено от начала до конца (RESTORE.md).