К содержимому
gtcnsl
EN RU

v1.3.0

2026-07-18 latest ● stable

Что изменилось

Added
  • **Gitea 1.27.x и Runner 2.x сверены и поддерживаются, а `--reencrypt` теперь защищён конвертом версий.** Криптография перешифровки SECRET_KEY (`internal/gitea/secretcrypto`/`secretdb`) заново сверена байт-в-байт с исходниками Gitea v1.27.0 — обе схемы, все пять зашифрованных мест, новых точек вызова не появилось, — а встроенная базовая схема конфигурации поднята с 1.26.2 до 1.27.0 (9 секций без изменений, 764 → 772 ключа); `scripts/update-gitea-schema`, на который `docs/ARCHITECTURE.md` ссылался ещё с ADR 0015, хотя скрипт никогда не существовал, теперь реально существует. Эта криптография перешифровки — не публичный контракт Gitea: апстрим может изменить её в любом миноре, не упомянув в changelog, — поэтому `secrets rotate SECRET_KEY --reencrypt` теперь жёстко отказывает, до любых изменений, на любом миноре Gitea вне сверенного конверта (сейчас `[1.24, 1.27]`), вместо того чтобы рисковать повторением инцидента с тихой порчей данных, из которого вырос v1.2.0; явный, отдельный обход — `--reencrypt-unverified-version-i-understand` (ADR 0028). COMPATIBILITY.md теперь прямо называет поддерживаемый диапазон апстрима (Gitea вплоть до 1.27.x, Runner ≥1.0 включая 2.x); ломающее изменение Runner 2.x (убраны неявные переменные окружения `DOCKER_USERNAME`/`DOCKER_PASSWORD`) не затрагивает конфиги, генерируемые gtcnsl.
  • **`gtcnsl runner register`/`runner reconfigure --token-file <path>`.** Новый флаг, взаимоисключающий с `--token`: gtcnsl сам читает и обрезает пробелы в файле, а при обнаруженном `gitea-runner` >= 2.1.0 (релиз, добавивший поддержку `--token-file` в апстриме) вообще не помещает токен в argv — записывает его во временный файл 0600, принадлежащий gtcnsl, внутри рабочей директории раннера, передаёт `--token-file` в подпроцесс `gitea-runner register` и удаляет временный файл независимо от результата. Токен в argv виден любому на хосте всё время жизни процесса (`/proc/<pid>/cmdline`, `ps`); это закрывает окно для раннеров, которые достаточно новы, чтобы это поддерживать. Раннеры ниже 2.1.0 сохраняют прежнее поведение с `--token` в argv — задокументированный запасной вариант, а не тихий отказ. Executor dind-rootless и так был основан на env-файле (ADR 0022) и не затронут в любом случае (ADR 0030).
  • **`gtcnsl runner register`/`reconfigure` теперь генерируют базовый `config.yaml`, а `gtcnsl runner config generate [--force]` (пере)пишет его по запросу.** Runner 2.x добавил пачку новых ключей config.yaml (`runner.post_task_script`, `action_shallow_clone`, `container.network_create_options.enable_ipv4/6` и другие), которые gtcnsl раньше не давал ни увидеть, ни задать — `runner register` всегда писал только `.runner`, так что каждый установленный раннер работал на недокументированных встроенных дефолтах gitea-runner. Регистрация/реконфигурация теперь пишут базовый файл через собственную подкоманду `generate-config` установленного бинарника (атомарно, 0640, с chown) — но только если `config.yaml` там ещё нет; существующий файл (отредактированный оператором или оставшийся с предыдущей регистрации) никогда не трогается. `ExecStart` юнита `gitea-runner.service` получает `--config <WorkDir>/config.yaml`, но строго монотонно: только после того, как этот файл подтверждённо есть на диске в момент рендера юнита, — так что апгрейд на месте установки, сделанной до этой фичи, никогда не укажет юниту на ещё не существующий файл (ADR 0031). Декларативное применение отдельных ключей config.yaml намеренно отложено; `runner config` — родительская команда-заготовка под это.
  • **`gtcnsl gitea upgrade [--to <v>] [--yes]` и `gtcnsl runner upgrade [--to <v>] [--yes]`** — глагол `upgrade`, который уже упоминали README.md и COMPATIBILITY.md, теперь оформлен как настоящие команды (ADR 0035). Обе — тонкие обёртки, а не новая машинерия: `gitea upgrade` отказывает, до каких-либо изменений, если ставить пока нечего, иначе передаёт весь поток тому же пути обновления на месте, что `gitea install --to <version>` использует с фичи 0067 (живые пути, сохранение чужого юнита, health-check); `runner upgrade` подключает CLI прямо к `internal/core.UpgradeRunner` (фича 0014), чей preflight уже отказывал при отсутствующей установке и уже следовал живому пути бинарника чужого или устаревше-именованного юнита (фича 0080, ADR 0034). Флаги намеренно минимальны (только `--to`/`--yes`) — `gitea install --to` остаётся более полным по флагам путём для `--data-dir`/`--rewrite-unit`/`--serving` в рамках обновления на месте.
  • **`gtcnsl runner adopt` — взять под управление уже существующий Gitea Actions Runner, никогда не перерегистрируя его.** Раннер-близнец `gitea adopt` (ADR 0032): регистрационные токены обычно одноразовые, поэтому в отличие от `runner reconfigure` эта команда не трогает `.runner`, кроме подтягивания его прав до 0640, если они слабее (файл также несёт регистрационный токен — никогда не логируется, не переписывается, не перемещается). По умолчанию dry-run; `--yes` применяет изменения. Распознаёт как текущее имя `gitea-runner.service`, так и устаревшее `act_runner.service` (ADR 0013/0018), считывая бинарник/рабочую директорию/пользователя прямо из директив живого юнита, а не из дефолтов gtcnsl — и — благодаря единой инспекции живого юнита из этого же релиза (ADR 0034, см. Fixed ниже) — последующий `runner install --to`/`runner upgrade` распознаёт усыновлённую установку и работает по тем же живым путям. Юнит, написанный не gtcnsl, по умолчанию сохраняется байт-в-байт (ADR 0025); `--rewrite-unit` заменяет его содержимое шаблоном gtcnsl, отрендеренным из живых значений, сохраняя имя файла (без переименования). Установки dind-rootless отклоняются как вне области действия (ADR 0022).
  • **Состояние раннера теперь путешествует вместе с `gtcnsl backup`/`restore`.** Регистрационные токены `.runner` обычно одноразовые, так что потеря файла раньше означала новый токен и потерянный UUID раннера. `backup` теперь дополнительно и опционально (Warning + пропуск при отсутствии, как и с `secrets.ini`/`custom/` сегодня) захватывает `<WorkDir>/.runner` (секретный класс — его токен никогда не попадает в события/логи), `config.yaml`, юнит `gitea-runner.service` (читается через инжектированный systemd-менеджер, никогда не через захардкоженный путь), и `registration.env` dind-rootless — последний с явным предупреждением и пометкой в манифесте о том, что его одного недостаточно для полного восстановления раннера dind-rootless (Docker-том `gitea-runner-data` остаётся вне области действия, ADR 0022). Схема манифеста поднимается с 1 до 2; бэкап со схемой 1 восстанавливается точно как раньше, а старый gtcnsl по-прежнему отказывает на манифесте со схемой 2. `restore` останавливает `gitea-runner.service` перед применением его файлов и запускает снова только после того, как пройдёт собственный health-check Gitea, воспринимая «юнит не установлен»/«не активен» как Warning, а не Failed (ADR 0033).
