UNRUIN
документация · v1.0

Первые шаги

Подключить сервер (пользователь для копий и ключ SSH)

Пользователь для копий, ключ SSH и минимум прав, при котором всё ещё работает.

Как подготовить хост, чтобы Unruin мог с него копировать. Unruin работает без агентов: оркестратор ходит на каждую цель по обычному SSH пользователем user сервера, ключом, на который указывает ssh_key. Ставить restic на хост больше не нужно — для источников-путей (папки, docker-тома) Unruin сам доставит туда свой статический restic по SSH, когда тот впервые понадобится. Единственное, что требуется от хоста, — чтобы пользователь SSH мог читать то, что копируется.

Доставка restic: перед копированием источника-пути Unruin щупает цель; если годного restic там нет, он передаёт свой в ~/.cache/unruin/restic (у пользователя, без root) и работает им. Когда restic уже есть — всё это незаметно. Отказаться: UNRUIN_NO_RESTIC_PUSH=1; отправить другой бинарник: UNRUIN_RESTIC_PUSH_BIN. См. SIMPLIFICATION.md.

Это делается один раз на хост, после чего сервер заводится (в конфигурации или в панели), а его ключ хоста записывается через validate --preflight.

Наименьшие права. Не тянитесь к root рефлекторно. Копии базы нужны роль только на чтение и обычный непривилегированный пользователь — и всё. Повышенные права на чтение нужны только копиям файлов, принадлежащих root, да и там возможность файла лучше, чем root (см. §4).


0. Что пользователь SSH должен уметь

Тип источника Где работает Что нужно пользователю SSH restic на цели?
postgres дамп на цели, restic --stdin на оркестраторе запускать pg_dump для базы (роль только на чтение) нет
mysql дамп на цели запускать mysqldump --single-transaction (SELECT + LOCK/RELOAD) нет
command команда на цели то, что нужно самой команде нет
files restic на цели читать путь доставится сам
docker_volume restic на цели docker volume inspect и читать данные тома доставится сам

Доступы к хранилищу restic (ключи только на дозапись и ключ шифрования) класть на хост не нужно: Unruin подставляет их в удалённый вызов restic в момент запуска, из своего хранилища секретов. У цели вообще бывают только доступы на дозапись; привилегированные доступы для уборки оркестратор не отдаёт никому.


1. Сделать отдельную пару ключей SSH (на оркестраторе)

Один ключ на хост держит радиус поражения маленьким: взломанный хост не сможет притвориться остальными. Без парольной фразы: Unruin работает без присмотра, а закрытый ключ лежит зашифрованным в его хранилище секретов — там он и защищён.

sh
ssh-keygen -t ed25519 -C "unruin@orchestrator" -f ~/unruin-keys/<host>
# получатся ~/unruin-keys/<host> (закрытый) и <host>.pub (открытый)

Дальше вы:

  • положите открытый ключ в authorized_keys пользователя для копий на цели (шаг 3);
  • отдадите закрытый ключ хранилищу секретов Unruin (шаг 5) и удалите копию с диска.

2. Завести на цели пользователя для копий

Запускается на каждом хосте — под root или через ваш обычный административный доступ. Отдельная учётная запись с заблокированным паролем, только по ключу:

sh
sudo useradd --create-home --shell /bin/bash unruin
sudo passwd --lock unruin            # входа по паролю нет, только ключ

Хочется совсем без оболочки? --shell /usr/sbin/nologin подойдёт, только если все источники этого хоста — источники-пути: restic по SSH работает и без интерактивной оболочки. Источники-потоки запускают команду в оболочке, поэтому, если вы копируете хоть одну базу или command, оставьте /bin/bash.


3. Положить открытый ключ на цель

sh
sudo install -d -m 700 -o unruin -g unruin /home/unruin/.ssh
# вставьте содержимое <host>.pub:
sudo tee /home/unruin/.ssh/authorized_keys >/dev/null <<'KEY'
ssh-ed25519 AAAA...ваш-открытый-ключ... unruin@orchestrator
KEY
sudo chmod 600 /home/unruin/.ssh/authorized_keys
sudo chown -R unruin:unruin /home/unruin/.ssh

