С чего всё началось
Первую версию я написал в 2024 году. Это был небольшой REST API на Go. Сервис принимал запрос с API-ключом и перезапускал один из юнитов systemd.
curl -X POST http://127.0.0.1:4035/api/v1.0/service \
-H "X-API-Key: $API_KEY" \
-H "Content-Type: application/json" \
-d '{"service": "dnl-hns", "method": "restart"}'
Список сервисов и действий, разрешённых для каждого из них, лежал в одном config.json. Из того же списка собирался белый список для sudo, так что через API нельзя было запустить ничего лишнего. За сервисом стоял простой rate limiter.
Для одного сервера и одного человека этого хватало. Ключ был один, и у кого он был, тот мог делать всё, что разрешено в конфиге. Дать другому человеку доступ к перезапуску только одного сервера было невозможно. На каждой машине крутилась своя копия со своим ключом, и управлять ими из одного места не получалось. К тому же ничего не логировалось, поэтому если сервис перезапускался посреди ночи, узнать, кто это сделал, было нельзя.
Что gorst умеет сейчас
Сейчас gorst состоит из двух частей.
- worker работает на каждом сервере и управляет его юнитами systemd;
- контрольный узел (
gorst-controlnode) хранит пользователей, политики и журнал действий, и все команды идут через него.
Контрольный узел решает, кто и что может делать и на каком сервере. Допустим, у вас есть сервер с nginx, и нужно, чтобы кто-то ещё мог его перезапускать, но без SSH и root. Вы создаёте политику, которая разрешает действие restart для nginx на этом сервере, и выдаёте её пользователю. Больше ему ничего не доступно. Перезапустить nginx он может из веб-панели, где у каждого сервиса видны только разрешённые ему действия, или через API по токену. Токен можно встроить в свои инструменты, например в бота для Telegram или Discord, который перезапускает сервис по команде из чата.