Fixed
  • **`secrets rotate SECRET_KEY` — guard ротации, `--reencrypt` и проба слабого ключа в `doctor` теперь учитывают `SECRET_KEY_URI` целиком.** Gitea поддерживает получение `[security] SECRET_KEY` из файла через `SECRET_KEY_URI` с версии 1.19, но gtcnsl везде читал только буквальное значение `SECRET_KEY`: на инстансе с URI-конфигурацией guard и `doctor` молча считали публичный дефолт Gitea «старым ключом» (ложное чтение), а хуже того — обычная ротация записывала новый ключ в `secrets.ini`, ни разу не тронув файл, который Gitea реально читает, — инстанс оставался работать на *старом* ключе, пока собственный учёт gtcnsl утверждал, что ключ уже новый. Это тот же класс тихого расхождения, из которого вырос инцидент v1.2.0, и он делал `--reencrypt` на таком хосте гарантированным откатом по health-check (новый шифротекст, старый работающий ключ). Теперь gtcnsl резолвит `SECRET_KEY`/`SECRET_KEY_URI` точно так же, как это делает сама Gitea при старте (перенесено построчно из `modules/setting/security.go`, fail-closed вместо собственного `log.Fatal` Gitea при одновременной установке обоих или нерезолвящемся URI), а при ротации атомарно переписывает и сам файл за URI — со своим бэкапом с меткой времени и откатом на любом сбое — в дополнение к `secrets.ini` (ADR 0029). Ротация инстанса с буквальным `SECRET_KEY` не затронута.
  • **Неудачный `gitea upgrade` (или `gitea install --to` поверх существующей установки) теперь восстанавливает предыдущий бинарник Gitea, вместо того чтобы оставить хост без него.** Путь обновления на месте снапшотит живой бинарник перед заменой и при любом сбое после замены (деплой юнита, рестарт сервиса, health-check) восстанавливает его и перезапускает сервис — тот же откат на основе снапшота, что уже был у `runner upgrade` (ADR 0036, закрывающий асимметрию, которую ADR 0035 фиксировал как открытый компромисс). Уже существующий файл юнита теперь тоже никогда не удаляется откатом неудачного апгрейда.
  • **`runner install`/`upgrade`/`register`/`reconfigure` теперь следуют собственным фактам живого юнита** — имя юнита (включая устаревшее `act_runner.service` до переименования), путь к бинарнику и рабочая директория читаются из реального systemd-юнита, а не из предположения о каноническом layout gtcnsl (ADR 0034). До этого усыновлённая устаревшая или перенесённая установка была невидима для `runner install --to`, а `--rewrite-unit` создавал второй канонический юнит рядом с существующим. Одновременное присутствие и канонического, и устаревшего юнита теперь — жёсткая, ясно сформулированная ошибка инспекции.

Загрузка

Выберите архитектуру. Прямые ссылки ведут на ассеты релиза.

$ curl -fsSL https://dl.gtcnsl.ru/v1.3.0/gtcnsl-v1.3.0-linux-amd64 -o /usr/local/bin/gtcnsl && chmod +x /usr/local/bin/gtcnsl
i
Проверка
Сверьте с манифестом контрольных сумм, прежде чем что-то запускать. checksums.txt