Можно укрепить — прибить исходный адрес оркестратора, чтобы ключ работал только с вашей управляющей машины. Допишите ограничения перед строкой ключа:

from="203.0.113.10",no-port-forwarding,no-X11-forwarding,no-agent-forwarding ssh-ed25519 AAAA... unruin@orchestrator

(Не используйте command="…" и restrict без pty: Unruin запускает на хосте несколько разных команд, и принудительная команда или запрет pty сломают источники-потоки.)


4. Выдать только то право, которое нужно источнику

Возьмите строку, которая соответствует тому, что копируется с этого хоста.

База (postgres / mysql) — никакого root, никогда

Заведите принципала базы только на чтение и дайте пользователю unruin входить им без вопросов.

PostgreSQL — роль, которая читает всё и не меняет ничего:

sql
CREATE ROLE unruin LOGIN PASSWORD 'придумайте-стойкий';
GRANT pg_read_all_data TO unruin;          -- PG 14+; раньше: GRANT SELECT по схемам

Дайте пользователю системы входить без запроса пароля — одним из способов:

  • ~unruin/.pgpass (chmod 600): localhost:5432:app:unruin:придумайте-стойкий;
  • аутентификация peer: имя пользователя системы совпадает с ролью базы, ходим через локальный сокет.

Источник postgres запускает pg_dump <база>; хост, порт и пользователя задавайте через .pgpass и переменные PG*, либо возьмите источник command с в точности тем вызовом pg_dump, который вам нужен.

MySQL / MariaDB — минимум прав для согласованного дампа:

sql
CREATE USER 'unruin'@'localhost' IDENTIFIED BY 'придумайте-стойкий';
GRANT SELECT, LOCK TABLES, SHOW VIEW, EVENT, TRIGGER, RELOAD, PROCESS
  ON *.* TO 'unruin'@'localhost';

Положите доступ в ~unruin/.my.cnf (chmod 600), чтобы mysqldump читал его и пароль не оказывался в командной строке:

ini
[client]
user=unruin
password=придумайте-стойкий

Unruin всегда передаёт --single-transaction ради дампа, согласованного на момент времени в InnoDB. Это охранное правило: зелёного запуска над рваным дампом не бывает.

Файлы (files) — достаточно права читать; возможность необязательна

Пользователю SSH нужно только читать путь. Свой restic Unruin доставит на хост сам, ставить нечего — restic в PATH не нужен вовсе. Для путей, которые пользователь и так читает, на этом всё.

Файлы, принадлежащие root (необязательный приём): если путь читает только root, а user: root вам не хочется, выдайте доставленному restic ровно ту возможность, которая ему нужна, чтобы обойти права на чтение. Бинарник Unruin кладёт в ~/.cache/unruin/restic, туда возможность и ставится — после того как первый запуск его создаст:

sh
sudo setcap cap_dac_read_search=+ep /home/unruin/.cache/unruin/restic

cap_dac_read_search даёт обход только для чтения: ни записи, ни прочих возможностей root. После замены доставленного бинарника её надо поставить заново. Хотите вести restic сами? Поставьте его в PATH, задайте UNRUIN_NO_RESTIC_PUSH=1, повесьте setcap на свой бинарник — Unruin возьмёт его. Иначе задайте этому серверу user: root.

Docker-тома (docker_volume) — нужен Docker и чтение тома

Unruin зовёт docker volume inspect, а затем restic по найденной точке монтирования внутри /var/lib/docker/volumes, которая принадлежит root. Добавьте пользователя в docker и учтите, что это на хосте фактически равно root (сам restic доставляется, ставить нечего):

sh
sudo usermod -aG docker unruin       # группа docker ≈ root; можно сразу взять root
# данные тома принадлежат root; дайте доставленному restic обход на чтение (после первого запуска):
sudo setcap cap_dac_read_search=+ep /home/unruin/.cache/unruin/restic

