Upgrades & self-update
gtcnsl updates itself from the download host with a SHA-256 check and a rollback snapshot. Gitea moves forward the same way you installed it — after a backup.
Update gtcnsl itself
# is a newer release available? (no write, no --yes needed)
gtcnsl self-update --dry-run
# update to the latest release
gtcnsl self-update --yes
# pin a specific version
gtcnsl self-update --to v0.6.1 --yes
Under the hood it resolves the latest tag from versions.json, downloads https://dl.gtcnsl.com/<tag>/gtcnsl-<tag>-linux-<arch>, verifies it against checksums.txt (SHA-256), snapshots the running binary, swaps it atomically, then runs gtcnsl --version to confirm — rolling back automatically if that check fails.
Roll gtcnsl back
gtcnsl self-update --rollback --yes
Restores the binary replaced by the most recent self-update. (--rollback can't be combined with --to or --dry-run.)
https://dl.gtcnsl.com. Point elsewhere with --dl-host or the $GTCNSL_DLHOST env var; https://dl.gtcnsl.ru is an identical mirror.Move Gitea forward
gtcnsl gitea upgrade [--to <v>] [--yes] is the short command once Gitea is already installed — it refuses cleanly if nothing is there yet, then rides the same in-place upgrade path gitea install --to <version> has always used: paths come from the live install (an upgrade never moves data), a --data-dir/--log-dir that contradicts the live install is refused, and a gitea.service unit gtcnsl did not write is preserved byte-for-byte — binary swap + restart only; pass --rewrite-unit if you deliberately want gtcnsl's template instead. Reach for gitea install --to directly when you need those extra flags in the same step. The TUI shows the same detection as an upgrade banner with the live paths prefilled.
If anything fails after the binary is swapped — deploying the unit, restarting, or the post-upgrade health check — gtcnsl restores the previous Gitea binary and restarts the service automatically; a failed upgrade never leaves the host without a running Gitea.
Gitea data is preserved across a binary swap, but take a snapshot first so you can return to a known-good state:
gtcnsl backup
gtcnsl gitea upgrade --to v1.27.0 --yes
gtcnsl doctor # confirm the unit is back and healthy
gtcnsl backup snapshots Gitea's binary, app.ini, secrets.ini, and the custom/ tree, plus — when a runner is installed — its .runner registration, config.yaml, and the gitea-runner.service unit, all to /var/lib/gtcnsl/backups/<id>/. List and restore them with:
gtcnsl backup list
gtcnsl restore <backup-id> --yes
restore snapshots the live files, stops Gitea (and the runner, if the backup carries runner state), swaps in the backup, restarts, and health-checks — undoing itself if Gitea won't come back. A backup taken before this release restores exactly as it always did; the runner items are simply absent from it.
gtcnsl doctor after each.1. prefix, so moving from 1.27 is a major upgrade (--to v28.0.0, with consent). Before you confirm, check what lands in your setup — gtcnsl's own defaults are already compatible:
[server] DOMAINis no longer read — the instance and SSH clone domain come fromROOT_URLalone. Make sureROOT_URLnames your real host.- Self-registration is closed by default. If you relied on open sign-up, set
[service] DISABLE_REGISTRATION = falseexplicitly. - Removed settings:
[migrations] ALLOWED_DOMAINS,BLOCKED_DOMAINS,ALLOW_LOCALNETWORKSand the[external]preset — migrations and mirrors now use[migrations] EGRESS_MODE/ALLOWED_HOST_LIST/BLOCKED_HOST_LIST. An invalidBLOCKED_HOST_LISTstops Gitea from starting. - Actions history expires: runs older than
[actions] RUN_RETENTION_DAYS(default 400) are deleted with their logs and artifacts. Set it to0before upgrading to keep everything. - Notifications move to WebSocket (
/-/ws): a reverse proxy in front of Gitea must pass theUpgradeheaders. gtcnsl's direct-HTTPS profiles need nothing. - git ≥ 2.25 is required — every distro gtcnsl supports ships newer.
Back up first: the upgrade runs one-way database migrations.
Major versions need your consent
Every upgrade gtcnsl runs — gitea upgrade, runner upgrade, self-update, and install over an existing install — stays within the major version you already run unless you say otherwise. The major is the first number: Gitea's long 1.x series is one major, and since 28.0.0 (upstream dropped the 1. prefix) every Gitea feature release — 28, 29, … — is a major of its own; the runner's majors are 3, 4, ….
- Without
--toyou get the newest release of your current major: a Gitea on 1.26 or 1.27 moves to the newest 1.27.x, a runner on 3.x to the newest 3.x. When a newer major exists, gtcnsl prints a warning naming it. A fresh install without--togets the newest major this gtcnsl release has been validated against. --tointo a newer major is a separate decision. On a terminal gtcnsl names both versions and asks you to confirm; in cron or CI pass--allow-major-upgrade.--yesis not consent to a major upgrade — without consent gtcnsl refuses before it changes anything.
# stays in your major, warns if a newer one exists
gtcnsl gitea upgrade --yes
# crossing a major, non-interactively
gtcnsl backup
gtcnsl gitea upgrade --to v28.0.0 --yes --allow-major-upgrade
Move the runner forward
gtcnsl runner upgrade [--to <v>] [--yes] is the runner's equivalent of gitea upgrade: it refuses cleanly if no runner is installed, otherwise swaps the binary in place and restarts the service. It follows the live unit's own facts — binary path, work dir, even a legacy act_runner.service name from before the v0.2 rename — instead of assuming gtcnsl's canonical layout, so an adopted or relocated runner (gtcnsl runner adopt) upgrades correctly too. Like gitea upgrade, a failed swap restores the previous runner binary and restarts the service automatically.
gtcnsl runner upgrade --yes
[!NOTE] Crossing into Runner 3.x gtcnsl handles the 3.x mechanics (the new single-process lock on
.runner, config generation) for you, but three upstream changes land inside your workflows, not in gtcnsl:
- Cache/artifact actions: stock
actions/upload-artifact,download-artifactandactions/cachenow work as published — drop thegitea-upload-artifactfork if you used it. Cache service v2 is on by default (cache.v2: falseinconfig.yamlto opt out).container.optionsthat reach the host (--pid,--cap-add, devices, …) are silently stripped unlesscontainer.privilegedis enabled inconfig.yaml.::set-env::/::add-path::(runner ≥ 3.1.0) are refused unless the step or job setsACTIONS_ALLOW_UNSECURE_COMMANDS.
[!NOTE] Crossing into Runner 4.x Runner 4 is a major upgrade (
--to v4.1.0, with consent). gtcnsl handles the runner itself; these upstream changes since 3.1 land in your workflows andconfig.yaml:
- Cache through the runner (4.0): jobs reach the cache via their runner. Jobs on
container.network: bridgecan no longer reach it — leavecontainer.networkempty or use a user-defined network; a remoteDOCKER_HOSTneedscache.host/cache.port.valid_volumes(4.0):*no longer matches across/— use/data/**instead of/data/*.- Tool cache not shared (3.2): each job keeps only what its image ships; set
runner.tool_cache_mode: sharedfor the old single volume.container.optionslimits (3.3.1): workflows can no longer pass--env-file,--label-fileor volume driver options, and a bare--env NAMEno longer reads the runner's environment.NODE_OPTIONS(3.3.2) can't be set from a workflow any more.
Next steps
If something looks wrong after an upgrade, start at Troubleshooting & FAQ.