v1.5.0
2026-08-06
latest
● stable
Что изменилось
Added
- **openSUSE Leap 16 присоединяется к поддерживаемой матрице — первое zypper-семейство.** `internal/pkgmgr` получил `FamilySUSE`/`DistroOpenSUSE`: `Install` запускает `zypper --non-interactive refresh`, затем `zypper --non-interactive install` (в отличие от RHEL-семейства, где dnf обновляет метаданные неявно, репозитории openSUSE по умолчанию идут с `autorefresh=0`, так что явный refresh обязателен, а не опционален), `IsInstalled` использует ту же `rpm -q`, что и RHEL-семейство (оба — RPM-based), а нераспознанный SUSE-клон, у которого `/etc/os-release` `ID_LIKE` содержит `suse`, теперь резолвится в нужное семейство через тот же fallback, что уже используют клоны Rocky/AlmaLinux. Docker ставится прямо из штатного репозитория Leap (`docker`, сторонний репозиторий не нужен — в отличие от танца с Docker CE репозиторием у RHEL-семейства); Podman — в том же белом списке, что Debian/Ubuntu/Rocky/AlmaLinux; docker-rootless на этом семействе тоже отклоняется, симметрично отказу RHEL-семейства (ADR 0040) — для реализации понадобился бы отдельный SUSE-пакет rootless-extras, вне текущего охвата. Проба CA-бандла `gtcnsl doctor` получила `/etc/ssl/ca-bundle.pem` (SUSE-семейство) третьим кандидатным путём рядом с существующими Debian- и RHEL-путями. См. ADR 0043.
- **Alpine Linux присоединяется к поддерживаемой матрице — только host-mode.** gtcnsl теперь работает на Alpine через второй, честный бэкенд `service.Manager` (init-агностичную абстракцию, описанную ниже в разделе «Изменено»): `internal/openrc` управляет через `rc-service`/`rc-update` и разворачивает supervise-daemon-скрипты `/etc/init.d`, которые gtcnsl рендерит сам (`internal/gitea`/`internal/runner` получили `RenderOpenRCUnit` рядом с уже существующими systemd-шаблонами). `internal/app` выбирает бэкенд автоматически через `service.Detect()` — без флага, без конфига. `internal/pkgmgr` получил `FamilyAlpine` (`apk update` + `apk add`, `apk info -e` для проверки установки); `internal/osuser` получил BusyBox-ветку `adduser`/`addgroup` для хостов без `useradd`/`usermod`. `gtcnsl doctor` знает про init-системы: на OpenRC-хосте проверка init-системы теперь проходит (PASS), а не жёстко падает с «systemd not running». **MVP-объём, намеренно (ADR 0020/0045): только host-mode Gitea и host-mode раннер.** Executor'ы раннера `docker`, `docker-rootless`, `podman` и `dind-rootless` на Alpine честно отказывают с объяснением (на Alpine нет systemd-logind — session-менеджера, от которого зависят rootless/dind-rootless сетапы этих executor'ов), а не падают непредсказуемо на полпути. См. ADR 0020/0044/0045.
- **Загрузки теперь восстанавливаются после зависшего соединения вместо бесконечного зависания.** Каждый путь загрузки — установка/апгрейд Gitea, установка/апгрейд gitea-runner и собственный self-update gtcnsl, все они используют единую реализацию `internal/download` — теперь следит за ответом, переставшим отдавать байты, и после 30 секунд тишины прерывает эту попытку и повторяет (до 3 попыток всего) с новым соединением, докачивая через `Range`, если сервер это поддерживает, вместо того чтобы начинать заново. Загрузка, которую в итоге не удалось восстановить, теперь падает с понятной ошибкой «giving up after N attempts» вместо зависания. Запросы списков версий (доступные версии Gitea/раннера, манифест self-update) тоже получили таймаут 15 секунд, так что зависшее соединение больше не может подвесить и экран «loading available versions». См. ADR 0041.
- **Экран операции TUI теперь показывает, происходит ли вообще что-то.** Живой спиннер крутится рядом с активной стадией всю операцию (а не только до первого события), события Progress рендерят процент плюс объём в человекочитаемом виде, когда размер известен (`download: 46% (19.9 MiB / 42.7 MiB)`), а уведомление о повторе из записи про устойчивость загрузок выше («retrying (attempt 2/3) from byte 307200…») теперь читается как предупреждение, а не теряется в логе.
- **Esc на выполняющейся операции TUI честно говорит, что делает.** Однократное нажатие Esc теперь показывает `operation still running (holds the gtcnsl lock): c cancel · esc hide` вместо того, чтобы молча выходить, пока операция продолжает работать под капотом — именно эта путаница стояла за прод-инцидентом, где оператор увидел «another gtcnsl operation is already in progress» и не понял, откуда. `c` отменяет: стадия загрузки останавливается немедленно (на отменяемом per-attempt контексте, введённом записью про устойчивость загрузок выше, ADR 0041), другие стадии останавливаются на ближайшей безопасной границе между стадиями, а уже начавшаяся мутирующая стадия (замена бинарника, рестарт юнита) всегда доводится до конца атомарно, а не обрывается на полпути. Второй Esc скрывает экран и возвращает на дашборд, который показывает индикатор «operation in progress…», пока скрытая операция не завершится. Отмена никогда не роняет TUI — экран показывает «Cancelled», а не сырую ошибку. См. ADR 0042.
- **Выбор версии в TUI теперь восстанавливается после неудачной/просроченной по таймауту загрузки списка версий вместо бесконечного ожидания.** При ошибке теперь предлагается `r` для повтора загрузки в дополнение к уже существующему ручному вводу.
Changed
- **Внутренний рефакторинг: управление сервисами теперь идёт через init-агностичный `internal/service.Manager` вместо прямого обращения к `systemd.Manager`.** Раньше `internal/core` безусловно вызывал `internal/systemd` для каждой операции с сервисом. Теперь он программируется против `service.Manager` — операций, которые умеет любая init-система (start/stop/restart/enable/deploy unit/reload), — плюс три опциональных интерфейса возможностей (`DropInCapable`, `LingerCapable`, `UnitInspector`) для systemd-специфичных функций (drop-in-оверлеи, session lingering, живая интроспекция юнита), с честным отказом, когда у бэкенда нет нужной возможности. `internal/systemd` не перемещён и не переименован — теперь это просто (единственный, пока) бэкенд, реализующий эту абстракцию. **Поведение не изменилось**: это подготовка к поддержке Alpine/OpenRC, а не сама поддержка Alpine — все существующие тесты проходят без изменений. См. ADR 0044 (уточняет ADR 0020).
Fixed
- **Зависшее соединение с Gitea/CDN могло подвесить апгрейд бесконечно.** Найдено в прод-инциденте, где HTTP/2-клиент Go завис на загрузке `dl.gitea.com` за Cloudflare: соединение оставалось в установленном состоянии с нулевой пропускной способностью и без ошибки, так что ничего никогда не срабатывало по таймауту. Клиенты загрузки и обнаружения версий в gtcnsl теперь принудительно используют HTTP/1.1, что воспроизводимо избавило от зависания в этом инциденте, и, согласно ADR 0041, применяется только к этим клиентам — административный API Gitea и клиенты health-check не затронуты. (Докачка через Range выше покрывает оставшийся случай «иногда сеть просто икает» поверх этого.)
- Прерванная загрузка (например, `SIGTERM` посреди передачи) могла оставлять после себя временный файл `gtcnsl-download-*`; теперь загрузка всегда удаляет его при любом неуспешном исходе, включая отменённый контекст.
Загрузка
Выберите архитектуру. Прямые ссылки ведут на ассеты релиза.
$ curl -fsSL https://dl.gtcnsl.ru/v1.5.0/gtcnsl-v1.5.0-linux-amd64 -o /usr/local/bin/gtcnsl && chmod +x /usr/local/bin/gtcnsl
i
Проверка
Сверьте с манифестом контрольных сумм, прежде чем что-то запускать.
checksums.txt