Для контейнера с состоянием лучше взять источник-поток из базы, чем копировать горячий том: такая копия согласована только на момент падения. Холодное копирование тома (остановить, снять, поднять) пока не сделано.


5. Отдать закрытый ключ Unruin и удалить копию с диска

Положите закрытый ключ в то хранилище секретов, которым вы пользуетесь; в jobs.yaml попадает только ссылка, никогда сам ключ. Каким бы способом вы ни воспользовались, ключ обязан быть:

  • закрытым (не .pub);
  • без парольной фразы — Unruin соединяется в режиме BatchMode=yes и не может её ввести. Снять: ssh-keygen -p -N '' -f <ключ>;
  • настоящим многострочным PEM, а не слепленным в одну строку. Перевод строк CRLF и лишний или недостающий перевод в конце Unruin исправит сам, прежде чем отдать ключ ssh, но раскатать обратно тело, у которого переводы строк вырезали, он не сможет — оно так и останется сломанным (Load key: error in libcrypto, см. разбор поломок).

Выберите один из трёх способов.

Способ Кому подходит Что нужно
kr:// (командная строка) у себя, без внешнего сервера секретов (по умолчанию) UNRUIN_MASTER_PASSPHRASE и постоянный том состояния
Вставить в панели машина с одним оператором, работа мышкой разрешённая правка (не только чтение) и UNRUIN_MASTER_PASSPHRASE
file:// бой, Docker, k8s примонтированный файл с ключом

file:// — примонтированный файл (в бою рекомендуется)

Самый устойчивый к порче: ключ остаётся настоящим файлом и не проходит ни через переменную окружения, ни через шаг set, поэтому переводы строк потерять негде. Монтируйте секретом Docker или k8s и ссылайтесь на путь:

yaml
# docker-compose.yml
    secrets: [ <host>_key ]
secrets:
  <host>_key: { file: ./secrets/<host>_key }   # сам файл закрытого ключа, режим 600
yaml
ssh_key: "file:///run/secrets/<host>_key"

kr:// — встроенное шифрованное хранилище (командная строка)

Внешнего сервера не нужно. Сохраняйте ключ дословно из файла (< keyfile, и никогда echo или вставка — они портят переводы строк), изнутри контейнера, чтобы он лёг на постоянный том состояния:

sh
export UNRUIN_MASTER_PASSPHRASE=…                     # та же, что у serve/run
unruin secret set ssh/<host> < ~/unruin-keys/<host>  # значение со stdin, не из argv
shred -u ~/unruin-keys/<host>                          # убрать открытую копию
# ссылка:  ssh_key: "kr://ssh/<host>"

Вставить в панели — поле «Закрытый ключ SSH» при добавлении машины

В ⚙ Хозяйство → Добавить машину вставьте многострочный PEM целиком в поле ключа. Панель положит значение в хранилище kr:// (зашифрованным, только на запись — обратно не прочитать) и сама напишет ssh_key: "kr://ssh/<имя-машины>"; сырой ключ до jobs.yaml не доходит. Что должно быть: правка разрешена (loopback или UNRUIN_API_TOKEN; под UNRUIN_READONLY — нет) и задана UNRUIN_MASTER_PASSPHRASE. Вставляйте весь блок -----BEGIN…END----- с настоящими переводами строк: буфер обмена, схлопнувший их или добавивший CRLF, даёт ту самую ошибку libcrypto.


6. Завести сервер и записать его ключ хоста

Добавьте сервер в jobs.yaml (или в панели, ⚙ Хозяйство → Добавить сервер, когда правка разрешена):

yaml
servers:
  <host>:
    host: 10.0.0.11
    port: 22
    user: unruin               # учётная запись из шага 2 (или root)
    ssh_key: "kr://ssh/<host>"  # ссылка из шага 5
    # host_key_policy: tofu     # по умолчанию; первое соединение доверяет ключу и запоминает его

