Skip to content
gtcnsl
EN RU

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.

Updated for v2.1.1

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.)

i
Pick your mirror
Downloads default to 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.

!
Read the changelog before a jump
Gitea runs its own migrations on first start after an upgrade; skipping several minor versions at once is riskier. Back up, move one step at a time, and check gtcnsl doctor after each.
i
Crossing into Gitea 28
Gitea 28.0.0 dropped the 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] DOMAIN is no longer read — the instance and SSH clone domain come from ROOT_URL alone. Make sure ROOT_URL names your real host.
  • Self-registration is closed by default. If you relied on open sign-up, set [service] DISABLE_REGISTRATION = false explicitly.
  • Removed settings: [migrations] ALLOWED_DOMAINS, BLOCKED_DOMAINS, ALLOW_LOCALNETWORKS and the [external] preset — migrations and mirrors now use [migrations] EGRESS_MODE / ALLOWED_HOST_LIST / BLOCKED_HOST_LIST. An invalid BLOCKED_HOST_LIST stops Gitea from starting.
  • Actions history expires: runs older than [actions] RUN_RETENTION_DAYS (default 400) are deleted with their logs and artifacts. Set it to 0 before upgrading to keep everything.
  • Notifications move to WebSocket (/-/ws): a reverse proxy in front of Gitea must pass the Upgrade headers. 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.

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 --to you 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 --to gets the newest major this gtcnsl release has been validated against.
  • --to into 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. --yes is 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
!
A major can't be rolled back by gtcnsl alone
A new major may run one-way database migrations. If the upgrade fails afterwards, gtcnsl restores the previous binary, but not data a migration already changed. Read the upstream release notes and take a backup before you confirm.

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-artifact and actions/cache now work as published — drop the gitea-upload-artifact fork if you used it. Cache service v2 is on by default (cache.v2: false in config.yaml to opt out).
  • container.options that reach the host (--pid, --cap-add, devices, …) are silently stripped unless container.privileged is enabled in config.yaml.
  • ::set-env:: / ::add-path:: (runner ≥ 3.1.0) are refused unless the step or job sets ACTIONS_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 and config.yaml:

  • Cache through the runner (4.0): jobs reach the cache via their runner. Jobs on container.network: bridge can no longer reach it — leave container.network empty or use a user-defined network; a remote DOCKER_HOST needs cache.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: shared for the old single volume.
  • container.options limits (3.3.1): workflows can no longer pass --env-file, --label-file or volume driver options, and a bare --env NAME no 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.