Как это устроено
Worker сам подключается к контрольному узлу, а узел к нему не подключается никогда. При старте worker открывает один исходящий gRPC-поток и ждёт по нему команды. Поэтому на серверах не нужно открывать порты, и машина за NAT работает так же, как VPS с публичным IP.
Соединение защищено mutual TLS. При первом запуске контрольный узел создаёт свой центр сертификации и дальше выдаёт сертификат каждому worker. Продлевают сертификаты worker сами. Если сервер больше не нужен или его ключ утёк, сертификат отзывается, и эта машина больше не сможет подключиться.
Установка
1. Сборка
Склонируйте репозиторий на сервер и соберите нужный ему бинарник. Контрольный узел и worker собираются отдельно, понадобится Go 1.27.1 или новее.
git clone https://github.com/happydez/gorst.git
cd gorst
# на сервере с контрольным узлом
CGO_ENABLED=0 go build -o bin/gorst-controlnode ./cmd/gorst-controlnode
# на каждом сервере с worker
CGO_ENABLED=0 go build -o bin/gorst ./cmd/gorst
Установочные скрипты в deploy/ сами берут бинарник из bin/, поэтому вручную ничего копировать не нужно.
2. Контрольный узел
Перед установкой сделайте свой конфиг рядом со скриптами. Если есть deploy/controlnode.yaml, установщик возьмёт его, а если нет, возьмёт конфиг .example.
cp deploy/controlnode.example.yaml deploy/controlnode.yaml
agent:
addr: "0.0.0.0:4040" # сюда подключаются worker
hosts: # имена и адреса, по которым worker будут подключаться
- cn.example.com
admin:
addr: "127.0.0.1:4050" # веб-панель и API
database:
dsn: "postgres://gorst:...@127.0.0.1:5432/gorst?sslmode=disable"
Имена из agent.hosts попадают в сертификат контрольного узла, и если worker подключится по другому имени, он не станет ему доверять. Панель лучше оставить на 127.0.0.1 за reverse proxy с HTTPS или дать ей отдельный сертификат в admin.tls. Без database.dsn контрольный узел тоже запустится, но при каждом перезапуске будет терять пользователей и журнал.
Теперь установите его и создайте администратора.
sudo bash deploy/install-controlnode.sh
sudo gorst-controlnode user add --admin --password '...' admin
Установщик создаёт системного пользователя gorst-cn, кладёт конфиг в /etc/gorst-controlnode/config.yaml, хранит центр сертификации в /var/lib/gorst-controlnode и запускает сервис. Логи можно посмотреть командой journalctl -u gorst-controlnode -f.
3. Worker на каждом сервере
Worker ставится так же. В его конфиге перечислены юниты, которыми gorst вообще разрешено управлять. Из этого списка установщик собирает белый список для sudo и проверяет его через visudo.
cp deploy/config.example.yaml deploy/config.yaml
services:
nginx:
actions: [status, reload, restart]
sudo bash deploy/install.sh
Конфиг окажется в /etc/gorst/config.yaml, а сервис будет работать от непривилегированного пользователя gorst. Если потом поменяете блок services, просто запустите установщик ещё раз. Конфиг он не тронет, а белый список для sudo пересоберёт.
4. Подключение к контрольному узлу
join-token обращается к API запущенного контрольного узла, а если у узла есть база, это API принимает только токен администратора. Поэтому сначала выпустите токен для учётной записи admin. Он показывается один раз, так что сохраните его.
sudo gorst-controlnode token create --name cli admin
С этим токеном запросите одноразовый токен для подключения. Вместе с ним выводится хеш сертификата CA.
gorst-controlnode join-token --admin <admin.addr> --token <токен администратора>
В --admin укажите адрес панели из admin.addr в конфиге контрольного узла. Если панель работает по HTTPS, добавьте --tls.
На worker обменяйте этот токен на сертификат.
sudo gorst join --controlnode cn.example.com:4040 --token ... --ca-hash sha256:...
gorst join создаёт ключ, получает подписанный сертификат и выводит блок controlnode. Команды от контрольного узла выполняются под локальной политикой, имя которой указано в этом блоке, поэтому сначала добавьте её в список policies в /etc/gorst/config.yaml. Токен ей не нужен, контрольный узел подтверждает себя сертификатом.
policies:
- name: controlnode
services:
nginx: ["*"]
Затем вставьте выведенный блок в конец файла и перезапустите worker.
controlnode:
addr: "cn.example.com:4040"
ca_cert: /etc/gorst/ca.pem
cert: /etc/gorst/worker.pem
key: /etc/gorst/worker-key.pem
policy: controlnode
sudo systemctl restart gorst
После перезапуска сервер появится в панели на странице Workers под своим hostname. Этот же список можно получить из командной строки.
gorst-controlnode workers --admin <admin.addr> --token <токен администратора>
5. Права и токены
Пока политика не выдаст пользователю доступ, он ничего не видит. В праве указываются сервер, сервис и действие, и вместо любого из них можно поставить *. Здесь пользователь happydez получает право перезапускать nginx на server-1, и больше ничего.
sudo gorst-controlnode policy add --description 'перезапуск nginx' nginx-restart
sudo gorst-controlnode policy allow nginx-restart server-1 nginx restart
sudo gorst-controlnode user add --password '...' happydez
sudo gorst-controlnode user grant happydez nginx-restart
Бот в gorst это обычный пользователь без пароля, но с токеном.
sudo gorst-controlnode user add deploy-bot
sudo gorst-controlnode user grant deploy-bot nginx-restart
sudo gorst-controlnode token create --name telegram deploy-bot # токен показывается один раз
Токен действует от имени своего владельца и ограничен теми же политиками, так что бот сможет перезапустить nginx на server-1, и ничего больше. Для бота это один HTTP-запрос.
func restartNginx(ctx context.Context) error {
url := "https://cn.example.com/api/v1/workers/server-1/nginx/restart"
req, err := http.NewRequestWithContext(ctx, http.MethodPost, url, nil)
if err != nil {
return err
}
req.Header.Set("Authorization", "Bearer "+os.Getenv("GORST_TOKEN"))
resp, err := http.DefaultClient.Do(req)
if err != nil {
return err
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
return fmt.Errorf("gorst: %s", resp.Status)
}
return nil
}
Полная документация, описание REST и gRPC API и все параметры конфигов есть в репозитории github.com/happydez/gorst.