Затем докажите, что всё работает, и закрепите ключ хоста:

sh
unruin validate jobs.yaml --preflight
# разрешит ключ, соединится по SSH (запишет known_hosts при первой встрече), пощупает хранилища

--preflight — это и есть ответ на вопрос «а это вообще пойдёт?»: неверный путь к ключу, недоступный хост или недостающее право в базе падают сейчас, а не в три часа ночи. В панели кнопка Проверить связь в форме сервера запускает ту же проверку.

Перейти на strict (в бою рекомендуется): после того как живая проверка записала ключ, сверьте отпечаток с хостом по другому каналу и задайте host_key_policy: strict — глобально или для сервера. Тогда неизвестный или изменившийся ключ станет жёстким отказом, а не тихим новым доверием.


7. Первая копия

sh
unruin run jobs.yaml <job>        # разово; видно копирование → рассылку → контрольное восстановление
unruin serve jobs.yaml            # или пусть планировщик сам по расписанию

Шпаргалка — весь скрипт на хост (источник-поток из базы)

Обычный случай: пользователь без root, который копирует базу Postgres. Запускается на цели; ключ SSH был сделан на оркестраторе в шаге 1.

sh
sudo useradd --create-home --shell /bin/bash unruin
sudo passwd --lock unruin
sudo install -d -m 700 -o unruin -g unruin /home/unruin/.ssh
echo 'ssh-ed25519 AAAA... unruin@orchestrator' | \
  sudo tee /home/unruin/.ssh/authorized_keys >/dev/null
sudo chmod 600 /home/unruin/.ssh/authorized_keys
sudo chown -R unruin:unruin /home/unruin/.ssh

sudo -u postgres psql -c "CREATE ROLE unruin LOGIN PASSWORD 'pw';"
sudo -u postgres psql -c "GRANT pg_read_all_data TO unruin;"
printf 'localhost:5432:app:unruin:pw\n' | sudo -u unruin tee /home/unruin/.pgpass >/dev/null
sudo -u unruin chmod 600 /home/unruin/.pgpass

Разбор поломок

Load key "…": error in libcryptoPermission denied (publickey)

Сохранённый закрытый ключ не разбирается. Unruin приводит в порядок переводы строк и хвостовой перевод, прежде чем записать временный файл для -i, и соединяется в BatchMode=yes без запросов — то есть CRLF и обрезанный хвостовой перевод он уже исправил, а OpenSSL всё равно отвергает тело ключа. Остаются причины: ключ слепили в одну строку (внутренние переводы потерялись, проходя через скаляр YAML, однострочную переменную окружения или echo), это по ошибке .pub, или он закрыт парольной фразойBatchMode её не ввести).

Разбираться надо внутри контейнера или на машине оркестратора — там та же libcrypto, которая отказывает:

sh
ssh-keygen -y -f /путь/к/ключу     # напечатал открытый ключ — хорошо; ошибка libcrypto — испорчен; спрашивает пароль — зашифрован
wc -l /путь/к/ключу                # должно быть > 1 (однострочный ключ — обычный виновник)
file /путь/к/ключу                 # "OpenSSH private key" хорошо; "with CRLF" плохо

Починить и сохранить заново, дословно:

sh
sed -i 's/\r$//' /путь/к/ключу             # убрать CRLF, если он есть
ssh-keygen -p -N '' -f /путь/к/ключу       # снять парольную фразу, если она есть
unruin secret set ssh/<host> < /путь/к/ключу   # сохраняет переводы строк (kr://)
unruin validate <cfg> --preflight

Многострочный PEM не переживает однострочную env:// — для ключей SSH берите kr:// или файл через file://.


Каждое поле — в CONFIGURATION.md, путь от нуля до копии — в GETTING-STARTED.md, восстановление после аварии — в RESTORE.md.

Бесплатно, целиком ваше, наружу ничего не уходит. Сайт статический: ни аналитики, ни куки, ни внешних запросов.