28 Commits
Author SHA1 Message Date
wxiaoguangandLunny Xiao 0ddb9a2071 codeowners (#634)
https://github.com/go-gitea/gitea/pull/40243
Co-authored-by: Lunny Xiao <[email protected]>
Reviewed-on: https://gitea.com/gitea/docs/pulls/634
Reviewed-by: Lunny Xiao <[email protected]>
Co-authored-by: wxiaoguang <[email protected]>
2026-11-06 20:00:46 +00:00
silverwind 71869c6e24 Document MSSQL database preparation (#636)
The database preparation page covered only MySQL and PostgreSQL. MSSQL setups without schema permissions break on upgrades, like in https://github.com/go-gitea/gitea/issues/40256.

The new section shows the login, database and user setup with the roles Gitea needs, for main and 28, including zh-cn and zh-tw.

_Written by Claude (Opus 5.5)._

Reviewed-on: https://gitea.com/gitea/docs/pulls/636
Reviewed-by: Lunny Xiao <[email protected]>
Co-authored-by: silverwind <[email protected]>
2026-11-06 17:12:50 +00:00
silverwind 46b5401a81 Document the new DEFAULT_DELETE_BRANCH_AFTER_MERGE default (#608)
Documents https://github.com/go-gitea/gitea/pull/39975 for main, which changes the default of `DEFAULT_DELETE_BRANCH_AFTER_MERGE` to `true`.

Reviewed-on: https://gitea.com/gitea/docs/pulls/608
Reviewed-by: Lunny Xiao <[email protected]>
2026-11-05 23:34:03 +00:00
Gitea BotandLunny Xiao f6e17361fe Update the runner reference pages (#628)
Automated update of the generated runner reference pages from gitea/runner (main for the develop docs, the newest stable tag for every documented series), opened by the "update runner reference" scheduled workflow.

<!-- cloudflare-preview --> Preview: https://pr-628.docs-gitea-com.pages.dev

---------

Co-authored-by: Lunny Xiao <[email protected]>
Reviewed-on: https://gitea.com/gitea/docs/pulls/628
Reviewed-by: Lunny Xiao <[email protected]>
Co-authored-by: Gitea Bot <[email protected]>
2026-11-02 05:07:00 +00:00
Gitea Bot c3cff4ab08 Update the latest api documents (#627)
Automated update of static/swagger-latest.json and static/openapi3-latest.json from the gitea main branch, opened by the "update api spec files" scheduled workflow.

<!-- cloudflare-preview --> Preview: https://pr-627.docs-gitea-com.pages.dev

Reviewed-on: https://gitea.com/gitea/docs/pulls/627
Reviewed-by: Lunny Xiao <[email protected]>
Co-authored-by: Gitea Bot <[email protected]>
2026-11-02 03:39:31 +00:00
Renovate Bot aa975da532 chore(deps): update dependencies (#626)
This PR contains the following updates:

| Package | Change | [Age](https://docs.renovatebot.com/merge-confidence/) | [Confidence](https://docs.renovatebot.com/merge-confidence/) | Type | Update | Pending |
|---|---|---|---|---|---|---|
| [@astrojs/check](https://github.com/withastro/astro/tree/main/packages/language-tools/astro-check) ([source](https://github.com/withastro/astro/tree/HEAD/packages/language-tools/astro-check)) | [`0.9.11` → `0.9.12`](https://renovatebot.com/diffs/npm/@astrojs%2fcheck/0.9.11/0.9.12) | ![age](https://developer.mend.io/api/mc/badges/age/npm/@astrojs%2fcheck/0.9.12?slim=true) | ![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/@astrojs%2fcheck/0.9.11/0.9.12?slim=true) | devDependencies | patch |  |
| [@astrojs/starlight](https://starlight.astro.build) ([source](https://github.com/withastro/starlight/tree/HEAD/packages/starlight)) | [`0.42.6` → `0.42.7`](https://renovatebot.com/diffs/npm/@astrojs%2fstarlight/0.42.6/0.42.7) | ![age](https://developer.mend.io/api/mc/badges/age/npm/@astrojs%2fstarlight/0.42.7?slim=true) | ![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/@astrojs%2fstarlight/0.42.6/0.42.7?slim=true) | dependencies | patch | `0.42.8` |
| [astro](https://astro.build) ([source](https://github.com/withastro/astro/tree/HEAD/packages/astro)) | [`7.4.1` → `7.4.2`](https://renovatebot.com/diffs/npm/astro/7.4.1/7.4.2) | ![age](https://developer.mend.io/api/mc/badges/age/npm/astro/7.4.2?slim=true) | ![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/astro/7.4.1/7.4.2?slim=true) | dependencies | patch | `7.4.4` (+1) |
| [node](https://github.com/actions/node-versions) | `24` → `26` | ![age](https://developer.mend.io/api/mc/badges/age/github-releases/actions%2fnode-versions/26.12.1?slim=true) | ![confidence](https://developer.mend.io/api/mc/badges/confidence/github-releases/actions%2fnode-versions/24.22.2/26.12.1?slim=true) | uses-with | major | `26.13.0` |
| [pnpm](https://pnpm.io) ([source](https://github.com/pnpm/pnpm/tree/HEAD/pnpm/npm/pnpm)) | [`12.15.0` → `12.16.1`](https://renovatebot.com/diffs/npm/pnpm/12.15.0/12.16.1) | ![age](https://developer.mend.io/api/mc/badges/age/npm/pnpm/12.16.1?slim=true) | ![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/pnpm/12.15.0/12.16.1?slim=true) | packageManager | minor |  |

---

### Release Notes

<details>
<summary>withastro/astro (@&#8203;astrojs/check)</summary>

### [`v0.9.12`](https://github.com/withastro/astro/blob/HEAD/packages/language-tools/astro-check/CHANGELOG.md#0912)

[Compare Source](https://github.com/withastro/astro/compare/@astrojs/[email protected]...@astrojs/[email protected])

##### Patch Changes

- [#&#8203;18455](https://github.com/withastro/astro/pull/18455) [`7af66fc`](https://github.com/withastro/astro/commit/7af66fc785d85b1df1f98e7123a52b2306250985) Thanks [@&#8203;astro-factory](https://github.com/apps/astro-factory)! - Fixes the `astro-check` binary ignoring its first two command-line arguments, so flags such as `--help`, `--root`, `--watch`, and `--tsconfig` are respected again. If your scripts padded the arguments to work around this (for example, `astro-check -- -- --tsconfig ./tsconfig.app.json`), remove the padding.
- Updated dependencies \[[`044148d`](https://github.com/withastro/astro/commit/044148d5cfbc90e0277b3205a52ec73bc452bb7e)]:
  - [@&#8203;astrojs/language-server](https://github.com/astrojs/language-server)@2.17.3

</details>

<details>
<summary>withastro/starlight (@&#8203;astrojs/starlight)</summary>

### [`v0.42.7`](https://github.com/withastro/starlight/blob/HEAD/packages/starlight/CHANGELOG.md#0427)

[Compare Source](https://github.com/withastro/starlight/compare/@astrojs/[email protected]...@astrojs/[email protected])

##### Patch Changes

- [#&#8203;4264](https://github.com/withastro/starlight/pull/4264) [`94e0985`](https://github.com/withastro/starlight/commit/94e0985ddbbbd5c0f50a4cea94dc2c92de0a607c) Thanks [@&#8203;chrispezza](https://github.com/chrispezza)! - Fixes a layout shift where the table of contents could be painted at the start of the content area and then jump to the end when a page's HTML arrived in several chunks

- [#&#8203;4285](https://github.com/withastro/starlight/pull/4285) [`6f22edd`](https://github.com/withastro/starlight/commit/6f22eddf21c90aed7bd4e3c82e48615a56268a69) Thanks [@&#8203;tunetheweb](https://github.com/tunetheweb)! - Improves hero image loading by adding `fetchpriority="high"` hint for browsers

</details>

<details>
<summary>withastro/astro (astro)</summary>

### [`v7.4.2`](https://github.com/withastro/astro/blob/HEAD/packages/astro/CHANGELOG.md#742)

[Compare Source](https://github.com/withastro/astro/compare/[email protected]@7.4.2)

##### Patch Changes

- [#&#8203;18430](https://github.com/withastro/astro/pull/18430) [`dda9ec3`](https://github.com/withastro/astro/commit/dda9ec395c89a1420abb3b8f88b5c7f1f385f82e) Thanks [@&#8203;Abhirup-99](https://github.com/Abhirup-99)! - Speeds up content sync with `experimental.collectionStorage: "chunked"` by computing each character's UTF-8 width from its code point instead of encoding every character through `TextEncoder`. On a store of \~44k entries this cuts the chunking step from about 60 seconds to well under a second, with byte-identical output.

- [#&#8203;18438](https://github.com/withastro/astro/pull/18438) [`5e0eaa4`](https://github.com/withastro/astro/commit/5e0eaa4415a9081efdaeb3d9af3717ce3226ad18) Thanks [@&#8203;astro-factory](https://github.com/apps/astro-factory)! - Fixes `astro dev` with `@astrojs/cloudflare` failing with ``Calling `require` for "node-fetch"`` when a server dependency such as `cross-fetch` requires `node-fetch`

- [#&#8203;18376](https://github.com/withastro/astro/pull/18376) [`a4e201d`](https://github.com/withastro/astro/commit/a4e201d79f19caa6bf4222f42167aeb602c066e6) Thanks [@&#8203;okxint](https://github.com/okxint)! - Fixes `astro dev` treating a stale lock file containing a Linux thread ID as a running Astro server. A successful process lookup with no matching PID now marks the lock as stale, preventing `astro dev stop` and `astro dev --force` from killing the unrelated process that owns the thread.

- [#&#8203;18467](https://github.com/withastro/astro/pull/18467) [`d1fb7d3`](https://github.com/withastro/astro/commit/d1fb7d36dad440c57d62b071e752ae220cca2baa) Thanks [@&#8203;astro-factory](https://github.com/apps/astro-factory)! - Fixes the `defineConfig()` types for `i18n.defaultLocale`, `i18n.fallback`, and `i18n.domains` so they accept a custom locale's `path` instead of its `codes`, matching how Astro validates and uses these values

- [#&#8203;18475](https://github.com/withastro/astro/pull/18475) [`cf27e70`](https://github.com/withastro/astro/commit/cf27e70a9057ce29ade2a8a843bd9275a696e9ab) Thanks [@&#8203;ematipico](https://github.com/ematipico)! - Bumps the minimum supported Node.js version to 22.23.4, which includes the fixes from the [October 2026 Node.js security releases](https://nodejs.org/en/blog/vulnerability/october-2026-security-releases)

  This is a patch release because the [Node.js support and upgrade policy](https://docs.astro.build/en/upgrade-astro/#nodejs-support-and-upgrade-policies) allows the Core team to raise the minimum Maintenance LTS version in a patch release when a disclosed and fixed Node.js security flaw affects Astro.

- [#&#8203;18451](https://github.com/withastro/astro/pull/18451) [`7a0ba6f`](https://github.com/withastro/astro/commit/7a0ba6f48ebae256a1ce49de4f1c62c009ff94e2) Thanks [@&#8203;DenisCDev](https://github.com/DenisCDev)! - Fixes `astro sync`, `astro check` and `astro build` replacing the optimized dependencies of a dev server running in the same project, which broke framework islands in that dev server until it was restarted with `--force`

- [#&#8203;18336](https://github.com/withastro/astro/pull/18336) [`e943003`](https://github.com/withastro/astro/commit/e943003259c3bb4d528d3d49c8ccd3eb8f84b8b5) Thanks [@&#8203;astro-factory](https://github.com/apps/astro-factory)! - Fixes inline styles in dev so that framework-transformed CSS is used, such as scoped styles and `v-bind()` in Vue single-file components

- [#&#8203;18399](https://github.com/withastro/astro/pull/18399) [`829fc00`](https://github.com/withastro/astro/commit/829fc00629ef715fc606b89c6e684ca93f9253b9) Thanks [@&#8203;astro-factory](https://github.com/apps/astro-factory)! - Fixes a dev server error (`Could not find server component name`) for a `server:defer` island that imports `astro:actions` or `astro:config/server`, when its page was not the first island page loaded after starting the server

- [#&#8203;18330](https://github.com/withastro/astro/pull/18330) [`4db9e0d`](https://github.com/withastro/astro/commit/4db9e0d1e6a54c2300b96a0ff0cfaf5c188cecda) Thanks [@&#8203;Samuteg](https://github.com/Samuteg)! - Fixes the `astro:env/server` virtual module being generated with invalid JS in dev when any environment variable contains `$` replacement patterns (`` $` ``, `$&`, `$'`, `$1`, …). The inlined env JSON was passed as a string to `String.replace`, so e.g. the `KITTY_PUBLIC_KEY` value set by default in Kitty terminals corrupted the module and broke dev with `Failed to parse source for import analysis`

- Updated dependencies \[[`cf27e70`](https://github.com/withastro/astro/commit/cf27e70a9057ce29ade2a8a843bd9275a696e9ab)]:
  - [@&#8203;astrojs/telemetry](https://github.com/astrojs/telemetry)@3.4.1
  - [@&#8203;astrojs/markdown-satteri](https://github.com/astrojs/markdown-satteri)@0.4.5

</details>

<details>
<summary>actions/node-versions (node)</summary>

### [`v26.12.1`](https://github.com/actions/node-versions/releases/tag/26.12.1-39618969937): 26.12.1

[Compare Source](https://github.com/actions/node-versions/compare/26.12.0-38668157440...26.12.1-39618969937)

Node.js 26.12.1

### [`v26.12.0`](https://github.com/actions/node-versions/releases/tag/26.12.0-38668157440): 26.12.0

[Compare Source](https://github.com/actions/node-versions/compare/26.11.1-37718106145...26.12.0-38668157440)

Node.js 26.12.0

### [`v26.11.1`](https://github.com/actions/node-versions/releases/tag/26.11.1-37718106145): 26.11.1

[Compare Source](https://github.com/actions/node-versions/compare/26.11.0-37718124002...26.11.1-37718106145)

Node.js 26.11.1

### [`v26.11.0`](https://github.com/actions/node-versions/releases/tag/26.11.0-37718124002): 26.11.0

[Compare Source](https://github.com/actions/node-versions/compare/26.10.0-35737674515...26.11.0-37718124002)

Node.js 26.11.0

### [`v26.10.0`](https://github.com/actions/node-versions/releases/tag/26.10.0-35737674515): 26.10.0

[Compare Source](https://github.com/actions/node-versions/compare/26.9.0-35174947414...26.10.0-35737674515)

Node.js 26.10.0

### [`v26.9.0`](https://github.com/actions/node-versions/releases/tag/26.9.0-35174947414): 26.9.0

[Compare Source](https://github.com/actions/node-versions/compare/26.8.2-34429851330...26.9.0-35174947414)

Node.js 26.9.0

### [`v26.8.2`](https://github.com/actions/node-versions/releases/tag/26.8.2-34429851330): 26.8.2

[Compare Source](https://github.com/actions/node-versions/compare/26.8.1-33034053154...26.8.2-34429851330)

Node.js 26.8.2

### [`v26.8.1`](https://github.com/actions/node-versions/releases/tag/26.8.1-33034053154): 26.8.1

[Compare Source](https://github.com/actions/node-versions/compare/26.8.0-33034064325...26.8.1-33034053154)

Node.js 26.8.1

### [`v26.8.0`](https://github.com/actions/node-versions/releases/tag/26.8.0-33034064325): 26.8.0

[Compare Source](https://github.com/actions/node-versions/compare/26.7.0-31064755789...26.8.0-33034064325)

Node.js 26.8.0

### [`v26.7.0`](https://github.com/actions/node-versions/releases/tag/26.7.0-31064755789): 26.7.0

[Compare Source](https://github.com/actions/node-versions/compare/26.6.0-30872438625...26.7.0-31064755789)

Node.js 26.7.0

### [`v26.6.0`](https://github.com/actions/node-versions/releases/tag/26.6.0-30872438625): 26.6.0

[Compare Source](https://github.com/actions/node-versions/compare/26.5.1-30508404249...26.6.0-30872438625)

Node.js 26.6.0

### [`v26.5.1`](https://github.com/actions/node-versions/releases/tag/26.5.1-30508404249): 26.5.1

[Compare Source](https://github.com/actions/node-versions/compare/26.5.0-28944078721...26.5.1-30508404249)

Node.js 26.5.1

### [`v26.5.0`](https://github.com/actions/node-versions/releases/tag/26.5.0-28944078721): 26.5.0

[Compare Source](https://github.com/actions/node-versions/compare/26.4.0-28176665805...26.5.0-28944078721)

Node.js 26.5.0

### [`v26.4.0`](https://github.com/actions/node-versions/releases/tag/26.4.0-28176665805): 26.4.0

[Compare Source](https://github.com/actions/node-versions/compare/26.3.1-27765697053...26.4.0-28176665805)

Node.js 26.4.0

### [`v26.3.1`](https://github.com/actions/node-versions/releases/tag/26.3.1-27765697053): 26.3.1

[Compare Source](https://github.com/actions/node-versions/compare/26.3.0-26776574275...26.3.1-27765697053)

Node.js 26.3.1

### [`v26.3.0`](https://github.com/actions/node-versions/releases/tag/26.3.0-26776574275): 26.3.0

[Compare Source](https://github.com/actions/node-versions/compare/26.2.0-26201774761...26.3.0-26776574275)

Node.js 26.3.0

### [`v26.2.0`](https://github.com/actions/node-versions/releases/tag/26.2.0-26201774761): 26.2.0

[Compare Source](https://github.com/actions/node-versions/compare/26.1.0-25496618813...26.2.0-26201774761)

Node.js 26.2.0

### [`v26.1.0`](https://github.com/actions/node-versions/releases/tag/26.1.0-25496618813): 26.1.0

[Compare Source](https://github.com/actions/node-versions/compare/26.0.0-25413736946...26.1.0-25496618813)

Node.js 26.1.0

### [`v26.0.0`](https://github.com/actions/node-versions/releases/tag/26.0.0-25413736946): 26.0.0

[Compare Source](https://github.com/actions/node-versions/compare/24.22.2-40806817314...26.0.0-25413736946)

Node.js 26.0.0

</details>

<details>
<summary>pnpm/pnpm (pnpm)</summary>

### [`v12.16.1`](https://github.com/pnpm/pnpm/releases/tag/v12.16.1): pnpm 12.16.1

[Compare Source](https://github.com/pnpm/pnpm/compare/v12.16.0...v12.16.1)

This release fixes `pnpm install` hanging on a cold store in workspaces that install Cargo crates, and `pnpm pack` shipping a file that `files` excludes by name.

##### Patch Changes

- `pnpm install` no longer hangs on a cold store in a workspace that also installs Cargo crates or a Rust toolchain.

- `pnpm pack` and `pnpm publish` now apply a `files` entry that names a file after the glob and directory entries, as npm does. A file excluded by name, such as `!dist/index.js`, stays out even when a later `dist/**` matches it. A file named by its path, such as `lib/secret.js`, ships even when a later `!lib/*.js` matches it.

- `pnpm install` no longer leaves a broken symlink in an injected workspace dependency when the package links to a file outside its published files and pnpm imports by hardlink. Installing a git dependency now skips internal directory symlinks when importing its files into the store.

- `pnpm install` now relinks a command that a workspace project renames into the projects that depend on it when `sharedWorkspaceLockfile` is `false` [pnpm/tasks#224](https://github.com/pnpm/tasks/issues/224).

- `pnpm install` and `pnpm rebuild` now refresh privately hoisted `.bin` shims in `node_modules/.pnpm/node_modules/.bin` after a dependency build replaces a script target with a native executable [#&#8203;17402](https://github.com/pnpm/pnpm/issues/17402).

- `pnpm install` now reads a package's manifest to decide its peer dependencies when it updates an existing lockfile, as it does when it resolves from scratch.

- Commands, `pnpm --version`, `pnpm self-update`, and invocations with unknown flags now apply the same `packageManager` and `devEngines.packageManager` settings when switching to the pinned pnpm version or recording it in `pnpm-lock.yaml`. `--config.<key>` flags and `lockfileDir` now reach the version switch and lockfile sync on every entry point. Unreadable settings in `pnpm-workspace.yaml` or the global `config.yaml` that do not affect the pin decision no longer block the version switch [pnpm/tasks#222](https://github.com/pnpm/tasks/issues/222).

- pnpm now accepts the `--cache-dir <dir>` flag, as in `pnpm install --cache-dir /tmp/pnpm-cache` [#&#8203;17422](https://github.com/pnpm/pnpm/issues/17422).

- When the version or range given to `pnpm unpublish` matches every published version, the error now says so and shows the same command with `--force`. Before, the error suggested running `pnpm unpublish <pkg>@<version>`, the command that had just failed.

<!-- sponsors -->

#### Platinum Sponsors

<table>
  <tbody>
    <tr>
      <td align="center" valign="middle">
        <a href="https://bit.cloud/?utm_source=pnpm&utm_medium=release_notes" target="_blank" rel="noopener noreferrer"><img src="https://pnpm.io/img/users/bit.svg" width="80" alt="Bit"></a>
      </td>
      <td align="center" valign="middle">
        <a href="https://openai.com/?utm_source=pnpm&utm_medium=release_notes" target="_blank" rel="noopener noreferrer">
          <picture>
            <source media="(prefers-color-scheme: light)" srcset="https://pnpm.io/img/users/openai_dark.svg" />
            <source media="(prefers-color-scheme: dark)" srcset="https://pnpm.io/img/users/openai_light.svg" />
            <img src="https://pnpm.io/img/users/openai_dark.svg" width="160" alt="OpenAI" />
          </picture>
        </a>
      </td>
      <td align="center" valign="middle">
        <a href="https://notion.com/?utm_source=pnpm&utm_medium=release_notes" target="_blank" rel="noopener noreferrer"><img src="https://pnpm.io/img/users/notion.svg" width="80" alt="Notion"></a>
      </td>
    </tr>
    <tr>
      <td align="center" valign="middle">
        <a href="https://coderabbit.ai/?utm_source=pnpm&utm_medium=release_notes" target="_blank" rel="noopener noreferrer">
          <picture>
            <source media="(prefers-color-scheme: light)" srcset="https://pnpm.io/img/users/coderabbit.svg" />
            <source media="(prefers-color-scheme: dark)" srcset="https://pnpm.io/img/users/coderabbit_light.svg" />
            <img src="https://pnpm.io/img/users/coderabbit.svg" width="220" alt="CodeRabbit" />
          </picture>
        </a>
      </td>
    </tr>
  </tbody>
</table>

#### Gold Sponsors

<table>
  <tbody>
    <tr>
      <td align="center" valign="middle">
        <a href="https://sanity.io/?utm_source=pnpm&utm_medium=release_notes" target="_blank" rel="noopener noreferrer">
          <picture>
            <source media="(prefers-color-scheme: light)" srcset="https://pnpm.io/img/users/sanity.svg" />
            <source media="(prefers-color-scheme: dark)" srcset="https://pnpm.io/img/users/sanity_light.svg" />
            <img src="https://pnpm.io/img/users/sanity.svg" width="120" alt="Sanity" />
          </picture>
        </a>
      </td>
      <td align="center" valign="middle">
        <a href="https://discord.com/?utm_source=pnpm&utm_medium=release_notes" target="_blank" rel="noopener noreferrer">
          <picture>
            <source media="(prefers-color-scheme: light)" srcset="https://pnpm.io/img/users/discord.svg" />
            <source media="(prefers-color-scheme: dark)" srcset="https://pnpm.io/img/users/discord_light.svg" />
            <img src="https://pnpm.io/img/users/discord.svg" width="220" alt="Discord" />
          </picture>
        </a>
      </td>
      <td align="center" valign="middle">
        <a href="https://vite.dev/?utm_source=pnpm&utm_medium=release_notes" target="_blank" rel="noopener noreferrer"><img src="https://pnpm.io/img/users/vitejs.svg" width="42" alt="Vite"></a>
      </td>
    </tr>
    <tr>
      <td align="center" valign="middle">
        <a href="https://serpapi.com/?utm_source=pnpm&utm_medium=release_notes" target="_blank" rel="noopener noreferrer">
          <picture>
            <source media="(prefers-color-scheme: light)" srcset="https://pnpm.io/img/users/serpapi_dark.svg" />
            <source media="(prefers-color-scheme: dark)" srcset="https://pnpm.io/img/users/serpapi_light.svg" />
            <img src="https://pnpm.io/img/users/serpapi_dark.svg" width="160" alt="SerpApi" />
          </picture>
        </a>
      </td>
      <td align="center" valign="middle">
        <a href="https://stackblitz.com/?utm_source=pnpm&utm_medium=release_notes" target="_blank" rel="noopener noreferrer">
          <picture>
            <source media="(prefers-color-scheme: light)" srcset="https://pnpm.io/img/users/stackblitz.svg" />
            <source media="(prefers-color-scheme: dark)" srcset="https://pnpm.io/img/users/stackblitz_light.svg" />
            <img src="https://pnpm.io/img/users/stackblitz.svg" width="190" alt="Stackblitz" />
          </picture>
        </a>
      </td>
      <td align="center" valign="middle">
        <a href="https://workleap.com/?utm_source=pnpm&utm_medium=release_notes" target="_blank" rel="noopener noreferrer">
          <picture>
            <source media="(prefers-color-scheme: light)" srcset="https://pnpm.io/img/users/workleap.svg" />
            <source media="(prefers-color-scheme: dark)" srcset="https://pnpm.io/img/users/workleap_light.svg" />
            <img src="https://pnpm.io/img/users/workleap.svg" width="190" alt="Workleap" />
          </picture>
        </a>
      </td>
    </tr>
    <tr>
      <td align="center" valign="middle">
        <a href="https://nx.dev/?utm_source=pnpm&utm_medium=release_notes" target="_blank" rel="noopener noreferrer">
          <picture>
            <source media="(prefers-color-scheme: light)" srcset="https://pnpm.io/img/users/nx.svg" />
            <source media="(prefers-color-scheme: dark)" srcset="https://pnpm.io/img/users/nx_light.svg" />
            <img src="https://pnpm.io/img/users/nx.svg" width="50" alt="Nx" />
          </picture>
        </a>
      </td>
      <td align="center" valign="middle">
        <a href="https://latitude.so/?utm_source=pnpm&utm_medium=release_notes" target="_blank" rel="noopener noreferrer"><img src="https://pnpm.io/img/users/latitude.svg" width="160" alt="Latitude"></a>
      </td>
    </tr>
  </tbody>
</table>

<!-- sponsors end -->

### [`v12.16.0`](https://github.com/pnpm/pnpm/releases/tag/v12.16.0): pnpm 12.16

[Compare Source](https://github.com/pnpm/pnpm/compare/v12.15.0...v12.16.0)

This release adds a global `--env-file` flag, updates to a symlinked `pnpm-lock.yaml`, and `pnpm setup --no-modify-path`. It also fixes two security issues, in zstd decompression and in signing.

##### Minor Changes

- Added a repeatable global `--env-file <path>` flag to load environment variables from dotenv-style files before running a command. Later files override earlier files, variables already in the environment are preserved, and loaded variables are visible to user-level `.npmrc` interpolation and lifecycle scripts [#&#8203;7111](https://github.com/pnpm/pnpm/issues/7111).

- pnpm can now update a `pnpm-lock.yaml` that is a symlink. List the directories the lockfile may link into in the `symlinkedLockfileTargetDirs` setting, in the global config or with `PNPM_CONFIG_SYMLINKED_LOCKFILE_TARGET_DIRS`. pnpm writes the update to the file the symlink points to if that file is inside one of these directories. The symlink stays in place. pnpm ignores this setting in `pnpm-workspace.yaml` [#&#8203;17222](https://github.com/pnpm/pnpm/issues/17222).

- Added a `--no-modify-path` option to `pnpm setup`. It installs pnpm without adding it to `PATH`, leaving the shell config file, the Windows registry, and the GitHub Actions environment files unchanged. `pnpm setup` then prints the lines to add yourself [get.pnpm.io#37](https://github.com/pnpm/get.pnpm.io/issues/37).

##### Patch Changes

##### Security

- Fixed memory exposure and allocation handling vulnerabilities when processing zstd-compressed data [#&#8203;17388](https://github.com/pnpm/pnpm/pull/17388).

- Signing in pnpm and pnpr no longer uses arithmetic that may leak the private key through timing. This covers shared artifacts, OCI registry tokens, and OIDC login state [advisory](https://rustsec.org/advisories/RUSTSEC-2026-0387).

##### Installing packages

- A dependency symlink that pnpm cannot point at the wanted target now fails the install with an error that names the link. This happens when the filesystem still serves the old link after pnpm removed it, as a `node_modules` served by a virtual filesystem does. pnpm used to remove the link again without end and crashed with a segmentation fault once the thread ran out of stack.

- Concurrent workspace installs no longer lose store-index rows when initializing the shared SQLite index [#&#8203;17388](https://github.com/pnpm/pnpm/pull/17388).

- Agent skill links now keep the same target across installs when a package version is installed with several sets of peer dependencies. An install no longer fails with `ERR_PNPM_AGENT_SKILL_PATH_OCCUPIED` on a skill link that pnpm created for another of those copies [#&#8203;17319](https://github.com/pnpm/pnpm/issues/17319).

- pnpm now fails at once, without retrying, when a downloaded package archive contains a rejected entry path [#&#8203;17205](https://github.com/pnpm/pnpm/issues/17205).

- pnpm now builds a git-hosted dependency in every new location it links the dependency into, such as a new global virtual store slot or a fresh `node_modules`. When the side-effects cache holds the build output, pnpm restores it from there. Before, only the install that fetched the dependency built it. This also fixes dependencies that are already in the store [#&#8203;17224](https://github.com/pnpm/pnpm/issues/17224).

- Changes a `preResolution` hook makes to the lockfiles now take effect. The hook now also runs when pnpm installs from an up-to-date lockfile. An error thrown by the hook now fails the install.

- A custom fetcher in the pnpmfile can now call `fetchers.directory` to fetch a package from a local directory. The call rejects with `ERR_PNPM_TARBALL_INTEGRITY` if the lockfile pins an archive integrity for the package. Previously, calling `fetchers.directory` failed with a `TypeError`.

- `packageImportMethod: copy` now also applies to packages that have lifecycle scripts or patches. On a filesystem that supports block cloning, such as ReFS or a Dev Drive on Windows, these packages were cloned [#&#8203;17195](https://github.com/pnpm/pnpm/issues/17195).

- With `nodeLinker: hoisted` and `autoInstallPeers: false`, a package that workspace projects use with different peer versions is now installed once per peer variant, so each project's copy resolves that project's peer [#&#8203;17395](https://github.com/pnpm/pnpm/issues/17395).

- Under `nodeLinker: hoisted`, `pnpm install` now leaves a patched package that is already in place alone. The same holds for a package whose peers include a patched package, such as every package peering on a patched `react-native` [#&#8203;17188](https://github.com/pnpm/pnpm/issues/17188).

- With `nodeLinker: hoisted`, an install that stops partway no longer leaves the next install trusting the packages it was replacing. Before, reverting the change that started the stopped install, such as a patch edit, kept the half-replaced package in `node_modules`.

##### Repeat installs and changed settings

- A repeat `pnpm install` now acts on a change to these settings. Before, it reported "Already up to date".

  - `storeDir` or `virtualStoreDir`: it recreates `node_modules`.
  - `globalVirtualStoreDir`: it links the packages from the new directory.
  - `virtualStoreDirMaxLength`: it renames the virtual store directories.
  - `dangerouslyAllowAllBuilds` turned on: it runs the builds it skipped.
  - `trustLockfile` turned off: it verifies the lockfile.

- `pnpm add`, `pnpm remove`, and `pnpm update` now fail if `node_modules` does not match the current settings. Run `pnpm install` to recreate it. Previously, these commands removed and reinstalled `node_modules` without asking. They report:

  - `ERR_PNPM_UNEXPECTED_STORE` if `node_modules` was linked from a different store.
  - `ERR_PNPM_UNEXPECTED_VIRTUAL_STORE` if it was linked from a different virtual store directory.
  - `ERR_PNPM_MODULES_BREAKING_CHANGE` if it was installed by an incompatible pnpm version, or if `node_modules/.modules.yaml` cannot be read.
  - `ERR_PNPM_NODE_LINKER_DIFF` if it was installed with a different `nodeLinker`.

  `pnpm update` now also fails with `ERR_PNPM_HOIST_PATTERN_DIFF` and the other errors `pnpm add` reports for a changed hoist pattern or `virtualStoreDirMaxLength`.

- An install filtered to some workspace projects now fails if `node_modules` was installed with a different `nodeLinker`, `hoistPattern`, `publicHoistPattern`, store, or virtual store. Before, it recreated `node_modules` and left the projects it skipped without their dependencies. A `pnpm install` of the whole workspace still recreates `node_modules` [pnpm/tasks#217](https://github.com/pnpm/tasks/issues/217).

- `pnpm install` and the `verifyDepsBeforeRun` check now notice an edited patch file when `sharedWorkspaceLockfile` is `false`.

- `pnpm install` now takes its fast path in a workspace with `patchedDependencies` when the previous install ran with another major version of pnpm.

- `pnpm install` now relinks the bins of a workspace package when its `bin` field changes, including with `--frozen-lockfile` and an unchanged lockfile. A filtered install relinks them in the selected projects, and the next install that covers the remaining projects relinks them there [#&#8203;17324](https://github.com/pnpm/pnpm/issues/17324).

- `pnpm install` now removes command shims from `node_modules/.bin` when a workspace dependency renames or removes an entry from its `bin` field [pnpm/tasks#211](https://github.com/pnpm/tasks/issues/211).

##### Resolving dependencies and the lockfile

- A package that a plain range and a `<prefix>:` specifier both resolve from one registry is now recorded once in `pnpm-lock.yaml` and installed once. The lockfile key names the registry a package resolved from. When that registry declares a prefix, through `prefix` in `registries` or through `namedRegistries`, the package is keyed `<name>@<prefix>:<version>` however its dependency was written: with the prefix, as a plain range its scope routes to that registry, or through the default registry. Before, the plain range was keyed `<name>@<version>`, so one tarball was installed twice and `instanceof` checks and module singletons broke between the two copies. The same name and version served by two registries still get separate entries. When several prefixes declare one registry, its packages are keyed by the lexicographically smallest prefix.

  If a scope you route reaches a registry that also declares a prefix, your next `pnpm install` re-keys those entries, which shows up as a lockfile diff. Commit it. `pnpm install --frozen-lockfile` installs the lockfile as recorded.

- `pnpm install` now resolves a scope's packages again when the scope is routed to another registry in `registries`. Before, the lockfile kept the versions and integrity resolved from the old registry, so the install failed when the new registry did not serve a locked version or served different content. pnpm records in the workspace state which registry each scope routed to, so a fresh clone without that record installs the lockfile as recorded. Changing the default registry resolves nothing again, and `pnpm install --frozen-lockfile` is unchanged.

- When `autoDedupe` is off, a workspace project's dependencies now resolve from its own ranges, like they do with `sharedWorkspaceLockfile: false`. Previously, another project's narrower range could hold them back. For example, `ajv: "*"` now resolves to the latest Ajv even if another project depends on `ajv@^6`. Packages that a project lists in `peerDependencies` still follow the versions other projects use. `pnpm dedupe` and installs with `autoDedupe: true` still pick one version that satisfies every range. Set `autoDedupe: true` if CI runs `pnpm dedupe --check` [#&#8203;17235](https://github.com/pnpm/pnpm/issues/17235).

- An optional dependency written with a named-registry prefix, such as `work:^1.0.0`, now fails the install when it cannot be resolved but the lockfile already has a matching entry from that registry. Such a dependency was skipped, which removed its entries from the lockfile.

- With `injectWorkspacePackages: true`, a `workspace:` dependency written as a relative path, such as `workspace:../lib-a`, in a workspace package now resolves from that package's directory [#&#8203;17284](https://github.com/pnpm/pnpm/issues/17284).

- Fixed `pnpm install` failing with `ERR_PNPM_UNSUPPORTED_PROTOCOL` when a workspace package injected through a relative path, such as `workspace:../lib`, has `catalog:` dependencies. Their catalog entries now resolve the way they do for a package injected by name [#&#8203;17280](https://github.com/pnpm/pnpm/issues/17280).

- `pnpm dedupe` now refreshes cached registry metadata while re-resolving locked versions [#&#8203;17208](https://github.com/pnpm/pnpm/issues/17208).

- `pnpm dedupe --check` no longer fails when the only difference is a `deprecated` notice that the registry added or removed after the lockfile was written [#&#8203;17198](https://github.com/pnpm/pnpm/issues/17198).

- Installs delegated to a pnpr server now resolve every prefix declared for a registry URL. A dependency or lockfile entry that used the second of two prefixes for the same registry, such as `zeta:` next to `work:` in `namedRegistries`, failed on the server [pnpm/tasks#204](https://github.com/pnpm/tasks/issues/204).

- Registry revisions are now addressed at `<registry>/<name>@<version>+r<revision>` [pnpm/tasks#208](https://github.com/pnpm/tasks/issues/208).

##### Adding packages and global packages

- `pnpm add <pkg>@<tag>` and `pnpm add <pkg>@<range>` now install the version the tag or range resolves to, even when the saved range allows a newer one [#&#8203;17234](https://github.com/pnpm/pnpm/issues/17234).

- `pnpm add <pkg>` without a version now installs the version under the dist-tag set by the `tag` setting. An `npm:` alias or a `jsr:` dependency without a version resolves that tag too. The setting is read from `pnpm-workspace.yaml`, the global config, `PNPM_CONFIG_TAG`, and `--config.tag`, and defaults to `latest`.

- `pnpm add` now accepts the lowercase short options `-p`, `-d`, `-o`, and `-e` as aliases of `--save-prod`, `--save-dev`, `--save-optional`, and `--save-exact`.

- `pnpm -D install <pkg>` now saves the package to devDependencies. An `add` option such as `-D` or `-E` written before `install <pkg>` was rejected as an unexpected argument.

- `pnpm add -T` now reports that the short option is ambiguous and points to `--save-types` and `--tilde`.

- Global packages installed with `pnpm add -g` are now linked from the global virtual store, so global packages that share dependencies share one copy. Set `virtualStoreType: project` to give each global install its own virtual store.

- `pnpm add -g` and the other `--global` commands now fail with `ERR_PNPM_CONFIG_CONFLICT_VIRTUAL_STORE_DIR_WITH_GLOBAL` when `--virtual-store-dir` is passed. A custom `--hoist-pattern` fails with `ERR_PNPM_CONFIG_CONFLICT_HOIST_PATTERN_WITH_GLOBAL`. Passing `--link-workspace-packages`, `--shared-workspace-lockfile`, or `--config.lockfile-dir` with `--global` also fails, with the matching `ERR_PNPM_CONFIG_CONFLICT_*_WITH_GLOBAL` code.

##### Registries, proxies, and login

- pnpm now reads the `strictSsl` and `localAddress` settings from `pnpm-workspace.yaml`, the global `config.yaml`, `PNPM_CONFIG_*` environment variables, and `--config.*` options. This includes `pnpm self-update` and the download of the pnpm version pinned in `packageManager`. Before, pnpm read them only from `.npmrc` as `strict-ssl` and `local-address` and ignored the other sources [pnpm/tasks#201](https://github.com/pnpm/tasks/issues/201).

- When Corepack runs pnpm and the native binary has to be downloaded, the download now uses the proxy from pnpm's config. That proxy comes from `PNPM_CONFIG_HTTPS_PROXY` and the matching variables, the global `config.yaml`, and the user `.npmrc`. Previously, the download read the proxy only from `HTTPS_PROXY`, `HTTP_PROXY`, and `NO_PROXY` [pnpm/tasks#215](https://github.com/pnpm/tasks/issues/215).

- When the `pnpm` package downloads its native binary on first run, as it does under Corepack, it now trusts the CA certificates from the `ca`, `ca[]`, and `cafile` settings of `.npmrc`, including the ones scoped to the registry. A registry behind a private CA failed with `fetch failed` before, unless `NODE_EXTRA_CA_CERTS` named the CA. A project `.npmrc` is used only while the download is checked against npm's signature. Node.js versions that cannot add CA certificates after startup still need `NODE_EXTRA_CA_CERTS`.

- `pnpm login` saves the token in `<pnpm config>/auth.ini` again. Since v12.1.0, an unscoped login had made its registry the default for every project that did not name a registry. A login now also removes the token an earlier login saved for that registry or scope under `_auth` in the global `config.yaml`. `pnpm login --scope` still routes the scope through the global `registries` setting.

- pnpm no longer warns that a `registries` entry routed through the `@` scope matches no configured registry. pnpm applied such an entry and its options such as `serverType` while reporting it as ignored [pnpm/tasks#184](https://github.com/pnpm/tasks/issues/184).

##### Running scripts and other commands

- `pnpm ci --lockfile` and `pnpm ci --config.lockfile=true` no longer delete `pnpm-lock.yaml`. `pnpm ci` now always keeps the lockfile and runs a frozen install from it.

- `pnpm deploy --legacy` now checks the workspace lockfile against `minimumReleaseAge` and the other resolution policies, as `pnpm install` does, and fails with `ERR_PNPM_MINIMUM_RELEASE_AGE_VIOLATION` when a locked version is too new. It used to deploy an older version that is not in the lockfile without a warning [#&#8203;17209](https://github.com/pnpm/pnpm/issues/17209).

- `pnpm deploy` now keeps a deployed symlink to a directory inside the project even if the directory is not among the deployed files, such as a link into `node_modules` [#&#8203;17288](https://github.com/pnpm/pnpm/issues/17288).

- `pnpm pack` and `pnpm publish` now keep `pnpm.config` in the published `package.json`. They also always include the file `pnpm.config` points to in the tarball [#&#8203;17210](https://github.com/pnpm/pnpm/issues/17210).

- `pnpm -r publish --batch` now sends `PUT /-/pnpr/v0/publish`, the batch publish endpoint that pnpr serves.

- `pnpm audit --ignore` and `pnpm audit --ignore-unfixable` now exit with a non-zero code when vulnerabilities at or above the audit level remain after ignoring. Before, these commands always exited with code 0.

- `pnpm audit --fix` and `pnpm audit --interactive` now fail with `ERR_PNPM_AUDIT_FIX_WITH_IGNORE` when combined with `--ignore` or `--ignore-unfixable`. Before, the fix ran and the ignore flags had no effect.

- `pnpm -r run <script>` now honors the `requiredScripts` setting from `pnpm-workspace.yaml`. If the script is listed there and a selected project does not define it, the run fails with `ERR_PNPM_RECURSIVE_RUN_NO_SCRIPT` and names the projects that lack it.

- `pnpm run` and `pnpm exec` no longer report the dependencies as out of date after a lockfile change when `dedupePeers` is `true` [#&#8203;17322](https://github.com/pnpm/pnpm/issues/17322).

- On macOS, killing pnpm while it runs scripts in several projects at once now ends every one of those scripts. Before, a script could keep running after pnpm died.

- `pnpm run`, `pnpm exec`, and `pnpm dlx` no longer dump a core file for `pnpm` itself when a command they started dies from a signal [#&#8203;17204](https://github.com/pnpm/pnpm/issues/17204).

- The `runtime` setting is now read. Setting `runtime: false` in `pnpm-workspace.yaml`, `pnpm_config_runtime=false`, or `--config.runtime=false` stops `pnpm install`, `pnpm add`, `pnpm dedupe`, and the other installing commands from installing the runtimes in `devEngines.runtime` and `engines.runtime`. `pnpm dedupe` now also accepts `--no-runtime`.

- Directory selectors with `legacyDirFiltering: true` now include the project in the named directory along with its descendants. Excluding a directory also excludes that project [pnpm/tasks#196](https://github.com/pnpm/tasks/issues/196).

- `pnpm help --all` and `pnpm help -a` now print the full list of commands. They used to fail with an "unrecognized subcommand" error.

- Reading the file-creation mask on Linux no longer sets it. pnpm had to replace the mask to learn what it was, and a file another thread created in the window between the two calls was created unmasked.

- pnpm now passes an interrupt on to the handler it replaced, with the arguments that handler expects. A handler that asked for the signal's details was called without them. Only a program that embeds pnpm and installs its own `SIGINT`, `SIGTERM` or `SIGHUP` handler was affected.

##### Store

- `pnpm store prune` now keeps the packages that a project still uses when pnpm copied or cloned them into `node_modules`. These include patched packages, built packages, and every package installed with `packageImportMethod: copy`. Before, the prune removed them, and the next install downloaded them again [#&#8203;7192](https://github.com/pnpm/pnpm/issues/7192).

- `pnpm store prune` now keeps the packages of a project when the store path reaches its directory through a symlink. Previously, prune dropped such a project from its list and removed the packages only that project used from the global virtual store.

- `pnpm store prune` now removes the package metadata cache, the `metadata*` directories in the cache directory. This clears the outdated integrity checksums that cause `ERR_PNPM_TARBALL_INTEGRITY` when a registry republishes a version.

##### Speed

- `pnpm install` resolves dependencies faster in large workspaces on Linux, macOS, and Windows on x64 and arm64.

- Sped up cold installs and `pnpm fetch` on macOS. pnpm now links downloaded packages into the virtual store on a separate thread, so a slow link pass no longer pauses the downloads that are still in flight [#&#8203;17271](https://github.com/pnpm/pnpm/issues/17271).

- Sped up installs that add new projects to a workspace with a peer-heavy lockfile. pnpm now reads the lockfile's locked peer versions once for all new projects [#&#8203;17311](https://github.com/pnpm/pnpm/issues/17311).

- Warm installs no longer read the `package.json` of every git-hosted dependency. pnpm now stores the manifest of a git-hosted package in the store index when it fetches the package.

- `pnpm update --depth <n>` with a large `n`, such as the `--depth=9999` that `update-browserslist-db` runs, now finishes as fast as `--depth Infinity`. It used memory and stack in proportion to `n` on lockfiles with dependency cycles, and could run out of memory or crash [#&#8203;17329](https://github.com/pnpm/pnpm/issues/17329).

##### Output and warnings

- Output that fills the terminal, such as `pnpm run --stream` script output, is now written once. Before, every new line also rewrote all the lines already on screen [#&#8203;17387](https://github.com/pnpm/pnpm/issues/17387).

- pnpm now warns when a pnpmfile defines the `importPackage` hook. pnpm does not run this hook. Use the `packageImportMethod` setting to choose how packages are written to `node_modules`.

- `pnpm` now warns when a project's `pnpm-workspace.yaml` sets `ci`, `globalShims`, or `tools`, and names where to set them instead. These settings were already ignored there without a warning. `pnpm config set` also refuses to write them to a project's `pnpm-workspace.yaml`.

- pnpm no longer documents the `scriptsPrependNodePath` setting, which has no effect. A `pnpm-workspace.yaml` that sets it still loads.

<!-- sponsors -->

#### Platinum Sponsors

<table>
  <tbody>
    <tr>
      <td align="center" valign="middle">
        <a href="https://bit.cloud/?utm_source=pnpm&utm_medium=release_notes" target="_blank" rel="noopener noreferrer"><img src="https://pnpm.io/img/users/bit.svg" width="80" alt="Bit"></a>
      </td>
      <td align="center" valign="middle">
        <a href="https://openai.com/?utm_source=pnpm&utm_medium=release_notes" target="_blank" rel="noopener noreferrer">
          <picture>
            <source media="(prefers-color-scheme: light)" srcset="https://pnpm.io/img/users/openai_dark.svg" />
            <source media="(prefers-color-scheme: dark)" srcset="https://pnpm.io/img/users/openai_light.svg" />
            <img src="https://pnpm.io/img/users/openai_dark.svg" width="160" alt="OpenAI" />
          </picture>
        </a>
      </td>
      <td align="center" valign="middle">
        <a href="https://notion.com/?utm_source=pnpm&utm_medium=release_notes" target="_blank" rel="noopener noreferrer"><img src="https://pnpm.io/img/users/notion.svg" width="80" alt="Notion"></a>
      </td>
    </tr>
    <tr>
      <td align="center" valign="middle">
        <a href="https://coderabbit.ai/?utm_source=pnpm&utm_medium=release_notes" target="_blank" rel="noopener noreferrer">
          <picture>
            <source media="(prefers-color-scheme: light)" srcset="https://pnpm.io/img/users/coderabbit.svg" />
            <source media="(prefers-color-scheme: dark)" srcset="https://pnpm.io/img/users/coderabbit_light.svg" />
            <img src="https://pnpm.io/img/users/coderabbit.svg" width="220" alt="CodeRabbit" />
          </picture>
        </a>
      </td>
    </tr>
  </tbody>
</table>

#### Gold Sponsors

<table>
  <tbody>
    <tr>
      <td align="center" valign="middle">
        <a href="https://sanity.io/?utm_source=pnpm&utm_medium=release_notes" target="_blank" rel="noopener noreferrer">
          <picture>
            <source media="(prefers-color-scheme: light)" srcset="https://pnpm.io/img/users/sanity.svg" />
            <source media="(prefers-color-scheme: dark)" srcset="https://pnpm.io/img/users/sanity_light.svg" />
            <img src="https://pnpm.io/img/users/sanity.svg" width="120" alt="Sanity" />
          </picture>
        </a>
      </td>
      <td align="center" valign="middle">
        <a href="https://discord.com/?utm_source=pnpm&utm_medium=release_notes" target="_blank" rel="noopener noreferrer">
          <picture>
            <source media="(prefers-color-scheme: light)" srcset="https://pnpm.io/img/users/discord.svg" />
            <source media="(prefers-color-scheme: dark)" srcset="https://pnpm.io/img/users/discord_light.svg" />
            <img src="https://pnpm.io/img/users/discord.svg" width="220" alt="Discord" />
          </picture>
        </a>
      </td>
      <td align="center" valign="middle">
        <a href="https://vite.dev/?utm_source=pnpm&utm_medium=release_notes" target="_blank" rel="noopener noreferrer"><img src="https://pnpm.io/img/users/vitejs.svg" width="42" alt="Vite"></a>
      </td>
    </tr>
    <tr>
      <td align="center" valign="middle">
        <a href="https://serpapi.com/?utm_source=pnpm&utm_medium=release_notes" target="_blank" rel="noopener noreferrer">
          <picture>
            <source media="(prefers-color-scheme: light)" srcset="https://pnpm.io/img/users/serpapi_dark.svg" />
            <source media="(prefers-color-scheme: dark)" srcset="https://pnpm.io/img/users/serpapi_light.svg" />
            <img src="https://pnpm.io/img/users/serpapi_dark.svg" width="160" alt="SerpApi" />
          </picture>
        </a>
      </td>
      <td align="center" valign="middle">
        <a href="https://stackblitz.com/?utm_source=pnpm&utm_medium=release_notes" target="_blank" rel="noopener noreferrer">
          <picture>
            <source media="(prefers-color-scheme: light)" srcset="https://pnpm.io/img/users/stackblitz.svg" />
            <source media="(prefers-color-scheme: dark)" srcset="https://pnpm.io/img/users/stackblitz_light.svg" />
            <img src="https://pnpm.io/img/users/stackblitz.svg" width="190" alt="Stackblitz" />
          </picture>
        </a>
      </td>
      <td align="center" valign="middle">
        <a href="https://workleap.com/?utm_source=pnpm&utm_medium=release_notes" target="_blank" rel="noopener noreferrer">
          <picture>
            <source media="(prefers-color-scheme: light)" srcset="https://pnpm.io/img/users/workleap.svg" />
            <source media="(prefers-color-scheme: dark)" srcset="https://pnpm.io/img/users/workleap_light.svg" />
            <img src="https://pnpm.io/img/users/workleap.svg" width="190" alt="Workleap" />
          </picture>
        </a>
      </td>
    </tr>
    <tr>
      <td align="center" valign="middle">
        <a href="https://nx.dev/?utm_source=pnpm&utm_medium=release_notes" target="_blank" rel="noopener noreferrer">
          <picture>
            <source media="(prefers-color-scheme: light)" srcset="https://pnpm.io/img/users/nx.svg" />
            <source media="(prefers-color-scheme: dark)" srcset="https://pnpm.io/img/users/nx_light.svg" />
            <img src="https://pnpm.io/img/users/nx.svg" width="50" alt="Nx" />
          </picture>
        </a>
      </td>
      <td align="center" valign="middle">
        <a href="https://latitude.so/?utm_source=pnpm&utm_medium=release_notes" target="_blank" rel="noopener noreferrer"><img src="https://pnpm.io/img/users/latitude.svg" width="160" alt="Latitude"></a>
      </td>
    </tr>
  </tbody>
</table>

<!-- sponsors end -->

</details>

---

### Configuration

📅 **Schedule**: (UTC)

- Branch creation
  - Between 12:00 AM and 03:59 AM, only on Monday (`* 0-3 * * 1`)
- Automerge
  - At any time (no schedule defined)

🚦 **Automerge**: Disabled by config. Please merge this manually once you are satisfied.

♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox.

👻 **Immortal**: This PR will be recreated if closed unmerged. Get [config help](https://github.com/renovatebot/renovate/discussions) if that's undesired.

---

 - [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check this box

---

This PR has been generated by [Mend Renovate CLI](https://github.com/renovatebot/renovate).
<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0NC4xODEuNyIsInVwZGF0ZWRJblZlciI6IjQ0LjE5OS4yIiwidGFyZ2V0QnJhbmNoIjoibWFpbiIsImxhYmVscyI6W119-->

Reviewed-on: https://gitea.com/gitea/docs/pulls/626
Reviewed-by: Lunny Xiao <[email protected]>
Co-authored-by: Renovate Bot <[email protected]>
2026-11-02 00:49:41 +00:00
silverwindandLunny Xiao 6632d38fe2 Document the commitstatus token scope (#622)
Documents https://github.com/go-gitea/gitea/pull/40144 for main, which adds `read:commitstatus` and `write:commitstatus` token scopes so CI integrations can report commit statuses without full repository access. The repository scopes include them.

Adds the scope to the OAuth2 provider scope table and the API usage permission list. Draft until the Gitea PR merges.

_Written by Claude (Opus 5.5)._

---------

Co-authored-by: Lunny Xiao <[email protected]>
Reviewed-on: https://gitea.com/gitea/docs/pulls/622
Reviewed-by: Lunny Xiao <[email protected]>
Co-authored-by: silverwind <[email protected]>
2026-11-01 18:19:38 +00:00
silverwindandbircni ef57d8d2c0 Document gitea-cli OAuth2 application (#620)
Documents https://github.com/go-gitea/gitea/pull/40036 for main, which renames the built-in tea OAuth2 application to gitea-cli and keeps `tea` as an alias in `[oauth2] DEFAULT_APPLICATIONS`.

The default list and the pre-configured applications table also gain the missing gitea-app and gitea-vscode entries.

_Written by Claude (Opus 5.5)._

---------

Co-authored-by: bircni <[email protected]>
Reviewed-on: https://gitea.com/gitea/docs/pulls/620
Reviewed-by: bircni <[email protected]>
Co-authored-by: silverwind <[email protected]>
2026-11-01 12:50:48 +00:00
bircni ba4abd749d update to 28.3.0 (#623)
Bump the v28 docs to the 28.3.0 release:

- point the `28` version variables (`version`, `sourceVersion`, `dockerVersion`, `displayVersion`) at 28.3.0
- update the v28 entry in `update_api_docs.sh` to `28.3.0`
- regenerate `static/swagger-28.json` and `static/openapi3-28.json` from the `v28.3.0` tag, which picks up the updated `Deadline` and `Labels` descriptions from go-gitea/gitea#39869 and go-gitea/gitea#39873

Reviewed-on: https://gitea.com/gitea/docs/pulls/623
Reviewed-by: silverwind <[email protected]>
2026-11-01 10:11:04 +00:00
silverwind 8197192134 Document GITEA_* env aliases, missing variables and accurate examples (#618)
https://gitea.com/gitea/runner/pulls/1358 mirrors every built-in `GITHUB_*` variable as `GITEA_*`. The variables page now lists both names for each, in the same paired style as the context table.

It also adds variables that runners already set but the page left out (`*_ACTOR_ID`, `*_REF_PROTECTED`, `*_REPOSITORY_ID`, `*_REPOSITORY_OWNER_ID`, `*_TRIGGERING_ACTOR`, `*_WORKFLOW_SHA`, `ACT`, `ImageOS`, `RUNNER_DEBUG`, `RUNNER_ENVIRONMENT`, `RUNNER_NAME`, `RUNNER_WORKSPACE`, `TRACEPARENT`, `TRACESTATE`).

Examples copied from GitHub's runner (`/home/runner/work/_temp/...` paths, `__run`, `ghs_***`) are replaced with the values gitea-runner and Gitea actually produce, using placeholder users, repositories and instance URLs.

The zh-cn and zh-tw pages get the environment variable section they never had.

Reviewed-on: https://gitea.com/gitea/docs/pulls/618
Reviewed-by: Lunny Xiao <[email protected]>
Co-authored-by: silverwind <[email protected]>
2026-10-31 05:25:00 +00:00
brock0124andLunny Xiao 97fa45dabc fix(Hem Chart Registry docs): typo (#614)
Co-authored-by: Lunny Xiao <[email protected]>
Reviewed-on: https://gitea.com/gitea/docs/pulls/614
Reviewed-by: Lunny Xiao <[email protected]>
Co-authored-by: brock0124 <[email protected]>
2026-10-29 02:19:31 +00:00
ee9e91cdeb Add project workflow documentation (#308)
Documentation for https://github.com/go-gitea/gitea/pull/30205

---------

Co-authored-by: silverwind <[email protected]>
Co-authored-by: silverwind <[email protected]>
Co-authored-by: silverwind <[email protected]>
Reviewed-on: https://gitea.com/gitea/docs/pulls/308
Reviewed-by: silverwind <[email protected]>
2026-10-28 16:12:53 +00:00
Renovate BotandLunny Xiao d301461648 chore(deps): update dependencies (#611)
This PR contains the following updates:

| Package | Change | [Age](https://docs.renovatebot.com/merge-confidence/) | [Confidence](https://docs.renovatebot.com/merge-confidence/) | Type | Update | Pending |
|---|---|---|---|---|---|---|
| [@astrojs/check](https://github.com/withastro/astro/tree/main/packages/language-tools/astro-check) ([source](https://github.com/withastro/astro/tree/HEAD/packages/language-tools/astro-check)) | [`0.9.10` → `0.9.11`](https://renovatebot.com/diffs/npm/@astrojs%2fcheck/0.9.10/0.9.11) | ![age](https://developer.mend.io/api/mc/badges/age/npm/@astrojs%2fcheck/0.9.11?slim=true) | ![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/@astrojs%2fcheck/0.9.10/0.9.11?slim=true) | devDependencies | patch | `0.9.12` |
| [astro](https://astro.build) ([source](https://github.com/withastro/astro/tree/HEAD/packages/astro)) | [`7.3.8` → `7.4.1`](https://renovatebot.com/diffs/npm/astro/7.3.8/7.4.1) | ![age](https://developer.mend.io/api/mc/badges/age/npm/astro/7.4.1?slim=true) | ![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/astro/7.3.8/7.4.1?slim=true) | dependencies | minor | `7.4.2` |
| [pnpm](https://pnpm.io) ([source](https://github.com/pnpm/pnpm/tree/HEAD/pnpm/npm/pnpm)) | [`12.13.0` → `12.15.0`](https://renovatebot.com/diffs/npm/pnpm/12.13.0/12.15.0) | ![age](https://developer.mend.io/api/mc/badges/age/npm/pnpm/12.15.0?slim=true) | ![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/pnpm/12.13.0/12.15.0?slim=true) | packageManager | minor |  |
| [ubuntu](https://github.com/actions/runner-images) | `24.04` → `26.04` | ![age](https://developer.mend.io/api/mc/badges/age/github-runners/ubuntu/26.04?slim=true) | ![confidence](https://developer.mend.io/api/mc/badges/confidence/github-runners/ubuntu/24.04/26.04?slim=true) | github-runner | major |  |

---

### Release Notes

<details>
<summary>withastro/astro (@&#8203;astrojs/check)</summary>

### [`v0.9.11`](https://github.com/withastro/astro/blob/HEAD/packages/language-tools/astro-check/CHANGELOG.md#0911)

[Compare Source](https://github.com/withastro/astro/compare/@astrojs/[email protected]...@astrojs/[email protected])

##### Patch Changes

- [#&#8203;18445](https://github.com/withastro/astro/pull/18445) [`d736df7`](https://github.com/withastro/astro/commit/d736df7a9cc03c12b22e538f058fb1f769672471) Thanks [@&#8203;astro-factory](https://github.com/apps/astro-factory)! - Fixes the `--preserveWatchOutput` help text, which described the opposite of the flag's behavior. Setting it to `true` keeps output between checks in watch mode.

- [#&#8203;18444](https://github.com/withastro/astro/pull/18444) [`c55134c`](https://github.com/withastro/astro/commit/c55134c1d3c238a43e17cfeff136375a4d9d5d35) Thanks [@&#8203;astro-factory](https://github.com/apps/astro-factory)! - Fixes `astro check --watch` exiting immediately when the project path contains `node_modules` or `.git`

</details>

<details>
<summary>withastro/astro (astro)</summary>

### [`v7.4.1`](https://github.com/withastro/astro/blob/HEAD/packages/astro/CHANGELOG.md#741)

[Compare Source](https://github.com/withastro/astro/compare/[email protected]@7.4.1)

##### Patch Changes

- [#&#8203;18425](https://github.com/withastro/astro/pull/18425) [`6fdc36e`](https://github.com/withastro/astro/commit/6fdc36ed97316d932d2e143b85739f0af37ab329) Thanks [@&#8203;astro-factory](https://github.com/apps/astro-factory)! - Preserves the last modified date of cached optimized images and incrementally restored build output when copying them into the build output on all platforms

- [#&#8203;18419](https://github.com/withastro/astro/pull/18419) [`395d195`](https://github.com/withastro/astro/commit/395d19525986a276bf81af7bd365e7a35dc20ef3) Thanks [@&#8203;astro-factory](https://github.com/apps/astro-factory)! - Fixes a typo in the error message shown when a font `cssVariable` is invalid

- [#&#8203;18401](https://github.com/withastro/astro/pull/18401) [`484822f`](https://github.com/withastro/astro/commit/484822fbba652745c4f2da5ef434a350203578e2) Thanks [@&#8203;danielmlr](https://github.com/danielmlr)! - Fixes a bug where projects whose path contains an apostrophe (`'`) failed to load sessions, middleware, a cache provider, or a `src/fetch.ts` handler

- [#&#8203;18407](https://github.com/withastro/astro/pull/18407) [`a6e00be`](https://github.com/withastro/astro/commit/a6e00beae6c134d0c1b5248eb02fbf119b7d64e9) Thanks [@&#8203;astro-factory](https://github.com/apps/astro-factory)! - Fixes a `TSCONFIG_ERROR` during `astro build` when a parent directory contains a `tsconfig.json` that cannot be resolved, such as one extending `astro/tsconfigs/strict` without installed dependencies

- [#&#8203;18427](https://github.com/withastro/astro/pull/18427) [`908f167`](https://github.com/withastro/astro/commit/908f1672a9fe6c40e6b73bfb0307f0d1f3ac2057) Thanks [@&#8203;astro-factory](https://github.com/apps/astro-factory)! - Fixes an `Unterminated string` build error when application code imports from `astro/config` (for example, `fontProviders` in a shared fonts module)

- [#&#8203;18428](https://github.com/withastro/astro/pull/18428) [`40e7d54`](https://github.com/withastro/astro/commit/40e7d54c7f969b402f011d32ccd87123835d0aa1) Thanks [@&#8203;astro-factory](https://github.com/apps/astro-factory)! - Fixes missing styles in production builds for conditionally rendered framework components (e.g. Svelte components inside `{#if}` blocks) imported by a hydrated island

- [#&#8203;18424](https://github.com/withastro/astro/pull/18424) [`617ff59`](https://github.com/withastro/astro/commit/617ff59820b4a5777eb5d3765c09d29082b11acc) Thanks [@&#8203;astro-factory](https://github.com/apps/astro-factory)! - Fixes style-only HMR in `.astro` components falling back to a full page reload when a PostCSS plugin (such as `@unocss/postcss`) lists the component as a dependency of its own style

- [#&#8203;18274](https://github.com/withastro/astro/pull/18274) [`70834bb`](https://github.com/withastro/astro/commit/70834bbea43695c563358ea055d32ecb5e0a78dd) Thanks [@&#8203;Princesseuh](https://github.com/Princesseuh)! - Fixes increasing memory usage when editing the Astro configuration while the dev server is running

### [`v7.4.0`](https://github.com/withastro/astro/blob/HEAD/packages/astro/CHANGELOG.md#740)

[Compare Source](https://github.com/withastro/astro/compare/[email protected]@7.4.0)

##### Minor Changes

- [#&#8203;17894](https://github.com/withastro/astro/pull/17894) [`b7a1bd9`](https://github.com/withastro/astro/commit/b7a1bd99ac7c68cf36078058f15f1bd1fab4cbe8) Thanks [@&#8203;Princesseuh](https://github.com/Princesseuh)! - Adds a new `--timings` flag to `astro build` to help you find what slows down your builds

  When a build takes longer than expected, it can be hard to tell whether the cause is an integration, a Markdown processor plugin, image processing, or even syntax highlighting. Add the `--timings` flag to your build command to print a summary once the build finishes:

  ```sh
  astro build --timings
  ```

  This is helpful to decide what to optimize first, or to compare build times before and after a change to your project.

- [#&#8203;17830](https://github.com/withastro/astro/pull/17830) [`a937b0d`](https://github.com/withastro/astro/commit/a937b0d3ec236c8dd1c62a7a4da7c65b2fdbb7c1) Thanks [@&#8203;florian-lefebvre](https://github.com/florian-lefebvre)! - Adds a new optional `getFontProperties()` callback to font providers

  Font providers can now expose the weights, styles, subsets and formats a font family actually has. Astro uses it when resolving your font configuration to warn about values a provider cannot serve, for example a weight a font family does not offer.

  All built-in providers implement it. If you maintain a custom font provider, implementing this callback is optional for now but it will be required in Astro 8:

  ```ts
  import type { FontProvider } from 'astro';

  const provider: FontProvider = {
  	name: 'my-provider',
  	resolveFont: ({ familyName }) => {
  		// ...
  	},
  	getFontProperties: ({ familyName }) => ({
  		weights: ['400', '700'],
  		styles: ['normal', 'italic'],
  		subsets: ['latin'],
  		formats: ['woff2'],
  	}),
  };
  ```

- [#&#8203;17830](https://github.com/withastro/astro/pull/17830) [`a937b0d`](https://github.com/withastro/astro/commit/a937b0d3ec236c8dd1c62a7a4da7c65b2fdbb7c1) Thanks [@&#8203;florian-lefebvre](https://github.com/florian-lefebvre)! - Adds a `fetch()` function to the font provider `init()` context

  This function requests a provider API, retrying transient failures and more. It lets providers that wrap a 3rd-party unifont provider pass the context to it directly:

  ```ts
  import type { FontProvider } from 'astro';
  import type { InitializedProvider } from 'unifont';
  import { acmeProvider } from '@acme/unifont-provider';

  export function acmeFontProvider(): FontProvider {
  	const provider = acmeProvider();
  	let initializedProvider: InitializedProvider | undefined;
  	return {
  		name: provider._name,
  		async init(context) {
  			initializedProvider = await provider(context);
  		},
  		// ...
  	};
  }
  ```

- [#&#8203;18015](https://github.com/withastro/astro/pull/18015) [`0b30239`](https://github.com/withastro/astro/commit/0b302391059dbc4f8ece24d7b44599c7ec9fef24) Thanks [@&#8203;florian-lefebvre](https://github.com/florian-lefebvre)! - Adds `experimental.redirectPage`, letting you provide your own `src/pages/3xx.astro` in place of the redirect page Astro generates in static builds.

  A static site has no server to send an HTTP `Location` header, so Astro generates a small HTML page with a `<meta http-equiv="refresh">` tag for each redirect it builds:

  - Your [`redirects`](https://docs.astro.build/en/reference/configuration-reference/#redirects) entries
  - `Astro.redirect()` calls in prerendered pages
  - i18n redirects

  That page's markup, wording, and refresh delay were fixed, so you could not brand or translate it.

  To enable this feature, add the experimental flag to your Astro config:

  ```js
  // astro.config.mjs
  import { defineConfig } from 'astro/config';

  export default defineConfig({
    experimental: {
      redirectPage: true,
    },
  });
  ```

  Then create `src/pages/3xx.astro`. Astro renders it once per redirect, passing that redirect's details as props:

  ```astro
  ---
  // src/pages/3xx.astro
  import type { RedirectPageProps as Props } from 'astro';

  const { from, to, status, delay } = Astro.props;
  ---

  <html lang="en">
    <head>
      <meta http-equiv="refresh" content={`${delay};url=${to}`} />
      <meta name="robots" content="noindex" />
      <title>Redirecting…</title>
    </head>
    <body>
      <p>This page has moved. <a href={to}>Continue to {to}</a>.</p>
    </body>
  </html>
  ```

  Your page must render a `<meta http-equiv="refresh">` tag pointing at `to`, because in a static build that tag is the redirect. Astro logs a warning during the build if it is missing.

  See the [experimental redirect page documentation](https://docs.astro.build/en/reference/experimental-flags/redirect-page/) for more information.

- [#&#8203;18265](https://github.com/withastro/astro/pull/18265) [`910ee69`](https://github.com/withastro/astro/commit/910ee69147c4c2460f2b31a294bc70c5269f44d4) Thanks [@&#8203;astro-factory](https://github.com/apps/astro-factory)! - Adds a `notFound()` handler to `astro/fetch` and `astro/hono` that renders the custom 404 page when no route matches, including a prerendered `404.astro`

  Add it before `pages()` in a custom worker or server entrypoint that does not run inside `App.render`, such as a Hono app used directly as a Cloudflare worker. Without it, `pages()` and `middleware()` return an empty 404 with an internal `X-Astro-Error` header for unmatched routes. `astro()` already includes this handling.

  ```ts
  import { Hono } from 'hono';
  import { notFound, pages } from 'astro/hono';
  import { cf } from '@astrojs/cloudflare/hono';

  const app = new Hono();
  app.use(cf());
  app.use(notFound());
  app.use(pages());
  export default app;
  ```

- [#&#8203;18177](https://github.com/withastro/astro/pull/18177) [`7d814e5`](https://github.com/withastro/astro/commit/7d814e5ac3e7c84b4e4fd4e6c7cc6820f553742c) Thanks [@&#8203;Princesseuh](https://github.com/Princesseuh)! - Adds support for separate `build` and `runtime` image services

  `image.service` now also accepts a `build` and a `runtime` service. The `build` service optimizes images on prerendered pages when you build your site, and is also used during development. The `runtime` service handles images on on-demand rendered pages and the image endpoint of your deployed site.

  The following example uses Sharp to optimize images on prerendered pages, and an image CDN for on-demand rendered pages:

  ```js
  // astro.config.mjs
  import { defineConfig } from 'astro/config';

  export default defineConfig({
    image: {
      service: {
        build: { entrypoint: 'astro/assets/services/sharp' },
        runtime: { entrypoint: 'my-image-cdn-service' },
      },
    },
  });
  ```

##### Changes for integration authors

`config.image.service` keeps the shape the user configured it with. When a project sets `build` and `runtime` services, `config.image.service.entrypoint` and `config.image.service.config` are `undefined`.

If your integration reads `image.service`, check for the `runtime` property to get the service you need:

```ts
'astro:config:done': ({ config }) => {
  const { service } = config.image;
  const runtimeService = 'runtime' in service ? service.runtime : service;

  if (runtimeService.entrypoint === 'astro/assets/services/sharp') {
    // ...
  }
},
```

Setting `image.service` with `updateConfig()` replaces the existing service instead of merging with it when the two use different shapes. For example, setting a single service in a project that uses `build` and `runtime` services replaces both.

- [#&#8203;18277](https://github.com/withastro/astro/pull/18277) [`2715958`](https://github.com/withastro/astro/commit/2715958666b0a706b069d0592da3a070b3796a9b) Thanks [@&#8203;florian-lefebvre](https://github.com/florian-lefebvre)! - Increases the minimum Node.js version to 22.22.0

  The minimum supported Node.js version is now 22.22.0, the latest Node.js 22 release supported by StackBlitz. This change is in line with Astro's [Node.js support policy](https://docs.astro.build/en/upgrade-astro/#support).

- [#&#8203;18313](https://github.com/withastro/astro/pull/18313) [`71e2b24`](https://github.com/withastro/astro/commit/71e2b24bb365e5a8809fc0432716f91ecc6d8ab5) Thanks [@&#8203;matthewp](https://github.com/matthewp)! - Allows integrations to control where Astro writes client, server, and prerender build output.

  Custom prerenderers can access the final output locations through the new `outputDirectories` context:

  ```ts
  setPrerenderer((defaultPrerenderer, { outputDirectories }) => {
    const { client, server, prerender } = outputDirectories

    return createPrerenderer({
      defaultPrerenderer,
      client,
      server,
      prerender,
    })
  })
  ```

- [#&#8203;18177](https://github.com/withastro/astro/pull/18177) [`7d814e5`](https://github.com/withastro/astro/commit/7d814e5ac3e7c84b4e4fd4e6c7cc6820f553742c) Thanks [@&#8203;Princesseuh](https://github.com/Princesseuh)! - Lets custom prerenderers report the images used on prerendered pages

  Astro now generates images on prerendered pages from the image information that a prerenderer's `getStaticPaths()` and `render()` return. If your prerenderer wraps Astro's default one, pass this information through:

  ```js
  setPrerenderer((defaultPrerenderer) => ({
    name: 'my-prerenderer',
    async setup() {
      await defaultPrerenderer.setup?.();
    },
    async getStaticPaths() {
      const result = await defaultPrerenderer.getStaticPaths();
      const { paths, metadata } = Array.isArray(result) ? { paths: result } : result;
      return { paths: paths.filter(({ pathname }) => !pathname.startsWith('/drafts')), metadata };
    },
    render(request, options) {
      return defaultPrerenderer.render(request, options);
    },
    async teardown() {
      await defaultPrerenderer.teardown?.();
    },
  }));
  ```

  Returning a plain array from `getStaticPaths()` or a plain `Response` from `render()` still works, but is deprecated: images used on those pages are not generated.

- [#&#8203;17932](https://github.com/withastro/astro/pull/17932) [`b905ff1`](https://github.com/withastro/astro/commit/b905ff1a8649b7a9a9056d9b05ca25ae7b3c07a1) Thanks [@&#8203;Princesseuh](https://github.com/Princesseuh)! - Adds support for experimental parallel prerendering

  This experimental feature makes `astro build` render your static pages in parallel, using multiple CPU cores at once instead of one. On sites with many pages, this can significantly reduce build times, at the cost of higher memory usage.

  To enable this feature, add the experimental flag in your Astro config:

  ```js
  // astro.config.mjs
  import { defineConfig } from 'astro/config';

  export default defineConfig({
    experimental: {
      parallelPrerender: true,
    },
  });
  ```

  By default, Astro uses all available CPU cores but one. Each additional worker increases the memory used by your build. If your build runs out of memory, or does not get faster, set `workers` to a lower number:

  ```js
  // astro.config.mjs
  import { defineConfig } from 'astro/config';

  export default defineConfig({
    experimental: {
      parallelPrerender: {
        workers: 4,
      },
    },
  });
  ```

  Each worker renders as many pages at once as your [`build.concurrency`](https://docs.astro.build/en/reference/configuration-reference/#buildconcurrency) setting allows.

  This feature has no effect when your adapter provides its own way of prerendering pages. Among official adapters, this only applies to `@astrojs/cloudflare`. Astro logs a warning during the build when the flag is ignored.

  For more information on enabling and using this feature in your project, see the [experimental parallel prerendering docs](https://docs.astro.build/en/reference/experimental-flags/parallel-prerender/).

##### Patch Changes

- [#&#8203;18313](https://github.com/withastro/astro/pull/18313) [`71e2b24`](https://github.com/withastro/astro/commit/71e2b24bb365e5a8809fc0432716f91ecc6d8ab5) Thanks [@&#8203;matthewp](https://github.com/matthewp)! - Fixes deployment integrations being unable to use Vite manifests when preparing build output

- [#&#8203;18270](https://github.com/withastro/astro/pull/18270) [`0c3bf11`](https://github.com/withastro/astro/commit/0c3bf116ee70aae4772313fc167a372c7d54869e) Thanks [@&#8203;ArmandPhilippot](https://github.com/ArmandPhilippot)! - Fixes the JSDoc for CSP utilities to point to the render context reference in docs.

- [#&#8203;18368](https://github.com/withastro/astro/pull/18368) [`1f04637`](https://github.com/withastro/astro/commit/1f04637a6ca23b59e8982424248ead47d6a3ec31) Thanks [@&#8203;43081j](https://github.com/43081j)! - Replaces internal use of `find-proc` with `tinyps`.

- [#&#8203;18355](https://github.com/withastro/astro/pull/18355) [`59b9cc0`](https://github.com/withastro/astro/commit/59b9cc00726495bb45f5c4b000dbbb7b5a19d662) Thanks [@&#8203;matthewp](https://github.com/matthewp)! - Fixes prerender errors being silently swallowed when using a custom `src/fetch.ts` with an adapter that prerenders in its own runtime, such as `@astrojs/cloudflare`. Previously, a page that threw during prerendering was written as an empty HTML file and the build succeeded. The build now fails with the underlying error.

- [#&#8203;18177](https://github.com/withastro/astro/pull/18177) [`7d814e5`](https://github.com/withastro/astro/commit/7d814e5ac3e7c84b4e4fd4e6c7cc6820f553742c) Thanks [@&#8203;Princesseuh](https://github.com/Princesseuh)! - Fixes original images being kept despite only `width` or `height` being read.

- [#&#8203;18353](https://github.com/withastro/astro/pull/18353) [`af54bfa`](https://github.com/withastro/astro/commit/af54bfaebff9180cb96822c0da11737b4b26e65a) Thanks [@&#8203;astro-factory](https://github.com/apps/astro-factory)! - Fixes `glob()` loader swallowing rendering errors during content sync, which allowed `astro build` to exit successfully (code 0) even when a remark/rehype plugin threw an error

- [#&#8203;18271](https://github.com/withastro/astro/pull/18271) [`23e8e28`](https://github.com/withastro/astro/commit/23e8e28c22effe006fe19abb193802e2fec8eaab) Thanks [@&#8203;ArmandPhilippot](https://github.com/ArmandPhilippot)! - Fixes the `sync` command description in `astro help`, which only mentioned content collections.

- Updated dependencies \[[`b7a1bd9`](https://github.com/withastro/astro/commit/b7a1bd99ac7c68cf36078058f15f1bd1fab4cbe8), [`2715958`](https://github.com/withastro/astro/commit/2715958666b0a706b069d0592da3a070b3796a9b)]:
  - [@&#8203;astrojs/internal-helpers](https://github.com/astrojs/internal-helpers)@0.13.0
  - [@&#8203;astrojs/markdown-satteri](https://github.com/astrojs/markdown-satteri)@0.4.4
  - [@&#8203;astrojs/telemetry](https://github.com/astrojs/telemetry)@3.4.0

</details>

<details>
<summary>pnpm/pnpm (pnpm)</summary>

### [`v12.15.0`](https://github.com/pnpm/pnpm/releases/tag/v12.15.0): pnpm 12.15

[Compare Source](https://github.com/pnpm/pnpm/compare/v12.14.0...v12.15.0)

This release lets `pnpm-workspace.yaml` delegate settings to other files and config dependencies with `include`, adds `lockfile.integrityTarget: tar` and tar-registry support, accepts zstd-compressed registry responses, and honors `SOURCE_DATE_EPOCH` in `pnpm deploy` and `pnpm install`. It also rejects registry metadata that names a different package than the one requested and speeds up workspace installs and macOS file linking.

##### Minor Changes

- `pnpm-workspace.yaml` can now delegate settings to other YAML files and to config dependencies through a new `include` field. Each source may define only the settings the root manifest delegates to it. Every setting has one owner. Commands that change settings write each one to the file that owns it. A relative path in an included file is relative to that file. See [Splitting Settings Across Files](https://pnpm.io/config-includes).

  A config dependency supplies its settings through the YAML file named in the `pnpm.config` field of its `package.json`. The new `bootConfig` setting sets the release-age and trust policy pnpm uses to install config dependencies and the pnpm version from `packageManager`.

- Added the `lockfile.integrityTarget` setting. With `integrityTarget: tar`, `pnpm-lock.yaml` pins the hash of every package's uncompressed tar, so the lockfile no longer changes when a registry recompresses its tarballs or serves them as tars. pnpm learns a version's tar hash from the store or by downloading it once. `pnpm audit signatures` ties the tar hash to the signed `.tgz` integrity.

  ```yaml title="pnpm-workspace.yaml"
  lockfile:
    integrityTarget: tar
  ```

- pnpm can now install from a registry that serves uncompressed tars with zstd or gzip content coding. `pnpm-lock.yaml` records the hash of each tar. Declare such a registry with `serverType: pnpr` in `registries` to keep tarball URLs out of the lockfile. See [Tar registries](https://pnpm.io/registries#tar-registries).

  pnpm now decodes the content coding of tarball downloads itself. It rejects a zstd response whose window is larger than 8 MiB.

  Added the `maxTarballUnpackedSize` setting. It caps the bytes the files of one package may unpack to, 4 GiB by default.

- Registry revisions now always pin the hash of the uncompressed tar, the original included. pnpm records an original selected through the revision protocol, for example with a `+r0` specifier, as `revision: 0` in the lockfile and fetches it from the registry's digest route. pnpm checks a revision's tar whether the registry sends it as a tar, gzip, or zstd. Revision metadata must list revision 0 and mark the selected revision. pnpm rejects incomplete revision metadata with `ERR_PNPM_MALFORMED_METADATA`.

- pnpm now accepts zstd-compressed responses. Registries that serve zstd send package metadata in fewer bytes than with gzip, most of all the full metadata that settings such as `minimumReleaseAge` and `trustPolicy` read.

- `pnpm deploy` and `pnpm install` now use the `SOURCE_DATE_EPOCH` environment variable as an upper bound for timestamps in `node_modules/.modules.yaml` and `node_modules/.pnpm-workspace-state-v1.json`. With an equivalently prepared deploy target and an epoch no later than the timestamps that would otherwise be recorded, deploying the same project twice produces identical files [#&#8203;17171](https://github.com/pnpm/pnpm/issues/17171).

- `pnpm update --interactive` now highlights the row under the cursor in cyan while keeping the colors of its version changes.

##### Patch Changes

##### Security

- pnpm now fails with `ERR_PNPM_REGISTRY_NAME_MISMATCH` if a registry returns metadata for a package other than the one requested. Before, pnpm installed the package under the name in the metadata, so `allowBuilds` rules for the requested package did not apply to it.

- `minimumReleaseAgeExcludePrune` and `trustPolicyExcludePrune` now keep exclusions for resolved configuration dependencies and their transitive dependencies [#&#8203;17142](https://github.com/pnpm/pnpm/issues/17142).

##### Resolving dependencies

- When `pnpm install` resolves a dependency again, it now reuses a version that the install already resolved for the same package, as a fresh install does. Before, it kept the version the lockfile pinned and added a second copy of the package. A locked version still stays when nothing else in the install prefers another version. To pin a transitive dependency on purpose, use `overrides` [#&#8203;17084](https://github.com/pnpm/pnpm/issues/17084).

- When another workspace project adds a different version of a package, a project's auto-installed peer on that package now moves to the version a fresh install picks. It used to stay on its locked version while any other project still accepted that version [#&#8203;17058](https://github.com/pnpm/pnpm/issues/17058).

- A package that lists the same dependency in `dependencies` and as an optional peer now gets the peer bound when a project adds a provider for it later. The lockfile now records every peer a package declares, including one its own dependency supplies [#&#8203;17056](https://github.com/pnpm/pnpm/issues/17056).

- `pnpm install --prod` and `pnpm deploy --prod` no longer drop a dependency that a package declares both as an optional peer and as its own dependency, when the package resolved it as its own dependency and a dev dependency provides the same package under another alias.

- `pnpm install` with an existing lockfile and a second `pnpm dedupe` now keep optional peer bindings that point into a dependency cycle or back into the dependent package. Before, the second run dropped the optional peer binding from the lockfile snapshot key.

- `pnpm install` and `pnpm dedupe` now preserve own-dependency and peer-dependency bindings when reusing a lockfile whose peer suffixes were hashed by `peersSuffixMaxLength`.

  Upgrading a direct dependency in one workspace project now refreshes matching transitive pins in sibling projects during the same `pnpm install` pass.

- With `resolvePeersFromWorkspaceRoot: false`, a workspace project no longer binds a peer to a version that only another project's dependencies provide. pnpm now auto-installs a version that satisfies the peer range for that project. Before, the project got a version outside the range, with no peer warning.

- `pnpm install`, `pnpm add`, and `pnpm update` now reuse the lockfile's resolution for dependencies that take part in a dependency cycle, such as `mlly` and `pkg-types`. They re-resolved those packages and everything that depends on them from the registry before [pnpm/tasks#130](https://github.com/pnpm/tasks/issues/130).

- pnpm now measures a `pnpm.overrides` entry written as a `workspace:` path, such as `workspace:./packages/foo`, from the directory holding `pnpm-workspace.yaml`. It used to be measured from each package the override rewrote. The install then linked a directory that does not exist, or failed if workspace packages were injected [#&#8203;17010](https://github.com/pnpm/pnpm/issues/17010).

- `pnpm install` now removes config dependencies that are no longer declared, including when the entire `configDependencies` setting is removed. Frozen installs reject a lockfile that still records removed config dependencies.

- `pnpm install --update-checksums` now re-resolves every locked package and records the integrity the registry reports now.

- `pnpm add` and `pnpm install` now accept `gist:<id>` dependencies and GitHub Gist URLs, and install them from the gist's archive. `pnpm pkg fix` normalizes a `gist:` repository the way `npm pkg fix` does, `pnpm licenses` links a gist package to its gist page, and `pnpm sbom` reports the repository of a package whose `repository` field is a `gist:` shorthand.

- `pnpm add` now reads the package name correctly from a tarball URL under a registry URL that has a path, such as `https://example.com/npm/`, and installs a registry tarball URL with a malformed percent escape as a plain tarball URL. Before, the registry path became part of the name, so a scoped package failed with `ERR_PNPM_INVALID_PACKAGE_NAME`.

- With `minimumReleaseAge` set, pnpm now revalidates a package's cached full metadata with a single ETag request so an unchanged registry answers with `304`, even when the registry does not list a publish time for every version. Previously, pnpm downloaded that metadata again whenever the cached copy was older than `minimumReleaseAge`. With `resolutionMode` set to `lowest-direct` or `time-based`, pnpm also reuses that metadata across every workspace project that depends on the package.

- When pnpm revalidates cached registry metadata, it now sends `If-Modified-Since` when the registry's modification time is a date without a time, and no longer crashes when the modification time is before 1970.

- Added protocol versioning between the pnpm client and pnpr server, verified applied resolution policies in resolution responses, and preserved structured error codes across the wire.

##### Performance

- Sped up `pnpm install` on macOS. On a 12-core Mac, installing 1352 packages from a warm store took 1.3 seconds, down from 2.2. pnpm now writes package files into `node_modules` with 4 threads on macOS, whatever the number of CPU cores. Set the `PNPM_MACOS_LINK_THREADS` environment variable to use a different number of threads, or to `0` to write files with the threads pnpm uses for the rest of the install.

- `pnpm install` is faster and uses less memory in large workspaces with `sharedWorkspaceLockfile: false`. Each project's install no longer reads the manifests of every other workspace project again. Installs that wait for their workspace dependencies now count toward `workspaceConcurrency` [#&#8203;16966](https://github.com/pnpm/pnpm/issues/16966).

- Sped up workspace project discovery, dependency resolution, lockfile up-to-date checks, and peer dependency checks in large workspaces [#&#8203;16839](https://github.com/pnpm/pnpm/issues/16839) [pnpm/tasks#9](https://github.com/pnpm/tasks/issues/9). On a workspace with 6,833 projects, the lockfile up-to-date check uses about 1 second less CPU time. The peer dependency check at the end of an install now checks linked workspace projects against the manifests already in memory, without reading each `package.json` again.

- Sped up bin linking and build planning during `pnpm install`. Package binary targets and shebang runtimes are now precalculated during tarball extraction and served in `pnpr` registry metadata alongside verified `requiresBuild`, tarball size, and file-count metadata.

- Lowered peak memory when pnpm downloads many packuments, such as during lockfile verification with `trustPolicy` on Linux. In one 1,800-entry workspace, a lockfile update now peaks at 1.8 to 2.0 GB. It peaked at 2.6 to 3.7 GB before [#&#8203;17020](https://github.com/pnpm/pnpm/issues/17020).

- `pnpm install` in a Cargo workspace skips crates that an earlier install already unpacked into the store. Before, a repeat install processed every crate in `Cargo.lock` again. `pnpm install --force` unpacks every crate again [pnpm/tasks#69](https://github.com/pnpm/tasks/issues/69).

##### Linking node_modules and workspaces

- Concurrent installs that import the same package into one project now all complete. One of them used to fail with `ENOTEMPTY` when its copy of the package collided with the copy another install had already placed [pnpm/tasks#48](https://github.com/pnpm/tasks/issues/48).

- `pnpm install` now succeeds when `node_modules` is a symlink to an existing directory. Dependency hoisting previously rejected a symlinked `node_modules` as an invalid destination [#&#8203;17157](https://github.com/pnpm/pnpm/issues/17157).

- A filtered first install under the isolated linker now links every workspace project by name into `node_modules/.pnpm/node_modules`, not only the selected ones [pnpm/tasks#155](https://github.com/pnpm/tasks/issues/155).

- With `nodeLinker: hoisted`, a filtered install such as `pnpm --filter app add <pkg>` keeps the root `node_modules` links and bins of the workspace projects it did not select. A project with no dependencies of its own lost them until the next full install.

- `pnpm install --filter` with `nodeLinker: hoisted` and `--prod` or `--dev` now preserves previously installed dependency groups for unselected workspace projects [pnpm/tasks#164](https://github.com/pnpm/tasks/issues/164). Repeat filtered installs also re-resolve when a transitive injected workspace dependency or a linked dependency's version changes.

- With `sharedWorkspaceLockfile: false`, `pnpm install` now runs the workspace root's pnpmfile for every project, after the project's own pnpmfile. Before, it ran only the pnpmfile in the project's directory.

- `pnpm install` and `pnpm peers check` now detect peer dependency issues and report linked package versions in linked projects that use `package.json5` or `package.yaml` [pnpm/tasks#172](https://github.com/pnpm/tasks/issues/172).

- `pnpm install` and `pnpm peers check` no longer report an unmet peer when a workspace package's peer is provided by a remote tarball URL. pnpm now compares the peer range with the version in the tarball's manifest [#&#8203;17037](https://github.com/pnpm/pnpm/issues/17037).

- `pnpm prune` now removes extraneous direct packages from `node_modules` when `package.json` and `pnpm-lock.yaml` are already in sync. `pnpm prune` also honors `--filter`, `--recursive`, `sharedWorkspaceLockfile: false`, and the `trustLockfile` setting and `--trust-lockfile` flag. With the lockfile trusted, `pnpm prune --prod` works offline when every package is in the store [#&#8203;16999](https://github.com/pnpm/pnpm/issues/16999).

##### Injected workspace packages

- `pnpm install --frozen-lockfile` now fails with `ERR_PNPM_OUTDATED_LOCKFILE` when a local directory dependency or injected workspace package changes a dependency to a different tarball URL or git commit [#&#8203;17129](https://github.com/pnpm/pnpm/issues/17129). It also fails when an injected workspace package changes a dependency's named-registry range, such as `work:^2.0.0`, to one the locked version no longer satisfies.

- `pnpm install --frozen-lockfile` now accepts the lockfile it wrote for a `file:` dependency on a directory without a `package.json`. It also accepts the lockfile for an injected workspace package that bundles a dependency in `bundledDependencies`, lists a dependency in both `dependencies` and `optionalDependencies`, or publishes from a `publishConfig.directory` whose `package.json` lists other dependencies than the project's own. Before, these failed with `ERR_PNPM_OUTDATED_LOCKFILE`.

- The lockfile now records a `catalog:` peer dependency of an injected workspace package as written in its `package.json`. It recorded the catalog's range before, so editing the catalog changed the consumer's lockfile entry [pnpm/tasks#153](https://github.com/pnpm/tasks/issues/153).

- `pnpm install` now resolves a git, tarball, or tag dependency of an injected workspace package again when its spec changes. Before, it kept the old version.

- Repeat installs and `verifyDepsBeforeRun` now re-check projects that inject a modified workspace package, including when `sharedWorkspaceLockfile` is `false` [#&#8203;17007](https://github.com/pnpm/pnpm/issues/17007), and accept aliased `workspace:` ranges on linked dependencies.

- An injected workspace package that was linked now becomes a copy when the project that depends on it starts providing one of its peers at a version the package's own workspace context lacks. The link used to stay until that dependency was updated, so the package could not find the peer the project provides [#&#8203;17057](https://github.com/pnpm/pnpm/issues/17057).

- An injected workspace package now keeps only the files its manifest lists after a lifecycle script or a script named in `syncInjectedDepsAfterScripts` rebuilds it. Before, these refreshes copied every file of the package directory into the injected copy, including files that `files` or `.npmignore` leave out. With `deployAllFiles: true` the copy still gets every file.

- A script listed in `syncInjectedDepsAfterScripts` now keeps a package's links to its own files as links in the injected copies, the way `pnpm install` writes them, and updates those copies when `sharedWorkspaceLockfile` is `false`. Before, a directory link the script added became a real directory or went missing with `deployAllFiles`, and with `sharedWorkspaceLockfile: false` the copies kept the files from the last install.

##### Running scripts and checking dependencies

- Pressing Ctrl+C during `pnpm -r run` or `pnpm --filter <pkg> run` no longer reports `ERR_PNPM_RECURSIVE_RUN_FIRST_FAIL`. pnpm now ends with the interrupted script's status, such as 130 for a script that SIGINT ended on Linux or macOS [#&#8203;16964](https://github.com/pnpm/pnpm/issues/16964).

- With `enableGlobalVirtualStore`, a Node.js resolve hook that a script registers now sees the importer as `context.parentURL` after pnpm's `NODE_PATH` fallback fails to find a bare specifier. Hooks that resolve tsconfig `paths` aliases from the importer failed with `ERR_MODULE_NOT_FOUND` [#&#8203;16979](https://github.com/pnpm/pnpm/issues/16979).

- `pnpm run`, `pnpm exec`, and `pnpm restart` now pass the install flags they were given, such as `--lockfile-dir`, `--modules-dir`, `--store-dir`, `--offline`, and `--ignore-scripts`, to the `pnpm install` that `verifyDepsBeforeRun: install` starts. When that install leaves the dependencies out of date, pnpm now prints a warning.

- A repeated `pnpm install --filter` now finishes without reinstalling when the selected projects are up to date, and so does `pnpm install` once filtered commands have installed every project. After a filtered install, `pnpm run` and `pnpm exec` now install a selected project whose dependencies changed in between [#&#8203;17011](https://github.com/pnpm/pnpm/issues/17011), require the workspace root when a filtered install with `pruneDirectDependencies: true` skipped it [pnpm/tasks#181](https://github.com/pnpm/tasks/issues/181), and preserve the recorded ignored build scripts of unselected projects.

- The check that runs before a script now holds the workspace projects that the selected project depends on, including with `sharedWorkspaceLockfile: false` and for workspace dependencies declared with `link:`. `--filter=<project>...` now selects `link:` dependencies as well. In a workspace that has no `package.json` at its root, `pnpm run` and `pnpm exec` no longer reinstall before every command when the dependencies are up to date.

- `verifyDepsBeforeRun` and repeat `pnpm install` runs now apply `overrides` before comparing a modified `package.json` with `pnpm-lock.yaml` [#&#8203;17127](https://github.com/pnpm/pnpm/issues/17127). `pnpm install` with `optimisticRepeatInstall` now re-runs when `configDependencies` changed, `verifyDepsBeforeRun` reports `node_modules` out of date when `useGitBranchLockfile` is enabled and the branch lockfile is missing, and in a single-project repository both checks record a successful lockfile check so later runs skip it until the lockfile changes [#&#8203;16993](https://github.com/pnpm/pnpm/issues/16993).

- `pnpm install` and `verifyDepsBeforeRun` now compare manifests, lockfiles, and patch files by content hash. They notice an edit that kept the file's modification time, as `cp -p`, `rsync -a`, or an archive extraction leave it, and a patch file that was copied or touched without changing its content no longer marks dependencies as outdated [#&#8203;16988](https://github.com/pnpm/pnpm/issues/16988).

##### Deploying, packing, and other commands

- `pnpm deploy` now deploys `file:` dependencies that live outside the workspace directory. It failed with "Refusing to deploy unsafe lockfile path" [#&#8203;16983](https://github.com/pnpm/pnpm/issues/16983).

- `pnpm deploy` now records the peer dependencies of the workspace packages it copies into the deployed lockfile. Before, pnpm rejected that lockfile with `--frozen-lockfile` when a deployed workspace package declared a peer and `injectWorkspacePackages` was off [#&#8203;17093](https://github.com/pnpm/pnpm/issues/17093).

- `pnpm install --frozen-lockfile` and `verifyDepsBeforeRun` now succeed inside a directory created by `pnpm deploy`. `pnpm deploy` writes self-contained workspace and lockfile settings, prunes unused `patchedDependencies`, and preserves workspace-level dependency resolutions on deployed directory dependencies.

- On macOS, `pnpm deploy` no longer writes a second, unused copy of every package to `node_modules/.pnpm/@` in the deploy directory [#&#8203;17116](https://github.com/pnpm/pnpm/issues/17116).

- `pnpm patch-commit` and `pnpm install` now apply a patch that renames or moves a file of the patched package. Such a patch failed with "rename/copy operations in patches are not yet supported".

- In workspaces with `sharedWorkspaceLockfile: false`, `pnpm patch`, `pnpm patch-commit`, and `pnpm patch-remove` now resolve patch files and `patchedDependencies` entries at the workspace root. `pnpm install` checks configured patches across all workspace projects before reporting unused patches.

- `pnpm pack` and `pnpm publish` no longer include editor swap files such as `.*.swp`, macOS `._*` files, `.lock-wscript`, `.wafpickle-*`, `build/config.gypi`, or `archived-packages/` in the tarball. Naming one of these files in `files` still includes it [#&#8203;16955](https://github.com/pnpm/pnpm/issues/16955).

- `pnpm pack` and `pnpm publish` now support extended glob patterns and brace ranges such as `part{1..3}.js` and `part{a..c}.js`, including descending ranges, steps, and zero padding, in `files`, `.npmignore`, and `.gitignore`. Explicitly listed internal directory symlinks are preserved below excluded directories, entry points honor nested ignore files, ignore negations can re-include default-excluded logs and backup files, and nested lockfiles follow the package's file selection rules.

- Trusted publishing with OIDC now works with registries whose URL has a path, such as `https://registry.example/npm/private/`. pnpm previously sent the token exchange request to the root of the registry host.

- `pnpm deprecate` and `pnpm unpublish` now work for packages whose registry metadata is larger than 10 MiB, such as `typescript`. They accept up to 50 MiB, the same limit as `pnpm owner`.

- `pnpm owner ls` now lists the owners of a package on the npm registry. It reported every package as not found.

- `pnpm licenses list --prod` now includes optional dependencies, including transitive ones. Pass `--no-optional` to leave them out. `pnpm licenses list` also reports no licenses for workspace projects not recorded in the lockfile, errors when `pnpm-lock.yaml` is missing, and formats empty results as the requested text or JSON [pnpm/tasks#174](https://github.com/pnpm/tasks/issues/174).

- `pnpm why` now reads each selected workspace project's lockfile and modules directory when `sharedWorkspaceLockfile` is `false` [pnpm/tasks#169](https://github.com/pnpm/tasks/issues/169).

- `pnpm pkg fix` now converts a `repository` string such as `github:pnpm/example` to the object form, as `npm pkg fix` does. It also normalizes a GitHub, GitLab, or Bitbucket repository URL the way `npm pkg fix` does [#&#8203;16980](https://github.com/pnpm/pnpm/issues/16980).

##### Configuration and setting up pnpm

- Registry credentials and TLS settings passed with `--config`, such as `--config.//registry.example.com/:_authToken=<token>`, are now applied. They take precedence over the same settings in `.npmrc` files.

- pnpm now reads only the settings it needs to decide whether to switch to the pnpm version a project pins. A setting outside them can no longer fail that decision or change its outcome. For example, `pnpm --version` no longer fails in a pinned project whose `pnpm-workspace.yaml` has a setting this pnpm cannot read. Projects that pin neither pnpm nor a runtime skip this check without reading any settings.

- When pnpm switches to the version a project pins, an option only the pinned pnpm knows now reaches that pnpm when it comes immediately before the command name, as in `pnpm --new-flag install`. When switching for `pnpm --version` or an unrecognized command line, pnpm also honors `--color`, `--no-color`, `--reporter`, and `--loglevel` wherever they appear on the command line.

- `pnpm self-update` now updates the project's pnpm pin when Homebrew installed pnpm or Corepack runs it, leaving the global pnpm alone [#&#8203;17001](https://github.com/pnpm/pnpm/issues/17001). Run inside a workspace package, `pnpm self-update` updates the pin in the workspace root's manifest, including in `package.yaml` or `package.json5`.

- `pnpm setup` now completes when Windows registry environment values cannot be read or written. It prints PowerShell commands to configure `PNPM_HOME` and `PATH` without registry access [#&#8203;17167](https://github.com/pnpm/pnpm/issues/17167).

- `pnpm setup` now succeeds when it cannot tell which shell configuration file to update, for example in a container where `SHELL` is not set. It installs pnpm and prints the `PNPM_HOME` and `PATH` lines to add yourself. Previously it failed with `ERR_PNPM_UNKNOWN_SHELL`, and so did the installer script at get.pnpm.io [#&#8203;16973](https://github.com/pnpm/pnpm/issues/16973).

- In StackBlitz WebContainers, `pnpm` no longer crashes intermittently with `Parking not supported on this platform`, and running a command that does not exist now reports that the command was not found. Previously it sometimes failed with a generic error.

<!-- sponsors -->

#### Platinum Sponsors

<table>
  <tbody>
    <tr>
      <td align="center" valign="middle">
        <a href="https://bit.cloud/?utm_source=pnpm&utm_medium=release_notes" target="_blank" rel="noopener noreferrer"><img src="https://pnpm.io/img/users/bit.svg" width="80" alt="Bit"></a>
      </td>
      <td align="center" valign="middle">
        <a href="https://openai.com/?utm_source=pnpm&utm_medium=release_notes" target="_blank" rel="noopener noreferrer">
          <picture>
            <source media="(prefers-color-scheme: light)" srcset="https://pnpm.io/img/users/openai_dark.svg" />
            <source media="(prefers-color-scheme: dark)" srcset="https://pnpm.io/img/users/openai_light.svg" />
            <img src="https://pnpm.io/img/users/openai_dark.svg" width="160" alt="OpenAI" />
          </picture>
        </a>
      </td>
      <td align="center" valign="middle">
        <a href="https://notion.com/?utm_source=pnpm&utm_medium=release_notes" target="_blank" rel="noopener noreferrer"><img src="https://pnpm.io/img/users/notion.svg" width="80" alt="Notion"></a>
      </td>
    </tr>
    <tr>
      <td align="center" valign="middle">
        <a href="https://coderabbit.ai/?utm_source=pnpm&utm_medium=release_notes" target="_blank" rel="noopener noreferrer">
          <picture>
            <source media="(prefers-color-scheme: light)" srcset="https://pnpm.io/img/users/coderabbit.svg" />
            <source media="(prefers-color-scheme: dark)" srcset="https://pnpm.io/img/users/coderabbit_light.svg" />
            <img src="https://pnpm.io/img/users/coderabbit.svg" width="220" alt="CodeRabbit" />
          </picture>
        </a>
      </td>
    </tr>
  </tbody>
</table>

#### Gold Sponsors

<table>
  <tbody>
    <tr>
      <td align="center" valign="middle">
        <a href="https://sanity.io/?utm_source=pnpm&utm_medium=release_notes" target="_blank" rel="noopener noreferrer">
          <picture>
            <source media="(prefers-color-scheme: light)" srcset="https://pnpm.io/img/users/sanity.svg" />
            <source media="(prefers-color-scheme: dark)" srcset="https://pnpm.io/img/users/sanity_light.svg" />
            <img src="https://pnpm.io/img/users/sanity.svg" width="120" alt="Sanity" />
          </picture>
        </a>
      </td>
      <td align="center" valign="middle">
        <a href="https://discord.com/?utm_source=pnpm&utm_medium=release_notes" target="_blank" rel="noopener noreferrer">
          <picture>
            <source media="(prefers-color-scheme: light)" srcset="https://pnpm.io/img/users/discord.svg" />
            <source media="(prefers-color-scheme: dark)" srcset="https://pnpm.io/img/users/discord_light.svg" />
            <img src="https://pnpm.io/img/users/discord.svg" width="220" alt="Discord" />
          </picture>
        </a>
      </td>
      <td align="center" valign="middle">
        <a href="https://vite.dev/?utm_source=pnpm&utm_medium=release_notes" target="_blank" rel="noopener noreferrer"><img src="https://pnpm.io/img/users/vitejs.svg" width="42" alt="Vite"></a>
      </td>
    </tr>
    <tr>
      <td align="center" valign="middle">
        <a href="https://serpapi.com/?utm_source=pnpm&utm_medium=release_notes" target="_blank" rel="noopener noreferrer">
          <picture>
            <source media="(prefers-color-scheme: light)" srcset="https://pnpm.io/img/users/serpapi_dark.svg" />
            <source media="(prefers-color-scheme: dark)" srcset="https://pnpm.io/img/users/serpapi_light.svg" />
            <img src="https://pnpm.io/img/users/serpapi_dark.svg" width="160" alt="SerpApi" />
          </picture>
        </a>
      </td>
      <td align="center" valign="middle">
        <a href="https://stackblitz.com/?utm_source=pnpm&utm_medium=release_notes" target="_blank" rel="noopener noreferrer">
          <picture>
            <source media="(prefers-color-scheme: light)" srcset="https://pnpm.io/img/users/stackblitz.svg" />
            <source media="(prefers-color-scheme: dark)" srcset="https://pnpm.io/img/users/stackblitz_light.svg" />
            <img src="https://pnpm.io/img/users/stackblitz.svg" width="190" alt="Stackblitz" />
          </picture>
        </a>
      </td>
      <td align="center" valign="middle">
        <a href="https://workleap.com/?utm_source=pnpm&utm_medium=release_notes" target="_blank" rel="noopener noreferrer">
          <picture>
            <source media="(prefers-color-scheme: light)" srcset="https://pnpm.io/img/users/workleap.svg" />
            <source media="(prefers-color-scheme: dark)" srcset="https://pnpm.io/img/users/workleap_light.svg" />
            <img src="https://pnpm.io/img/users/workleap.svg" width="190" alt="Workleap" />
          </picture>
        </a>
      </td>
    </tr>
    <tr>
      <td align="center" valign="middle">
        <a href="https://nx.dev/?utm_source=pnpm&utm_medium=release_notes" target="_blank" rel="noopener noreferrer">
          <picture>
            <source media="(prefers-color-scheme: light)" srcset="https://pnpm.io/img/users/nx.svg" />
            <source media="(prefers-color-scheme: dark)" srcset="https://pnpm.io/img/users/nx_light.svg" />
            <img src="https://pnpm.io/img/users/nx.svg" width="50" alt="Nx" />
          </picture>
        </a>
      </td>
      <td align="center" valign="middle">
        <a href="https://latitude.so/?utm_source=pnpm&utm_medium=release_notes" target="_blank" rel="noopener noreferrer"><img src="https://pnpm.io/img/users/latitude.svg" width="160" alt="Latitude"></a>
      </td>
    </tr>
  </tbody>
</table>

<!-- sponsors end -->

### [`v12.14.0`](https://github.com/pnpm/pnpm/releases/tag/v12.14.0): pnpm 12.14.0

[Compare Source](https://github.com/pnpm/pnpm/compare/v12.13.0...v12.14.0)

This release adds the `sideEffectsCache.exclude` setting, stops `pnpm install` from waiting on the daily update check, and keeps pnpm from aborting with a core dump when the reader of its output exits.

##### Minor Changes

- Added the `sideEffectsCache.exclude` setting for packages whose build depends on something pnpm cannot include in the cache key, such as the system libraries a native addon links against. pnpm builds the listed packages in every project and never restores or saves their builds through the side-effects cache, locally or on a `remoteCache` server. Under the global virtual store, each project gets its own copy of these packages and of the packages that depend on them. Entries use the same `name`, `@scope/*`, and `name@version` patterns as `minimumReleaseAgeExclude` [#&#8203;5271](https://github.com/pnpm/pnpm/issues/5271).

- `pnpm login` now prints the `npm-notice` response header during web-based login. Registries use it to show the verification code that completes the sign-in in the browser.

##### Patch Changes

##### Installing packages

- `pnpm install` and `pnpm add` no longer wait for the daily update check. If the command finishes first, pnpm skips the update notice and checks again next time. On a registry that accepted the connection but never answered, an install that had nothing to do used to take minutes [#&#8203;16839](https://github.com/pnpm/pnpm/issues/16839).

- `pnpm install`, `pnpm add` and `pnpm update` now reuse `pnpm-lock.yaml` when the workspace configures a catalog entry that no dependency references, such as an unused entry or one whose package an `overrides` entry replaces. Before, every resolution in such a workspace resolved all dependencies again from the registry [#&#8203;16923](https://github.com/pnpm/pnpm/issues/16923).

- `pnpm install` now looks up a git dependency's branch or tag once per install, even when many workspace projects depend on it. With `linkWorkspacePackages` enabled, it used to run `git ls-remote` once per project [#&#8203;16899](https://github.com/pnpm/pnpm/issues/16899).

- A filtered install, such as `pnpm install --filter <pkg>`, now keeps the `pnpm-lock.yaml` entries of workspace projects that are missing from disk, as in a sparse checkout. Their importers, packages, and catalog entries stay even when the filter matches every project on disk [#&#8203;16931](https://github.com/pnpm/pnpm/issues/16931).

- pnpm now decides whether a dependency that a package also declares as an optional peer is linked as a dependency or as a peer from the shallowest path that reaches the package. When an existing lockfile records such a dependency as a regular dependency and a newly added dependency now provides the peer, pnpm records it as a peer, as a fresh install does. The lockfile entry of such a package can change on the next install [#&#8203;16901](https://github.com/pnpm/pnpm/issues/16901).

- An install that adds nothing to the store no longer changes the modification time of the store directory. CI caches that check modification times, such as GitLab's, no longer re-upload an unchanged store [#&#8203;16929](https://github.com/pnpm/pnpm/issues/16929).

##### Running commands and scripts

- pnpm no longer aborts with a core dump when the reader of its output has exited, as in `pnpm list | head -1` or `pnpm start 2>&1 | head -1`. The command finishes with its usual exit code, and a failing script's exit code is kept. A write to stdout that fails for another reason, such as a full disk, ends the command with exit code 1 [#&#8203;16904](https://github.com/pnpm/pnpm/issues/16904) [pnpm/tasks#132](https://github.com/pnpm/tasks/issues/132).

- On Windows, `pnpm exec`, `pnpm run`, and commands such as `pnpm nx` now return when the command they run exits, even if it left a detached process running and pnpm's output is redirected or piped. Previously a shell reading that output waited until the detached process, such as the Nx daemon, exited [#&#8203;16930](https://github.com/pnpm/pnpm/issues/16930).

- Commands that do not write the project manifest now leave a missing `package.json` absent. This covers queries such as `pnpm why` and `pnpm outdated`, `pnpm fetch`, `pnpm dedupe --check`, and commands that exit with an error, such as `pnpm rt ls`. These commands used to create one from the initialization template. `pnpm sbom` run from a lockfile alone now reports the root component as `unknown` with version `0.0.0` [#&#8203;16907](https://github.com/pnpm/pnpm/issues/16907).

- `pnpm run` now reports a failed script as a JSON record under `--reporter=ndjson`. It printed a plain `[ELIFECYCLE]` line to stderr.

- `pnpm run` no longer prints `[ELIFECYCLE] Command failed with exit code N.` to stderr when a script fails under `--reporter=silent`, `--silent`, or `--loglevel=silent`.

- `pnpm pipeline --dry-run` no longer writes a run report when no projects are selected. With `--json` it prints `{"tasks": []}` [pnpm/tasks#126](https://github.com/pnpm/tasks/issues/126).

##### Packing and publishing

- `pnpm pack` and `pnpm publish` now ship the whole gitignored directory that `main`, `browser`, or `bin` points into, as npm does. Previously only the `main` and `bin` files themselves were packed, and a gitignored `browser` file was left out [#&#8203;16908](https://github.com/pnpm/pnpm/issues/16908).

- `pnpm pack` and `pnpm publish` now honor a `.npmignore` or `.gitignore` inside a subdirectory when `package.json` has a `files` field. The package root's ignore files and those inherited from the workspace are still skipped [#&#8203;16909](https://github.com/pnpm/pnpm/issues/16909).

##### Setting up pnpm and toolchains

- `--registry`, `--https-proxy`, `--http-proxy`, and `--no-proxy` now apply when pnpm resolves and downloads the pnpm version a project pins in `packageManager`. Before, these lookups went to `https://registry.npmjs.org/`, which a firewall that only allows a private registry blocks [#&#8203;16911](https://github.com/pnpm/pnpm/discussions/16911).

- On Windows, `pnpm install` now installs the Rust toolchain for rustup's default host, so a machine set up with `rustup set default-host x86_64-pc-windows-gnu` gets the MinGW toolchain. Without a default host, pnpm installs the MSVC toolchain for the machine's architecture, as rustup does. This includes a pnpm built for MinGW, such as the MSYS2 package, which has installed the MinGW toolchain since 12.13.0. An x64 pnpm on ARM64 Windows now installs the ARM64 toolchain [#&#8203;16854](https://github.com/pnpm/pnpm/issues/16854).

<!-- sponsors -->

#### Platinum Sponsors

<table>
  <tbody>
    <tr>
      <td align="center" valign="middle">
        <a href="https://bit.cloud/?utm_source=pnpm&utm_medium=release_notes" target="_blank" rel="noopener noreferrer"><img src="https://pnpm.io/img/users/bit.svg" width="80" alt="Bit"></a>
      </td>
      <td align="center" valign="middle">
        <a href="https://openai.com/?utm_source=pnpm&utm_medium=release_notes" target="_blank" rel="noopener noreferrer">
          <picture>
            <source media="(prefers-color-scheme: light)" srcset="https://pnpm.io/img/users/openai_dark.svg" />
            <source media="(prefers-color-scheme: dark)" srcset="https://pnpm.io/img/users/openai_light.svg" />
            <img src="https://pnpm.io/img/users/openai_dark.svg" width="160" alt="OpenAI" />
          </picture>
        </a>
      </td>
      <td align="center" valign="middle">
        <a href="https://notion.com/?utm_source=pnpm&utm_medium=release_notes" target="_blank" rel="noopener noreferrer"><img src="https://pnpm.io/img/users/notion.svg" width="80" alt="Notion"></a>
      </td>
    </tr>
    <tr>
      <td align="center" valign="middle">
        <a href="https://coderabbit.ai/?utm_source=pnpm&utm_medium=release_notes" target="_blank" rel="noopener noreferrer">
          <picture>
            <source media="(prefers-color-scheme: light)" srcset="https://pnpm.io/img/users/coderabbit.svg" />
            <source media="(prefers-color-scheme: dark)" srcset="https://pnpm.io/img/users/coderabbit_light.svg" />
            <img src="https://pnpm.io/img/users/coderabbit.svg" width="220" alt="CodeRabbit" />
          </picture>
        </a>
      </td>
    </tr>
  </tbody>
</table>

#### Gold Sponsors

<table>
  <tbody>
    <tr>
      <td align="center" valign="middle">
        <a href="https://sanity.io/?utm_source=pnpm&utm_medium=release_notes" target="_blank" rel="noopener noreferrer">
          <picture>
            <source media="(prefers-color-scheme: light)" srcset="https://pnpm.io/img/users/sanity.svg" />
            <source media="(prefers-color-scheme: dark)" srcset="https://pnpm.io/img/users/sanity_light.svg" />
            <img src="https://pnpm.io/img/users/sanity.svg" width="120" alt="Sanity" />
          </picture>
        </a>
      </td>
      <td align="center" valign="middle">
        <a href="https://discord.com/?utm_source=pnpm&utm_medium=release_notes" target="_blank" rel="noopener noreferrer">
          <picture>
            <source media="(prefers-color-scheme: light)" srcset="https://pnpm.io/img/users/discord.svg" />
            <source media="(prefers-color-scheme: dark)" srcset="https://pnpm.io/img/users/discord_light.svg" />
            <img src="https://pnpm.io/img/users/discord.svg" width="220" alt="Discord" />
          </picture>
        </a>
      </td>
      <td align="center" valign="middle">
        <a href="https://vite.dev/?utm_source=pnpm&utm_medium=release_notes" target="_blank" rel="noopener noreferrer"><img src="https://pnpm.io/img/users/vitejs.svg" width="42" alt="Vite"></a>
      </td>
    </tr>
    <tr>
      <td align="center" valign="middle">
        <a href="https://serpapi.com/?utm_source=pnpm&utm_medium=release_notes" target="_blank" rel="noopener noreferrer">
          <picture>
            <source media="(prefers-color-scheme: light)" srcset="https://pnpm.io/img/users/serpapi_dark.svg" />
            <source media="(prefers-color-scheme: dark)" srcset="https://pnpm.io/img/users/serpapi_light.svg" />
            <img src="https://pnpm.io/img/users/serpapi_dark.svg" width="160" alt="SerpApi" />
          </picture>
        </a>
      </td>
      <td align="center" valign="middle">
        <a href="https://stackblitz.com/?utm_source=pnpm&utm_medium=release_notes" target="_blank" rel="noopener noreferrer">
          <picture>
            <source media="(prefers-color-scheme: light)" srcset="https://pnpm.io/img/users/stackblitz.svg" />
            <source media="(prefers-color-scheme: dark)" srcset="https://pnpm.io/img/users/stackblitz_light.svg" />
            <img src="https://pnpm.io/img/users/stackblitz.svg" width="190" alt="Stackblitz" />
          </picture>
        </a>
      </td>
      <td align="center" valign="middle">
        <a href="https://workleap.com/?utm_source=pnpm&utm_medium=release_notes" target="_blank" rel="noopener noreferrer">
          <picture>
            <source media="(prefers-color-scheme: light)" srcset="https://pnpm.io/img/users/workleap.svg" />
            <source media="(prefers-color-scheme: dark)" srcset="https://pnpm.io/img/users/workleap_light.svg" />
            <img src="https://pnpm.io/img/users/workleap.svg" width="190" alt="Workleap" />
          </picture>
        </a>
      </td>
    </tr>
    <tr>
      <td align="center" valign="middle">
        <a href="https://nx.dev/?utm_source=pnpm&utm_medium=release_notes" target="_blank" rel="noopener noreferrer">
          <picture>
            <source media="(prefers-color-scheme: light)" srcset="https://pnpm.io/img/users/nx.svg" />
            <source media="(prefers-color-scheme: dark)" srcset="https://pnpm.io/img/users/nx_light.svg" />
            <img src="https://pnpm.io/img/users/nx.svg" width="50" alt="Nx" />
          </picture>
        </a>
      </td>
      <td align="center" valign="middle">
        <a href="https://latitude.so/?utm_source=pnpm&utm_medium=release_notes" target="_blank" rel="noopener noreferrer"><img src="https://pnpm.io/img/users/latitude.svg" width="160" alt="Latitude"></a>
      </td>
    </tr>
  </tbody>
</table>

<!-- sponsors end -->

</details>

---

### Configuration

📅 **Schedule**: (UTC)

- Branch creation
  - Between 12:00 AM and 03:59 AM, only on Monday (`* 0-3 * * 1`)
- Automerge
  - At any time (no schedule defined)

🚦 **Automerge**: Disabled by config. Please merge this manually once you are satisfied.

♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox.

👻 **Immortal**: This PR will be recreated if closed unmerged. Get [config help](https://github.com/renovatebot/renovate/discussions) if that's undesired.

---

 - [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check this box

---

This PR has been generated by [Mend Renovate CLI](https://github.com/renovatebot/renovate).
<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0My4xOTEuMiIsInVwZGF0ZWRJblZlciI6IjQ0LjE4MS43IiwidGFyZ2V0QnJhbmNoIjoibWFpbiIsImxhYmVscyI6W119-->

<!-- cloudflare-preview --> Preview: https://pr-611.docs-gitea-com.pages.dev

---------

Co-authored-by: Lunny Xiao <[email protected]>
Reviewed-on: https://gitea.com/gitea/docs/pulls/611
Reviewed-by: Lunny Xiao <[email protected]>
Co-authored-by: Renovate Bot <[email protected]>
2026-10-26 22:30:15 +00:00
Gitea Bot 4ea46e6315 Update the latest api documents (#612)
Automated update of static/swagger-latest.json and static/openapi3-latest.json from the gitea main branch, opened by the "update api spec files" scheduled workflow.

<!-- cloudflare-preview --> Preview: https://pr-612.docs-gitea-com.pages.dev

Reviewed-on: https://gitea.com/gitea/docs/pulls/612
Co-authored-by: Gitea Bot <[email protected]>
2026-10-26 16:10:40 +00:00
silverwindandLunny Xiao cb424640a2 docs: document Forwarded header and client port for REVERSE_PROXY_LIMIT (#560)
Document https://github.com/go-gitea/gitea/pull/39190, which reads the RFC 7239 `Forwarded` header when `X-Forwarded-For` is absent and keeps the forwarded client port.

- `REVERSE_PROXY_LIMIT`: list `X-Real-IP`, `X-Forwarded-For` and `Forwarded` in the order they are read, and where the client port comes from, also in zh-cn and zh-tw
- Reverse proxies: require the proxy to set `X-Real-IP`, since Caddy, Apache and HAProxy pass a client-sent one through by default, and mention `X-Real-Port` for logging the client port

Co-authored-by: Lunny Xiao <[email protected]>
Reviewed-on: https://gitea.com/gitea/docs/pulls/560
Reviewed-by: Lunny Xiao <[email protected]>
2026-10-23 22:44:51 +00:00
bircniandsilverwind 09bb4cabcf Add documentation for gt, the Gitea CLI (#603)
Adds a CLI product for gt (https://gitea.com/gitea/cli), set up like the runner, with short guides for installation, logging in, CI and scripting. Each command's details stay in gt <command> --help. Until the first release, the docs are served at /cli/; make cut-version PRODUCT=cli VERSION=0.1 adds the release series.

Closes https://gitea.com/gitea/cli/issues/16

<!-- cloudflare-preview --> Preview: https://pr-603.docs-gitea-com.pages.dev

---------

Co-authored-by: silverwind <[email protected]>
Reviewed-on: https://gitea.com/gitea/docs/pulls/603
Reviewed-by: Lunny Xiao <[email protected]>
Reviewed-by: silverwind <[email protected]>
2026-10-23 12:16:49 +00:00
silverwind 9699ec2c87 docs: clarify that team package permissions cover the whole org (#605)
Packages belong to the owner, so a team's packages permission covers every package of the organization, regardless of the team's repositories. State that in the access restrictions section, matching the team settings change in https://github.com/go-gitea/gitea/pull/39953.

The old note announced a dedicated team permission for finer control, which has existed since the packages unit was added, so it is replaced.

Reviewed-on: https://gitea.com/gitea/docs/pulls/605
Reviewed-by: Lunny Xiao <[email protected]>
Co-authored-by: silverwind <[email protected]>
2026-10-22 23:46:15 +00:00
silverwindandbircni c00af83676 Document the artifact retention limit (#602)
Documents https://github.com/go-gitea/gitea/pull/39894 for main and 28. Workflows can request a shorter artifact retention, down to 1 day, and `github.retention_days` carries `ARTIFACT_RETENTION_DAYS`, empty when artifacts are kept forever.

---------

Co-authored-by: bircni <[email protected]>
Reviewed-on: https://gitea.com/gitea/docs/pulls/602
Reviewed-by: bircni <[email protected]>
Co-authored-by: silverwind <[email protected]>
2026-10-22 20:05:39 +00:00
silverwind c9282b96da Clarify reverse proxy authentication requirements (#604)
Document that reverse proxy authentication requires Gitea to be reachable only through the authenticating proxy, and which routes each setting covers. Companion to https://github.com/go-gitea/gitea/pull/39940.

_Written by Claude._

Reviewed-on: https://gitea.com/gitea/docs/pulls/604
Reviewed-by: bircni <[email protected]>
Co-authored-by: silverwind <[email protected]>
2026-10-22 16:33:31 +00:00
49d912f7fb docs: fix repository path in binary install restore example (#601)
Fixes the binary-install restore example in the Backup and Restore docs.

The default `[repository] ROOT` is `{APP_DATA_PATH}/gitea-repositories`, but the example restored repos to `/var/lib/gitea/data/repositories/`, so restored repositories did not show up. Changed it to `/var/lib/gitea/data/gitea-repositories/`.

The Docker and Docker-rootless examples are unchanged: their app.ini templates set `[repository] ROOT` explicitly, so their paths are already correct.

Refs go-gitea/gitea#39736

---------

Co-authored-by: silverwind <[email protected]>
Co-authored-by: silverwind <[email protected]>
Co-authored-by: souvik-biswas-dev <[email protected]>
Reviewed-on: https://gitea.com/gitea/docs/pulls/601
Reviewed-by: silverwind <[email protected]>
Co-authored-by: souvikbiswasdev <[email protected]>
2026-10-22 03:46:06 +00:00
398f418387 docs: document protected branch deletion and system users (#583)
Documents protected branch deletion and system users in push, force-push and deletion allowlists, added in https://github.com/go-gitea/gitea/pull/39243 and https://github.com/go-gitea/gitea/pull/39735 (both merged).

Updates the Actions token permissions guide and audit actor names to match.

---------

Co-authored-by: silverwind <[email protected]>
Co-authored-by: Lunny Xiao <[email protected]>
Reviewed-on: https://gitea.com/gitea/docs/pulls/583
Reviewed-by: silverwind <[email protected]>
Co-authored-by: Enoque Duarte <[email protected]>
2026-10-22 03:43:37 +00:00
silverwind be6aefc7e8 Simplify the runner docs and fix their outdated facts (#600)
The runner pages had drifted from the runner. They claimed Podman is unsupported, a registration retry limit of 10, a `container.env` proxy override that has no effect, and they used the deprecated `generate-config` command, while the actual instructions were buried under implementation details.

The develop and 5.x pages are rewritten from the current runner sources (`main` is `v5.2.0`). They are shorter and aimed at operators, with a new page covering what jobs get. The reference generator now documents the `config` command, and the quickstart pages in every language use the `gitea-runner` binary name.

Reviewed-on: https://gitea.com/gitea/docs/pulls/600
Reviewed-by: Lunny Xiao <[email protected]>
Co-authored-by: silverwind <[email protected]>
2026-10-21 05:25:45 +00:00
bircni a874918bb7 Document runner 4 and 5, regenerate the 28.2.0 api documents (#599)
The 28.2.0 bump in #594 only changed the version string of the api documents, so they still listed the 28.1.0 endpoints. They are now regenerated from v28.2.0 (mostly new `423 repoArchivedError` responses).

The runner docs stopped at 3.3.0 while the runner is at 5.1.1:

- cut the runner `4` and `5` series, `/runner/` now serves 5.1.1 and `/runner/4/` 4.1.0, the `3` series is bumped to 3.5.0
- series specific image tags and versions in the new copies, the `3` series no longer claims the `latest` tag
- 4.0 and 5.0 sections in the upgrade guide, and the Git 2.34.1 requirement of 5.0 in the requirements
- regenerated every reference page from the newest stable tag of its series

Reviewed-on: https://gitea.com/gitea/docs/pulls/599
Reviewed-by: TheFox0x7 <[email protected]>
2026-10-20 19:19:28 +00:00
Renovate Bot 5304217222 chore(deps): update dependencies (#595)
Co-authored-by: Renovate Bot <[email protected]>
2026-10-20 18:53:10 +00:00
Shudhanshu Singh 88356280ec docs: mention release reactions in the REACTIONS setting (#598)
Documents the `[ui] RELEASE_REACTIONS` setting added in https://github.com/go-gitea/gitea/pull/38415, which sets the reactions available on releases (default `+1, laugh, hooray, heart, rocket, eyes`). `REACTIONS` now only covers issues, pull requests and comments. Updated in English, 简体中文 and 繁體中文.

Reviewed-on: https://gitea.com/gitea/docs/pulls/598
Reviewed-by: silverwind <[email protected]>
Co-authored-by: Shudhanshu Singh <[email protected]>
2026-10-20 13:21:46 +00:00
Gitea BotandLunny Xiao c7dc13cf83 Update the latest api documents (#596)
Automated update of static/swagger-latest.json and static/openapi3-latest.json from the gitea main branch, opened by the "update api spec files" scheduled workflow.

<!-- cloudflare-preview --> Preview: https://pr-596.docs-gitea-com.pages.dev

---------

Co-authored-by: Lunny Xiao <[email protected]>
Reviewed-on: https://gitea.com/gitea/docs/pulls/596
Reviewed-by: Lunny Xiao <[email protected]>
Co-authored-by: Gitea Bot <[email protected]>
2026-10-19 14:05:10 +00:00
Gitea BotandLunny Xiao bed218d62d Update the runner reference pages (#597)
Automated update of the generated runner reference pages from gitea/runner (main for the develop docs, the newest stable tag for every documented series), opened by the "update runner reference" scheduled workflow.

<!-- cloudflare-preview --> Preview: https://pr-597.docs-gitea-com.pages.dev

---------

Co-authored-by: Lunny Xiao <[email protected]>
Reviewed-on: https://gitea.com/gitea/docs/pulls/597
Reviewed-by: Lunny Xiao <[email protected]>
Co-authored-by: Gitea Bot <[email protected]>
2026-10-19 09:24:38 +00:00
Lunny Xiao 08aff1bae9 update to v28.2.0 (#594)
<!-- cloudflare-preview --> Preview: https://pr-594.docs-gitea-com.pages.dev

Reviewed-on: https://gitea.com/gitea/docs/pulls/594
Reviewed-by: silverwind <[email protected]>
2026-10-19 04:58:10 +00:00
156 changed files with 9946 additions and 6135 deletions

No files matched your search

+2 -2
View File
@@ -8,7 +8,7 @@ on:
jobs:
build-docs:
runs-on: ubuntu-24.04
runs-on: ubuntu-26.04
env:
NODE_OPTIONS: --max-old-space-size=8192
# search only appears once the credentials are set, the build falls back
@@ -21,7 +21,7 @@ jobs:
- uses: pnpm/action-setup@0977fd99725f1db4007ccb2928dbb4e90d06cc86 # v6
- uses: actions/setup-node@949feb2413d6458794dcd2491c4babbbce0c15c1 # v7
with:
node-version: 24
node-version: 26
cache: pnpm
- name: prepare awesome list
run: |
+1 -1
View File
@@ -18,7 +18,7 @@ jobs:
- uses: pnpm/action-setup@0977fd99725f1db4007ccb2928dbb4e90d06cc86 # v6
- uses: actions/setup-node@949feb2413d6458794dcd2491c4babbbce0c15c1 # v7
with:
node-version: 24
node-version: 26
cache: pnpm
- name: prepare awesome list
run: |
+1 -1
View File
@@ -92,7 +92,7 @@ update-runner-docs:
update-runner-docs-released:
./update_runner_docs.sh --released
# freezes the current docs or runner tree as a new version, see
# freezes the current docs, runner or cli tree as a new version, see
# scripts/cut-version.mjs
.PHONY: cut-version
cut-version:
+19 -6
View File
@@ -3,13 +3,14 @@
The sources of [docs.gitea.com](https://docs.gitea.com), built with
[Astro](https://astro.build) and [Starlight](https://starlight.astro.build).
The site covers three products, all served from this repository:
The site covers four products, all served from this repository:
| Product | Content | Versions | Languages |
| --- | --- | --- | --- |
| Docs | `docs/`, `versioned_docs/`, `i18n/` | next, 28, 1.27 … 1.22 | English, 简体中文, 繁體中文 |
| API | `static/swagger-*.json`, `static/openapi3-*.json` | next, 28, 1.27 … 1.22 | English |
| Runner | `runner-docs/`, `runner-docs_versioned_docs/` | develop, 3 … 0 | English |
| Runner | `runner-docs/`, `runner-docs_versioned_docs/` | develop, 5 … 0 | English |
| CLI | `cli-docs/`, `cli-docs_versioned_docs/` | develop | English |
The enterprise documentation is built and deployed from its own repository and
reached through `/enterprise/`, which a Cloudflare worker maintained elsewhere
@@ -52,7 +53,8 @@ group from the `_category_.json` of its directory.
```shell
make cut-version PRODUCT=docs VERSION=1.28
make cut-version PRODUCT=runner VERSION=4
make cut-version PRODUCT=runner VERSION=6
make cut-version PRODUCT=cli VERSION=0.1
```
This freezes the current tree, its translations and its sidebar. The label of
@@ -88,8 +90,8 @@ documented.
| Version | Content | URL |
| --- | --- | --- |
| develop | `runner-docs/` | `/runner/develop/` |
| current series | `runner-docs_versioned_docs/version-3/` | `/runner/` |
| older series | `runner-docs_versioned_docs/version-2/` | `/runner/2/` |
| current series | `runner-docs_versioned_docs/version-5/` | `/runner/` |
| older series | `runner-docs_versioned_docs/version-4/` | `/runner/4/` |
A version directory covers a whole release series (`version-3` documents every
`3.x` release), so a patch release only needs a content update, not a new
@@ -101,7 +103,8 @@ Its sidebar is written by hand: `runner-sidebars.js` for develop, and
version.
The pages under `reference/` are generated from the runner sources — the command
line reference from `--help`, the example configuration from `generate-config`:
line reference from `--help`, the example configuration from `config generate`
(`generate-config` before 3.1):
```shell
make update-runner-docs # develop docs, from the main branch of gitea/runner
@@ -114,6 +117,16 @@ workflow runs weekly and opens a pull request whenever the runner's CLI or
example configuration changed. Generating them needs Go, since the script builds
the runner binary.
## CLI docs
The documentation of `gt`, the Gitea CLI from
[gitea/cli](https://gitea.com/gitea/cli), is laid out like the runner docs, with
its sidebar in `cli-sidebars.js`. No series is documented yet, so `cli-docs/` is
served at `/cli/`. Once a release exists, cut its series and list it in
`cliSeries` in `packages/content-loader/src/products.ts`, which moves the develop
docs to `/cli/develop/`. The guides leave the flags of each command to
`gt <command> --help`.
## Deployment
`main` is built and published by the `Build and Publish Docs site` workflow, to
+55
View File
@@ -0,0 +1,55 @@
---
sidebar_position: 3
---
# Logging in
On a terminal, `gt auth login` asks for the instance and how to log in. gt never asks for
your password.
## OAuth in a browser
```bash
gt auth login --host https://gitea.example.com --oauth
```
gt opens the instance's authorization page, so single sign-on and two-factor
authentication work as on the web. The browser has to run on the same machine as gt.
`--scopes` limits the token to fewer [scopes](/development/oauth2-provider#scopes); gt
refreshes OAuth tokens on its own.
## Access token
Create a token under **Settings > Applications** on the instance and pass it on stdin:
```bash
gt auth login --host https://gitea.example.com --with-token < token.txt
```
The token needs `read:user`, and the scopes of what you do with gt, such as
`write:repository` and `write:issue` for working on pull requests.
## SSH key
gt can sign its requests with an SSH key registered for your account instead of using a
token:
```bash
gt auth login --host https://gitea.example.com --ssh-key ~/.ssh/id_ed25519
```
## Accounts
Tokens go to the system keyring, or with `--credential-store file` to gt's configuration
directory on machines without one. Run `gt auth login` once per instance and account.
gt picks the instance from the repository's remote or `--host`, and the account with
`gt auth switch` or `--user`. `gt auth status` lists your accounts and `gt auth logout`
removes one.
For instances behind an access proxy, `--header` and `--client-cert` add headers and a
client certificate.
## Git over HTTPS
`gt auth setup-git` lets `git push` and `git fetch` over HTTPS use gt's login. gt's own
clone and checkout commands need no setup.
+40
View File
@@ -0,0 +1,40 @@
---
sidebar_position: 4
---
# Using gt in CI
Give gt a token and an instance through the environment, and turn prompts off:
| Variable | Description |
| --- | --- |
| `GITEA_TOKEN` | Access token; gt never stores it |
| `GITEA_CLI_HOST` or `GITEA_INSTANCE_URL` | URL of the instance |
| `GITEA_CLI_REPO` | Repository, like `--repo` |
`--no-input` makes a command fail instead of prompting, and `--yes` confirms changes in
advance.
## Gitea Actions
Gitea does not allow secret names that start with `GITEA_`, so store the token as, for
example, `GT_TOKEN`:
```yaml
on:
push:
branches: [main]
jobs:
staging:
runs-on: ubuntu-latest
env:
GITEA_TOKEN: ${{ secrets.GT_TOKEN }}
GITEA_CLI_HOST: ${{ gitea.server_url }}
GITEA_CLI_REPO: ${{ gitea.repository }}
steps:
- run: curl -fsSL https://gitea.com/gitea/cli/raw/branch/main/scripts/install.sh | sh
- run: gt workflow run deploy.yml --ref main --field env=staging --no-input
```
`gt run watch` and `gt pr checks --watch` wait for runs and checks and fail when one fails.
+38
View File
@@ -0,0 +1,38 @@
---
sidebar_position: 1
slug: /
---
# Gitea CLI
[gt](https://gitea.com/gitea/cli) is the official command line client for Gitea. It works
with repositories, issues, pull requests, releases, Actions and the other resources of a
Gitea instance, from a terminal or a script.
:::info Development version
These pages describe builds from the `main` branch, published as `nightly` binaries and images.
:::
gt needs Gitea 28 or later, and Git for the commands that clone, check out or push.
## Getting started
1. [Install gt](installation.md).
2. [Log in](authentication.md):
```bash
gt auth login
```
3. Run commands in a clone of a repository on that instance, or select one with `--repo`:
```bash
gt issue list --label bug
gt pr checkout 13
gt issue view 42 --repo gitea.com/gitea/cli
```
`gt doctor` checks your setup and suggests a fix for each problem it finds.
`gt <command> --help` lists the flags of a command, and `gt help environment` the global
flags and environment variables.
+37
View File
@@ -0,0 +1,37 @@
---
sidebar_position: 2
---
# Installation
gt is a single binary for Linux, macOS, Windows and FreeBSD.
## Install script
The install script downloads gt from [dl.gitea.com](https://dl.gitea.com/gitea-cli/),
verifies its checksum and installs it.
```bash
# Linux, macOS, FreeBSD
curl -fsSL https://gitea.com/gitea/cli/raw/branch/main/scripts/install.sh | sh
```
```powershell
# Windows
irm https://gitea.com/gitea/cli/raw/branch/main/scripts/install.ps1 | iex
```
Set `GITEA_CLI_VERSION` to install a specific version, or `nightly` for the latest build
of `main`.
## Other ways
- **Binaries:** download them from [dl.gitea.com/gitea-cli](https://dl.gitea.com/gitea-cli/).
- **Docker:** the [`gitea/gitea-cli`](https://hub.docker.com/r/gitea/gitea-cli) image runs
`gt` as its entry point.
- **Go:** `go install gitea.dev/cli/cmd/gt@latest`
## Shell completion and man pages
`gt completion <shell>` prints the completion script for bash, zsh, fish or PowerShell;
`gt completion <shell> --help` shows how to load it. `gt man` prints the manual.
+22
View File
@@ -0,0 +1,22 @@
---
sidebar_position: 5
---
# Output and scripting
Scripts should read JSON: the text output is meant for people and may change.
```bash
gt pr view 123 --json number,title,state
gt issue list --json number,title --jq '.[] | "\(.number)\t\(.title)"'
```
`--json` prints all fields or the ones you list; the **JSON FIELDS** section of a command's
`--help` names them. `--jq` filters the result without needing jq installed. Piped lists
print as tab-separated values.
gt exits with 0 on success, 1 when a command fails, 2 on a usage error and 4 when
authentication is missing or rejected; `gt help exit-codes` lists all codes.
`gt api` calls any API endpoint with gt's login, and `gt alias set` defines shortcuts for
commands you use often.
+1
View File
@@ -0,0 +1 @@
[]
+16
View File
@@ -0,0 +1,16 @@
// Sidebar of the gt (Gitea CLI) documentation (develop version).
// Released versions carry their own copy in
// cli-docs_versioned_sidebars/version-<version>-sidebars.json.
module.exports = {
cli: [
{
type: 'doc',
id: 'index',
label: 'What is gt?',
},
'installation',
'authentication',
'ci',
'scripting',
],
};
+4 -2
View File
@@ -1,11 +1,13 @@
# Redirects for legacy URLs and routes the site no longer serves directly.
# The api reference and the runner docs are separate products, published in
# english only, so they have no localized copy.
# The api reference, the runner and the cli docs are separate products,
# published in english only, so they have no localized copy.
/zh-cn/api/* /api/:splat 301
/zh-tw/api/* /api/:splat 301
/zh-cn/runner/* /runner/:splat 301
/zh-tw/runner/* /runner/:splat 301
/zh-cn/cli/* /cli/:splat 301
/zh-tw/cli/* /cli/:splat 301
# The version served at the root of a product is also reachable under its
# number, which is what the release notes and old links use.
+3 -1
View File
@@ -7,6 +7,7 @@
"https://docs.gitea.com/zh-cn/",
"https://docs.gitea.com/zh-tw/",
"https://docs.gitea.com/runner/",
"https://docs.gitea.com/cli/",
"https://docs.gitea.com/api/",
"https://docs.gitea.com/enterprise/"
],
@@ -15,7 +16,8 @@
"https://docs.gitea.com/1.2*/**",
"https://docs.gitea.com/*/1.2*/**",
"https://docs.gitea.com/api/1.2*/**",
"https://docs.gitea.com/runner/[0-9]/**"
"https://docs.gitea.com/runner/[0-9]/**",
"https://docs.gitea.com/cli/[0-9]*/**"
],
"selectors": {
"lvl0": { "selector": "//nav[contains(@aria-labelledby,'starlight__sidebar')]//a[@aria-current='page']/ancestor::details//summary//span", "type": "xpath", "global": true, "default_value": "Documentation" },
+4 -4
View File
@@ -94,16 +94,16 @@ Most web and API requests pick up the signed-in user automatically.
Other entry points record a system actor:
- `(gitea-cli)` for `gitea admin` commands and the `system:startup` and
- `gitea:cli` for `gitea admin` commands and the `system:startup` and
`system:shutdown` events
- `(gitea-auth-source)` for authentication-source syncs and for users an
- `gitea:auth-source` for authentication-source syncs and for users an
authentication source creates or updates at sign-in
- `gitea-actions` for Gitea Actions tasks
- `(deploy-key)` for pushes with a deploy key
- `gitea:deploy-key` for pushes with a deploy key
- `(Cron)` for scheduled cron tasks
- `Unknown` when no actor can be resolved
Events of the `(gitea-cli)` actor use origin `cli`. Cron tasks, git hooks and
Events of the `gitea:cli` actor use origin `cli`. Cron tasks, git hooks and
other background work use origin `system`.
## What is recorded
+6 -2
View File
@@ -337,9 +337,13 @@ The default login user email is `X-WEBAUTH-EMAIL`, you can change it via changin
If set `ENABLE_REVERSE_PROXY_FULL_NAME=true`, a user full name expected in `X-WEBAUTH-FULLNAME` will be assigned to the user when auto creating the user. You can also change the header name with `REVERSE_PROXY_AUTHENTICATION_FULL_NAME`.
You can also limit the reverse proxy's IP address range with `REVERSE_PROXY_TRUSTED_PROXIES` which default value is `127.0.0.0/8,::1/128`. By `REVERSE_PROXY_LIMIT`, you can limit trusted proxies level.
It applies to the web interface, Git over HTTP, Git LFS and the package registry.
You can enable the this authentication method for the API with
:::warning
Gitea trusts these headers on every request it receives. Make sure Gitea is only reachable through your authenticating proxy, and that the proxy sets or removes these headers on every request, including on paths you exempt from authentication, for example for Git or package clients. `REVERSE_PROXY_TRUSTED_PROXIES` does not restrict them.
:::
To also accept them on the REST API under `/api/v1`, enable:
```ini
[service]
+1 -1
View File
@@ -83,7 +83,7 @@ cd gitea-dump-1610949662
mv app.ini /etc/gitea/conf/app.ini
mv data/* /var/lib/gitea/data/
mv log/* /var/lib/gitea/log/
mv repos/* /var/lib/gitea/data/repositories/
mv repos/* /var/lib/gitea/data/gitea-repositories/
chown -R gitea:gitea /etc/gitea/conf/app.ini /var/lib/gitea
# mysql
+9 -7
View File
@@ -179,7 +179,7 @@ In addition, there is _`StaticRootPath`_ which can be set as a built-in at build
- `POPULATE_SQUASH_COMMENT_WITH_COMMIT_MESSAGES`: **false**: In default squash-merge messages include the commit message of all commits comprising the pull request.
- `ADD_CO_COMMITTER_TRAILERS`: **true**: Add co-authored-by and co-committed-by trailers to merge commit messages if committer does not match author.
- `RETARGET_CHILDREN_ON_MERGE`: **true**: Retarget child pull requests to the parent pull request branch target on merge of parent pull request. It only works on merged PRs where the head and base branch target the same repo.
- `DEFAULT_DELETE_BRANCH_AFTER_MERGE`: **false**: Set the default value for "Delete pull request branch after merge by default" for new repositories.
- `DEFAULT_DELETE_BRANCH_AFTER_MERGE`: **true**: Set the default value for "Delete pull request branch after merge by default" for new repositories.
- `DEFAULT_TITLE_SOURCE`: **auto**: Default source for the pull request title when opening a new PR. Valid options:
- `first-commit`: Uses the oldest commit's summary as the title. If there are multiple commits, still uses the first commit's message.
- `auto`: Uses the commit's summary when the PR contains a single commit; when there are multiple commits, converts the branch name into a human-readable title by normalizing separators and casing.
@@ -270,9 +270,10 @@ The following configuration set `Content-Type: application/vnd.android.package-a
- `FOLDER_ICON_THEME`: **basic**: The icon theme for folders (basic/material).
- `MAX_DISPLAY_FILE_SIZE`: **8388608**: Max size of files to be displayed (default is 8MiB)
- `AMBIGUOUS_UNICODE_DETECTION`: **true**: Detect ambiguous unicode characters in file contents and show warnings on the UI
- `REACTIONS`: All available reactions users can choose on issues/prs and comments
- `REACTIONS`: All available reactions users can choose on issues, pull requests and comments.
Values can be emoji alias (:smile:) or a unicode emoji.
For custom reactions, add a tightly cropped square image to public/assets/img/emoji/reaction_name.png
- `RELEASE_REACTIONS`: **+1, laugh, hooray, heart, rocket, eyes**: Reactions available for releases, in the same format as `REACTIONS`.
- `REACTION_MAX_USER_NUM`: **10**: Change the number of users that are displayed in reactions tooltip (triggered by mouse hover).
- `CUSTOM_EMOJIS`: **gitea, codeberg, gitlab, git, github, gogs**: Additional Emojis not defined in the utf8 standard.
By default, we support Gitea (:gitea:), to add more copy them to public/assets/img/emoji/emoji_name.png and
@@ -594,7 +595,8 @@ And the following unique queues:
authentication provided email.
- `REVERSE_PROXY_AUTHENTICATION_FULL_NAME`: **X-WEBAUTH-FULLNAME**: Header name for reverse proxy
authentication provided full name.
- `REVERSE_PROXY_LIMIT`: **1**: Interpret X-Forwarded-For header or the X-Real-IP header and set this as the remote IP for the request.
- `REVERSE_PROXY_LIMIT`: **1**: Interpret the X-Real-IP, X-Forwarded-For or Forwarded header, in that order, and set this as the remote IP for the request.
The client port is taken from the header value or, with X-Real-IP, from X-Real-Port.
Number of trusted proxy count. Set to zero to not use these headers.
- `REVERSE_PROXY_TRUSTED_PROXIES`: **127.0.0.0/8,::1/128**: List of IP addresses and networks separated by comma of trusted proxy servers. Use `*` to trust all.
- `X_FRAME_OPTIONS`: **SAMEORIGIN**: Set the `X-Frame-Options` header value for all HTTP responses. Set to `unset` to not send the header. Previously located in `[cors]`.
@@ -739,8 +741,8 @@ And the following unique queues:
If you set it to false, maybe it also needs to set `ENABLE_BASIC_AUTHENTICATION` to false to
completely disable password-based authentication.
- `ENABLE_PASSKEY_AUTHENTICATION`: **true**: Allow users to sign-in with a passkey
- `ENABLE_REVERSE_PROXY_AUTHENTICATION`: **false**: Enable this to allow reverse proxy authentication for web requests
- `ENABLE_REVERSE_PROXY_AUTHENTICATION_API`: **false**: Enable this to allow reverse proxy authentication for API requests, the reverse proxy is responsible for ensuring that no CSRF is possible.
- `ENABLE_REVERSE_PROXY_AUTHENTICATION`: **false**: Enable reverse proxy authentication for the web interface, Git over HTTP, Git LFS and the package registry. Gitea must only be reachable through the proxy, see [Reverse Proxy](authentication.md#reverse-proxy).
- `ENABLE_REVERSE_PROXY_AUTHENTICATION_API`: **false**: Enable reverse proxy authentication for the REST API under `/api/v1`.
- `ENABLE_REVERSE_PROXY_AUTO_REGISTRATION`: **false**: Enable this to allow auto-registration
for reverse authentication.
- `ENABLE_REVERSE_PROXY_EMAIL`: **false**: Enable this to allow to auto-registration with a
@@ -1354,7 +1356,7 @@ These options are written into the gitconfig file under `[git] HOME_PATH` when G
- `JWT_SECRET_URI`: **_empty_**: Instead of defining JWT_SECRET in the configuration, this configuration option can be used to give Gitea a path to a file that contains the secret (example value: `file:/etc/gitea/oauth2_jwt_secret`)
- `JWT_SIGNING_PRIVATE_KEY_FILE`: **jwt/private.pem**: Private key file path used to sign OAuth2 tokens. The path is relative to `APP_DATA_PATH`. This setting is only needed if `JWT_SIGNING_ALGORITHM` is set to `RS256`, `RS384`, `RS512`, `ES256`, `ES384` or `ES512`. The file must contain a RSA or ECDSA private key in the PKCS8 format. If no key exists a 4096 bit key will be created for you.
- `MAX_TOKEN_LENGTH`: **32767**: Maximum length of token/cookie to accept from OAuth2 provider
- `DEFAULT_APPLICATIONS`: **git-credential-oauth, git-credential-manager, tea, gitea-app**: Pre-register OAuth applications for some services on startup. See the [OAuth2 documentation](/development/oauth2-provider.md) for the list of available options.
- `DEFAULT_APPLICATIONS`: **git-credential-oauth, git-credential-manager, gitea-cli, gitea-app, gitea-vscode**: Pre-register OAuth applications for some services on startup. See the [OAuth2 documentation](/development/oauth2-provider.md) for the list of available options. `tea` is accepted as an alias of `gitea-cli`.
- `CUSTOM_SCHEMES`: **_empty_**: By default, OAuth2 applications can only use "http" and "https" as their redirect URI schemes.
If you need to use other schemes (e.g. for desktop applications), you can specify them here as a comma-separated list.
For example: set "my-scheme, com.example.app" to support "my-scheme://..." and "com.example.app://..." redirect URIs.
@@ -1706,7 +1708,7 @@ PROXY_HOSTS = *.github.com
But it will save storage space and network bandwidth, so it's still recommended to use compression.
- `ARTIFACT_RETENTION_DAYS`: **90**: Days to keep artifacts. Old artifacts will be deleted after this period. 0 means keep forever.
Changes only apply to newly uploaded artifacts, existing ones keep the expiry stored when they were uploaded.
Artifacts could have their own retention periods by setting the `retention-days` option in `actions/upload-artifact` step.
Workflows can request a shorter retention period, down to 1 day.
- `ARTIFACT_PREVIEW_MAX_SIZE`: **10485760**: Maximum total artifact size in bytes that can be browsed or previewed in the web UI. Set to `0` to disable artifact previews or `-1` for no limit. Individual files are also limited by [`MAX_DISPLAY_FILE_SIZE`](#ui-ui).
- `RUN_RETENTION_DAYS`: **400**: Days to keep completed runs. Old runs and everything under them will be deleted after this period. 0 means keep forever.
- `ZOMBIE_TASK_TIMEOUT`: **10m**: Timeout to stop the task which have running status, but haven't been updated for a long time
+1
View File
@@ -16,6 +16,7 @@ aliases:
4. Make sure `Host` and `X-Forwarded-Proto` headers are correctly passed to Gitea to make Gitea see the real URL being visited.
5. Make sure your webserver has a certificate, including all intermediate and RootCA certificates, for `git clone` and `git pull` to work.
6. Gitea uses a WebSocket connection at `/-/ws` for real-time updates like the notification counter. Make sure the reverse-proxy talks HTTP/1.1 and forwards the `Upgrade` and `Connection` headers, see the examples below. Without it, Gitea falls back to periodic polling.
7. Make sure the reverse-proxy sets the `X-Real-IP` header, otherwise Gitea uses a client-sent value. For example `header_up X-Real-IP {remote_host}` in Caddy's `reverse_proxy`, `RequestHeader set X-Real-IP "expr=%{REMOTE_ADDR}"` in Apache or `http-request set-header X-Real-IP %[src]` in HAProxy. To log the client port, also set `X-Real-Port`, for example `proxy_set_header X-Real-Port $remote_port;` in nginx.
### Use a sub-path
+1
View File
@@ -73,6 +73,7 @@ By default, this creates a token with very limited permissions. To complete your
- `activitypub`
- `admin`
- `commitstatus` (included in `repository`)
- `issue`
- `misc`
- `notification`
+6 -1
View File
@@ -111,6 +111,9 @@ Gitea token scopes are as follows:
| **admin** | `/admin/*` API routes: Site-wide administrative operations (hidden for non-admin accounts). |
| &nbsp;&nbsp;&nbsp; **read:admin** | Grants read access for admin operations, such as getting cron jobs or registered user emails. |
| &nbsp;&nbsp;&nbsp; **write:admin** | Grants read/write/delete access for admin operations, such as running cron jobs or updating user accounts. |
| **commitstatus** | `/repos/{owner}/{repo}/statuses/{sha}` and `/repos/{owner}/{repo}/commits/{ref}/status(es)` API routes: Commit status operations. |
| &nbsp;&nbsp;&nbsp; **read:commitstatus** | Grants read access to commit statuses. Included in `read:repository`. |
| &nbsp;&nbsp;&nbsp; **write:commitstatus** | Grants read/write access to commit statuses, such as reporting CI results. Included in `write:repository`. |
| **issue** | `issues/*`, `labels/*`, `milestones/*` API routes: Issue-related operations. |
| &nbsp;&nbsp;&nbsp; **read:issue** | Grants read access for issues operations, such as getting issue comments, issue attachments, and milestones. |
| &nbsp;&nbsp;&nbsp; **write:issue** | Grants read/write/delete access for issues operations, such as posting or editing an issue comment or attachment, and updating milestones. |
@@ -141,7 +144,9 @@ Gitea creates OAuth applications for the following services by default on startu
|-----------|-----------|---------|
|[git-credential-oauth](https://github.com/hickford/git-credential-oauth)|Git credential helper|`a4792ccc-144e-407e-86c9-5e7d8d9c3269`|
|[Git Credential Manager](https://github.com/git-ecosystem/git-credential-manager)|Git credential helper|`e90ee53c-94e2-48ac-9358-a874fb9e0662`|
|[tea](https://gitea.com/gitea/tea)|tea|`d57cb8c4-630c-4168-8324-ec79935e18d4`|
|[Gitea CLI](https://gitea.com/gitea/cli)|Gitea CLI|`d57cb8c4-630c-4168-8324-ec79935e18d4`|
|Gitea App|Official Gitea mobile app|`b757811a-05c8-4c76-8d74-a5ee3d2073f2`|
|[gitea-vscode](https://gitea.com/gitea/gitea-vscode)|VS Code extension|`75a9ec3e-42b5-40cb-9144-9ea96ecd56a6`|
To prevent unexpected behavior, they are being displayed as locked in the UI and their creation can instead be controlled by the `DEFAULT_APPLICATIONS` parameter in `app.ini`.
+23 -1
View File
@@ -8,7 +8,7 @@ aliases:
# Database Preparation
You need a database to use Gitea. Gitea supports PostgreSQL (>= 12), MySQL (>= 8.0), MariaDB (>= 10.4), SQLite (builtin), and MSSQL (>= 2012 SP4). This page will guide into preparing database. Only PostgreSQL and MySQL will be covered here since those database engines are widely-used in production. If you plan to use SQLite, you can ignore this chapter.
You need a database to use Gitea. Gitea supports PostgreSQL (>= 12), MySQL (>= 8.0), MariaDB (>= 10.4), SQLite (builtin), and MSSQL (>= 2012 SP4). This page will guide into preparing database. PostgreSQL, MySQL and MSSQL are covered here. If you plan to use SQLite, you can ignore this chapter.
:::warning
Converting one database type to another is not a well-tested process and you may experience issues. It is better to choose the final database type at the type of the first installation. Be aware that SQLite does not scale; if you expect your instance to grow at a later time, you should choose another database type.
@@ -177,6 +177,28 @@ All steps below requires that the database engine of your choice is installed on
You should be prompted to enter password for the database user, and connected to the database.
## MSSQL
1. As an administrator, create the login, a database with case-sensitive collation, and its user. Replace the password `'gitea'` with a secure one:
```sql
CREATE LOGIN gitea WITH PASSWORD = 'gitea';
CREATE DATABASE giteadb COLLATE Latin1_General_CS_AS;
GO
USE giteadb;
CREATE USER gitea FOR LOGIN gitea;
ALTER ROLE db_owner ADD MEMBER gitea;
GO
```
Upgrades change the database schema, so the user needs `db_owner`, or `db_ddladmin`, `db_datareader` and `db_datawriter`. With the latter, also run `ALTER DATABASE giteadb SET READ_COMMITTED_SNAPSHOT ON;` as administrator.
2. On your Gitea server, test the connection to the database:
```sh
sqlcmd -S 203.0.113.3 -U gitea -d giteadb
```
## Database Connection over TLS
If the communication between Gitea and your database instance is performed through a private network, or if Gitea and the database are running on the same server, this section can be omitted since the security between Gitea and the database instance is not critically exposed. If instead the database instance is on a public network, use TLS to encrypt the connection to the database, as it is possible for third-parties to intercept the traffic data.
@@ -10,13 +10,13 @@ aliases:
# Protected branches
Protected branches prevent unwanted changes by enforcing push and merge policies on selected branches. The rules are enforced for every Git protocol (HTTP(S), SSH), the web editor, the API, and background jobs such as auto-merge. Only repository owners and administrators can manage the rules, and the Branches page is read-only while a repository is archived.
Protected branches prevent unwanted changes by enforcing push, deletion, and merge policies on selected branches. The rules are enforced for every Git protocol (HTTP(S), SSH), the web editor, the API, and background jobs such as auto-merge. Only repository owners and administrators can manage the rules, and the Branches page is read-only while a repository is archived.
## Creating or editing a rule
1. Open the repository and select **Settings → Branches** (repository admin permission required).
2. Select **Add new rule** or **Edit** next to an existing rule.
3. Fill in the **Protected branch name pattern** and optional file patterns, then configure the push, merge, and review options described below.
3. Fill in the **Protected branch name pattern** and optional file patterns, then configure the push, deletion, merge, and review options described below.
4. Select **Save rule**.
The rule immediately applies to all matching branches, even if the branches are created in the future.
@@ -48,7 +48,9 @@ The **Push** section controls direct pushes (including the web editor and API).
- **Disable push** makes the branch read-only. Any attempt to push directly fails, and changes must be merged through pull requests.
- **Enable push** allows anyone with [write access](./permissions.md) to push (force pushes are still blocked unless explicitly allowed).
- **Allowlist restricted push** requires being on the allowlist. Choose users and, for organization-owned repositories, teams. Deploy keys that already have write access can also be allowlisted.
- **Allowlist restricted push** requires being on the allowlist. Choose users and, for organization-owned repositories, teams.
The user allowlists for push, force-push and deletion can include the **Gitea Actions** and **Deploy Key** system users. They still require write access; **Deploy Key** also covers deploy tokens.
When a push is blocked, the server-side hook rejects the update with an explanation.
@@ -58,7 +60,17 @@ Force pushes have their own set of options:
- **Disable force push** completely forbids rewriting history on the branch.
- **Enable force push** allows anyone who can push to also force push.
- **Allowlist restricted force push** limits force pushes to a separate allowlist (users, teams, and optionally deploy keys) **and** requires the person to already have regular push access.
- **Allowlist restricted force push** limits force pushes to a separate allowlist of users or teams **and** requires regular push access.
## Branch deletion
The **Branch Deletion** section controls whether matching protected branches can be deleted:
- **Disable branch deletion** prevents deletion. This is the default.
- **Enable branch deletion** allows anyone who can push to delete the branch.
- **Allowlist restricted branch deletion** limits deletion to a separate allowlist of users or teams **and** requires regular push access.
These rules apply to Git (`git push --delete`), the API, the web interface and post-merge cleanup. The default branch and configured default pull request target branch cannot be deleted.
## Pull request merges and approvals
+2 -2
View File
@@ -40,7 +40,7 @@ However, it is recommended to use Docker to run the jobs, because it is more sec
Before running a runner, you should first register it to your Gitea instance using the following command:
```bash
./runner register --no-interactive --instance <instance> --token <token>
gitea-runner register --no-interactive --instance <instance> --token <token>
```
There are two arguments required, `instance` and `token`.
@@ -69,7 +69,7 @@ If this file is missing or corrupted, you can simply remove it and register agai
Finally, it's time to start the runner:
```bash
./runner daemon
gitea-runner daemon
```
And you can see the new runner in the management page:
+4
View File
@@ -99,6 +99,10 @@ You can configure defaults and maximums at:
Note: Repository-level token permission settings are only shown once **Repository Actions** are enabled.
## Protected branches
Actions job tokens must have code write permission and satisfy [branch protection rules](../access-control/protected-branches.md), including any **Gitea Actions** allowlist requirements.
## Fork pull requests
For security reasons, workflows triggered by pull requests from forks are always restricted to read-only permissions for repository contents, regardless of workflow `permissions:` or settings. They also get no OpenID Connect tokens.
+73 -59
View File
@@ -9,53 +9,67 @@ sidebar_position: 25
## Environment variables
These environment variables are set automatically in every workflow run and can be accessed directly (e.g. `$CI` in shell scripts).
A `GITEA_*` name listed with a `GITHUB_*` name holds the same value.
| Name | Description | Example |
|---|---|---|
| `ACT` | Always set to `true`, unless the runner is configured with `runner.set_act_env: false`. | `true` |
| `CI` | Always set to `true`. | `true` |
| `GITEA_ACTIONS` | Always set to `true`. Useful to distinguish Gitea Actions from other CI systems. | `true` |
| `GITEA_ACTIONS_RUNNER_VERSION` | The version of the runner executing the workflow. | `1.0.8` |
| `GITEA_DOCKER_WORKSPACE` | The workspace path as the Docker daemon sees it, for bind mounts into containers a job starts, e.g. `${GITEA_DOCKER_WORKSPACE:-.}/data:/app/data` in a compose file. Only set for jobs running in a container. | `/var/lib/docker/volumes/GITEA-ACTIONS-TASK-42_***/_data` |
| `GITEA_ENV`<br/>`GITHUB_ENV` | Path to the file that sets environment variables for subsequent steps. | `/home/runner/work/_temp/_runner_file_commands/set_env_***` |
| `GITEA_OUTPUT`<br/>`GITHUB_OUTPUT` | Path to the file that sets step output parameters. | `/home/runner/work/_temp/_runner_file_commands/set_output_***` |
| `GITEA_PATH`<br/>`GITHUB_PATH` | Path to the file that adds system `PATH` entries for subsequent steps. | `/home/runner/work/_temp/_runner_file_commands/add_path_***` |
| `GITEA_STATE`<br/>`GITHUB_STATE` | Path to the file that sets step state variables. | `/home/runner/work/_temp/_runner_file_commands/save_state_***` |
| `GITEA_STEP_SUMMARY`<br/>`GITHUB_STEP_SUMMARY` | Path to the file for writing job summaries. | `/home/runner/work/_temp/_runner_file_commands/step_summary_***` |
| `GITHUB_ACTIONS` | Always set to `true`. | `true` |
| `GITHUB_ACTION` | The name of the action currently running, or the step `id`. | `__run` |
| `GITHUB_ACTION_PATH` | The path where the action is located. | `/home/runner/work/_actions/actions/checkout/v4` |
| `GITHUB_ACTION_REF` | The ref of the action being executed. | `v4` |
| `GITHUB_ACTION_REPOSITORY` | The owner and repository of the action. | `actions/checkout` |
| `GITHUB_ACTOR` | The username of the user that triggered the workflow. | `silverwind` |
| `GITHUB_API_URL` | The URL of the REST API. | `https://gitea.com/api/v1` |
| `GITHUB_BASE_REF` | The target branch of a pull request. | `main` |
| `GITHUB_EVENT_NAME` | The name of the event that triggered the workflow. | `push` |
| `GITHUB_EVENT_PATH` | Path to the file containing the event webhook payload. | `/home/runner/work/_temp/_github_workflow/event.json` |
| `GITHUB_GRAPHQL_URL` | Empty in Gitea (GraphQL is not supported). | _(empty)_ |
| `GITHUB_HEAD_REF` | The source branch of a pull request. | `feature-branch` |
| `GITHUB_JOB` | The `job_id` of the current job. | `build` |
| `GITHUB_REF` | The fully-formed ref that triggered the workflow. | `refs/heads/main` |
| `GITHUB_REF_NAME` | The short ref name. | `main` |
| `GITHUB_REF_TYPE` | The type of ref: `branch` or `tag`. | `branch` |
| `GITHUB_REPOSITORY` | The owner and repository name. | `gitea/docs` |
| `GITHUB_REPOSITORY_OWNER` | The repository owner's username. | `gitea` |
| `GITHUB_RETENTION_DAYS` | The number of days that workflow run logs and artifacts are kept. | `90` |
| `GITHUB_RUN_ATTEMPT` | The attempt number of the workflow run. | `1` |
| `GITHUB_RUN_ID` | A unique number for each workflow run. | `1234` |
| `GITHUB_RUN_NUMBER` | A unique number for each run of a particular workflow. | `42` |
| `GITHUB_SERVER_URL` | The URL of the Gitea instance. | `https://gitea.com` |
| `GITHUB_SHA` | The commit SHA that triggered the workflow. | `a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2` |
| `GITHUB_WORKFLOW` | The name of the workflow. | `CI` |
| `GITHUB_WORKSPACE` | The default working directory on the runner. | `/workspace/gitea/docs` |
| `GITEA_ACTION`<br/>`GITHUB_ACTION` | The `id` of the current step, or its index when it has none. | `0` |
| `GITEA_ACTION_PATH`<br/>`GITHUB_ACTION_PATH` | The path where the action is located. Only set in composite actions. | `/var/run/act/actions/***` |
| `GITEA_ACTION_REF`<br/>`GITHUB_ACTION_REF` | The ref of the action being executed. | `v4` |
| `GITEA_ACTION_REPOSITORY`<br/>`GITHUB_ACTION_REPOSITORY` | The owner and repository of the action. | `actions/checkout` |
| `GITEA_ACTIONS`<br/>`GITHUB_ACTIONS` | Always set to `true`. `GITEA_ACTIONS` is useful to distinguish Gitea Actions from other CI systems. | `true` |
| `GITEA_ACTIONS_RUNNER_VERSION` | The version of the runner executing the workflow. | `v5.2.4` |
| `GITEA_ACTOR`<br/>`GITHUB_ACTOR` | The username of the user that triggered the workflow. | `alice` |
| `GITEA_ACTOR_ID`<br/>`GITHUB_ACTOR_ID` | The ID of the user that triggered the workflow. | `1` |
| `GITEA_API_URL`<br/>`GITHUB_API_URL` | The URL of the REST API. | `https://gitea.example.com/api/v1` |
| `GITEA_BASE_REF`<br/>`GITHUB_BASE_REF` | The target branch of a pull request. | `main` |
| `GITEA_DOCKER_WORKSPACE` | The workspace path as the Docker daemon sees it, for bind mounts into containers a job starts, e.g. `${GITEA_DOCKER_WORKSPACE:-.}/data:/app/data` in a compose file. Only set for jobs running in a container. | `/var/lib/docker/volumes/GITEA-ACTIONS-TASK-42-***/_data` |
| `GITEA_ENV`<br/>`GITHUB_ENV` | Path to the file that sets environment variables for subsequent steps. | `/var/run/act/workflow/envs.txt` |
| `GITEA_EVENT_NAME`<br/>`GITHUB_EVENT_NAME` | The name of the event that triggered the workflow. | `push` |
| `GITEA_EVENT_PATH`<br/>`GITHUB_EVENT_PATH` | Path to the file containing the event webhook payload. | `/var/run/act/workflow/event.json` |
| `GITEA_GRAPHQL_URL`<br/>`GITHUB_GRAPHQL_URL` | Empty in Gitea (GraphQL is not supported). | _(empty)_ |
| `GITEA_HEAD_REF`<br/>`GITHUB_HEAD_REF` | The source branch of a pull request. | `feature-branch` |
| `GITEA_JOB`<br/>`GITHUB_JOB` | The `job_id` of the current job. | `build` |
| `GITEA_OUTPUT`<br/>`GITHUB_OUTPUT` | Path to the file that sets step output parameters. | `/var/run/act/workflow/outputcmd.txt` |
| `GITEA_PATH`<br/>`GITHUB_PATH` | Path to the file that adds system `PATH` entries for subsequent steps. | `/var/run/act/workflow/pathcmd.txt` |
| `GITEA_REF`<br/>`GITHUB_REF` | The fully-formed ref that triggered the workflow. | `refs/heads/main` |
| `GITEA_REF_NAME`<br/>`GITHUB_REF_NAME` | The short ref name. | `main` |
| `GITEA_REF_PROTECTED`<br/>`GITHUB_REF_PROTECTED` | `true` if branch protections are configured for the ref that triggered the workflow. | `true` |
| `GITEA_REF_TYPE`<br/>`GITHUB_REF_TYPE` | The type of ref: `branch` or `tag`. | `branch` |
| `GITEA_REPOSITORY`<br/>`GITHUB_REPOSITORY` | The owner and repository name. | `owner/repo` |
| `GITEA_REPOSITORY_ID`<br/>`GITHUB_REPOSITORY_ID` | The ID of the repository. | `123` |
| `GITEA_REPOSITORY_OWNER`<br/>`GITHUB_REPOSITORY_OWNER` | The repository owner's username. | `owner` |
| `GITEA_REPOSITORY_OWNER_ID`<br/>`GITHUB_REPOSITORY_OWNER_ID` | The ID of the repository owner. | `45` |
| `GITEA_RETENTION_DAYS`<br/>`GITHUB_RETENTION_DAYS` | The number of days that artifacts are kept, empty when kept forever. | `90` |
| `GITEA_RUN_ATTEMPT`<br/>`GITHUB_RUN_ATTEMPT` | The attempt number of the workflow run. | `1` |
| `GITEA_RUN_ID`<br/>`GITHUB_RUN_ID` | A unique number for each workflow run. | `1234` |
| `GITEA_RUN_NUMBER`<br/>`GITHUB_RUN_NUMBER` | A unique number for each run of a particular workflow. | `42` |
| `GITEA_SERVER_URL`<br/>`GITHUB_SERVER_URL` | The URL of the Gitea instance. | `https://gitea.example.com` |
| `GITEA_SHA`<br/>`GITHUB_SHA` | The commit SHA that triggered the workflow. | `a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2` |
| `GITEA_STATE`<br/>`GITHUB_STATE` | Path to the file that sets step state variables. | `/var/run/act/workflow/statecmd.txt` |
| `GITEA_STEP_SUMMARY`<br/>`GITHUB_STEP_SUMMARY` | Path to the file for writing job summaries. | `/var/run/act/workflow/step-summary-0.md` |
| `GITEA_TRIGGERING_ACTOR`<br/>`GITHUB_TRIGGERING_ACTOR` | The username of the user that initiated the workflow run. May differ from `GITEA_ACTOR` on re-runs. | `alice` |
| `GITEA_WORKFLOW`<br/>`GITHUB_WORKFLOW` | The name of the workflow. | `CI` |
| `GITEA_WORKFLOW_SHA`<br/>`GITHUB_WORKFLOW_SHA` | The commit SHA of the workflow file. | `a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2` |
| `GITEA_WORKSPACE`<br/>`GITHUB_WORKSPACE` | The default working directory on the runner. | `/workspace/owner/repo` |
| `TRACEPARENT` | The W3C trace context of the job's span, so instrumented tools can add spans to the job's trace. Only set when the runner exports traces, see [Monitoring](/runner/monitoring/#opentelemetry). | `00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01` |
| `TRACESTATE` | Vendor-specific trace data that accompanies `TRACEPARENT`. Only set when the trace carries any. | `rojo=00f067aa0ba902b7` |
### Runner environment variables
| Name | Description | Example |
|---|---|---|
| `ImageOS` | The OS and release of the job's image, read by `setup-*` actions. | `ubuntu24` |
| `RUNNER_ARCH` | The architecture of the runner. | `X64` |
| `RUNNER_DEBUG` | Set to `1` when the `ACTIONS_STEP_DEBUG` secret is `true`, unset otherwise. | `1` |
| `RUNNER_ENVIRONMENT` | Always set to `self-hosted`. | `self-hosted` |
| `RUNNER_NAME` | The name of the runner executing the job. | `my-runner` |
| `RUNNER_OS` | The operating system of the runner. | `Linux` |
| `RUNNER_TEMP` | Path to a temporary directory on the runner. | `/tmp` |
| `RUNNER_TOOL_CACHE` | Path to the tool cache directory on the runner. | `/opt/hostedtoolcache` |
| `RUNNER_WORKSPACE` | The parent directory of `GITEA_WORKSPACE`. | `/workspace/owner` |
### Internal environment variables
@@ -65,10 +79,10 @@ These are used internally by the runner and actions. They are typically not need
|---|---|---|
| `ACTIONS_CACHE_URL` | URL for the actions cache service. | `http://192.168.1.10:8088/` |
| `ACTIONS_ID_TOKEN_REQUEST_TOKEN` | Token for OIDC requests. Only set when `id-token: write` is granted. | `***` |
| `ACTIONS_ID_TOKEN_REQUEST_URL` | URL to request OIDC tokens. Only set when `id-token: write` is granted. | `https://gitea.com/api/actions/oidc/token?` |
| `ACTIONS_RESULTS_URL` | URL for storing artifacts. | `https://gitea.com` |
| `ACTIONS_ID_TOKEN_REQUEST_URL` | URL to request OIDC tokens. Only set when `id-token: write` is granted. | `https://gitea.example.com/api/actions/oidc/token?` |
| `ACTIONS_RESULTS_URL` | URL for storing artifacts. | `https://gitea.example.com` |
| `ACTIONS_RUNTIME_TOKEN` | Authentication token for the Actions pipeline API. | `***` |
| `ACTIONS_RUNTIME_URL` | URL for the Gitea Actions pipeline API. | `https://gitea.com/api/actions_pipeline/` |
| `ACTIONS_RUNTIME_URL` | URL for the Gitea Actions pipeline API. | `https://gitea.example.com/api/actions_pipeline/` |
## Context variables
@@ -76,39 +90,39 @@ These variables are available in workflow expressions via `${{ gitea.<name> }}`.
| Name | Description | Example |
|---|---|---|
| `gitea.action`<br/>`github.action` | The name of the action currently running, or the `id` of a step. | `__run` |
| `gitea.action_path`<br/>`github.action_path` | The path where an action is located. Only supported in composite actions. | `/home/runner/work/_actions/actions/checkout/v4` |
| `gitea.action`<br/>`github.action` | The `id` of the current step, or its index when it has none. | `0` |
| `gitea.action_path`<br/>`github.action_path` | The path where an action is located. Only supported in composite actions. | `/var/run/act/actions/***` |
| `gitea.action_ref`<br/>`github.action_ref` | The ref of the action being executed. | `v4` |
| `gitea.action_repository`<br/>`github.action_repository` | The owner and repository name of the action. | `actions/checkout` |
| `gitea.action_status`<br/>`github.action_status` | The current result of a composite action. | `success` |
| `gitea.actor`<br/>`github.actor` | The username of the user that triggered the initial workflow run. | `silverwind` |
| `gitea.api_url`<br/>`github.api_url` | The URL of the REST API. | `https://gitea.com/api/v1` |
| `gitea.actor`<br/>`github.actor` | The username of the user that triggered the initial workflow run. | `alice` |
| `gitea.api_url`<br/>`github.api_url` | The URL of the REST API. | `https://gitea.example.com/api/v1` |
| `gitea.base_ref`<br/>`github.base_ref` | The target branch of a pull request. Only set for `pull_request` and `pull_request_target` events. | `main` |
| `gitea.env`<br/>`github.env` | Path on the runner to the file that sets environment variables from workflow commands. Unique to each step. | `/home/runner/work/_temp/_runner_file_commands/set_env_***` |
| `gitea.env`<br/>`github.env` | Path on the runner to the file that sets environment variables from workflow commands. | `/var/run/act/workflow/envs.txt` |
| `gitea.event`<br/>`github.event` | The full event webhook payload as an object. See [Event payload](#event-payload). | `{...}` |
| `gitea.event_name`<br/>`github.event_name` | The name of the event that triggered the workflow run. | `push` |
| `gitea.event_path`<br/>`github.event_path` | Path on the runner to the file containing the full event webhook payload. | `/home/runner/work/_temp/_github_workflow/event.json` |
| `gitea.event_path`<br/>`github.event_path` | Path on the runner to the file containing the full event webhook payload. | `/var/run/act/workflow/event.json` |
| `gitea.head_ref`<br/>`github.head_ref` | The source branch of a pull request. Only set for `pull_request` and `pull_request_target` events. | `feature-branch` |
| `gitea.job`<br/>`github.job` | The `job_id` of the current job. | `build` |
| `gitea.ref`<br/>`github.ref` | The fully-formed ref that triggered the workflow. | `refs/heads/main` |
| `gitea.ref_name`<br/>`github.ref_name` | The short ref name. | `main` |
| `gitea.ref_protected`<br/>`github.ref_protected` | `true` if branch protections are configured for the ref that triggered the workflow run. | `true` |
| `gitea.ref_type`<br/>`github.ref_type` | The type of ref: `branch` or `tag`. | `branch` |
| `gitea.path`<br/>`github.path` | Path on the runner to the file that sets system `PATH` variables from workflow commands. Unique to each step. | `/home/runner/work/_temp/_runner_file_commands/add_path_***` |
| `gitea.repository`<br/>`github.repository` | The owner and repository name. | `gitea/docs` |
| `gitea.repository_owner`<br/>`github.repository_owner` | The repository owner's username. | `gitea` |
| `gitea.repositoryUrl`<br/>`github.repositoryUrl` | The HTML URL to the repository. | `https://gitea.com/gitea/docs` |
| `gitea.retention_days`<br/>`github.retention_days` | The number of days that workflow run logs and artifacts are kept. | `90` |
| `gitea.path`<br/>`github.path` | Path on the runner to the file that sets system `PATH` variables from workflow commands. | `/var/run/act/workflow/pathcmd.txt` |
| `gitea.repository`<br/>`github.repository` | The owner and repository name. | `owner/repo` |
| `gitea.repository_owner`<br/>`github.repository_owner` | The repository owner's username. | `owner` |
| `gitea.repositoryUrl`<br/>`github.repositoryUrl` | The HTML URL to the repository. | `https://gitea.example.com/owner/repo` |
| `gitea.retention_days`<br/>`github.retention_days` | The number of days that artifacts are kept, empty when kept forever. | `90` |
| `gitea.run_id`<br/>`github.run_id` | A unique number for each workflow run within a repository. Does not change on re-run. | `1234` |
| `gitea.run_number`<br/>`github.run_number` | A unique number for each run of a particular workflow. Starts at 1 and increments with each new run. | `42` |
| `gitea.run_attempt`<br/>`github.run_attempt` | A unique number for each re-run attempt. Starts at 1 and increments with each re-run. | `1` |
| `gitea.secret_source`<br/>`github.secret_source` | The source of a secret used in a workflow. Always `Actions` in Gitea. | `Actions` |
| `gitea.server_url`<br/>`github.server_url` | The URL of the Gitea instance. | `https://gitea.com` |
| `gitea.server_url`<br/>`github.server_url` | The URL of the Gitea instance. | `https://gitea.example.com` |
| `gitea.sha`<br/>`github.sha` | The commit SHA that triggered the workflow. | `a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2` |
| `gitea.token`<br/>`github.token` | A token to authenticate on behalf of the Gitea App installed on the repository. See [Token permissions](token-permissions.md). | `ghs_***` |
| `gitea.triggering_actor`<br/>`github.triggering_actor` | The username of the user that initiated the workflow run. May differ from `actor` on re-runs. | `silverwind` |
| `gitea.token`<br/>`github.token` | A token to authenticate on behalf of the Gitea App installed on the repository. See [Token permissions](token-permissions.md). | `***` |
| `gitea.triggering_actor`<br/>`github.triggering_actor` | The username of the user that initiated the workflow run. May differ from `actor` on re-runs. | `alice` |
| `gitea.workflow`<br/>`github.workflow` | The name of the workflow. If unnamed, the full path of the workflow file. | `CI` |
| `gitea.workspace`<br/>`github.workspace` | The default working directory on the runner and the default location of your repository when using the `checkout` action. | `/workspace/gitea/docs` |
| `gitea.workspace`<br/>`github.workspace` | The default working directory on the runner and the default location of your repository when using the `checkout` action. | `/workspace/owner/repo` |
| `gitea.gitea_default_actions_url` | The default URL for downloading actions. Gitea-specific. | `https://github.com` |
### Event payload
@@ -124,11 +138,11 @@ These fields exist in almost every payload:
| Name | Description | Example |
|---|---|---|
| `gitea.event.action` | The activity type. Only set for events that have activity types, see [supported events](faq.md#what-workflow-trigger-events-does-gitea-support). | `opened` |
| `gitea.event.repository.full_name` | The owner and name of the repository. | `gitea/docs` |
| `gitea.event.repository.full_name` | The owner and name of the repository. | `owner/repo` |
| `gitea.event.repository.default_branch` | The default branch of the repository. | `main` |
| `gitea.event.repository.private` | Whether the repository is private. | `false` |
| `gitea.event.repository.html_url` | The HTML URL of the repository. | `https://gitea.com/gitea/docs` |
| `gitea.event.sender.login` | The username of the user that triggered the event. Also available as `sender.username`. | `silverwind` |
| `gitea.event.repository.html_url` | The HTML URL of the repository. | `https://gitea.example.com/owner/repo` |
| `gitea.event.sender.login` | The username of the user that triggered the event. Also available as `sender.username`. | `alice` |
The remaining fields are specific to one or a few events:
@@ -143,12 +157,12 @@ The remaining fields are specific to one or a few events:
| `gitea.event.commits` | `push` | The commits contained in the push, each with `id`, `message`, `url`, `author`, `added`, `removed` and `modified`. | `[{...}]` |
| `gitea.event.head_commit.message` | `push` | The message of the most recent commit of the push. | `fix: broken link` |
| `gitea.event.total_commits` | `push` | The number of commits in the push. Gitea-specific. | `3` |
| `gitea.event.compare_url` | `push` | The URL comparing `before` with `after`. GitHub names this field `compare`. | `https://gitea.com/gitea/docs/compare/a1b2c3d...e5f6a1b` |
| `gitea.event.pusher.login` | `push` | The username of the user that pushed. | `silverwind` |
| `gitea.event.forkee.full_name` | `fork` | The owner and name of the new fork. | `silverwind/docs` |
| `gitea.event.compare_url` | `push` | The URL comparing `before` with `after`. GitHub names this field `compare`. | `https://gitea.example.com/owner/repo/compare/a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2...f6e5d4c3b2a1f6e5d4c3b2a1f6e5d4c3b2a1f6e5` |
| `gitea.event.pusher.login` | `push` | The username of the user that pushed. | `alice` |
| `gitea.event.forkee.full_name` | `fork` | The owner and name of the new fork. | `alice/repo` |
| `gitea.event.page` | `gollum` | The name of the wiki page. | `Home` |
| `gitea.event.number` | `issues`, `pull_request`, `pull_request_target`, `pull_request_review`, `pull_request_review_comment` | The index of the issue or pull request. Not set for `issue_comment`, use `gitea.event.issue.number` there. | `386` |
| `gitea.event.issue.number`<br/>`gitea.event.issue.title`<br/>`gitea.event.issue.body`<br/>`gitea.event.issue.state`<br/>`gitea.event.issue.labels`<br/>`gitea.event.issue.user.login` | `issues`, `issue_comment` | The issue the event happened on. | `Expand gitea.event in actions docs` |
| `gitea.event.issue.number`<br/>`gitea.event.issue.title`<br/>`gitea.event.issue.body`<br/>`gitea.event.issue.state`<br/>`gitea.event.issue.labels`<br/>`gitea.event.issue.user.login` | `issues`, `issue_comment` | The issue the event happened on. | `Fix typo in README` |
| `gitea.event.comment.body` | `issue_comment` | The body of the comment. | `LGTM` |
| `gitea.event.is_pull` | `issue_comment` | Whether the comment was made on a pull request. Gitea-specific, GitHub sets `issue.pull_request` on the issue object instead. | `true` |
| `gitea.event.pull_request.number`<br/>`gitea.event.pull_request.title`<br/>`gitea.event.pull_request.body`<br/>`gitea.event.pull_request.draft`<br/>`gitea.event.pull_request.merged`<br/>`gitea.event.pull_request.state`<br/>`gitea.event.pull_request.labels`<br/>`gitea.event.pull_request.user.login` | `pull_request`, `pull_request_target`, `pull_request_review`, `pull_request_review_comment` | The pull request the event happened on. | `Add caching to CI` |
+2 -2
View File
@@ -55,11 +55,11 @@ and shows a link to the repository on the package site (as well as a link to the
| Package owner type | User | Organization |
|--------------------|------|--------------|
| **read** access | public, if user is public too; otherwise for this user only | public, if org is public, otherwise for org members only |
| **write** access | owner only | org members with admin or write access to the org |
| **write** access | owner only | members of a team with write access to packages |
Please note, that your respective package manager needs to have a [personal access token](development/api-usage.md#authentication) with the permissions `package` set to either Read or Read & Write to download and publish packages respectively.
N.B.: These access restrictions are [subject to change](https://github.com/go-gitea/gitea/issues/19270), where more finegrained control will be added via a dedicated organization team permission.
The packages permission of an organization team applies to all packages of the organization, regardless of the repositories assigned to the team.
## Create or upload a package
+9
View File
@@ -0,0 +1,9 @@
{
"label": "Projects",
"position": 60,
"link": {
"type": "generated-index",
"slug": "/usage/projects",
"description": "Plan and track work with project boards"
}
}
+82
View File
@@ -0,0 +1,82 @@
---
date: "2025-11-25T18:58:22Z"
slug: "project-workflows"
sidebar_position: 1
description: "Automate project boards with event-driven filters and actions."
---
# Project Workflows
Project workflows automate a project board. When an issue or pull request in the project triggers an event, each enabled workflow for that event checks its filters and applies its actions. Workflows run in Gitea and need no runner.
## Edit workflows
1. Open a project and select **Workflows**.
2. Select an event in the sidebar.
3. Set filters and actions, then select **Save**. To change an existing workflow, select **Edit** first.
An event can have several workflows. **Clone** copies the selected workflow so you can adapt it to other items. **Disable** pauses a workflow and keeps its configuration. **Remove** deletes it.
## Events
| Event | Runs when | Filters | Actions |
| --- | --- | --- | --- |
| Item added to project | An item is added to the project or created in it | Apply to, labels | Move to column, labels, issue state |
| Item removed from project | An item is removed from the project | Apply to, labels | Labels, issue state |
| Item reopened | An item is reopened | Apply to, labels | Move to column, labels |
| Item closed | An item is closed | Apply to, labels | Move to column, labels |
| Item column changed | An item moves to another column | Apply to, columns, labels | Labels, issue state |
| Code changes requested | An official reviewer requests changes on a pull request | Labels | Move to column, labels |
| Code review approved | An official reviewer approves a pull request | Labels | Move to column, labels |
| Pull request merged | A pull request is merged | Labels | Move to column, labels |
## Filters
A workflow without filters runs for every item.
- **Apply to**: issues, pull requests, or both.
- **When moved from column**, **When moved to column**: the column the item left or entered.
- **Only if it has labels**: the item must have all selected labels.
## Actions
A workflow needs at least one action. Actions run in this order:
1. **Move to column**: moves the item to a column.
2. **Add labels**, **Remove labels**: changes the item's labels.
3. **Issue state**: closes or reopens the item. Workflows never reopen pull requests.
Workflow changes appear in the timeline as "Project workflow" followed by the event, and don't trigger other workflows. Workflows don't run in closed projects or for items in archived repositories.
## Permissions
Anyone who can read a project can view its workflows. Managing them requires write access to the project and:
- Repository project: write access to the repository's issues and pull requests.
- Organization project: organization owner or site administrator.
- User project: the project's owner.
Access tokens limited to public repositories can't manage workflows for organization or user projects. Workflows apply their actions regardless of who triggered the event.
## API
Manage workflows under the project routes of a repository (`/repos/{owner}/{repo}/projects/{id}`), an organization (`/orgs/{org}/projects/{id}`) or the authenticated user (`/user/projects/{id}`):
| Method | Path | Purpose |
| --- | --- | --- |
| `GET` | `.../workflows` | List workflows |
| `GET` | `.../workflows/{workflow_id}` | Get a workflow |
| `POST` | `.../workflows` | Create a workflow |
| `PATCH` | `.../workflows/{workflow_id}` | Edit a workflow, omitted fields stay unchanged |
| `DELETE` | `.../workflows/{workflow_id}` | Delete a workflow |
A workflow has an `event` (`item_added_to_project`, `item_removed_from_project`, `item_reopened`, `item_closed`, `item_column_changed`, `code_changes_requested`, `code_review_approved` or `pull_request_merged`), an `enabled` flag, `filters` (`issue_type`, `source_column_id`, `target_column_id`, `label_ids`) and `actions` (`column_id`, `add_label_ids`, `remove_label_ids`, `issue_state`). Each event accepts only the filters and actions listed in [Events](#events).
## Example
To route new bug reports to triage, create a workflow for **Item added to project**:
- **Apply to**: Issues only
- **Only if it has labels**: `bug`
- **Move to column**: Triage
- **Add labels**: `needs-triage`
+69 -13
View File
@@ -9,25 +9,36 @@ aliases:
# Code Owners
Gitea maintains code owner files. It looks for it in the following locations in this order:
Gitea reads the first CODEOWNERS file found at these locations, in order:
- `./CODEOWNERS`
- `./docs/CODEOWNERS`
- `./.gitea/CODEOWNERS`
And stops at the first found file.
Pull requests use the base branch's rules. File views show owners from the commit
being viewed, including when browsing a branch, tag, or historical commit.
File format: `<regexp rule> <@user or @org/team> [@user or @org/team]...`
## Patterns and owners
Regexp specified in golang Regex format.
Regexp can start with `!` for negative rules - match all files except specified.
Each rule contains a pattern followed by users or organization teams.
Patterns starting with `*` or containing `/*` are parsed as glob expressions.
General patterns are parsed as regular expressions.
A leading `!` inverts a match.
Special rule characters like space, backslash and `#` must be escaped with a backslash.
When using regexp, since backslash needs to be escaped in rule level, literal dot should be written as `\\.`
Example file:
```bash
.*\\.go @user1 @user2 # This is comment
```text
*.js @alice @bob # This is an inline comment
doc/*.md @charles # Patterns starting with "*" or containing "/*" are parsed as glob
src/**/db*.py @david # Use "**" to match any-level sub directories
.*\\.go @user1 @user2 # General patterns are parsed as regexp
test\\..*\\.js @user3 # !!! Do remeber: NOT "test.*.js", only regexp can be used for such cases
# Comment too
# You can assigning code owning for users or teams
frontend/src/.*\\.js @org1/team1 @org1/team2 @user3
@@ -42,24 +53,69 @@ docs/(aws|google|azure)/[^/]*\\.(md|txt) @user8 @org1/team4
Code owners listed in this file will automatically be requested to review pull requests
that modify files matching at least one corresponding rule.
### Escaping
## Sections
You can escape characters `#`, ` ` (space) and `\` with `\`, like:
A section heading can supply default owners for the rules that follow it:
```text
[Documentation] @org1/team-doc
*.md
README.md @alice
[Frontend] @bob
*.js
```
Owners on a rule replace the section's default owners. Section names are
case-insensitive; repeated headings belong to the same section. For each file, the
last matching rule in each named section applies. Matching rules outside named
sections retain Gitea's existing behavior: they all apply.
## Review request annotations
Use `## review-request=true` or `## review-request=false` to control automatic
review requests. A line annotation overrides a section annotation, which overrides
the global value. When none is set, individual code owners receive automatic review
requests only if they watch the repository and include pull requests in their watch
preferences. Team-level requests are enabled by default. An explicit `true` requests
a review regardless of the individual owner's watch preferences; `false` disables
automatic requests for the rule.
```text
## review-request=false # global default for following rules
*.js @alice ## review-request=true # only this rule
[Documentation] @org1/team-doc ## review-request=true
*.md
private/* @bob ## review-request=false
```
A standalone annotation changes the global default for subsequent rules.
Annotations on section headings apply until the next heading. Ordinary comments
start with `#`, including comments after annotations.
Disabling review requests keeps code owners visible when viewing a file and
does not waive approvals required by branch protection.
## Escaping
You need to escape characters `#`, ` ` (space) and `\` with backslash `\`, e.g.:
```text
dir/with\#hashtag @user1
path\ with\ space @user2
path/with\\backslash @user3
```
Some character (`.+*?()|[]{}^$\`) should be escaped with `\\` inside regexp, like:
Some regexp characters (`.+*?()|[]{}^$\`) should be escaped with `\\`, e.g.:
```
path/\\.with\\.dots
path/with\\+plus
```
### Branch Protection Rules
## Branch Protection Rules
When setting up branch protection rules, you may choose to enforce code owner approval of
modified files. This will block a PR from being merged until at least one code owner
@@ -67,7 +123,7 @@ has given approval for every applicable rule listed in the `CODEOWNERS` file.
For example, say you have the following `CODEOWNERS` file:
```bash
```text
.*\\.go @Bob
.*\\.md @Sally @Chris
migrations/.* @Alice
@@ -80,7 +80,7 @@ cd gitea-dump-1610949662
mv app.ini /etc/gitea/conf/app.ini
mv data/* /var/lib/gitea/data/
mv log/* /var/lib/gitea/log/
mv repos/* /var/lib/gitea/repositories/
mv repos/* /var/lib/gitea/data/gitea-repositories/
chown -R gitea:gitea /etc/gitea/conf/app.ini /var/lib/gitea
# mysql
@@ -141,7 +141,7 @@ aliases:
- `POPULATE_SQUASH_COMMENT_WITH_COMMIT_MESSAGES`: **false**: 在默认的 squash 合并消息中,包括构成拉取请求的所有提交的提交消息。
- `ADD_CO_COMMITTER_TRAILERS`: **true**: 如果提交者与作者不匹配,在合并提交消息中添加`co-authored-by`和`co-committed-by`标记。
- `RETARGET_CHILDREN_ON_MERGE`: **true**: 当父合并请求被合并时,将子合并请求重新定向到父合并请求的目标分支。仅适用于头分支和基分支指向同一仓库的已合并合并请求。
- `DEFAULT_DELETE_BRANCH_AFTER_MERGE`: **false**: 为新仓库设置"合并后默认删除合并请求分支"的默认值。
- `DEFAULT_DELETE_BRANCH_AFTER_MERGE`: **true**: 为新仓库设置"合并后默认删除合并请求分支"的默认值。
### 仓库 - 工单 (`repository.issue`)
@@ -232,6 +232,7 @@ aliases:
- `REACTIONS`: 用户可以在问题(Issue)、Pull Request(PR)以及评论中选择的所有可选的反应。
这些值可以是表情符号别名(例如::smile:)或 Unicode 表情符号。
对于自定义的反应,在 public/assets/img/emoji/ 目录下添加一个紧密裁剪的正方形图像,文件名为 reaction_name.png。
- `RELEASE_REACTIONS`: **+1, laugh, hooray, heart, rocket, eyes**: 版本发布中可用的反应。取值方式与 `REACTIONS` 相同。
- `REACTION_MAX_USER_NUM`: **10**: 更改在反应工具提示中显示的用户数量(鼠标悬停时触发)。
- `CUSTOM_EMOJIS`: **gitea, codeberg, gitlab, git, github, gogs**: 不在 utf8 标准中定义的额外表情符号。
默认情况下,我们支持 Gitea 表情符号(:gitea:)。要添加更多表情符号,请将它们复制到 public/assets/img/emoji/ 目录下,
@@ -523,7 +524,8 @@ Gitea 创建以下非唯一队列:
- `REVERSE_PROXY_AUTHENTICATION_USER`: **X-WEBAUTH-USER**: 反向代理认证的 HTTP 头部名称,用于提供用户信息。
- `REVERSE_PROXY_AUTHENTICATION_EMAIL`: **X-WEBAUTH-EMAIL**: 反向代理认证的 HTTP 头部名称,用于提供邮箱信息。
- `REVERSE_PROXY_AUTHENTICATION_FULL_NAME`: **X-WEBAUTH-FULLNAME**: 反向代理认证的 HTTP 头部名称,用于提供全名信息。
- `REVERSE_PROXY_LIMIT`: **1**: 解释 X-Forwarded-For 标头或 X-Real-IP 标头,并将其设置为请求的远程 IP。
- `REVERSE_PROXY_LIMIT`: **1**: 按顺序解释 X-Real-IP、X-Forwarded-For 或 Forwarded 标头,并将其设置为请求的远程 IP。
客户端端口取自标头值,或在使用 X-Real-IP 时取自 X-Real-Port。
可信代理计数。设置为零以不使用这些标头。
- `REVERSE_PROXY_TRUSTED_PROXIES`: **127.0.0.0/8,::1/128**: 逗号分隔的受信任代理服务器的 IP 地址和网络列表。使用 `*` 来信任全部。
- `DISABLE_GIT_HOOKS`: **true**: 设置为 `false` 以允许具有 Git 钩子权限的用户创建自定义 Git 钩子。
@@ -1194,7 +1196,7 @@ In-Reply-To =
- `JWT_SECRET_URI`:**_empty_**: 可以使用此配置选项,而不是在配置中定义`JWT_SECRET`,以向 Gitea 提供包含密钥的文件的路径(示例值:`file:/etc/gitea/oauth2_jwt_secret`)。
- `JWT_SIGNING_PRIVATE_KEY_FILE`:**jwt/private.pem**: 用于签署 OAuth2 令牌的私钥文件路径。路径相对于`APP_DATA_PATH`。仅当`JWT_SIGNING_ALGORITHM`设置为`RS256`,`RS384`,`RS512`,`ES256`,`ES384`或`ES512`时才需要此设置。文件必须包含 PKCS8 格式的 RSA 或 ECDSA 私钥。如果不存在密钥,则将为您创建一个 4096 位密钥。
- `MAX_TOKEN_LENGTH`:**32767**: 从 OAuth2 提供者接受的令牌/cookie 的最大长度。
- `DEFAULT_APPLICATIONS`:**git-credential-oauth,git-credential-manager, tea**: 在启动时预注册用于某些服务的 OAuth 应用程序。有关可用选项列表,请参阅[OAuth2 文档](/development/oauth2-provider.md)。
- `DEFAULT_APPLICATIONS`:**git-credential-oauth, git-credential-manager, gitea-cli, gitea-app, gitea-vscode**: 在启动时预注册用于某些服务的 OAuth 应用程序。有关可用选项列表,请参阅[OAuth2 文档](/development/oauth2-provider.md)。
## i18n (`i18n`)
@@ -8,7 +8,7 @@ aliases:
# 数据库准备
在使用 Gitea 前,您需要准备一个数据库。Gitea 支持 PostgreSQL(>= 12)、MySQL(>= 8.0)、MariaDB(>= 10.4)、SQLite(内置) 和 MSSQL(>= 2012 SP4)这几种数据库。本页将指导您准备数据库。由于 PostgreSQL 和 MySQL 在生产环境中被广泛使用,因此本文档将仅涵盖这两种数据库。如果您计划使用 SQLite,则可以忽略本章内容。
在使用 Gitea 前,您需要准备一个数据库。Gitea 支持 PostgreSQL(>= 12)、MySQL(>= 8.0)、MariaDB(>= 10.4)、SQLite(内置) 和 MSSQL(>= 2012 SP4)这几种数据库。本页将指导您准备数据库。本文档涵盖 PostgreSQL、MySQL 和 MSSQL。如果您计划使用 SQLite,则可以忽略本章内容。
如果您使用不受支持的数据库版本,请通过 [联系我们](/help/support) 以获取有关我们的扩展支持的信息。我们可以为旧数据库提供测试和支持,并将这些修复集成到 Gitea 代码库中。
@@ -168,6 +168,28 @@ aliases:
您应该会被提示输入数据库用户的密码,并连接到数据库。
## MSSQL
1. 以管理员身份创建登录名、使用大小写敏感排序规则的数据库及其用户。请将密码 `'gitea'` 替换为安全的密码:
```sql
CREATE LOGIN gitea WITH PASSWORD = 'gitea';
CREATE DATABASE giteadb COLLATE Latin1_General_CS_AS;
GO
USE giteadb;
CREATE USER gitea FOR LOGIN gitea;
ALTER ROLE db_owner ADD MEMBER gitea;
GO
```
升级会更改数据库结构,因此该用户需要 `db_owner`,或者 `db_ddladmin`、`db_datareader` 和 `db_datawriter`。使用后者时,还需以管理员身份执行 `ALTER DATABASE giteadb SET READ_COMMITTED_SNAPSHOT ON;`。
2. 在您的 Gitea 服务器上,测试与数据库的连接:
```sh
sqlcmd -S 203.0.113.3 -U gitea -d giteadb
```
## 使用 TLS 进行数据库连接
如果 Gitea 和您的数据库实例之间的通信是通过私有网络进行的,或者如果 Gitea 和数据库运行在同一台服务器上,那么可以省略本节,因为 Gitea 和数据库实例之间的安全性不会受到严重威胁。但是,如果数据库实例位于公共网络上,请使用 TLS 对数据库连接进行加密,以防止第三方拦截流量数据。
@@ -40,7 +40,7 @@ Gitea Actions需要[runner](https://gitea.com/gitea/runner) 来运行Job。
在运行Runner之前,您需要使用以下命令将其注册到Gitea实例中:
```bash
./runner register --no-interactive --instance <instance> --token <token>
gitea-runner register --no-interactive --instance <instance> --token <token>
```
需要两个必需的参数:`instance` 和 `token`。
@@ -52,10 +52,10 @@ Runner和Job容器(由Runner启动以执行Job)将连接到此地址。
如果不确定使用哪个地址,通常选择局域网地址即可。
`token` 用于身份验证和标识,例如 `P2U1U0oB4XaRCi8azcngmPCLbRpUGapalhmddh23`。
它只能使用一次,并且不能用于注册多个Runner。
每个令牌都可用于注册多个Runner。通过重置链接替换为新令牌后,旧令牌将失效。
您可以从以下位置获取不同级别的`token`,从而创建出相应级别的`runner`
- 实例级别:管理员设置页面,例如 `<your_gitea.com>/admin/actions/runners`。
- 实例级别:管理员设置页面,例如 `<your_gitea.com>/-/admin/actions/runners`。
- 组织级别:组织设置页面,例如 `<your_gitea.com>/<org>/settings/actions/runners`。
- 存储库级别:存储库设置页面,例如 `<your_gitea.com>/<owner>/<repo>/settings/actions/runners`。
@@ -68,7 +68,7 @@ Runner和Job容器(由Runner启动以执行Job)将连接到此地址。
最后,是时候启动Runner了:
```bash
./runner daemon
gitea-runner daemon
```
您可以在管理页面上看到新的Runner:
@@ -28,7 +28,7 @@ aliases:
Runner级别决定了从哪里获取注册令牌。
- 实例级别:管理员设置页面,例如 `<your_gitea.com>/admin/actions/runners`。
- 实例级别:管理员设置页面,例如 `<your_gitea.com>/-/admin/actions/runners`。
- 组织级别:组织设置页面,例如 `<your_gitea.com>/<org>/settings/actions/runners`。
- 存储库级别:存储库设置页面,例如 `<your_gitea.com>/<owner>/<repo>/settings/actions/runners`。
@@ -6,10 +6,90 @@ sidebar_position: 25
# 变量
## 环境变量
以下环境变量会在每次工作流运行时自动设置,可以直接访问(例如在 shell 脚本中使用 `$CI`)。
同时列出 `GITEA_*` 和 `GITHUB_*` 名称的变量值相同。
| 名称 | 描述 | 示例 |
|---|---|---|
| `ACT` | 始终为 `true`,除非 Runner 配置了 `runner.set_act_env: false`。 | `true` |
| `CI` | 始终为 `true`。 | `true` |
| `GITEA_ACTION`<br/>`GITHUB_ACTION` | 当前步骤的 `id`,未设置时为步骤的序号。 | `0` |
| `GITEA_ACTION_PATH`<br/>`GITHUB_ACTION_PATH` | Action 所在的路径。仅在复合 Action 中设置。 | `/var/run/act/actions/***` |
| `GITEA_ACTION_REF`<br/>`GITHUB_ACTION_REF` | 正在执行的 Action 的 ref。 | `v4` |
| `GITEA_ACTION_REPOSITORY`<br/>`GITHUB_ACTION_REPOSITORY` | Action 的所有者和仓库名称。 | `actions/checkout` |
| `GITEA_ACTIONS`<br/>`GITHUB_ACTIONS` | 始终为 `true`。`GITEA_ACTIONS` 可用于区分 Gitea Actions 和其他 CI 系统。 | `true` |
| `GITEA_ACTIONS_RUNNER_VERSION` | 执行工作流的 Runner 的版本。 | `v5.2.4` |
| `GITEA_ACTOR`<br/>`GITHUB_ACTOR` | 触发工作流的用户的用户名。 | `alice` |
| `GITEA_ACTOR_ID`<br/>`GITHUB_ACTOR_ID` | 触发工作流的用户的 ID。 | `1` |
| `GITEA_API_URL`<br/>`GITHUB_API_URL` | REST API 的 URL。 | `https://gitea.example.com/api/v1` |
| `GITEA_BASE_REF`<br/>`GITHUB_BASE_REF` | 拉取请求的目标分支。 | `main` |
| `GITEA_DOCKER_WORKSPACE` | Docker 守护进程所见的工作区路径,用于绑定挂载到 Job 启动的容器中,例如在 compose 文件中使用 `${GITEA_DOCKER_WORKSPACE:-.}/data:/app/data`。仅在 Job 运行于容器中时设置。 | `/var/lib/docker/volumes/GITEA-ACTIONS-TASK-42-***/_data` |
| `GITEA_ENV`<br/>`GITHUB_ENV` | 为后续步骤设置环境变量的文件路径。 | `/var/run/act/workflow/envs.txt` |
| `GITEA_EVENT_NAME`<br/>`GITHUB_EVENT_NAME` | 触发工作流的事件名称。 | `push` |
| `GITEA_EVENT_PATH`<br/>`GITHUB_EVENT_PATH` | 包含事件 Webhook 负载的文件路径。 | `/var/run/act/workflow/event.json` |
| `GITEA_GRAPHQL_URL`<br/>`GITHUB_GRAPHQL_URL` | 在 Gitea 中为空(不支持 GraphQL)。 | _(空)_ |
| `GITEA_HEAD_REF`<br/>`GITHUB_HEAD_REF` | 拉取请求的源分支。 | `feature-branch` |
| `GITEA_JOB`<br/>`GITHUB_JOB` | 当前 Job 的 `job_id`。 | `build` |
| `GITEA_OUTPUT`<br/>`GITHUB_OUTPUT` | 设置步骤输出参数的文件路径。 | `/var/run/act/workflow/outputcmd.txt` |
| `GITEA_PATH`<br/>`GITHUB_PATH` | 为后续步骤添加系统 `PATH` 条目的文件路径。 | `/var/run/act/workflow/pathcmd.txt` |
| `GITEA_REF`<br/>`GITHUB_REF` | 触发工作流的完整 ref。 | `refs/heads/main` |
| `GITEA_REF_NAME`<br/>`GITHUB_REF_NAME` | ref 的短名称。 | `main` |
| `GITEA_REF_PROTECTED`<br/>`GITHUB_REF_PROTECTED` | 如果触发工作流的 ref 配置了分支保护,则为 `true`。 | `true` |
| `GITEA_REF_TYPE`<br/>`GITHUB_REF_TYPE` | ref 的类型:`branch` 或 `tag`。 | `branch` |
| `GITEA_REPOSITORY`<br/>`GITHUB_REPOSITORY` | 所有者和仓库名称。 | `owner/repo` |
| `GITEA_REPOSITORY_ID`<br/>`GITHUB_REPOSITORY_ID` | 仓库的 ID。 | `123` |
| `GITEA_REPOSITORY_OWNER`<br/>`GITHUB_REPOSITORY_OWNER` | 仓库所有者的用户名。 | `owner` |
| `GITEA_REPOSITORY_OWNER_ID`<br/>`GITHUB_REPOSITORY_OWNER_ID` | 仓库所有者的 ID。 | `45` |
| `GITEA_RETENTION_DAYS`<br/>`GITHUB_RETENTION_DAYS` | Artifact 的保留天数,永久保留时为空。 | `90` |
| `GITEA_RUN_ATTEMPT`<br/>`GITHUB_RUN_ATTEMPT` | 工作流运行的尝试次数。 | `1` |
| `GITEA_RUN_ID`<br/>`GITHUB_RUN_ID` | 每次工作流运行的唯一编号。 | `1234` |
| `GITEA_RUN_NUMBER`<br/>`GITHUB_RUN_NUMBER` | 特定工作流每次运行的唯一编号。 | `42` |
| `GITEA_SERVER_URL`<br/>`GITHUB_SERVER_URL` | Gitea 实例的 URL。 | `https://gitea.example.com` |
| `GITEA_SHA`<br/>`GITHUB_SHA` | 触发工作流的提交 SHA。 | `a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2` |
| `GITEA_STATE`<br/>`GITHUB_STATE` | 设置步骤状态变量的文件路径。 | `/var/run/act/workflow/statecmd.txt` |
| `GITEA_STEP_SUMMARY`<br/>`GITHUB_STEP_SUMMARY` | 写入 Job 摘要的文件路径。 | `/var/run/act/workflow/step-summary-0.md` |
| `GITEA_TRIGGERING_ACTOR`<br/>`GITHUB_TRIGGERING_ACTOR` | 发起工作流运行的用户的用户名。重新运行时可能与 `GITEA_ACTOR` 不同。 | `alice` |
| `GITEA_WORKFLOW`<br/>`GITHUB_WORKFLOW` | 工作流的名称。 | `CI` |
| `GITEA_WORKFLOW_SHA`<br/>`GITHUB_WORKFLOW_SHA` | 工作流文件的提交 SHA。 | `a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2` |
| `GITEA_WORKSPACE`<br/>`GITHUB_WORKSPACE` | Runner 上的默认工作目录。 | `/workspace/owner/repo` |
| `TRACEPARENT` | Job span 的 W3C 追踪上下文,供已接入追踪的工具将 span 添加到 Job 的追踪中。仅在 Runner 导出追踪数据时设置,参见[监控](/runner/monitoring/#opentelemetry)。 | `00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01` |
| `TRACESTATE` | 随 `TRACEPARENT` 附带的厂商特定追踪数据。仅在追踪包含此类数据时设置。 | `rojo=00f067aa0ba902b7` |
### Runner 环境变量
| 名称 | 描述 | 示例 |
|---|---|---|
| `ImageOS` | Job 镜像的操作系统和版本,供 `setup-*` Action 读取。 | `ubuntu24` |
| `RUNNER_ARCH` | Runner 的架构。 | `X64` |
| `RUNNER_DEBUG` | 当 `ACTIONS_STEP_DEBUG` 密钥为 `true` 时设置为 `1`,否则不设置。 | `1` |
| `RUNNER_ENVIRONMENT` | 始终为 `self-hosted`。 | `self-hosted` |
| `RUNNER_NAME` | 执行 Job 的 Runner 的名称。 | `my-runner` |
| `RUNNER_OS` | Runner 的操作系统。 | `Linux` |
| `RUNNER_TEMP` | Runner 上临时目录的路径。 | `/tmp` |
| `RUNNER_TOOL_CACHE` | Runner 上工具缓存目录的路径。 | `/opt/hostedtoolcache` |
| `RUNNER_WORKSPACE` | `GITEA_WORKSPACE` 的父目录。 | `/workspace/owner` |
### 内部环境变量
这些变量供 Runner 和 Action 内部使用,工作流中通常不需要直接使用。
| 名称 | 描述 | 示例 |
|---|---|---|
| `ACTIONS_CACHE_URL` | Actions 缓存服务的 URL。 | `http://192.168.1.10:8088/` |
| `ACTIONS_ID_TOKEN_REQUEST_TOKEN` | 用于 OIDC 请求的令牌。仅在授予 `id-token: write` 时设置。 | `***` |
| `ACTIONS_ID_TOKEN_REQUEST_URL` | 请求 OIDC 令牌的 URL。仅在授予 `id-token: write` 时设置。 | `https://gitea.example.com/api/actions/oidc/token?` |
| `ACTIONS_RESULTS_URL` | 存储 Artifact 的 URL。 | `https://gitea.example.com` |
| `ACTIONS_RUNTIME_TOKEN` | Actions 流水线 API 的认证令牌。 | `***` |
| `ACTIONS_RUNTIME_URL` | Gitea Actions 流水线 API 的 URL。 | `https://gitea.example.com/api/actions_pipeline/` |
## 用户定义的变量
您可以创建用户、组织和仓库级别的变量。变量的级别取决于创建它的位置。当创建变量时,变量的名称会被
转换为大写,在yaml文件中引用时需要使用大写。
## 命名规则
### 命名规则
以下规则适用于变量名:
@@ -20,11 +100,11 @@ sidebar_position: 25
- 变量名称在创建它们的级别上必须是唯一的。
- 变量名称不能为 `CI`。
## 使用
### 使用
创建配置变量后,它们将自动填充到 `vars` 上下文中。您可以在工作流中使用类似 `${{ vars.VARIABLE_NAME }}` 这样的表达式来使用它们。
## 优先级
### 优先级
如果同名变量存在于多个级别,则级别最低的变量优先。
仓库级别的变量总是比组织或者用户级别的变量优先被选中。
@@ -0,0 +1,9 @@
{
"label": "项目",
"position": 60,
"link": {
"type": "generated-index",
"slug": "/usage/projects",
"description": "使用项目看板规划和跟踪工作"
}
}
@@ -0,0 +1,82 @@
---
date: "2025-11-25T18:58:22Z"
slug: "project-workflows"
sidebar_position: 1
description: "通过事件驱动的过滤器和动作自动化项目看板。"
---
# 项目工作流
项目工作流可自动化项目看板。当项目中的问题或合并请求触发事件时,该事件下每个已启用的工作流都会检查其过滤条件并执行相应动作。工作流在 Gitea 中运行,无需运行器。
## 编辑工作流
1. 打开项目并选择 **Workflows**。
2. 在侧边栏中选择事件。
3. 设置过滤器和动作,然后选择 **Save**。要修改现有工作流,请先选择 **Edit**。
一个事件可以有多个工作流。**Clone** 会复制选中的工作流,便于调整后用于其他条目。**Disable** 会暂停工作流并保留其配置。**Remove** 会删除工作流。
## 事件
| 事件 | 触发时机 | 过滤器 | 动作 |
| --- | --- | --- | --- |
| Item added to project | 条目被添加到项目或在项目中创建时 | Apply to、标签 | Move to column、标签、问题状态 |
| Item removed from project | 条目被移出项目时 | Apply to、标签 | 标签、问题状态 |
| Item reopened | 条目被重新打开时 | Apply to、标签 | Move to column、标签 |
| Item closed | 条目被关闭时 | Apply to、标签 | Move to column、标签 |
| Item column changed | 条目移至另一列时 | Apply to、列、标签 | 标签、问题状态 |
| Code changes requested | 正式审查者要求修改合并请求时 | 标签 | Move to column、标签 |
| Code review approved | 正式审查者批准合并请求时 | 标签 | Move to column、标签 |
| Pull request merged | 合并请求被合并时 | 标签 | Move to column、标签 |
## 过滤器
未设置过滤器的工作流会对每个条目运行。
- **Apply to**:问题、合并请求或两者。
- **When moved from column**、**When moved to column**:条目离开或进入的列。
- **Only if it has labels**:条目必须包含所有选中的标签。
## 动作
工作流至少需要一个动作。动作按以下顺序执行:
1. **Move to column**:将条目移至某一列。
2. **Add labels**、**Remove labels**:更改条目的标签。
3. **Issue state**:关闭或重新打开条目。工作流不会重新打开合并请求。
工作流所做的更改在时间线中显示为“Project workflow”及其后的事件名称,且不会触发其他工作流。工作流不会在已关闭的项目中运行,也不会处理已归档仓库中的条目。
## 权限
任何拥有项目读取权限的人都可以查看其工作流。管理工作流需要项目的写入权限,并满足以下条件:
- 仓库项目:拥有该仓库的问题和合并请求的写入权限。
- 组织项目:是组织所有者或站点管理员。
- 用户项目:是项目所有者。
仅限公开仓库的访问令牌无法管理组织或用户项目的工作流。无论谁触发事件,工作流都会执行其动作。
## API
可通过仓库(`/repos/{owner}/{repo}/projects/{id}`)、组织(`/orgs/{org}/projects/{id}`)或已认证用户(`/user/projects/{id}`)的项目路由管理工作流:
| 方法 | 路径 | 用途 |
| --- | --- | --- |
| `GET` | `.../workflows` | 列出工作流 |
| `GET` | `.../workflows/{workflow_id}` | 获取工作流 |
| `POST` | `.../workflows` | 创建工作流 |
| `PATCH` | `.../workflows/{workflow_id}` | 编辑工作流,未提供的字段保持不变 |
| `DELETE` | `.../workflows/{workflow_id}` | 删除工作流 |
工作流包含 `event`(`item_added_to_project`、`item_removed_from_project`、`item_reopened`、`item_closed`、`item_column_changed`、`code_changes_requested`、`code_review_approved` 或 `pull_request_merged`)、`enabled` 标志、`filters`(`issue_type`、`source_column_id`、`target_column_id`、`label_ids`)以及 `actions`(`column_id`、`add_label_ids`、`remove_label_ids`、`issue_state`)。每个事件仅接受[事件](#事件)表中列出的过滤器和动作。
## 示例
要将新的缺陷报告分流至待分类列,请为 **Item added to project** 创建工作流:
- **Apply to**:Issues only
- **Only if it has labels**:`bug`
- **Move to column**:Triage
- **Add labels**:`needs-triage`
@@ -81,7 +81,7 @@ cd gitea-dump-1610949662
mv app.ini /etc/gitea/conf/app.ini
mv data/* /var/lib/gitea/data/
mv log/* /var/lib/gitea/log/
mv repos/* /var/lib/gitea/gitea-repositories/
mv repos/* /var/lib/gitea/data/gitea-repositories/
chown -R gitea:gitea /etc/gitea/conf/app.ini /var/lib/gitea
# mysql
@@ -115,7 +115,7 @@ cd gitea-dump-1610949662
# 恢复 Gitea 数据
mv data/* /data/gitea
# 恢复仓库本身
mv repos/* /data/git/gitea-repositories/
mv repos/* /data/git/repositories/
# 调整文件权限
chown -R git:git /data
# 重新生成 Git 钩子
@@ -139,7 +139,7 @@ mv data/conf/app.ini /etc/gitea/app.ini
# 恢复 Gitea 数据
mv data/* /var/lib/gitea
# 恢复仓库本身
mv repos/* /var/lib/gitea/git/gitea-repositories
mv repos/* /var/lib/gitea/git/repositories
# 调整文件权限
chown -R git:git /etc/gitea/app.ini /var/lib/gitea
# 重新生成 Git 钩子
@@ -80,7 +80,7 @@ cd gitea-dump-1610949662
mv app.ini /etc/gitea/conf/app.ini
mv data/* /var/lib/gitea/data/
mv log/* /var/lib/gitea/log/
mv repos/* /var/lib/gitea/gitea-repositories/
mv repos/* /var/lib/gitea/data/gitea-repositories/
chown -R gitea:gitea /etc/gitea/conf/app.ini /var/lib/gitea
# mysql
@@ -114,7 +114,7 @@ cd gitea-dump-1610949662
# 恢复 Gitea 数据
mv data/* /data/gitea
# 恢复仓库本身
mv repos/* /data/git/gitea-repositories/
mv repos/* /data/git/repositories/
# 调整文件权限
chown -R git:git /data
# 重新生成 Git 钩子
@@ -138,7 +138,7 @@ mv data/conf/app.ini /etc/gitea/app.ini
# 恢复 Gitea 数据
mv data/* /var/lib/gitea
# 恢复仓库本身
mv repos/* /var/lib/gitea/git/gitea-repositories
mv repos/* /var/lib/gitea/git/repositories
# 调整文件权限
chown -R git:git /etc/gitea/app.ini /var/lib/gitea
# 重新生成 Git 钩子
@@ -80,7 +80,7 @@ cd gitea-dump-1610949662
mv app.ini /etc/gitea/conf/app.ini
mv data/* /var/lib/gitea/data/
mv log/* /var/lib/gitea/log/
mv repos/* /var/lib/gitea/gitea-repositories/
mv repos/* /var/lib/gitea/data/gitea-repositories/
chown -R gitea:gitea /etc/gitea/conf/app.ini /var/lib/gitea
# mysql
@@ -114,7 +114,7 @@ cd gitea-dump-1610949662
# 恢复 Gitea 数据
mv data/* /data/gitea
# 恢复仓库本身
mv repos/* /data/git/gitea-repositories/
mv repos/* /data/git/repositories/
# 调整文件权限
chown -R git:git /data
# 重新生成 Git 钩子
@@ -138,7 +138,7 @@ mv data/conf/app.ini /etc/gitea/app.ini
# 恢复 Gitea 数据
mv data/* /var/lib/gitea
# 恢复仓库本身
mv repos/* /var/lib/gitea/git/gitea-repositories
mv repos/* /var/lib/gitea/git/repositories
# 调整文件权限
chown -R git:git /etc/gitea/app.ini /var/lib/gitea
# 重新生成 Git 钩子
@@ -80,7 +80,7 @@ cd gitea-dump-1610949662
mv app.ini /etc/gitea/conf/app.ini
mv data/* /var/lib/gitea/data/
mv log/* /var/lib/gitea/log/
mv repos/* /var/lib/gitea/repositories/
mv repos/* /var/lib/gitea/data/gitea-repositories/
chown -R gitea:gitea /etc/gitea/conf/app.ini /var/lib/gitea
# mysql
@@ -80,7 +80,7 @@ cd gitea-dump-1610949662
mv app.ini /etc/gitea/conf/app.ini
mv data/* /var/lib/gitea/data/
mv log/* /var/lib/gitea/log/
mv repos/* /var/lib/gitea/repositories/
mv repos/* /var/lib/gitea/data/gitea-repositories/
chown -R gitea:gitea /etc/gitea/conf/app.ini /var/lib/gitea
# mysql
@@ -80,7 +80,7 @@ cd gitea-dump-1610949662
mv app.ini /etc/gitea/conf/app.ini
mv data/* /var/lib/gitea/data/
mv log/* /var/lib/gitea/log/
mv repos/* /var/lib/gitea/repositories/
mv repos/* /var/lib/gitea/data/gitea-repositories/
chown -R gitea:gitea /etc/gitea/conf/app.ini /var/lib/gitea
# mysql
@@ -80,7 +80,7 @@ cd gitea-dump-1610949662
mv app.ini /etc/gitea/conf/app.ini
mv data/* /var/lib/gitea/data/
mv log/* /var/lib/gitea/log/
mv repos/* /var/lib/gitea/repositories/
mv repos/* /var/lib/gitea/data/gitea-repositories/
chown -R gitea:gitea /etc/gitea/conf/app.ini /var/lib/gitea
# mysql
@@ -8,7 +8,7 @@ aliases:
# 数据库准备
在使用 Gitea 前,您需要准备一个数据库。Gitea 支持 PostgreSQL(>= 12)、MySQL(>= 8.0)、MariaDB(>= 10.4)、SQLite(内置) 和 MSSQL(>= 2012 SP4)这几种数据库。本页将指导您准备数据库。由于 PostgreSQL 和 MySQL 在生产环境中被广泛使用,因此本文档将仅涵盖这两种数据库。如果您计划使用 SQLite,则可以忽略本章内容。
在使用 Gitea 前,您需要准备一个数据库。Gitea 支持 PostgreSQL(>= 12)、MySQL(>= 8.0)、MariaDB(>= 10.4)、SQLite(内置) 和 MSSQL(>= 2012 SP4)这几种数据库。本页将指导您准备数据库。本文档涵盖 PostgreSQL、MySQL 和 MSSQL。如果您计划使用 SQLite,则可以忽略本章内容。
如果您使用不受支持的数据库版本,请通过 [联系我们](/help/support) 以获取有关我们的扩展支持的信息。我们可以为旧数据库提供测试和支持,并将这些修复集成到 Gitea 代码库中。
@@ -168,6 +168,28 @@ aliases:
您应该会被提示输入数据库用户的密码,并连接到数据库。
## MSSQL
1. 以管理员身份创建登录名、使用大小写敏感排序规则的数据库及其用户。请将密码 `'gitea'` 替换为安全的密码:
```sql
CREATE LOGIN gitea WITH PASSWORD = 'gitea';
CREATE DATABASE giteadb COLLATE Latin1_General_CS_AS;
GO
USE giteadb;
CREATE USER gitea FOR LOGIN gitea;
ALTER ROLE db_owner ADD MEMBER gitea;
GO
```
升级会更改数据库结构,因此该用户需要 `db_owner`,或者 `db_ddladmin`、`db_datareader` 和 `db_datawriter`。使用后者时,还需以管理员身份执行 `ALTER DATABASE giteadb SET READ_COMMITTED_SNAPSHOT ON;`。
2. 在您的 Gitea 服务器上,测试与数据库的连接:
```sh
sqlcmd -S 203.0.113.3 -U gitea -d giteadb
```
## 使用 TLS 进行数据库连接
如果 Gitea 和您的数据库实例之间的通信是通过私有网络进行的,或者如果 Gitea 和数据库运行在同一台服务器上,那么可以省略本节,因为 Gitea 和数据库实例之间的安全性不会受到严重威胁。但是,如果数据库实例位于公共网络上,请使用 TLS 对数据库连接进行加密,以防止第三方拦截流量数据。
@@ -40,7 +40,7 @@ Gitea Actions需要[runner](https://gitea.com/gitea/runner) 来运行Job。
在运行Runner之前,您需要使用以下命令将其注册到Gitea实例中:
```bash
./runner register --no-interactive --instance <instance> --token <token>
gitea-runner register --no-interactive --instance <instance> --token <token>
```
需要两个必需的参数:`instance` 和 `token`。
@@ -52,10 +52,10 @@ Runner和Job容器(由Runner启动以执行Job)将连接到此地址。
如果不确定使用哪个地址,通常选择局域网地址即可。
`token` 用于身份验证和标识,例如 `P2U1U0oB4XaRCi8azcngmPCLbRpUGapalhmddh23`。
它只能使用一次,并且不能用于注册多个Runner。
每个令牌都可用于注册多个Runner。通过重置链接替换为新令牌后,旧令牌将失效。
您可以从以下位置获取不同级别的`token`,从而创建出相应级别的`runner`
- 实例级别:管理员设置页面,例如 `<your_gitea.com>/admin/actions/runners`。
- 实例级别:管理员设置页面,例如 `<your_gitea.com>/-/admin/actions/runners`。
- 组织级别:组织设置页面,例如 `<your_gitea.com>/<org>/settings/actions/runners`。
- 存储库级别:存储库设置页面,例如 `<your_gitea.com>/<owner>/<repo>/settings/actions/runners`。
@@ -68,7 +68,7 @@ Runner和Job容器(由Runner启动以执行Job)将连接到此地址。
最后,是时候启动Runner了:
```bash
./runner daemon
gitea-runner daemon
```
您可以在管理页面上看到新的Runner:
@@ -28,7 +28,7 @@ aliases:
Runner级别决定了从哪里获取注册令牌。
- 实例级别:管理员设置页面,例如 `<your_gitea.com>/admin/actions/runners`。
- 实例级别:管理员设置页面,例如 `<your_gitea.com>/-/admin/actions/runners`。
- 组织级别:组织设置页面,例如 `<your_gitea.com>/<org>/settings/actions/runners`。
- 存储库级别:存储库设置页面,例如 `<your_gitea.com>/<owner>/<repo>/settings/actions/runners`。
@@ -80,7 +80,7 @@ cd gitea-dump-1610949662
mv app.ini /etc/gitea/conf/app.ini
mv data/* /var/lib/gitea/data/
mv log/* /var/lib/gitea/log/
mv repos/* /var/lib/gitea/repositories/
mv repos/* /var/lib/gitea/data/gitea-repositories/
chown -R gitea:gitea /etc/gitea/conf/app.ini /var/lib/gitea
# mysql
@@ -142,7 +142,7 @@ aliases:
- `POPULATE_SQUASH_COMMENT_WITH_COMMIT_MESSAGES`: **false**: 在預設的 squash 合併消息中,包括構成拉取請求的所有提交的提交消息。
- `ADD_CO_COMMITTER_TRAILERS`: **true**: 如果提交者與作者不匹配,在合併提交消息中添加`co-authored-by`和`co-committed-by`標記。
- `RETARGET_CHILDREN_ON_MERGE`: **true**: 當父合併請求被合併時,將子合併請求重新定向到父合併請求的目標分支。僅適用於頭分支和基分支指向同一儲存庫的已合併合併請求。
- `DEFAULT_DELETE_BRANCH_AFTER_MERGE`: **false**: 為新儲存庫設定"合併後預設刪除合併請求分支"的預設值。
- `DEFAULT_DELETE_BRANCH_AFTER_MERGE`: **true**: 為新儲存庫設定"合併後預設刪除合併請求分支"的預設值。
### 儲存庫 - 工單 (`repository.issue`)
@@ -234,6 +234,7 @@ aliases:
- `REACTIONS`: 使用者可以在問題(Issue)、Pull Request(PR)以及評論中選擇的所有可選的反應。
這些值可以是表情符號別名(例如::smile:)或 Unicode 表情符號。
對於自訂的反應,在 public/assets/img/emoji/ 目錄下添加一個緊密裁剪的正方形圖像,文件名為 reaction_name.png。
- `RELEASE_REACTIONS`: **+1, laugh, hooray, heart, rocket, eyes**: 版本發佈中可用的反應。取值方式與 `REACTIONS` 相同。
- `REACTION_MAX_USER_NUM`: **10**: 更改在反應工具提示中顯示的使用者數量(鼠標懸停時觸發)。
- `CUSTOM_EMOJIS`: **gitea, codeberg, gitlab, git, github, gogs**: 不在 utf8 標準中定義的額外表情符號。
預設情況下,我們支援 Gitea 表情符號(:gitea:)。要添加更多表情符號,請將它們複製到 public/assets/img/emoji/ 目錄下,
@@ -528,7 +529,8 @@ Gitea 建立以下非唯一隊列:
- `REVERSE_PROXY_AUTHENTICATION_USER`: **X-WEBAUTH-USER**: 反向代理認證的 HTTP 頭部名稱,用於提供使用者資訊。
- `REVERSE_PROXY_AUTHENTICATION_EMAIL`: **X-WEBAUTH-EMAIL**: 反向代理認證的 HTTP 頭部名稱,用於提供郵箱資訊。
- `REVERSE_PROXY_AUTHENTICATION_FULL_NAME`: **X-WEBAUTH-FULLNAME**: 反向代理認證的 HTTP 頭部名稱,用於提供全名資訊。
- `REVERSE_PROXY_LIMIT`: **1**: 解釋 X-Forwarded-For 標頭或 X-Real-IP 標頭,並將其設定為請求的遠程 IP。
- `REVERSE_PROXY_LIMIT`: **1**: 依序解釋 X-Real-IP、X-Forwarded-For 或 Forwarded 標頭,並將其設定為請求的遠程 IP。
客戶端連接埠取自標頭值,或在使用 X-Real-IP 時取自 X-Real-Port。
可信代理計數。設定為零以不使用這些標頭。
- `REVERSE_PROXY_TRUSTED_PROXIES`: **127.0.0.0/8,::1/128**: 逗號分隔的受信任代理伺服器的 IP 地址和網路列表。使用 `*` 來信任全部。
- `DISABLE_GIT_HOOKS`: **true**: 設定為 `false` 以允許具有 Git 鉤子權限的使用者建立自訂 Git 鉤子。
@@ -1200,7 +1202,7 @@ In-Reply-To =
- `JWT_SECRET_URI`:**_empty_**: 可以使用此設定選項,而不是在設定中定義`JWT_SECRET`,以向 Gitea 提供包含密鑰的文件的路徑(範例值:`file:/etc/gitea/oauth2_jwt_secret`)。
- `JWT_SIGNING_PRIVATE_KEY_FILE`:**jwt/private.pem**: 用於簽署 OAuth2 令牌的私鑰文件路徑。路徑相對於`APP_DATA_PATH`。僅當`JWT_SIGNING_ALGORITHM`設定為`RS256`,`RS384`,`RS512`,`ES256`,`ES384`或`ES512`時才需要此設定。文件必須包含 PKCS8 格式的 RSA 或 ECDSA 私鑰。如果不存在密鑰,則將為您建立一個 4096 位密鑰。
- `MAX_TOKEN_LENGTH`:**32767**: 從 OAuth2 提供者接受的令牌/cookie 的最大長度。
- `DEFAULT_APPLICATIONS`:**git-credential-oauth,git-credential-manager, tea**: 在啟動時預註冊用於某些服務的 OAuth 應用程式。有關可用選項列表,請參閱[OAuth2 文件](/development/oauth2-provider.md)。
- `DEFAULT_APPLICATIONS`:**git-credential-oauth, git-credential-manager, gitea-cli, gitea-app, gitea-vscode**: 在啟動時預註冊用於某些服務的 OAuth 應用程式。有關可用選項列表,請參閱[OAuth2 文件](/development/oauth2-provider.md)。
## i18n (`i18n`)
@@ -8,7 +8,7 @@ aliases:
# 資料庫準備
在使用 Gitea 前,您需要準備一個資料庫。Gitea 支援 PostgreSQL(>= 12)、MySQL(>= 8.0)、MariaDB(>= 10.4)、SQLite(內置) 和 MSSQL(>= 2012 SP4)這幾種資料庫。本頁將指導您準備資料庫。由於 PostgreSQL 和 MySQL 在生產環境中被廣泛使用,因此本文件將僅涵蓋這兩種資料庫。如果您計劃使用 SQLite,則可以忽略本章內容。
在使用 Gitea 前,您需要準備一個資料庫。Gitea 支援 PostgreSQL(>= 12)、MySQL(>= 8.0)、MariaDB(>= 10.4)、SQLite(內置) 和 MSSQL(>= 2012 SP4)這幾種資料庫。本頁將指導您準備資料庫。本文件涵蓋 PostgreSQL、MySQL 和 MSSQL。如果您計劃使用 SQLite,則可以忽略本章內容。
如果您使用不受支援的資料庫版本,請通過 [聯繫我們](/help/support) 以獲取有關我們的擴展支援的資訊。我們可以為舊資料庫提供測試和支援,並將這些修復整合到 Gitea 程式碼庫中。
@@ -168,6 +168,28 @@ aliases:
您應該會被提示輸入資料庫使用者的密碼,並連接到資料庫。
## MSSQL
1. 以管理員身份建立登入名稱、使用大小寫敏感排序規則的資料庫及其使用者。請將密碼 `'gitea'` 替換為安全的密碼:
```sql
CREATE LOGIN gitea WITH PASSWORD = 'gitea';
CREATE DATABASE giteadb COLLATE Latin1_General_CS_AS;
GO
USE giteadb;
CREATE USER gitea FOR LOGIN gitea;
ALTER ROLE db_owner ADD MEMBER gitea;
GO
```
升級會更改資料庫結構,因此該使用者需要 `db_owner`,或者 `db_ddladmin`、`db_datareader` 和 `db_datawriter`。使用後者時,還需以管理員身份執行 `ALTER DATABASE giteadb SET READ_COMMITTED_SNAPSHOT ON;`。
2. 在您的 Gitea 伺服器上,測試與資料庫的連接:
```sh
sqlcmd -S 203.0.113.3 -U gitea -d giteadb
```
## 使用 TLS 進行資料庫連接
如果 Gitea 和您的資料庫實例之間的通信是通過私有網路進行的,或者如果 Gitea 和資料庫運行在同一臺伺服器上,那麼可以省略本節,因為 Gitea 和資料庫實例之間的安全性不會受到嚴重威脅。但是,如果資料庫實例位於公共網路上,請使用 TLS 對資料庫連接進行加密,以防止第三方攔截流量資料。
@@ -40,7 +40,7 @@ Gitea Actions需要[runner](https://gitea.com/gitea/runner) 來運行Job。
在運行Runner之前,您需要使用以下命令將其註冊到Gitea實例中:
```bash
./runner register --no-interactive --instance <instance> --token <token>
gitea-runner register --no-interactive --instance <instance> --token <token>
```
需要兩個必需的參數:`instance` 和 `token`。
@@ -52,10 +52,10 @@ Runner和Job容器(由Runner啟動以執行Job)將連接到此地址。
如果不確定使用哪個地址,通常選擇局域網地址即可。
`token` 用於身份驗證和標識,例如 `P2U1U0oB4XaRCi8azcngmPCLbRpUGapalhmddh23`。
它只能使用一次,並且不能用於註冊多個Runner。
每個令牌都可用來註冊多個Runner。透過重設連結換成新令牌後,舊令牌就會失效。
您可以從以下位置獲取不同級別的`token`,從而建立出相應級別的`runner`
- 實例級別:管理員設定頁面,例如 `<your_gitea.com>/admin/actions/runners`。
- 實例級別:管理員設定頁面,例如 `<your_gitea.com>/-/admin/actions/runners`。
- 組織級別:組織設定頁面,例如 `<your_gitea.com>/<org>/settings/actions/runners`。
- 儲存庫級別:儲存庫設定頁面,例如 `<your_gitea.com>/<owner>/<repo>/settings/actions/runners`。
@@ -68,7 +68,7 @@ Runner和Job容器(由Runner啟動以執行Job)將連接到此地址。
最後,是時候啟動Runner了:
```bash
./runner daemon
gitea-runner daemon
```
您可以在管理頁面上看到新的Runner:
@@ -28,7 +28,7 @@ aliases:
Runner級別決定了從哪裡獲取註冊令牌。
- 實例級別:管理員設定頁面,例如 `<your_gitea.com>/admin/actions/runners`。
- 實例級別:管理員設定頁面,例如 `<your_gitea.com>/-/admin/actions/runners`。
- 組織級別:組織設定頁面,例如 `<your_gitea.com>/<org>/settings/actions/runners`。
- 儲存庫級別:儲存庫設定頁面,例如 `<your_gitea.com>/<owner>/<repo>/settings/actions/runners`。
@@ -6,10 +6,90 @@ sidebar_position: 25
# 變量
## 環境變量
以下環境變量會在每次工作流執行時自動設定,可以直接存取(例如在 shell 腳本中使用 `$CI`)。
同時列出 `GITEA_*` 和 `GITHUB_*` 名稱的變量值相同。
| 名稱 | 描述 | 範例 |
|---|---|---|
| `ACT` | 始終為 `true`,除非 Runner 設定了 `runner.set_act_env: false`。 | `true` |
| `CI` | 始終為 `true`。 | `true` |
| `GITEA_ACTION`<br/>`GITHUB_ACTION` | 目前步驟的 `id`,未設定時為步驟的序號。 | `0` |
| `GITEA_ACTION_PATH`<br/>`GITHUB_ACTION_PATH` | Action 所在的路徑。僅在複合 Action 中設定。 | `/var/run/act/actions/***` |
| `GITEA_ACTION_REF`<br/>`GITHUB_ACTION_REF` | 正在執行的 Action 的 ref。 | `v4` |
| `GITEA_ACTION_REPOSITORY`<br/>`GITHUB_ACTION_REPOSITORY` | Action 的所有者和儲存庫名稱。 | `actions/checkout` |
| `GITEA_ACTIONS`<br/>`GITHUB_ACTIONS` | 始終為 `true`。`GITEA_ACTIONS` 可用於區分 Gitea Actions 和其他 CI 系統。 | `true` |
| `GITEA_ACTIONS_RUNNER_VERSION` | 執行工作流的 Runner 的版本。 | `v5.2.4` |
| `GITEA_ACTOR`<br/>`GITHUB_ACTOR` | 觸發工作流的使用者的使用者名稱。 | `alice` |
| `GITEA_ACTOR_ID`<br/>`GITHUB_ACTOR_ID` | 觸發工作流的使用者的 ID。 | `1` |
| `GITEA_API_URL`<br/>`GITHUB_API_URL` | REST API 的 URL。 | `https://gitea.example.com/api/v1` |
| `GITEA_BASE_REF`<br/>`GITHUB_BASE_REF` | 拉取請求的目標分支。 | `main` |
| `GITEA_DOCKER_WORKSPACE` | Docker 守護程式所見的工作區路徑,用於綁定掛載到 Job 啟動的容器中,例如在 compose 檔案中使用 `${GITEA_DOCKER_WORKSPACE:-.}/data:/app/data`。僅在 Job 於容器中執行時設定。 | `/var/lib/docker/volumes/GITEA-ACTIONS-TASK-42-***/_data` |
| `GITEA_ENV`<br/>`GITHUB_ENV` | 為後續步驟設定環境變量的檔案路徑。 | `/var/run/act/workflow/envs.txt` |
| `GITEA_EVENT_NAME`<br/>`GITHUB_EVENT_NAME` | 觸發工作流的事件名稱。 | `push` |
| `GITEA_EVENT_PATH`<br/>`GITHUB_EVENT_PATH` | 包含事件 Webhook 負載的檔案路徑。 | `/var/run/act/workflow/event.json` |
| `GITEA_GRAPHQL_URL`<br/>`GITHUB_GRAPHQL_URL` | 在 Gitea 中為空(不支援 GraphQL)。 | _(空)_ |
| `GITEA_HEAD_REF`<br/>`GITHUB_HEAD_REF` | 拉取請求的來源分支。 | `feature-branch` |
| `GITEA_JOB`<br/>`GITHUB_JOB` | 目前 Job 的 `job_id`。 | `build` |
| `GITEA_OUTPUT`<br/>`GITHUB_OUTPUT` | 設定步驟輸出參數的檔案路徑。 | `/var/run/act/workflow/outputcmd.txt` |
| `GITEA_PATH`<br/>`GITHUB_PATH` | 為後續步驟新增系統 `PATH` 項目的檔案路徑。 | `/var/run/act/workflow/pathcmd.txt` |
| `GITEA_REF`<br/>`GITHUB_REF` | 觸發工作流的完整 ref。 | `refs/heads/main` |
| `GITEA_REF_NAME`<br/>`GITHUB_REF_NAME` | ref 的短名稱。 | `main` |
| `GITEA_REF_PROTECTED`<br/>`GITHUB_REF_PROTECTED` | 如果觸發工作流的 ref 設定了分支保護,則為 `true`。 | `true` |
| `GITEA_REF_TYPE`<br/>`GITHUB_REF_TYPE` | ref 的類型:`branch` 或 `tag`。 | `branch` |
| `GITEA_REPOSITORY`<br/>`GITHUB_REPOSITORY` | 所有者和儲存庫名稱。 | `owner/repo` |
| `GITEA_REPOSITORY_ID`<br/>`GITHUB_REPOSITORY_ID` | 儲存庫的 ID。 | `123` |
| `GITEA_REPOSITORY_OWNER`<br/>`GITHUB_REPOSITORY_OWNER` | 儲存庫所有者的使用者名稱。 | `owner` |
| `GITEA_REPOSITORY_OWNER_ID`<br/>`GITHUB_REPOSITORY_OWNER_ID` | 儲存庫所有者的 ID。 | `45` |
| `GITEA_RETENTION_DAYS`<br/>`GITHUB_RETENTION_DAYS` | Artifact 的保留天數,永久保留時為空。 | `90` |
| `GITEA_RUN_ATTEMPT`<br/>`GITHUB_RUN_ATTEMPT` | 工作流執行的嘗試次數。 | `1` |
| `GITEA_RUN_ID`<br/>`GITHUB_RUN_ID` | 每次工作流執行的唯一編號。 | `1234` |
| `GITEA_RUN_NUMBER`<br/>`GITHUB_RUN_NUMBER` | 特定工作流每次執行的唯一編號。 | `42` |
| `GITEA_SERVER_URL`<br/>`GITHUB_SERVER_URL` | Gitea 實例的 URL。 | `https://gitea.example.com` |
| `GITEA_SHA`<br/>`GITHUB_SHA` | 觸發工作流的提交 SHA。 | `a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2` |
| `GITEA_STATE`<br/>`GITHUB_STATE` | 設定步驟狀態變量的檔案路徑。 | `/var/run/act/workflow/statecmd.txt` |
| `GITEA_STEP_SUMMARY`<br/>`GITHUB_STEP_SUMMARY` | 寫入 Job 摘要的檔案路徑。 | `/var/run/act/workflow/step-summary-0.md` |
| `GITEA_TRIGGERING_ACTOR`<br/>`GITHUB_TRIGGERING_ACTOR` | 發起工作流執行的使用者的使用者名稱。重新執行時可能與 `GITEA_ACTOR` 不同。 | `alice` |
| `GITEA_WORKFLOW`<br/>`GITHUB_WORKFLOW` | 工作流的名稱。 | `CI` |
| `GITEA_WORKFLOW_SHA`<br/>`GITHUB_WORKFLOW_SHA` | 工作流檔案的提交 SHA。 | `a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2` |
| `GITEA_WORKSPACE`<br/>`GITHUB_WORKSPACE` | Runner 上的預設工作目錄。 | `/workspace/owner/repo` |
| `TRACEPARENT` | Job span 的 W3C 追蹤上下文,供已接入追蹤的工具將 span 新增到 Job 的追蹤中。僅在 Runner 匯出追蹤資料時設定,參見[監控](/runner/monitoring/#opentelemetry)。 | `00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01` |
| `TRACESTATE` | 隨 `TRACEPARENT` 附帶的廠商特定追蹤資料。僅在追蹤包含此類資料時設定。 | `rojo=00f067aa0ba902b7` |
### Runner 環境變量
| 名稱 | 描述 | 範例 |
|---|---|---|
| `ImageOS` | Job 映像的作業系統和版本,供 `setup-*` Action 讀取。 | `ubuntu24` |
| `RUNNER_ARCH` | Runner 的架構。 | `X64` |
| `RUNNER_DEBUG` | 當 `ACTIONS_STEP_DEBUG` 密鑰為 `true` 時設定為 `1`,否則不設定。 | `1` |
| `RUNNER_ENVIRONMENT` | 始終為 `self-hosted`。 | `self-hosted` |
| `RUNNER_NAME` | 執行 Job 的 Runner 的名稱。 | `my-runner` |
| `RUNNER_OS` | Runner 的作業系統。 | `Linux` |
| `RUNNER_TEMP` | Runner 上暫存目錄的路徑。 | `/tmp` |
| `RUNNER_TOOL_CACHE` | Runner 上工具快取目錄的路徑。 | `/opt/hostedtoolcache` |
| `RUNNER_WORKSPACE` | `GITEA_WORKSPACE` 的上層目錄。 | `/workspace/owner` |
### 內部環境變量
這些變量供 Runner 和 Action 內部使用,工作流中通常不需要直接使用。
| 名稱 | 描述 | 範例 |
|---|---|---|
| `ACTIONS_CACHE_URL` | Actions 快取服務的 URL。 | `http://192.168.1.10:8088/` |
| `ACTIONS_ID_TOKEN_REQUEST_TOKEN` | 用於 OIDC 請求的令牌。僅在授予 `id-token: write` 時設定。 | `***` |
| `ACTIONS_ID_TOKEN_REQUEST_URL` | 請求 OIDC 令牌的 URL。僅在授予 `id-token: write` 時設定。 | `https://gitea.example.com/api/actions/oidc/token?` |
| `ACTIONS_RESULTS_URL` | 儲存 Artifact 的 URL。 | `https://gitea.example.com` |
| `ACTIONS_RUNTIME_TOKEN` | Actions 管線 API 的驗證令牌。 | `***` |
| `ACTIONS_RUNTIME_URL` | Gitea Actions 管線 API 的 URL。 | `https://gitea.example.com/api/actions_pipeline/` |
## 使用者定義的變量
您可以建立使用者、組織和儲存庫級別的變量。變量的級別取決於建立它的位置。當建立變量時,變量的名稱會被
轉換為大寫,在yaml文件中引用時需要使用大寫。
## 命名規則
### 命名規則
以下規則適用於變量名:
@@ -20,11 +100,11 @@ sidebar_position: 25
- 變量名稱在建立它們的級別上必須是唯一的。
- 變量名稱不能為 `CI`。
## 使用
### 使用
建立設定變量後,它們將自動填充到 `vars` 上下文中。您可以在工作流中使用類似 `${{ vars.VARIABLE_NAME }}` 這樣的表達式來使用它們。
## 優先級
### 優先級
如果同名變量存在於多個級別,則級別最低的變量優先。
儲存庫級別的變量總是比組織或者使用者級別的變量優先被選中。
@@ -0,0 +1,9 @@
{
"label": "專案",
"position": 60,
"link": {
"type": "generated-index",
"slug": "/usage/projects",
"description": "使用專案看板規劃與追蹤工作"
}
}
@@ -0,0 +1,82 @@
---
date: "2025-11-25T18:58:22Z"
slug: "project-workflows"
sidebar_position: 1
description: "透過事件驅動的篩選條件與動作,讓專案看板自動化。"
---
# 專案工作流程
專案工作流程可讓專案看板自動化。當專案中的問題或合併請求觸發事件時,該事件的每個已啟用工作流程都會檢查篩選條件並執行動作。工作流程在 Gitea 中執行,不需要 runner。
## 編輯工作流程
1. 開啟專案並選取 **Workflows**。
2. 在側邊欄選取事件。
3. 設定篩選條件與動作,然後選取 **Save**。若要變更現有工作流程,請先選取 **Edit**。
一個事件可以有多個工作流程。**Clone** 會複製選取的工作流程,方便您調整後用於其他項目。**Disable** 會暫停工作流程並保留設定。**Remove** 會刪除工作流程。
## 事件
| 事件 | 執行時機 | 篩選條件 | 動作 |
| --- | --- | --- | --- |
| Item added to project | 項目加入專案或在專案中建立時 | Apply to、標籤 | Move to column、標籤、問題狀態 |
| Item removed from project | 項目從專案移除時 | Apply to、標籤 | 標籤、問題狀態 |
| Item reopened | 項目重新開啟時 | Apply to、標籤 | Move to column、標籤 |
| Item closed | 項目關閉時 | Apply to、標籤 | Move to column、標籤 |
| Item column changed | 項目移至另一欄時 | Apply to、欄、標籤 | 標籤、問題狀態 |
| Code changes requested | 正式審查者要求修改合併請求時 | 標籤 | Move to column、標籤 |
| Code review approved | 正式審查者核准合併請求時 | 標籤 | Move to column、標籤 |
| Pull request merged | 合併請求合併時 | 標籤 | Move to column、標籤 |
## 篩選條件
未設定篩選條件的工作流程會對所有項目執行。
- **Apply to**:問題、合併請求或兩者。
- **When moved from column**、**When moved to column**:項目離開或進入的欄。
- **Only if it has labels**:項目必須具有所有選取的標籤。
## 動作
工作流程至少需要一個動作。動作依下列順序執行:
1. **Move to column**:將項目移至某一欄。
2. **Add labels**、**Remove labels**:變更項目的標籤。
3. **Issue state**:關閉或重新開啟項目。工作流程不會重新開啟合併請求。
工作流程所做的變更會在時間軸中顯示為「Project workflow」加上事件名稱,且不會觸發其他工作流程。專案已關閉或項目位於已封存的儲存庫時,工作流程不會執行。
## 權限
任何具有專案讀取權限的使用者都能檢視其工作流程。管理工作流程需要專案的寫入權限,以及:
- 儲存庫專案:該儲存庫的問題與合併請求的寫入權限。
- 組織專案:組織擁有者或網站管理員身分。
- 使用者專案:專案擁有者身分。
僅限公開儲存庫的存取權杖無法管理組織或使用者專案的工作流程。無論誰觸發事件,工作流程都會執行其動作。
## API
透過儲存庫(`/repos/{owner}/{repo}/projects/{id}`)、組織(`/orgs/{org}/projects/{id}`)或已驗證使用者(`/user/projects/{id}`)的專案路由管理工作流程:
| 方法 | 路徑 | 用途 |
| --- | --- | --- |
| `GET` | `.../workflows` | 列出工作流程 |
| `GET` | `.../workflows/{workflow_id}` | 取得工作流程 |
| `POST` | `.../workflows` | 建立工作流程 |
| `PATCH` | `.../workflows/{workflow_id}` | 編輯工作流程,未提供的欄位保持不變 |
| `DELETE` | `.../workflows/{workflow_id}` | 刪除工作流程 |
工作流程包含 `event`(`item_added_to_project`、`item_removed_from_project`、`item_reopened`、`item_closed`、`item_column_changed`、`code_changes_requested`、`code_review_approved` 或 `pull_request_merged`)、`enabled` 旗標、`filters`(`issue_type`、`source_column_id`、`target_column_id`、`label_ids`)與 `actions`(`column_id`、`add_label_ids`、`remove_label_ids`、`issue_state`)。每個事件僅接受[事件](#事件)中列出的篩選條件與動作。
## 範例
若要將新的錯誤回報送往分類處理,請為 **Item added to project** 建立工作流程:
- **Apply to**:Issues only
- **Only if it has labels**:`bug`
- **Move to column**:Triage
- **Add labels**:`needs-triage`
@@ -81,7 +81,7 @@ cd gitea-dump-1610949662
mv app.ini /etc/gitea/conf/app.ini
mv data/* /var/lib/gitea/data/
mv log/* /var/lib/gitea/log/
mv repos/* /var/lib/gitea/gitea-repositories/
mv repos/* /var/lib/gitea/data/gitea-repositories/
chown -R gitea:gitea /etc/gitea/conf/app.ini /var/lib/gitea
# mysql
@@ -115,7 +115,7 @@ cd gitea-dump-1610949662
# 恢復 Gitea 數據
mv data/* /data/gitea
# 恢復倉庫本身
mv repos/* /data/git/gitea-repositories/
mv repos/* /data/git/repositories/
# 調整文件權限
chown -R git:git /data
# 重新生成 Git 鉤子
@@ -139,7 +139,7 @@ mv data/conf/app.ini /etc/gitea/app.ini
# 恢復 Gitea 數據
mv data/* /var/lib/gitea
# 恢復倉庫本身
mv repos/* /var/lib/gitea/git/gitea-repositories
mv repos/* /var/lib/gitea/git/repositories
# 調整文件權限
chown -R git:git /etc/gitea/app.ini /var/lib/gitea
# 重新生成 Git 鉤子
@@ -115,7 +115,7 @@ cd gitea-dump-1610949662
# 恢復 gitea 數據
mv data/* /data/gitea
# 恢復儲存庫本身
mv repos/* /data/git/gitea-repositories/
mv repos/* /data/git/repositories/
# 調整文件權限
chown -R git:git /data
# 重新生成 Git Hooks
@@ -139,7 +139,7 @@ mv data/conf/app.ini /etc/gitea/app.ini
# 恢復 gitea 數據
mv data/* /var/lib/gitea
# 恢復儲存庫本身
mv repos/* /var/lib/gitea/git/gitea-repositories
mv repos/* /var/lib/gitea/git/repositories
# 調整文件權限
chown -R git:git /etc/gitea/app.ini /var/lib/gitea
# 重新生成 Git Hooks
@@ -80,7 +80,7 @@ cd gitea-dump-1610949662
mv app.ini /etc/gitea/conf/app.ini
mv data/* /var/lib/gitea/data/
mv log/* /var/lib/gitea/log/
mv repos/* /var/lib/gitea/gitea-repositories/
mv repos/* /var/lib/gitea/data/gitea-repositories/
chown -R gitea:gitea /etc/gitea/conf/app.ini /var/lib/gitea
# mysql
@@ -114,7 +114,7 @@ cd gitea-dump-1610949662
# 恢復 Gitea 數據
mv data/* /data/gitea
# 恢復存放庫本身
mv repos/* /data/git/gitea-repositories/
mv repos/* /data/git/repositories/
# 調整文件權限
chown -R git:git /data
# 重新生成 Git 鉤子
@@ -138,7 +138,7 @@ mv data/conf/app.ini /etc/gitea/app.ini
# 恢復 Gitea 數據
mv data/* /var/lib/gitea
# 恢復存放庫本身
mv repos/* /var/lib/gitea/git/gitea-repositories
mv repos/* /var/lib/gitea/git/repositories
# 調整文件權限
chown -R git:git /etc/gitea/app.ini /var/lib/gitea
# 重新生成 Git 鉤子
@@ -80,7 +80,7 @@ cd gitea-dump-1610949662
mv app.ini /etc/gitea/conf/app.ini
mv data/* /var/lib/gitea/data/
mv log/* /var/lib/gitea/log/
mv repos/* /var/lib/gitea/repositories/
mv repos/* /var/lib/gitea/data/gitea-repositories/
chown -R gitea:gitea /etc/gitea/conf/app.ini /var/lib/gitea
# mysql
@@ -80,7 +80,7 @@ cd gitea-dump-1610949662
mv app.ini /etc/gitea/conf/app.ini
mv data/* /var/lib/gitea/data/
mv log/* /var/lib/gitea/log/
mv repos/* /var/lib/gitea/repositories/
mv repos/* /var/lib/gitea/data/gitea-repositories/
chown -R gitea:gitea /etc/gitea/conf/app.ini /var/lib/gitea
# mysql
@@ -80,7 +80,7 @@ cd gitea-dump-1610949662
mv app.ini /etc/gitea/conf/app.ini
mv data/* /var/lib/gitea/data/
mv log/* /var/lib/gitea/log/
mv repos/* /var/lib/gitea/repositories/
mv repos/* /var/lib/gitea/data/gitea-repositories/
chown -R gitea:gitea /etc/gitea/conf/app.ini /var/lib/gitea
# mysql
@@ -80,7 +80,7 @@ cd gitea-dump-1610949662
mv app.ini /etc/gitea/conf/app.ini
mv data/* /var/lib/gitea/data/
mv log/* /var/lib/gitea/log/
mv repos/* /var/lib/gitea/repositories/
mv repos/* /var/lib/gitea/data/gitea-repositories/
chown -R gitea:gitea /etc/gitea/conf/app.ini /var/lib/gitea
# mysql
@@ -8,7 +8,7 @@ aliases:
# 資料庫準備
在使用 Gitea 前,您需要準備一個資料庫。Gitea 支援 PostgreSQL(>= 12)、MySQL(>= 8.0)、MariaDB(>= 10.4)、SQLite(內置) 和 MSSQL(>= 2012 SP4)這幾種資料庫。本頁將指導您準備資料庫。由於 PostgreSQL 和 MySQL 在生產環境中被廣泛使用,因此本文件將僅涵蓋這兩種資料庫。如果您計劃使用 SQLite,則可以忽略本章內容。
在使用 Gitea 前,您需要準備一個資料庫。Gitea 支援 PostgreSQL(>= 12)、MySQL(>= 8.0)、MariaDB(>= 10.4)、SQLite(內置) 和 MSSQL(>= 2012 SP4)這幾種資料庫。本頁將指導您準備資料庫。本文件涵蓋 PostgreSQL、MySQL 和 MSSQL。如果您計劃使用 SQLite,則可以忽略本章內容。
如果您使用不受支援的資料庫版本,請通過 [聯繫我們](/help/support) 以獲取有關我們的擴展支援的資訊。我們可以為舊資料庫提供測試和支援,並將這些修復整合到 Gitea 程式碼庫中。
@@ -168,6 +168,28 @@ aliases:
您應該會被提示輸入資料庫使用者的密碼,並連接到資料庫。
## MSSQL
1. 以管理員身份建立登入名稱、使用大小寫敏感排序規則的資料庫及其使用者。請將密碼 `'gitea'` 替換為安全的密碼:
```sql
CREATE LOGIN gitea WITH PASSWORD = 'gitea';
CREATE DATABASE giteadb COLLATE Latin1_General_CS_AS;
GO
USE giteadb;
CREATE USER gitea FOR LOGIN gitea;
ALTER ROLE db_owner ADD MEMBER gitea;
GO
```
升級會更改資料庫結構,因此該使用者需要 `db_owner`,或者 `db_ddladmin`、`db_datareader` 和 `db_datawriter`。使用後者時,還需以管理員身份執行 `ALTER DATABASE giteadb SET READ_COMMITTED_SNAPSHOT ON;`。
2. 在您的 Gitea 伺服器上,測試與資料庫的連接:
```sh
sqlcmd -S 203.0.113.3 -U gitea -d giteadb
```
## 使用 TLS 進行資料庫連接
如果 Gitea 和您的資料庫實例之間的通信是通過私有網路進行的,或者如果 Gitea 和資料庫運行在同一臺伺服器上,那麼可以省略本節,因為 Gitea 和資料庫實例之間的安全性不會受到嚴重威脅。但是,如果資料庫實例位於公共網路上,請使用 TLS 對資料庫連接進行加密,以防止第三方攔截流量資料。
@@ -40,7 +40,7 @@ Gitea Actions需要[runner](https://gitea.com/gitea/runner) 來運行Job。
在運行Runner之前,您需要使用以下命令將其註冊到Gitea實例中:
```bash
./runner register --no-interactive --instance <instance> --token <token>
gitea-runner register --no-interactive --instance <instance> --token <token>
```
需要兩個必需的參數:`instance` 和 `token`。
@@ -52,10 +52,10 @@ Runner和Job容器(由Runner啟動以執行Job)將連接到此地址。
如果不確定使用哪個地址,通常選擇局域網地址即可。
`token` 用於身份驗證和標識,例如 `P2U1U0oB4XaRCi8azcngmPCLbRpUGapalhmddh23`。
它只能使用一次,並且不能用於註冊多個Runner。
每個令牌都可用來註冊多個Runner。透過重設連結換成新令牌後,舊令牌就會失效。
您可以從以下位置獲取不同級別的`token`,從而建立出相應級別的`runner`
- 實例級別:管理員設定頁面,例如 `<your_gitea.com>/admin/actions/runners`。
- 實例級別:管理員設定頁面,例如 `<your_gitea.com>/-/admin/actions/runners`。
- 組織級別:組織設定頁面,例如 `<your_gitea.com>/<org>/settings/actions/runners`。
- 儲存庫級別:儲存庫設定頁面,例如 `<your_gitea.com>/<owner>/<repo>/settings/actions/runners`。
@@ -68,7 +68,7 @@ Runner和Job容器(由Runner啟動以執行Job)將連接到此地址。
最後,是時候啟動Runner了:
```bash
./runner daemon
gitea-runner daemon
```
您可以在管理頁面上看到新的Runner:
@@ -28,7 +28,7 @@ aliases:
Runner級別決定了從哪裡獲取註冊令牌。
- 實例級別:管理員設定頁面,例如 `<your_gitea.com>/admin/actions/runners`。
- 實例級別:管理員設定頁面,例如 `<your_gitea.com>/-/admin/actions/runners`。
- 組織級別:組織設定頁面,例如 `<your_gitea.com>/<org>/settings/actions/runners`。
- 儲存庫級別:儲存庫設定頁面,例如 `<your_gitea.com>/<owner>/<repo>/settings/actions/runners`。
+1 -1
View File
@@ -13,5 +13,5 @@
"engines": {
"node": ">=22"
},
"packageManager": "[email protected]0.1"
"packageManager": "[email protected]6.1"
}
+49 -7
View File
@@ -8,7 +8,7 @@
export type LocaleId = 'en-us' | 'zh-cn' | 'zh-tw';
export type ProductId = 'docs' | 'api' | 'runner' | 'enterprise';
export type ProductId = 'docs' | 'api' | 'runner' | 'cli' | 'enterprise';
/** Values substituted for `@name@` placeholders in the markdown sources. */
export interface VersionVariables {
@@ -89,11 +89,11 @@ const variables: Record<string, VersionVariables> = {
goVersion: '1.27',
minGoVersion: '1.27',
minNodeVersion: '22',
version: '28.1.0',
sourceVersion: 'v28.1.0',
version: '28.3.0',
sourceVersion: 'v28.3.0',
sourceBranch: 'release/v28',
dockerVersion: '28.1.0',
displayVersion: '28.1.0',
dockerVersion: '28.3.0',
displayVersion: '28.3.0',
},
'1.27': {
goVersion: '1.26',
@@ -213,7 +213,9 @@ const docsProduct: ProductDef = {
* bumped here when a new runner release is documented.
*/
const runnerSeries: { id: string; latestRelease: string }[] = [
{ id: '3', latestRelease: '3.3.0' },
{ id: '5', latestRelease: '5.2.0' },
{ id: '4', latestRelease: '4.1.0' },
{ id: '3', latestRelease: '3.5.0' },
{ id: '2', latestRelease: '2.3.0' },
{ id: '1', latestRelease: '1.0.8' },
{ id: '0', latestRelease: '0.6.1' },
@@ -244,6 +246,40 @@ const runnerProduct: ProductDef = {
],
};
/**
* gt, the Gitea CLI: laid out like the runner. Until the first series is listed
* here the develop docs are served at the product root, without the unreleased
* banner, since there is no release to point to. Listing a series moves them to
* `/cli/develop/`.
*/
const cliSeries: { id: string; latestRelease: string }[] = [];
const cliProduct: ProductDef = {
id: 'cli',
label: 'CLI',
base: 'cli',
locales: ['en-us'],
versions: [
{
id: 'develop',
label: 'develop',
sources: { 'en-us': 'cli-docs' },
...(cliSeries.length > 0
? { path: 'develop', banner: 'unreleased' as const }
: { path: '', latest: true }),
},
...cliSeries.map(
({ id, latestRelease }, index): VersionDef => ({
id,
label: latestRelease,
path: index === 0 ? '' : id,
latest: index === 0,
sources: { 'en-us': `cli-docs_versioned_docs/version-${id}` },
}),
),
],
};
/** API reference, rendered from the swagger documents by starlight-openapi. */
const apiProduct: ProductDef = {
id: 'api',
@@ -299,7 +335,13 @@ const enterpriseProduct: ProductDef = {
externalBaseUrl: 'https://docs.gitea.com/enterprise',
};
export const products: ProductDef[] = [docsProduct, apiProduct, runnerProduct, enterpriseProduct];
export const products: ProductDef[] = [
docsProduct,
apiProduct,
runnerProduct,
cliProduct,
enterpriseProduct,
];
export function getProduct(id: ProductId): ProductDef {
const product = products.find((candidate) => candidate.id === id);
+3 -3
View File
@@ -1,7 +1,7 @@
/**
* Builds one sidebar per (product, version, language) out of the loaded pages,
* deriving it from `_category_.json` and from the
* handwritten sidebar files of the runner docs.
* handwritten sidebar files of the runner and the cli docs.
*/
import { defaultLocale } from './products';
@@ -44,7 +44,7 @@ export interface SidebarGroup {
export type SidebarEntry = SidebarLink | SidebarGroup;
/** A hand written sidebar file, as used by the runner docs. */
/** A hand written sidebar file, as used by the runner and the cli docs. */
export type SidebarSourceItem =
| string
| { type: 'doc'; id: string; label?: string }
@@ -148,7 +148,7 @@ function treeOf(pages: SidebarPage[]): Tree {
return { build, label };
}
/** Sidebar described by a hand written sidebar file, used by the runner docs. */
/** Sidebar described by a hand written sidebar file, used by the runner and the cli docs. */
function fromSidebarSource(
items: SidebarSourceItem[],
pages: SidebarPage[],
+323 -224
View File
File diff suppressed because it is too large. Load diff
+64 -93
View File
@@ -1,107 +1,78 @@
---
sidebar_position: 5
sidebar_position: 6
---
# Caching
Every runner starts its own cache server, so `actions/cache` works without any configuration. Cache entries are local to that runner: two runners do not share a cache unless you make them.
## Cache service v2
`actions/[email protected]` and later can use the *cache service v2* API. The runner serves it from the same store as v1, on by default, and it also works behind a shared cache server. Turn it off with:
```yaml
cache:
v2: false
```
Those actions refuse any host they do not take for GitHub. Rather than misreport the server URL, the runner edits that check out of the action's own JavaScript bundle and keeps the untouched copy beside it; a bundle it does not recognise is left alone and keeps to v1. The same edit lets the stock `actions/upload-artifact` and `actions/download-artifact` work from `v4.4.0` on, without the `gitea-upload-artifact` fork.
Because `ACTIONS_RESULTS_URL` names one origin that has to serve the whole results API, the cache server also forwards the artifact half of it to the Gitea instance the job belongs to. Jobs are therefore pointed at the cache server, which is what makes clients such as `docker buildx` find the cache service instead of receiving a 404 from Gitea.
## Where cache blobs are stored
```yaml
cache:
enabled: true
dir: /var/lib/gitea-runner/cache # default: $HOME/.cache/actcache
```
The directory grows with use; entries are evicted as they expire, so give it a filesystem with room to spare and monitor it like any other build cache.
## Dockerized runners
When the runner itself runs in a container and creates a network per job, the address it detects for its own cache server is often unreachable from the job containers. `actions/cache` then fails with:
```text
Failed to restore: getCacheEntry failed: connect ETIMEDOUT IP:PORT
```
Pin the address and the port the job containers should use, and make that endpoint reachable:
1. take an address of the host that job containers can reach, and a free port on it;
2. configure them:
```yaml
cache:
enabled: true
dir: ""
host: "192.168.8.17"
port: 8088
```
3. publish the port when starting the runner container:
```bash
docker run -d --name gitea-runner -p 8088:8088 ... docker.io/gitea/runner:nightly
```
Putting the runner and the job containers on one shared `container.network` instead works too, and then the auto-detected address is reachable.
By default, each runner runs a cache server for `actions/cache`, so caching works without setup.
The runner stores entries in `cache.dir`, which defaults to `$HOME/.cache/actcache`. It does not share entries with other runners.
## Sharing a cache between runners
Run one dedicated cache server that every runner points at.
Run one `gitea-runner cache-server` and configure each runner to use it. Use the same secret on the cache server and every runner. Generate the secret with `openssl rand -hex 32`.
1. Config for the cache server host:
```yaml
cache:
dir: /data/actcache
port: 8088
external_secret: "replace-with-a-strong-random-secret"
# external_secret_file: /run/secrets/cache-secret # or keep it out of this file
```
2. Start it:
```bash
gitea-runner -c cache-server-config.yaml cache-server
```
3. On every runner:
```yaml
cache:
external_server: "http://cache-host:8088/"
external_secret: "replace-with-a-strong-random-secret" # must match the server
```
The secret authenticates runners against the shared server and must be identical on all of them; generate one with `openssl rand -hex 32`. Setting both `external_secret` and `external_secret_file` is an error.
`cache-server` accepts `--dir`, `--host` and `--port`, which override the corresponding `cache.*` keys. Every other setting, `external_secret` included, has to come from the config file.
### Alternatives
- **Shared filesystem** — mount the same NFS/CIFS share on every runner and point `cache.dir` at it. Simpler, but repositories are less isolated from each other than behind a cache server.
- **Object storage** — mount S3 or MinIO as a FUSE filesystem, e.g. with [s3fs](https://github.com/s3fs-fuse/s3fs-fuse) or [goofys](https://github.com/kahing/goofys), and set `cache.dir` to the mount point.
## Action repository cache
Actions pulled by `uses:` are cached too, and by default refreshed on every job so a moved tag is picked up. To pin them to what has already been fetched:
On the cache server:
```yaml
cache:
offline_mode: true
dir: /data/actcache
port: 8088
external_secret_file: /run/secrets/cache-secret
```
A re-tagged `v6` or an updated branch then stays at the cached commit until its entry expires or is removed. Combined with `runner.action_shallow_clone` (on by default, fetching only the requested ref at depth 1), this keeps job startup fast on runners with limited bandwidth.
```bash
gitea-runner -c cache-server.yaml cache-server
```
On each runner:
```yaml
cache:
external_server: "http://cache-host:8088/"
external_secret_file: /run/secrets/cache-secret
```
Jobs send cache requests to their runner, which forwards them to the shared cache server. Only the runners need to reach the shared cache server. Set `external_secret` to supply the secret inline instead of from a file.
Runners can also share a `cache.dir` directory on a filesystem that supports file locks. This makes cache requests slower.
## S3-compatible storage
Use an S3-compatible bucket such as AWS S3, MinIO or RustFS to share the cache without a separate cache server:
```yaml
cache:
s3:
endpoint: "http://minio:9000"
bucket: "runner-cache"
access_key_file: "/run/secrets/cache-s3-access-key"
secret_key_file: "/run/secrets/cache-s3-secret-key"
```
Runners with the same settings share the cache. An entry saved on one runner becomes available to the others within 30 seconds. The eviction settings below do not apply to the bucket. Add lifecycle rules to expire old entries and abort incomplete multipart uploads.
## Eviction
| Option | Default | Removes |
| --- | --- | --- |
| `cache.retention` | `168h` | Entries not read or written within this time |
| `cache.repo_size_limit` | `10GB` | Least recently used entries when a repository's cache exceeds this size |
| `cache.size_limit` | off | Least recently used entries when the whole cache exceeds this size |
The cache also keeps 1024 MiB free on its volume, or `health_check.min_free_disk_space_mb` when [health checks](monitoring.md#health-checks) are enabled. With a shared cache server, set these options on the server.
## Artifacts
The runner adapts `actions/cache`, `actions/upload-artifact` and `actions/download-artifact` while a job runs so these actions work with Gitea. With these changes, the cache uses the cache service v2 protocol.
Artifact uploads go through the runner's cache server, so jobs must be able to reach it. Set `cache.v2: false` to send artifact uploads directly to Gitea. Set `runner.patch_actions: false` to run every action exactly as published. This uses the cache service v1 protocol and causes the published artifact actions to fail.
## Jobs cannot reach the cache
If `actions/cache` fails with `connect ETIMEDOUT`, jobs cannot reach the runner's cache server.
A runner container connects itself to each job network when it uses the same Docker daemon as its jobs. For jobs on another Docker daemon, for example through a remote `DOCKER_HOST`, set `cache.host` and `cache.port` to an address and port the jobs can reach. Jobs using `container.network: bridge` cannot reach a runner container outside that network. Leave `container.network` empty or use a network that includes the runner container.
## Action cache
For each job, the runner checks action tags and branches for updates, so it picks up moved tags such as `v6`. With `cache.offline_mode: true`, the runner reuses its downloaded copy. This speeds up jobs on slow networks, but moved tags and updated branches stay at the cached commit.
+41 -118
View File
@@ -4,135 +4,58 @@ sidebar_position: 3
# Configuration
The runner is configured with a single YAML file. It is optional: without one, the built-in defaults apply, which are the same as an empty YAML document and safe to run with.
The runner works without a configuration file. Create one to change a default:
```bash
gitea-runner generate-config > config.yaml
gitea-runner -c config.yaml register
gitea-runner config init
gitea-runner config set runner.capacity 4
gitea-runner -c config.yaml daemon
```
`-c` / `--config` is a global flag and is accepted by every command that loads configuration (`register`, `daemon`, `cache-server`). The generated file is fully commented and is reproduced in [Example configuration](reference/config-example.md).
`config init` writes a minimal `config.yaml` in the working directory. Pass the file with `-c` when running `register`, `daemon` or `cache-server`. These commands do not find configuration files automatically. The runner reads its configuration only at startup. Restart it after each change.
:::warning No environment variable overrides
The runner process is configured only through the YAML file. Earlier releases let a few variables (`GITEA_DEBUG`, `GITEA_TRACE`, `GITEA_RUNNER_CAPACITY`, `GITEA_RUNNER_FILE`, `GITEA_RUNNER_ENVIRON`, `GITEA_RUNNER_ENV_FILE`) override parts of the config; those overrides have been removed.
## Editing
The variables understood by the Docker images belong to their [entrypoint](installation/docker.md#entrypoint-environment-variables), not to the runner, and `GITEA_RUNNER_LABELS` / `GITEA_RUNNER_REGISTRATION_TOKEN` are read by the corresponding CLI flags only.
:::
The `config` command edits the configuration file in place and preserves its comments:
Values with a duration type accept Go duration strings such as `30s`, `10m`, `3h`.
```bash
gitea-runner config set runner.timeout 90m
gitea-runner config set runner.envs.MY_VAR value
gitea-runner config add container.valid_volumes '/data/**'
gitea-runner config remove container.valid_volumes '/data/**'
gitea-runner config get runner.capacity
```
## `log`
A key is the path to a YAML value, with dots between levels. The command rejects unknown keys and values of the wrong type before changing the file. Without `-c`, these commands look for `config.yaml` or `config.yml` in the working directory, then in the directory containing the binary.
Controls the runner's own log, not how step output is streamed to the UI.
`gitea-runner config generate` prints every option with its default and a description. This matches the [example configuration](reference/config-example.md).
Use `${NAME}` in a value to read an environment variable from the runner's environment. The configuration fails to load if `NAME` is unset. Use `$${NAME}` for a literal `${NAME}`:
```yaml
runner:
capacity: ${RUNNER_CAPACITY}
```
## Common options
| Option | Default | Description |
| --- | --- | --- |
| `log.level` | `info` | `trace`, `debug`, `info`, `warn`, `error`, `fatal` or `panic`. `trace` and `debug` add the caller's `file:line`. |
| `runner.capacity` | `1` | Maximum number of jobs that can run at once. |
| `runner.timeout` | `3h` | Maximum job duration. Gitea's own job timeout may stop a job earlier. |
| `runner.shutdown_timeout` | `0s` | Time to let running jobs finish before canceling them during shutdown. |
| `runner.envs` | | Environment variables for every job. |
| `runner.env_file` | | File containing additional environment variables for every job. |
| `runner.labels` | | See [Labels](labels.md). |
| `runner.insecure` | `false` | Skip verification of Gitea's TLS certificate. |
| `runner.client_cert_file`, `runner.client_key_file` | | Client certificate and key for a Gitea instance that requires mutual TLS. |
| `runner.extra_headers` | | HTTP headers for requests to Gitea, for example for a reverse proxy. |
| `runner.github_mirror` | | URL to use instead of `https://github.com` when downloading actions from GitHub. |
| `container.network` | | Docker network for job containers. If empty, the runner creates a network for each job. |
| `container.options` | | Additional `docker run` options for every job container. |
| `container.privileged` | `false` | Run job containers in privileged mode. See [Jobs](jobs.md#volumes-and-privileges). |
| `container.valid_volumes` | | Volumes and host paths that workflows may mount. |
| `container.force_pull` | `false` | Pull job images even when they are already present. |
| `log.level` | `info` | Verbosity of the runner's log. |
## `runner`
| Option | Default | Description |
| --- | --- | --- |
| `file` | `.runner` | path of the registration file. Each runner process needs its own. |
| `capacity` | `1` | jobs executed concurrently. With an empty `container.network`, every concurrent docker job takes a subnet from the daemon's address pool, so a high capacity can exhaust it (see `default-address-pools` in the daemon config). |
| `envs` | – | extra environment variables given to every job. |
| `env_file` | `.env` | same, read from a file; ignored when empty or missing. |
| `timeout` | `3h` | maximum job duration. Gitea has its own timeout (3h by default) and may stop the job earlier. |
| `shutdown_timeout` | `0s` | how long a shutdown waits for running jobs before cancelling them. |
| `insecure` | `false` | skip TLS verification of the Gitea instance. |
| `fetch_timeout` | `5s` | timeout of a single job fetch. |
| `fetch_interval` | `2s` | base polling interval. |
| `fetch_interval_max` | `5s` | upper bound of the exponential backoff applied while idle. `0`, or the same value as `fetch_interval`, disables the backoff. |
| `labels` | see [Labels](labels.md) | labels used at registration, and by `daemon` when the flag is absent. |
| `github_mirror` | – | replaces `https://github.com` when actions are pulled and the instance's `DEFAULT_ACTIONS_URL` points at GitHub. |
| `action_shallow_clone` | `true` | fetch only the requested ref of an action repository at depth 1 instead of its full history. |
| `set_act_env` | `true` | inject `ACT=true` into jobs. Set to `false` so workflows gated on `if: ${{ !env.ACT }}` behave as they do on GitHub. |
| `allocate_pty` | `false` | allocate a pseudo-TTY per step. Enable only when a job needs an interactive terminal; tools like `docker build` then write redrawing progress frames into the log. |
| `workdir_cleanup_age` | `24h` | age at which stale task workspaces and orphaned host-mode scratch directories are removed while idle. |
| `idle_cleanup_interval` | `10m` | cadence of the idle cleanup pass. Setting either this or `workdir_cleanup_age` to `0` disables all idle cleanup. |
| `post_task_script` | – | host script run after each task's cleanup, see [Post-task script](hooks/post-task-script.md). |
| `post_task_script_timeout` | `5m` | hard limit for that script. |
| `hooks.job_started` / `hooks.job_completed` | – | scripts run inside the job environment, see [Job hooks](hooks/job-hooks.md). |
Log and state reporting can be tuned when the UI updates too slowly or the instance sees too many requests:
| Option | Default | Description |
| --- | --- | --- |
| `log_report_interval` | `5s` | base interval of the periodic log flush. |
| `log_report_max_latency` | `3s` | maximum time a single log row waits. Only has an effect below `log_report_interval`. |
| `log_report_batch_size` | `100` | flush immediately once this many rows are buffered, so bursty output arrives promptly. |
| `state_report_interval` | `5s` | interval of task state reports. State is also sent on every step transition. |
| `report_close_timeout` | `10s` | per-attempt deadline for the final log and state flush of a finished job. |
### Idle cleanup
While no job is running, the runner cleans up after earlier ones:
- stale task workspaces older than `workdir_cleanup_age` are removed when `container.bind_workdir` is enabled. Only purely numeric subdirectories of `container.workdir_parent` are treated as workspaces, and the path is assumed not to be shared with another runner;
- orphaned host-mode scratch directories are removed on the same schedule;
- per-job docker networks left behind by jobs the runner did not live to tear down are removed. They are recognised by the `com.gitea.runner.uuid` label carrying this runner's uuid, so leftovers of other runners on the same daemon are left alone. Without this, each leaked network keeps holding a subnet of the daemon's address pool.
## `cache`
See [Caching](cache.md) for the full picture, including shared cache servers.
| Option | Default | Description |
| --- | --- | --- |
| `enabled` | `true` | run the built-in cache server used by `actions/cache` and friends. |
| `dir` | `$HOME/.cache/actcache` | where cache blobs are stored. Ignored with `external_server`. |
| `host` | – | address job containers use to reach this runner's cache server. Empty means auto-detect; `0.0.0.0` is not valid. |
| `port` | `0` | port of the built-in server, `0` picks a free one. |
| `external_server` | – | URL of a shared `cache-server` to use instead of a local one. |
| `external_secret` | – | shared secret, required with `external_server`; must be identical everywhere. Generate with `openssl rand -hex 32`. |
| `external_secret_file` | – | read that secret from a file instead. Setting both is an error. |
| `offline_mode` | `false` | reuse a cached action instead of fetching it on every job. A moved tag or updated branch then stays at the cached commit until the entry expires or is removed. |
| `v2` | `true` | serve the cache service v2 API used by `actions/[email protected]` and later. |
## `container`
Applies to jobs that run in containers.
| Option | Default | Description |
| --- | --- | --- |
| `network` | – | network the job container joins: `host`, `bridge`, or a custom network name. Empty means the runner creates one per job. `network_mode` is still accepted for old configs. |
| `network_create_options.enable_ipv4` / `enable_ipv6` | Docker defaults | only apply to auto-created networks. IPv6 additionally requires `dockerd --ipv6`. |
| `privileged` | `false` | run job containers privileged; required for Docker-in-Docker inside jobs. |
| `options` | – | extra `docker run` options, e.g. `--add-host=my.gitea.url:host-gateway`. A volume declared here replaces the one the runner mounts on the same container path, which is how the tool cache can be kept on the host (`--volume /host/toolcache:/opt/hostedtoolcache`); its source must also be allowed by `valid_volumes`. |
| `workdir_parent` | `/workspace` | parent directory of a job's working directory inside the container. A leading `/` is trimmed and re-added. |
| `valid_volumes` | `[]` | volumes and bind mounts a job may mount, as [glob](https://github.com/gobwas/glob) patterns. `[]` forbids all, `['**']` allows all. |
| `docker_host` | – | override the docker host. Empty auto-detects it, `-` auto-detects it but does not mount the socket into job containers. |
| `force_pull` | `false` | pull images even when present. Images pinned by digest are never re-pulled, and a failed pull with a local copy available only warns. |
| `force_rebuild` | `false` | rebuild local action images even when present. |
| `require_docker` | `false` | always require a reachable daemon, even for host-only labels. |
| `docker_timeout` | `0s` | how long to wait for the daemon to become reachable. |
| `bind_workdir` | `false` | bind-mount the workspace from the host instead of using a docker volume, so jobs can bind-mount it by its own path into containers they start (`.:/app` in `docker compose`). Not needed when workflows use `GITEA_DOCKER_WORKSPACE`. The parent directory must then be mounted into the runner container. |
:::note Privileged mode and workflow container options
A workflow's own `jobs.<job_id>.container.options` are untrusted input. While `container.privileged` is disabled, the options that would break out of the container are stripped with a warning in the job log: `--pid`, `--ipc`, `--uts`, `--cgroupns`, `--userns`, `--cap-add`, `--security-opt`, `--device`, `--device-cgroup-rule`, `--gpus`, `--volumes-from`, `--runtime`, `--cgroup-parent` and `--sysctl`. They are honoured once privileged mode is enabled, because the operator has then opted into host access.
:::
## `host`
| Option | Default | Description |
| --- | --- | --- |
| `host.workdir_parent` | `$HOME/.cache/act/` | parent directory of a job's working directory for host-mode jobs. |
## `health_check` and `metrics`
Both are covered in [Monitoring](monitoring.md).
| Option | Default | Description |
| --- | --- | --- |
| `health_check.enabled` | `false` | pause fetching new tasks while the machine looks unhealthy. |
| `health_check.min_free_disk_space_mb` | `1024` | minimum free space on the filesystem holding the workspaces. |
| `health_check.script` | – | extra executable; a non-zero exit, a timeout or a start failure marks the runner unavailable. |
| `health_check.interval` | `30s` | how long a result is cached. |
| `health_check.timeout` | `10s` | maximum script runtime. |
| `metrics.enabled` | `false` | serve `/metrics`, `/healthz` and `/readyz`. |
| `metrics.addr` | `127.0.0.1:9101` | listen address. There is no authentication, so only expose it behind a firewall. |
| `metrics.readiness_grace` | `30s` | how long consecutive polling failures may last before `/readyz` returns 503. |
## Reloading
The runner reads its configuration at startup only. Restart the process after a change — with `shutdown_timeout` set, running jobs are given that much time to finish first.
See [Caching](cache.md), [hooks](hooks/job-hooks.md) and [monitoring](monitoring.md) for their configuration options.
+15 -43
View File
@@ -4,7 +4,7 @@ sidebar_position: 1
# Job hooks
Job hooks are operator-provided scripts that run **inside the job environment**, before the job's first step and after its last one. They are the equivalent of GitHub's [job hooks](https://docs.github.com/en/actions/how-tos/manage-runners/self-hosted-runners/run-scripts):
Job hooks are scripts you provide. They run in the job environment before its first step and after its last step. Use them for setup that applies to every job, such as registry logins, mirror configuration or masking secrets used by all jobs.
```yaml
runner:
@@ -13,62 +13,34 @@ runner:
job_completed: /hooks/completed.sh
```
| Setting | Runs |
| --- | --- |
| `runner.hooks.job_started` | before the job's first step, before any action is downloaded |
| `runner.hooks.job_completed` | after the job's last post step, while the job environment is still up |
`job_started` runs before any action is downloaded. `job_completed` runs after the last post step, including when the job fails or is cancelled. If these settings are unset, the runner reads GitHub's `ACTIONS_RUNNER_HOOK_JOB_STARTED` and `ACTIONS_RUNNER_HOOK_JOB_COMPLETED` from `runner.envs` or `runner.env_file`. The runner ignores workflow values for these hook variables.
`ACTIONS_RUNNER_HOOK_JOB_STARTED` and `ACTIONS_RUNNER_HOOK_JOB_COMPLETED` are read from the runner's environment (`runner.envs`, `runner.env_file`) when the settings are unset, so a configuration carried over from `actions/runner` keeps working. The settings take precedence. A workflow cannot point the runner at a different hook: those variables are only read from the runner's own environment, never from the job's.
Both hooks are **synchronous** and block the job while they run, and a non-zero exit from either one fails the job. There is no `continue-on-error` and no per-hook timeout — the job's `runner.timeout` is the only bound. Run anything long in the background from within the hook.
Use them for per-job setup that no workflow should have to carry: registry logins, mirror configuration, or masking runner-wide secrets with `::add-mask::`.
A hook that exits with a non-zero status fails the job. Hooks block the job while they run and count against its timeout. Keep them fast.
## Where they run
The hooks run where the job's steps run: inside the job container, or on the host in host mode. The paths are resolved *there*, so the script has to exist in the job image or on the host — a path that only exists on the runner host is not visible to a containerized job. For host-wide cleanup after the job environment is gone, use the [post-task script](post-task-script.md) instead.
Hooks run inside the job container, or on the host for host jobs. GitHub always runs hooks on the host. The script must exist in the job image or on the host where it runs. Job containers cannot access a path that exists only in the runner container. To make a host directory available to jobs, mount it with `container.options` and allow it in `container.valid_volumes`.
:::note
This is a deliberate difference from `actions/runner`, which runs its job hooks on the host, outside any container the job declares. Running them where the steps run is what lets a hook prepare the environment the steps actually see.
:::
The script is run according to its extension:
| Extension | Command |
| Extension | Runs as |
| --- | --- |
| `.sh` | `bash -e <path>` |
| `.ps1` | `pwsh -command . '<path>'` |
| anything else | the file itself, which needs its own shebang and executable bit |
| `.ps1` | `pwsh -command ". '<path>'"` |
| Anything else | The file itself |
As on GitHub, the shell flags applied to `run:` steps are **not** applied to a hook — set `pipefail` or anything else you want inside the script.
The shell options of `run:` steps do not apply to hooks. Set `pipefail` and other shell options inside the script.
A hook path that does not exist inside the job environment fails the job with `No such file or directory`, naming the path.
## Environment and output
### Docker-in-Docker and Docker-out-of-Docker
A hook receives the job's environment, including `runner.envs`, the workflow's `env:` and the `GITHUB_*` and `GITEA_*` variables. The runner includes hook output in the job log and masks secrets as it does for step output.
The hook is executed and its files are exchanged over the Docker API, addressed by container ID, so no path is translated between the runner and the daemon. Both setups work unchanged, but they differ in where the hook file has to be:
- **DinD** — the daemon has its own filesystem. Bake the hook into the job image; a path from the runner's filesystem is not visible to it.
- **DooD** — the job container is created by the host's daemon, so a bind mount in `container.options` is resolved against the **host**, not against the runner container. Either bake the hook into the job image, or mount a host directory and add it to `container.valid_volumes`.
## Environment
A hook sees the job's environment: the workflow, job and `container:` `env:`, the runner's `envs`, and the `GITHUB_*` context variables, with the same masking applied to its output as to a step's. The step-specific ones (`GITHUB_ACTION`, `GITHUB_OUTPUT`, `GITHUB_STATE`) are not set — a hook is not a step, so `::save-state::` and `::set-output::` have nowhere to go.
Its stdout is part of the job log, inside a collapsible group, and is scanned for workflow commands: `::add-mask::` registers a value to be masked for the rest of the job, `::set-env::` and `::add-path::` apply to the steps that follow.
`$GITHUB_ENV` and `$GITHUB_PATH` point at files that are read back after the hook exits, so the file-command form works too:
Use `$GITHUB_ENV` and `$GITHUB_PATH` to pass environment variables and paths to later steps. Mask values with `::add-mask::`:
```bash
#!/bin/bash
echo "REGISTRY_TOKEN=$(fetch-token)" >> "$GITHUB_ENV"
token="$(fetch-token)"
echo "::add-mask::$token"
echo "REGISTRY_TOKEN=$token" >> "$GITHUB_ENV"
echo "/opt/tooling/bin" >> "$GITHUB_PATH"
```
Both files are the hook's own, separate from the per-step ones, so nothing a hook writes is truncated by the first step.
## Recommendations
- Keep hooks **fast** and return the right exit code: they are on the critical path of every job, and nothing bounds them.
- Use **idempotent** operations, and expect `job_completed` to run after success, failure, and cancellation alike.
- Mask anything secret the hook prints or exports with `::add-mask::`.
For cleanup on the host after the job environment is gone, use the [post-task script](post-task-script.md).
+21 -76
View File
@@ -4,98 +4,43 @@ sidebar_position: 2
# Post-task script
The post-task script is an optional host hook that runs **once after every task**, after the runner has finished its normal per-task cleanup. Typical uses are pruning Docker images, vacuuming ephemeral disks, or resetting VM state between jobs.
The post-task script runs on the runner's host after each job, once the runner has removed the job's containers. Use it for housekeeping such as pruning Docker images or freeing disk space.
```yaml
runner:
# Path to an executable on the host. Empty or omitted disables the hook.
post_task_script: /usr/local/bin/gitea-post-task.sh
# Hard limit on script runtime. Default when post_task_script is set: 5m.
post_task_script_timeout: 2m
```
| Option | Default | Description |
| --- | --- | --- |
| `runner.post_task_script` | disabled | host path to the script or binary. Relative paths are resolved from the runner's working directory. |
| `runner.post_task_script_timeout` | `5m` when a script is set | maximum runtime before the runner kills the script and moves on. |
## When it runs
For each task, the order is:
1. the workflow runs (steps, actions, containers);
2. in-job cleanup (action `post:` steps, container stop and removal);
3. job outputs are reported to Gitea;
4. the bind-workdir workspace is removed, when `container.bind_workdir` is enabled;
5. **the post-task script**;
6. the final task acknowledgement to Gitea.
The script is **additive**: it does not replace any built-in cleanup. With `container.bind_workdir` enabled, the workspace directory has usually already been deleted before the script starts, but `GITEA_WORKSPACE` still names the path the job used.
## The runner stays offline until the script finishes
This is the most important operational detail. When the script starts, the runner **stops sending task heartbeats**, so from Gitea's perspective it is not available for new work until the script exits and the final task flush has been sent.
While the script runs:
- Gitea does not assign another task to this runner for the current job slot;
- the capacity slot stays occupied locally — with `capacity: 1`, no other task starts;
- a shutdown counts this phase as part of the in-flight task, so a slow script delays graceful shutdown.
If the script never exits, the runner stays in this state until `runner.post_task_script_timeout` elapses (default **5 minutes**), then kills it and proceeds. Set that timeout to what your housekeeping is allowed to take, and keep the script short and bounded.
## Environment variables
The script receives `runner.envs` / `runner.env_file` values plus:
| Variable | Description |
| --- | --- |
| `GITEA_TASK_ID` | numeric task ID |
| `GITEA_RUN_ID` | workflow run ID, when the server provides it |
| `GITEA_REPOSITORY` | repository slug (`owner/name`) |
| `GITEA_WORKSPACE` | workspace path the job used, which may already be deleted |
| `GITEA_JOB_RESULT` | `success`, `failure`, `cancelled`, `skipped` or `unknown` |
The environment is **not** a copy of the job container's. Even `PATH` is only present if `runner.envs` or `runner.env_file` defines it.
## Output and errors
- stdout and stderr go to the **runner process log**, prefixed with `post-task script stdout:` / `post-task script stderr:` — not to the job log;
- a non-zero exit is logged as a warning and does not change the job result already reported to Gitea;
- timeouts and start failures are warnings too; the runner still acknowledges the task.
## Interaction with other timeouts
| Timeout | Effect on the post-task script |
| --- | --- |
| `runner.post_task_script_timeout` | kills the script if it runs too long. The **only** timeout that bounds it. |
| `runner.timeout` | caps the task **up to** the script. The script detaches from the task deadline, so a job that nearly hit the runner timeout does not cut it short. |
| `runner.shutdown_timeout` | bounds how long a shutdown waits for the **task**. The script detaches from cancellation and may extend shutdown until its own timeout elapses. |
## Examples
Prune dangling Docker resources on Linux:
```sh
#!/bin/sh
set -eu
docker image prune -f
docker builder prune -f --filter 'until=24h'
```
On Windows, use a `.exe`, `.bat` or `.cmd` path; `.ps1` is not supported as the configured path, so wrap PowerShell in a batch file:
The job's slot stays busy until the script exits. With `capacity: 1`, the runner accepts no new jobs while the script runs. The runner kills the script if it runs longer than `post_task_script_timeout`, which defaults to `5m`. The runner also waits for the script during shutdown.
The runner writes the script's output to its own log, not to the job log. If the script exits with a non-zero status, the runner logs a warning without changing the job's result.
## Environment
The script receives variables from `runner.envs` and `runner.env_file`, but not from the runner's own environment. Set `PATH` in one of these settings if the script needs it. The runner also sets these variables:
| Variable | Value |
| --- | --- |
| `GITEA_TASK_ID` | The task ID |
| `GITEA_RUN_ID` | The workflow run ID |
| `GITEA_REPOSITORY` | The repository as `owner/name` |
| `GITEA_WORKSPACE` | The job's workspace path, which may already be deleted |
| `GITEA_JOB_RESULT` | `success`, `failure`, `cancelled`, `skipped` or `unknown` |
## Windows
The configured path must be an `.exe`, `.bat` or `.cmd` file. Wrap a PowerShell script in a batch file:
```bat
@echo off
powershell.exe -NoProfile -NonInteractive -ExecutionPolicy Bypass -File "%~dp0post-task.ps1"
```
`.sh` files on Windows need a Unix shell on `PATH`, unless `post_task_script` points at the interpreter itself.
## Notes
- `gitea-runner exec` does not load the runner YAML and never runs this hook.
- Use idempotent operations: the script runs after success, failure and cancellation alike.
- Watch the runner log when testing failure modes — a hung script, a non-zero exit, a missing executable.
- Bind-workdir idle cleanup (`runner.workdir_cleanup_age`) is separate from this hook and only runs while the runner is idle.
- For work that has to happen inside the job environment, use [job hooks](job-hooks.md) instead.
For work inside the job environment, use [job hooks](job-hooks.md).
+25 -27
View File
@@ -5,46 +5,44 @@ slug: /
# Gitea Runner
The [Gitea Runner](https://gitea.com/gitea/runner) executes the jobs of [Gitea Actions](/usage/actions/overview).
It polls a Gitea instance for queued jobs, runs their steps in a container or directly on the machine it is installed on, and streams the logs and the result back.
[Gitea Runner](https://gitea.com/gitea/runner) runs [Gitea Actions](/usage/actions/overview) jobs.
It asks a Gitea instance for jobs, runs them, and sends their logs and results back.
:::info Development version
These pages describe the `main` branch of `gitea.com/gitea/runner`, which is published as the `nightly` binaries and images.
Features documented here may not be part of a release yet. Pick a released version in the **Runner Version** dropdown for stable documentation.
These pages describe builds from the `main` branch, published as `nightly` binaries and images.
Select a release in the **Runner Version** dropdown for stable documentation.
:::
## Requirements
A runner needs a Gitea instance with Actions enabled, a [registration token](registration.md), and, for containerized jobs, a Docker daemon. Actions are enabled by default since Gitea 1.21; on older instances they have to be turned on:
- Gitea 1.21 or later with [Actions enabled](/administration/config-cheat-sheet#actions-actions), the default setting.
- Git 2.34.1 or later on the runner's `PATH`.
- Docker, or Podman with `podman.socket` enabled, for jobs in containers, Docker actions, and `docker://` steps.
```ini
[actions]
ENABLED=true
```
Jobs need these tools wherever steps run, in the job image or on the host:
Other OCI engines that implement the Docker API may work, but are untested. Podman is not a supported configuration.
- `bash` or `sh` for `run` steps, or `pwsh` or `powershell` on Windows.
- `node` for JavaScript actions and `hashFiles()`.
- `git` 2.34.1 or later for [`builtin:checkout`](jobs.md#built-in-checkout), plus `git-lfs` for its `lfs` input and `ssh` for its `ssh-key` input.
## Execution modes
## Where jobs run
A runner can run jobs in three different ways. The mode is not a global setting: it follows from the [labels](labels.md) the runner is registered with, so a single runner can offer both container and host labels.
The runner's [labels](labels.md) determine where each job runs. One runner can use a mix of these environments:
| Mode | How jobs run | Docker daemon | Notes |
| --- | --- | --- | --- |
| Docker (recommended) | in a container created from the label's image | external, e.g. the host's `/var/run/docker.sock` | jobs are isolated from each other, but share the daemon |
| Docker-in-Docker | in a container created by a daemon that lives next to the runner | bundled in the `dind` / `dind-rootless` images | strongest isolation, more setup, needs `--privileged` |
| Host | directly on the machine, with the tools installed there | only needed for `docker://` actions and service containers | no isolation between jobs |
| Label | Where the job runs |
| --- | --- |
| `docker://<image>` | In a container |
| `kubernetes://<image>` | In a pod in a Kubernetes cluster |
| `host` | Directly on the runner's machine |
Host jobs run as the runner's user without isolation. Use them only for trusted repositories.
## Getting started
1. [Install the runner](installation/binary.md) as a binary, [in Docker](installation/docker.md), or [on Kubernetes](installation/kubernetes.md).
2. [Register it](registration.md) against your instance with a registration token.
3. [Configure it](configuration.md), and pick the [labels](labels.md) that decide which jobs it accepts.
4. Optionally set up a [shared cache](cache.md), [job hooks](hooks/job-hooks.md), a [post-task script](hooks/post-task-script.md), or [metrics and health checks](monitoring.md).
1. Install the runner [from a binary](installation/binary.md), [with Docker](installation/docker.md), or [on Kubernetes](installation/kubernetes.md).
2. [Register it](registration.md) with your Gitea instance.
3. Start it with `gitea-runner daemon`.
Every command and flag is listed in the [command line reference](reference/cli.md).
The runner works without a configuration file. [Configuration](configuration.md) explains when you need one.
## Versioning and compatibility
The runner is released independently of Gitea and its version numbers are unrelated to the instance's. Gitea 1.21 or later is expected — older instances cannot accept the runner's label declaration — and individual features need a newer instance still, which is called out where they apply.
When moving between major runner versions, read [Upgrading](upgrade.md) first: `2.0.0` and `3.0.0` both contain breaking changes.
The runner is released independently of Gitea. Read [Upgrading](upgrade.md) before moving to a new major version.
+34 -55
View File
@@ -4,62 +4,46 @@ sidebar_position: 1
# Install from a binary
The runner is a single static binary called `gitea-runner`. It has no dependencies apart from a Docker daemon for containerized jobs.
The runner is a single binary, `gitea-runner`, built for Linux, macOS, Windows, and FreeBSD.
## Download
- released builds: [dl.gitea.com/gitea-runner](https://dl.gitea.com/gitea-runner/) or the [release page](https://gitea.com/gitea/runner/releases)
- development builds of the `main` branch: [dl.gitea.com/gitea-runner/nightly](https://dl.gitea.com/gitea-runner/nightly/)
Each file is published next to a `.sha256` checksum and an `.xz` compressed variant:
Download releases from [dl.gitea.com/gitea-runner](https://dl.gitea.com/gitea-runner/). Find builds from the `main` branch in its [`nightly`](https://dl.gitea.com/gitea-runner/nightly/) directory. Each binary has a matching `.sha256` checksum file:
```bash
VERSION=nightly
curl -sSLO "https://dl.gitea.com/gitea-runner/$VERSION/gitea-runner-$VERSION-linux-amd64"
curl -sSLO "https://dl.gitea.com/gitea-runner/$VERSION/gitea-runner-$VERSION-linux-amd64.sha256"
sha256sum -c "gitea-runner-$VERSION-linux-amd64.sha256"
install -m 0755 "gitea-runner-$VERSION-linux-amd64" /usr/local/bin/gitea-runner
sudo install -m 0755 "gitea-runner-$VERSION-linux-amd64" /usr/local/bin/gitea-runner
```
Check that the binary matches your platform:
```bash
gitea-runner --version
```
## Build from source
Building requires the Go version declared in the repository's `go.mod`:
```bash
git clone https://gitea.com/gitea/runner.git
cd runner
make build
```
To build the runner yourself, run `make build` in a clone of [gitea/runner](https://gitea.com/gitea/runner).
## First run
```bash
gitea-runner generate-config > config.yaml # optional, defaults are safe
gitea-runner -c config.yaml register # see "Registering a runner"
gitea-runner -c config.yaml daemon
gitea-runner register --instance https://gitea.example.com --token <token>
gitea-runner daemon
```
The `daemon` command runs in the foreground. It reads the registration file (`runner.file`, `.runner` by default) relative to its working directory, so keep the working directory stable across restarts.
`register` prompts for the remaining settings and writes the `.runner` registration file to the working directory. Run `daemon` from the same directory so it can read the file. See [Registration](../registration.md).
## Run as a systemd service
## systemd
Create an unprivileged user, install the binary, and register the runner as that user so the `.runner` file ends up in the service's working directory:
Create a user and a configuration file. Then register the runner as that user from its home directory:
```bash
sudo useradd --system --home-dir /var/lib/gitea-runner --create-home gitea-runner
sudo install -d /etc/gitea-runner
sudo -u gitea-runner gitea-runner generate-config | sudo tee /etc/gitea-runner/config.yaml >/dev/null
sudo mkdir -p /etc/gitea-runner
sudo gitea-runner config init -c /etc/gitea-runner/config.yaml
sudo chown gitea-runner /etc/gitea-runner/config.yaml
cd /var/lib/gitea-runner
sudo -u gitea-runner gitea-runner register -c /etc/gitea-runner/config.yaml
sudo -u gitea-runner gitea-runner register -c /etc/gitea-runner/config.yaml \
--instance https://gitea.example.com --token <token>
```
Then install the unit as `/etc/systemd/system/gitea-runner.service`:
Install the unit as `/etc/systemd/system/gitea-runner.service`:
```ini
[Unit]
@@ -79,8 +63,6 @@ User=gitea-runner
Group=gitea-runner
Restart=on-failure
RestartSec=5s
# Allow running jobs to finish before the runner is stopped. Keep this in sync
# with runner.shutdown_timeout in the config.
TimeoutStopSec=3h
[Install]
@@ -92,13 +74,17 @@ sudo systemctl daemon-reload
sudo systemctl enable --now gitea-runner
```
If jobs use the host's Docker daemon, the `gitea-runner` user also needs access to the daemon socket. Adding it to the `docker` group grants that access and is [equivalent to root on the host](https://docs.docker.com/engine/security/#docker-daemon-attack-surface).
Stopping the service cancels running jobs unless `runner.shutdown_timeout` gives them time to finish. Keep `runner.shutdown_timeout` below `TimeoutStopSec`:
Environment variables for the process — most importantly [proxy variables](../proxy.md) — belong in `Environment=` lines or a drop-in file, not in the runner config.
```bash
sudo -u gitea-runner gitea-runner config set -c /etc/gitea-runner/config.yaml runner.shutdown_timeout 2h50m
```
## Run as a launchd daemon (macOS)
For jobs in containers, give the `gitea-runner` user access to the Docker daemon's socket, for example through the `docker` group. That access is [equivalent to root](https://docs.docker.com/engine/security/#docker-daemon-attack-surface) on the host. Set the runner's environment variables, such as [proxy settings](../proxy.md), with `Environment=` lines in the unit.
macOS uses `launchd` instead of systemd. Daemons run as `root` by default; an unprivileged `_gitea-runner` user can be created with `dscl`. Install the following as `/Library/LaunchDaemons/com.gitea.runner.plist` and adjust the paths to your installation:
## launchd (macOS)
Adjust the paths and user for your setup, then save this file as `/Library/LaunchDaemons/com.gitea.runner.plist`:
```xml
<?xml version="1.0" encoding="UTF-8"?>
@@ -114,16 +100,10 @@ macOS uses `launchd` instead of systemd. Daemons run as `root` by default; an un
<string>--config</string>
<string>/etc/gitea-runner/config.yaml</string>
</array>
<key>RunAtLoad</key>
<true/>
<key>KeepAlive</key>
<true/>
<key>UserName</key>
<string>_gitea-runner</string>
<key>WorkingDirectory</key>
<string>/var/lib/gitea-runner</string>
<key>StandardOutPath</key>
<string>/var/lib/gitea-runner/runner.log</string>
<key>StandardErrorPath</key>
<string>/var/lib/gitea-runner/runner.err</string>
<key>EnvironmentVariables</key>
<dict>
<key>PATH</key>
@@ -131,23 +111,22 @@ macOS uses `launchd` instead of systemd. Daemons run as `root` by default; an un
<key>HOME</key>
<string>/var/lib/gitea-runner</string>
</dict>
<key>UserName</key>
<string>_gitea-runner</string>
<key>StandardErrorPath</key>
<string>/var/lib/gitea-runner/runner.log</string>
<key>RunAtLoad</key>
<true/>
<key>KeepAlive</key>
<true/>
</dict>
</plist>
```
```bash
sudo launchctl load /Library/LaunchDaemons/com.gitea.runner.plist
sudo launchctl bootstrap system /Library/LaunchDaemons/com.gitea.runner.plist
```
On macOS and Windows hosts, jobs usually run with [host labels](../labels.md) and the tools installed on the machine.
## Windows
Install the `windows-amd64` binary and register it as a service with any service wrapper (for example `sc.exe` plus a wrapper such as [WinSW](https://github.com/winsw/winsw), or a scheduled task at boot). The runner itself has no service-installer subcommand.
Run `gitea-runner daemon` with a service wrapper such as [WinSW](https://github.com/winsw/winsw). The runner has no service installer of its own.
Keep in mind for Windows hosts:
- `runner.post_task_script` accepts `.exe`, `.bat` and `.cmd` paths; `.ps1` is not supported as the configured path.
- host-mode jobs are terminated as a process tree, so tools that daemonize are not left behind.
On macOS and Windows, jobs usually run directly on the runner's machine with [host labels](../labels.md).
+53 -100
View File
@@ -4,120 +4,74 @@ sidebar_position: 2
# Install with Docker
The official images are published on [Docker Hub](https://hub.docker.com/r/gitea/runner/tags) as `docker.io/gitea/runner`.
`latest` is the newest release, `nightly` is built from the `main` branch, and every release is also tagged with its version.
In the container the registration and the daemon are combined: the entrypoint registers the runner on first start (when no registration file exists yet) and then execs `gitea-runner daemon`.
## Image flavours
All flavours contain the same `gitea-runner` binary and differ only in how a Docker daemon is made available to jobs.
| Tag | Base image | Docker daemon | Supervisor | Runs as |
| --- | --- | --- | --- | --- |
| `nightly`, `latest`, `<version>` | `alpine` | none, you provide one | `tini` | `root` |
| `nightly-dind`, `latest-dind` | `docker:dind` | bundled, needs `--privileged` | `s6` | `root` |
| `nightly-dind-rootless`, `latest-dind-rootless` | `docker:dind-rootless` | bundled, rootless | `s6` | `rootless` (UID 1000) |
The rootless flavour's UID is fixed at 1000 by the upstream base image, and its daemon always listens on `/run/user/1000/docker.sock`, so `--user 1001` does not work. To talk to a *host* rootless daemon under another UID, use the basic flavour and bind-mount that daemon's socket instead.
## Basic flavour
The default image ships no daemon of its own, so jobs that use `docker://` images need one from outside the container — usually the host's socket:
Runner images are available on [Docker Hub](https://hub.docker.com/r/gitea/runner/tags) as `docker.io/gitea/runner`. On its first start, the container registers and starts the runner:
```bash
docker run -d --name my_runner \
-e GITEA_INSTANCE_URL=<instance_url> \
-e GITEA_RUNNER_REGISTRATION_TOKEN=<registration_token> \
-e GITEA_RUNNER_NAME=<runner_name> \
-v $PWD/data:/data \
-v /var/run/docker.sock:/var/run/docker.sock \
docker.io/gitea/runner:nightly
```
This flavour does not need `--privileged`. The trade-off is that jobs share the host's daemon and can therefore see its other containers and images. A job that can reach the socket can also read the reusable `GITEA_RUNNER_REGISTRATION_TOKEN` from the runner container's `docker inspect` output.
## Docker-in-Docker
The `dind` flavour bundles its own daemon, so no socket has to be mounted:
```bash
docker run -d --name my_runner --privileged \
-e GITEA_INSTANCE_URL=<instance_url> \
-e GITEA_RUNNER_REGISTRATION_TOKEN=<registration_token> \
-v $PWD/data:/data \
docker run -d --name gitea-runner --restart always --privileged \
-e GITEA_INSTANCE_URL=https://gitea.example.com \
-e GITEA_RUNNER_REGISTRATION_TOKEN=<token> \
-v ./data:/data \
-v runner-docker:/var/lib/docker \
docker.io/gitea/runner:nightly-dind
```
`s6` starts `dockerd` first and the runner service waits for it before registering. Use `nightly-dind-rootless` to run both the daemon and the runner as an unprivileged user; rootless Docker's usual limitations around networking, cgroups and storage drivers apply.
The `-dind` image runs its own Docker daemon for jobs. Jobs never get access to the host's Docker daemon socket, which grants the equivalent of root access on the host. The two volumes preserve registration and job images when you replace the container:
## Volumes
- `/data` holds the `.runner` registration file. Without it, a new container registers as another runner.
- `/var/lib/docker` holds the images pulled for jobs. Without it, a new container pulls them again.
Two different pieces of state are worth persisting, and neither implies the other:
## Image flavours
- `/data` is the runner's working directory. It holds the `.runner` registration file and, optionally, the config file. Without it, a recreated container registers itself again as a new runner, leaving a stale entry in Gitea, and fails outright if the token has been reset in the meantime.
- the Docker daemon's data root holds the images pulled for jobs. It is **not** under `/data`: for `dind` it is `/var/lib/docker` inside the container, for `dind-rootless` it is `/home/rootless/.local/share/docker`. Give it its own volume, or every new container re-pulls the job images.
| Tag | Docker daemon for jobs |
| --- | --- |
| `nightly-dind` (recommended) | Bundled. Requires `--privileged`. |
| `nightly-dind-rootless` | Bundled. Runs as UID 1000. Requires `--privileged` and [rootless Docker](https://docs.docker.com/engine/security/rootless/) support on the host. Stores its images in `/home/rootless/.local/share/docker`. |
| `nightly` | None. Mount the host's Docker daemon socket with `-v /var/run/docker.sock:/var/run/docker.sock`, or set `DOCKER_HOST`. |
## Entrypoint environment variables
The `latest` and release version tags use the same suffixes, for example `latest-dind`. The `-dind` flavours are available for amd64 and arm64. The basic image also supports riscv64 and s390x. The images are designed for Docker, not Podman.
The entrypoint ([`scripts/run.sh`](https://gitea.com/gitea/runner/src/branch/main/scripts/run.sh)) understands:
When the basic image uses the host's Docker daemon, jobs share that daemon. Use this setup only for trusted repositories.
## Environment variables
| Variable | Meaning |
| --- | --- |
| `GITEA_INSTANCE_URL` | instance to register against, e.g. `https://gitea.example.com/` |
| `GITEA_RUNNER_REGISTRATION_TOKEN` | registration token; unset before the daemon starts |
| `GITEA_RUNNER_REGISTRATION_TOKEN_FILE` | file to read the token from, for Docker/Kubernetes secrets |
| `GITEA_RUNNER_NAME` | runner name, defaults to the container hostname |
| `GITEA_RUNNER_LABELS` | labels, passed to both `register` and `daemon` |
| `GITEA_RUNNER_EPHEMERAL` | any non-empty value registers the runner as [ephemeral](../registration.md#ephemeral-runners) |
| `GITEA_RUNNER_ONCE` | any non-empty value runs a single job, then exits |
| `GITEA_MAX_REG_ATTEMPTS` | registration attempts before giving up, default `10` |
| `RUNNER_STATE_FILE` | registration file name inside `/data`, default `.runner` |
| `CONFIG_FILE` | config file inside the container, passed as `--config` |
| `GITEA_INSTANCE_URL` | Gitea instance to register with. |
| `GITEA_RUNNER_REGISTRATION_TOKEN` | Registration token, needed only until registration completes. |
| `GITEA_RUNNER_REGISTRATION_TOKEN_FILE` | File to read the registration token from, such as a Docker secret. |
| `GITEA_RUNNER_NAME` | Runner name. Defaults to the container's hostname. |
| `GITEA_RUNNER_LABELS` | Comma-separated [labels](../labels.md), applied on every start. |
| `GITEA_RUNNER_EPHEMERAL` | Any nonempty value registers an [ephemeral runner](../registration.md#ephemeral-runners). |
| `GITEA_RUNNER_ONCE` | Any nonempty value makes the runner exit after one job. |
| `GITEA_MAX_REG_ATTEMPTS` | Maximum number of registration attempts. Unlimited by default, with 5 seconds between attempts. |
| `CONFIG_FILE` | Path to a [configuration file](../configuration.md) inside the container. |
These are entrypoint variables, not runner settings: the runner process itself is configured only through the [config file](../configuration.md).
Mount the config file when you need one:
To use a configuration file, mount it and set `CONFIG_FILE` to its path:
```bash
docker run -v $PWD/config.yaml:/config.yaml -e CONFIG_FILE=/config.yaml ...
docker run ... -v ./config.yaml:/config.yaml -e CONFIG_FILE=/config.yaml docker.io/gitea/runner:nightly-dind
```
A config file can be generated with the image itself:
```bash
docker run --rm --entrypoint="" docker.io/gitea/runner:nightly gitea-runner generate-config > config.yaml
```
## docker compose
## Docker Compose
```yaml
services:
runner:
image: docker.io/gitea/runner:nightly
image: docker.io/gitea/runner:nightly-dind
restart: always
privileged: true
environment:
CONFIG_FILE: /config.yaml
GITEA_INSTANCE_URL: "${INSTANCE_URL}"
GITEA_RUNNER_REGISTRATION_TOKEN: "${REGISTRATION_TOKEN}"
GITEA_RUNNER_NAME: "${RUNNER_NAME}"
GITEA_RUNNER_LABELS: "${RUNNER_LABELS}"
GITEA_INSTANCE_URL: https://gitea.example.com
GITEA_RUNNER_REGISTRATION_TOKEN: <token>
volumes:
- ./config.yaml:/config.yaml
- ./data:/data
- /var/run/docker.sock:/var/run/docker.sock
- runner-docker:/var/lib/docker
volumes:
runner-docker:
```
When Gitea runs in the same compose project, depend on its health check so the runner does not try to register before the instance answers:
```yaml
depends_on:
gitea:
condition: service_healthy
restart: true
```
The rootless Docker-in-Docker variant needs a few extra options:
The rootless flavour needs these additional settings:
```yaml
services:
@@ -126,22 +80,21 @@ services:
restart: always
privileged: true
security_opt:
# for hosts running AppArmor (Ubuntu, Debian), whose default profile blocks
# the user namespace changes the bundled daemon needs
- apparmor=rootlesskit
volumes:
- ./data/runner:/data
environment:
- GITEA_INSTANCE_URL=<instance_url>
- GITEA_RUNNER_REGISTRATION_TOKEN=<registration_token>
- DOCKER_HOST=unix:///var/run/user/1000/docker.sock
# slirp4netns gives significantly better network throughput than vpnkit
- DOCKERD_ROOTLESS_ROOTLESSKIT_NET=slirp4netns
- DOCKERD_ROOTLESS_ROOTLESSKIT_MTU=65520
GITEA_INSTANCE_URL: https://gitea.example.com
GITEA_RUNNER_REGISTRATION_TOKEN: <token>
DOCKERD_ROOTLESS_ROOTLESSKIT_NET: slirp4netns
DOCKERD_ROOTLESS_ROOTLESSKIT_MTU: 65520
volumes:
- runner-data:/data
volumes:
runner-data:
```
## Cache from a dockerized runner
- Set `security_opt` on AppArmor hosts such as Ubuntu and Debian. Docker's `docker-ce-rootless-extras` package provides the `rootlesskit` profile.
- Use `slirp4netns` for better job network throughput than the default.
- Make `/data` writable by UID 1000. Use a named volume or a bind-mounted host directory owned by that UID.
A runner in a container creates a separate network per job by default, so the address it detects for its own cache server is often unreachable from job containers and `actions/cache` fails with a connection timeout. Set `cache.host` and `cache.port` explicitly and publish that port, or put the job containers on a shared network — see [Caching](../cache.md#dockerized-runners).
More deployment examples live in the [`examples`](https://gitea.com/gitea/runner/src/branch/main/examples) directory of the runner repository.
See the runner repository's [examples](https://gitea.com/gitea/runner/src/branch/main/examples) for more deployments.
+55 -33
View File
@@ -4,46 +4,68 @@ sidebar_position: 3
# Install on Kubernetes
Ready-to-adapt manifests live in [`examples/kubernetes`](https://gitea.com/gitea/runner/src/branch/main/examples/kubernetes) of the runner repository, and a Helm chart is maintained at [gitea/helm-actions](https://gitea.com/gitea/helm-actions).
A runner in Kubernetes can run jobs in two ways:
## Choosing a manifest
- **Job pods**: Each job runs in its own pod under the cluster's quotas and policies, without privileged containers. Docker actions and `docker://` steps are not supported.
- **Docker-in-Docker**: Jobs run in a Docker daemon alongside the runner. All workflow features are supported. The daemon is privileged, so a job can break out of its container and access the node.
| Example | Shape | Docker daemon |
| --- | --- | --- |
| `dind-docker.yaml` | `Deployment` with a native sidecar (`initContainer` with `restartPolicy: Always`, needs Kubernetes 1.29+) | privileged `docker:dind` sidecar, socket shared through an `emptyDir` |
| `statefulset-dind.yaml` | `StatefulSet` with `volumeClaimTemplates` | same as above |
| `rootless-docker.yaml` | `Deployment` with a single container | bundled rootless daemon of the `dind-rootless` image, reached over `tcp://localhost:2376` with TLS |
The manifests below are in the runner repository's [`examples/kubernetes`](https://gitea.com/gitea/runner/src/branch/main/examples/kubernetes) directory. They read the registration token from the `runner-secret` Secret and store the `.runner` registration file in a volume at `/data`. This preserves registration when the pod restarts.
Prefer the `StatefulSet` variant when you scale past one replica: each pod then gets a stable identity and its own volume, so it keeps its `.runner` registration across restarts and reschedules instead of registering itself again as a new runner.
## Job pods
## Two volumes, two purposes
Deploy [`kubernetes-pods.yaml`](https://gitea.com/gitea/runner/src/branch/main/examples/kubernetes/kubernetes-pods.yaml). It registers the runner with a `kubernetes` label:
- `/data` — the runner's working directory, holding the `.runner` registration file and optionally the config file.
- the daemon's data root — `/var/lib/docker` for the `dind` sidecar, `/home/rootless/.local/share/docker` for `dind-rootless`. It holds the images pulled for jobs and is *not* under `/data`. Dropping it still works, but every recreated pod re-pulls all job images.
With the rootless image, both volumes must be writable by UID/GID 1000, which is what `securityContext.fsGroup: 1000` in the example is for.
## Registration token
The examples read the token from a `Secret`:
```yaml
env:
- name: GITEA_INSTANCE_URL
value: http://gitea-http.gitea.svc.cluster.local:3000
- name: GITEA_RUNNER_REGISTRATION_TOKEN
valueFrom:
secretKeyRef:
name: runner-secret
key: token
```text
ubuntu-latest:kubernetes://docker.gitea.com/runner-images:ubuntu-latest
```
The token stays valid for further registrations until it is reset, but each registration creates another runner entry. For pods that are recreated without their volume, use the instance-wide token configured on the Gitea side, and expect stale runner entries — or start an [ephemeral runner](../registration.md#ephemeral-runners) per job.
The runner creates job pods in its own namespace using its service account. The manifest grants the permissions it needs:
## Privileges
| Resource | Verbs |
| --- | --- |
| `pods` | `create`, `get`, `list`, `delete` |
| `pods/exec` | `create`, `get` |
| `pods/log` | `get` |
| `secrets` | `create`, `delete` |
Docker-in-Docker needs `securityContext.privileged: true`, which lets a malicious job break out of the container. Weigh that against the alternatives:
The cluster must run Kubernetes 1.30 or later. Job images need `sh`, `tar`, `tail`, and `env`. A runner outside the cluster uses the current context from the first file in `$KUBECONFIG`, or from `~/.kube/config` if `$KUBECONFIG` is unset.
- the rootless flavour, which reduces but does not remove the exposure;
- pointing the basic flavour at a daemon outside the cluster;
- keeping such runners on a dedicated node pool and only granting them to trusted repositories.
Use pod templates to set resources, node selectors, image pull secrets, and other settings for job pods:
```yaml
kubernetes:
pod_template:
spec:
containers:
- name: job # merged into the job container
resources:
limits:
memory: 4Gi
- name: $services # merged into every service container
resources:
limits:
memory: 1Gi
pod_templates:
gpu: # merged on top for jobs with runs-on: gpu
spec:
nodeSelector:
nvidia.com/gpu.present: "true"
```
Services share the job pod's network. Steps reach them by their service ID or `localhost`. Two services cannot use the same port. Only `--health-*` is supported in a service's `options`. Job pods do not receive a service account token unless the template sets `automountServiceAccountToken: true`.
## Docker-in-Docker
| Manifest | Deployment type |
| --- | --- |
| [`dind-docker.yaml`](https://gitea.com/gitea/runner/src/branch/main/examples/kubernetes/dind-docker.yaml) | `Deployment` with a privileged `docker:dind` sidecar |
| [`statefulset-dind.yaml`](https://gitea.com/gitea/runner/src/branch/main/examples/kubernetes/statefulset-dind.yaml) | `StatefulSet` with a privileged `docker:dind` sidecar, for multiple replicas |
| [`rootless-docker.yaml`](https://gitea.com/gitea/runner/src/branch/main/examples/kubernetes/rootless-docker.yaml) | `Deployment` with the `dind-rootless` image |
Use the `StatefulSet` for multiple replicas so each has its own registration volume. A [Helm chart](https://gitea.com/gitea/helm-actions) is also available.
The Docker daemon stores images pulled for jobs in `/var/lib/docker`, or `/home/rootless/.local/share/docker` in the rootless image. The manifests mount this directory on a volume so a new pod does not pull every image again.
## Shutdown
The manifests set `terminationGracePeriodSeconds` to give a pod three hours to stop. To let running jobs finish, set `runner.shutdown_timeout` a little lower than that grace period. Otherwise, the runner cancels running jobs when the pod stops.
+77
View File
@@ -0,0 +1,77 @@
---
sidebar_position: 5
---
# Jobs
## Service containers
A job's `services` start before its steps. If a service defines a health check in its image or `options`, the runner waits until the service is healthy:
```yaml
services:
postgres:
image: postgres:18
options: >-
--health-cmd pg_isready
--health-interval 5s
--health-retries 10
```
If a service is unhealthy, the runner fails the job and shows the service's container log. If a service never becomes healthy, the runner fails the job after `container.service_ready_timeout`, which defaults to `5m`.
Connect to a service from a step using its ID and listening port, for example `psql -h postgres -p 5432`.
A job with a `host` label does not start services. Set `container:` on the job if it needs services.
## Docker inside jobs
Container jobs can access the runner's Docker daemon, usually through a proxy. Containers started by the job can bind-mount the workspace using its path:
```bash
docker run -v "$PWD:/src" alpine ls /src
```
When the job ends, the proxy removes the containers, networks and volumes the job created, including `docker compose` services. Set `container.sweep: false` when jobs deploy services that should keep running.
Without the proxy, for example with a remote `DOCKER_HOST`, prefix workspace bind mounts with `GITEA_DOCKER_WORKSPACE`. This variable contains the workspace path as the Docker daemon sees it:
```yaml
volumes:
- ${GITEA_DOCKER_WORKSPACE:-.}/data:/app/data
```
To keep the Docker socket out of job containers, set `container.docker_host: "-"`.
## Volumes and privileges
Workflows can mount only the volumes and host paths allowed by `container.valid_volumes`. The list is empty by default:
```yaml
container:
valid_volumes:
- data # the volume named data
- /src/*.json # * stays within one path segment, ** crosses them
- /etc/ssl/certs:ro # read-only mounts only
```
Unless you enable `container.privileged`, the runner drops options in a workflow's `container.options` that could allow the job to escape the container. Examples include `--privileged`, `--cap-add`, `--device` and `--security-opt`. The runner writes a warning in the job log when it drops an option. The runner's own `container.options` always apply.
## Tool cache
Setup actions such as `actions/setup-go` install tools in `/opt/hostedtoolcache`. `runner.tool_cache_mode` determines how the runner stores this cache:
| Mode | Tool cache |
| --- | --- |
| `none` (default) | Tools included in the job image. Tools installed by a job are removed when it ends. |
| `shared` | One volume reused by every job. Use only with `runner.capacity: 1`. Two jobs installing the same tool version at once corrupt the cache. |
With `none`, include tools in the job image at `/opt/hostedtoolcache/<tool>/<version>/<arch>`. Place an empty `<arch>.complete` file next to the directory. The `-full` variant of the [runner images](https://gitea.com/gitea/runner-images) includes its tools this way.
## Built-in checkout
`uses: builtin:checkout` checks out the repository like `actions/checkout`. It does not download an action or require Node in the job image. It runs `git` in the job. See [Requirements](index.md#requirements) and the inputs listed in [builtin-actions.md](https://gitea.com/gitea/runner/src/branch/main/docs/builtin-actions.md).
## Secrets
The runner masks secrets and `::add-mask::` values in job logs, job summaries, and the names of containers it creates. The runner does not send job outputs that contain a secret. A `needs.<job>.outputs.<name>` expression that reads such an output is empty.
+17 -46
View File
@@ -4,67 +4,38 @@ sidebar_position: 4
# Labels
Labels decide **which jobs a runner accepts** and **how it runs them**. A job's `runs-on` value is matched against the runner's label names; the first match wins and selects the execution environment for that job.
A runner's labels determine which jobs it accepts and where it runs them. Gitea sends a job to a runner that has the labels specified in the job's `runs-on`. The runner uses the environment of the first matching entry in `runs-on`.
A label is written as:
```text
<name>[:<schema>[:<args>]]
```
| Part | Meaning |
| Label | Job environment |
| --- | --- |
| `name` | the name a workflow refers to in `runs-on`, e.g. `ubuntu-latest` |
| `schema` | either `docker` or `host`, defaulting to `host` when omitted |
| `args` | only used by the `docker` schema: the image the job runs in |
| `ubuntu-latest:docker://docker.gitea.com/runner-images:ubuntu-latest` | Container using the specified image. |
| `ubuntu-latest:kubernetes://docker.gitea.com/runner-images:ubuntu-latest` | Pod using the specified image. See [job pods](installation/kubernetes.md#job-pods). |
| `macos:host`, or just `macos` | The runner's machine. |
Two schemas are supported:
- `docker://<image>` — the job runs in a container created from `<image>`:
```text
ubuntu-latest:docker://docker.gitea.com/runner-images:ubuntu-latest
```
- `host` — the job's steps run directly on the machine, with the tools installed there:
```text
macos:host
```
So a runner registered with
The label name is the part before the first `:docker`, `:kubernetes` or `:host`. Workflows use this name in `runs-on`. For example:
```text
ubuntu-latest:docker://docker.gitea.com/runner-images:ubuntu-latest,macos:host
```
runs `runs-on: ubuntu-latest` jobs in the `runner-images:ubuntu-latest` container and `runs-on: macos` jobs directly on the host.
With these labels, `runs-on: ubuntu-latest` jobs run in a container. Jobs with `runs-on: macos` run on the runner's machine.
Names may themselves contain a colon, for example `pool:e57e18d4-10d4-406f-93bf-60f127221bdd`; only `host` and `docker` are treated as schemas.
A job's `container:` image replaces the label's image. The job runs in a container even with a `host` label.
If a job's `runs-on` matches none of the runner's labels, the job still runs, in the default `docker.gitea.com/runner-images:ubuntu-latest` image. Images maintained for this purpose are listed at [gitea/runner-images](https://gitea.com/gitea/runner-images); community images such as the [act images](https://github.com/nektos/act/blob/master/IMAGES.md) work too.
A job whose `runs-on` matches none of the runner's labels uses `runner.default_image`, which defaults to `docker.gitea.com/runner-images:ubuntu-latest`. It runs in a container, as a pod on runners with `kubernetes` but no `docker` labels, or on the host when no Docker daemon is reachable.
:::note
A runner that only exposes `host` labels still needs access to a Docker daemon whenever a job uses a `docker://` action or a service container. `host` labels only change where the job's own steps run.
:::
## Changing labels
## Registration versus config labels
Labels are set during registration and stored in the registration file. To change them, set them in the configuration file or on the command line, then restart the runner. The runner reports its labels to Gitea at startup:
Labels are chosen at registration time (`--labels`, or the interactive prompt) and stored in the registration file. Afterwards they can be changed without re-registering, and the most explicit source wins:
```text
--labels / GITEA_RUNNER_LABELS > runner.labels in the config file > labels in the .runner file
```bash
gitea-runner config set runner.labels 'ubuntu-latest:docker://node:24' 'macos:host'
```
- during `register`, `runner.labels` from the config file takes precedence and the `--labels` flag is ignored;
- `daemon --labels` (which defaults to `GITEA_RUNNER_LABELS`) overrides the labels of an already registered runner;
- whenever the resulting labels differ from those in the registration file, they are written back to it and re-declared to the instance on startup;
- labels that fail to parse are skipped with a warning instead of stopping the runner.
Labels can also be edited in the Gitea UI under the runner's settings.
`daemon --labels` or the `GITEA_RUNNER_LABELS` environment variable takes precedence over `runner.labels`. The `runner.labels` setting takes precedence over labels in the registration file.
## Choosing images
- Pick an image that already contains the tools your workflows expect. The default images are small; a job that installs a toolchain on every run is usually better served by a purpose-built image.
- Pin images by tag or digest for reproducible jobs. An image pinned by digest is never re-pulled, even with `container.force_pull` enabled.
- Use distinct names such as `linux_amd64:host` or `windows:host` for host labels, so a workflow written for GitHub's `ubuntu-latest` does not accidentally run unsandboxed on your machine.
- Use the [runner images](https://gitea.com/gitea/runner-images) for tools that common workflows need. To avoid installing a toolchain on every run, choose an image that already includes it.
- Pin images by tag or digest for reproducible jobs.
- Give host labels distinct names such as `macos-arm64` to prevent workflows written for GitHub's `ubuntu-latest` from running directly on your machine without a sandbox.
+39 -51
View File
@@ -1,93 +1,81 @@
---
sidebar_position: 7
sidebar_position: 8
---
# Monitoring and health
# Monitoring
## Prometheus metrics
## Metrics
```yaml
metrics:
enabled: true
addr: "127.0.0.1:9101"
readiness_grace: 30s
```
With `metrics.enabled`, the runner serves three endpoints on `metrics.addr`:
With metrics enabled, the runner serves these endpoints:
| Endpoint | Purpose |
| --- | --- |
| `/metrics` | Prometheus exposition of the runner's own metrics |
| `/healthz` | liveness: the process is up |
| `/readyz` | task admission: `503` while a [health check](#local-health-checks) reports the machine as unavailable, once polling has been failing for longer than `readiness_grace`, or after the runner has been deleted on the instance |
| `/metrics` | Prometheus metrics |
| `/healthz` | Liveness, always `200` while the process runs |
| `/readyz` | Readiness, `503` when a [health check](#health-checks) fails, the runner cannot reach Gitea for longer than `metrics.readiness_grace` (`30s`), or the runner is shutting down |
There is **no authentication** on this listener. The default address binds to localhost only; expose it more widely (`":9101"`) only behind a firewall or a scrape-only network.
The listener has no authentication. If you expose it beyond localhost, for example with `addr: ":9101"`, protect it with a firewall.
All series are prefixed with `gitea_runner_`, among them:
The runner exports these metrics in addition to the standard Go and process metrics:
| Metric | Meaning |
| --- | --- |
| `gitea_runner_info` | always `1`, with the version and runner name as labels |
| `gitea_runner_uptime_seconds` | seconds since the daemon started |
| `gitea_runner_capacity` / `gitea_runner_job_running` / `gitea_runner_job_capacity_utilization_ratio` | configured capacity, jobs in flight, and their ratio |
| `gitea_runner_job_total` | jobs by result (`success`, `failure`, `cancelled`, `skipped`, `unknown`) |
| `gitea_runner_job_duration_seconds` | job duration histogram |
| `gitea_runner_poll_fetch_total` / `gitea_runner_poll_fetch_duration_seconds` | task fetches by result (`task`, `empty`, `error`) and their latency |
| `gitea_runner_poll_backoff_seconds` | last polling backoff interval |
| `gitea_runner_report_log_total` / `gitea_runner_report_state_total` | log and state reports by result |
| `gitea_runner_report_log_buffer_rows` | log rows buffered but not yet sent |
| `gitea_runner_client_errors_total` | RPC errors by method |
| `gitea_runner_state` | `1` for the current `state`: `busy`, `idle` or `unavailable` |
| `gitea_runner_job_running`, `gitea_runner_capacity` | Running jobs and configured capacity |
| `gitea_runner_job_capacity_utilization_ratio` | Running jobs divided by capacity |
| `gitea_runner_job_total` | Finished jobs by `status`: `success`, `failure`, `cancelled`, `skipped`, `timeout` or `unknown` |
| `gitea_runner_job_duration_seconds` | Job duration histogram |
| `gitea_runner_poll_fetch_total` | Job fetches by `result`: `task`, `empty` or `error` |
| `gitea_runner_client_errors_total` | Failed requests to Gitea by `method` and `code` |
Useful things to alert on: `gitea_runner_poll_fetch_total{result="error"}` rising (the instance is unreachable or the runner was deleted), a `capacity_utilization_ratio` pinned at `1` (the pool is too small), and a growing `report_log_buffer_rows` (log delivery is falling behind).
Consider alerts for `gitea_runner_state{state="unavailable"}`, a rising `gitea_runner_poll_fetch_total{result="error"}`, and a utilization ratio that stays at `1`, which means jobs may be waiting for a free slot.
## OpenTelemetry tracing
## OpenTelemetry
Setting `OTEL_EXPORTER_OTLP_ENDPOINT` in the runner's environment sends a trace of every job, with a span per step, to an OTLP collector over `http/protobuf`:
Set `OTEL_EXPORTER_OTLP_ENDPOINT` in the runner's environment to send traces and the metrics above to an OpenTelemetry collector. Each job has a trace with a span for each step:
```sh
OTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:4318
```
Jobs receive `TRACEPARENT`, so instrumented tools in them join the job's trace. See [docs/telemetry.md](https://gitea.com/gitea/runner/src/branch/main/docs/telemetry.md) for headers, sampling and the security notes.
Jobs receive `TRACEPARENT`, so instrumented tools can add spans to the job's trace. See [telemetry.md](https://gitea.com/gitea/runner/src/branch/main/docs/telemetry.md) for all variables and security notes.
## Local health checks
## Health checks
Health checks let a runner take itself out of rotation when the machine it runs on is not fit for work — most commonly when the disk is full.
Health checks stop a runner from accepting new jobs while its machine has a problem, such as a full disk:
```yaml
health_check:
enabled: false
enabled: true
min_free_disk_space_mb: 1024
script: ''
interval: 30s
timeout: 10s
script: /usr/local/bin/check-runner.sh
```
- while a check fails, the runner stops fetching **new** tasks; jobs already running are unaffected and finish normally;
- no check runs while a job is active — the last result is reused until the runner is idle again;
- `min_free_disk_space_mb` is measured on the filesystem holding the runner's workspaces, and defaults to 1024 MiB when omitted or zero;
- `script` is any executable. A non-zero exit, a timeout, or a failure to start marks the runner unavailable. Its result is cached for `interval` and it is killed after `timeout`;
- recovery is automatic and logged (`runner local health recovered, resuming task polling`), and the state is reflected by `/readyz`.
- The disk check measures free space on the filesystem containing `host.workdir_parent`, or `container.workdir_parent` when `container.bind_workdir` is enabled.
- `script` is optional. The runner becomes unavailable if the script exits with a non-zero status, fails to start, or runs longer than `health_check.timeout` (`10s`). The runner reuses the script's result for `health_check.interval` (`30s`).
- Checks only run while the runner is idle. Running jobs are never interrupted.
- The runner resumes accepting jobs once the checks pass again.
## Logs
The runner logs to stderr; `log.level` controls the verbosity, with `debug` and `trace` adding `file:line` to each line. Under systemd the log ends up in the journal (`journalctl -u gitea-runner`), in Docker in `docker logs`.
The runner writes logs to stderr. View them in the systemd journal or with `docker logs` for a runner container. Set `log.level` to control verbosity, from `trace` to `error`.
The runner log is not the job log: step output is streamed to Gitea and is tuned with the `runner.log_report_*` settings, while things like [post-task script](hooks/post-task-script.md) output only ever appear in the runner log.
The runner streams step output to Gitea without including it in its own log. To also keep a local copy of every job log, set a directory:
```yaml
log:
job:
dir: /var/log/gitea-runner
```
The runner writes each job log to `<start time>-task-<id>.log` in this directory and masks secrets. It keeps the files for `log.job.retention` (`168h`). Only the user running the runner can read them.
## Reporting a problem
`gitea-runner bug-report` prints the version, Go version, OS/architecture and CPU count — paste its output into an issue at [gitea/runner](https://gitea.com/gitea/runner/issues):
```bash
gitea-runner bug-report
```
```text
Runner version: nightly
Go version: go1.26.5
OS/Arch: linux/amd64
NumCPU: 8
```
To reproduce a workflow locally, without a Gitea instance and without touching the runner's registration, use `gitea-runner exec` — see the [command line reference](reference/cli.md#exec).
`gitea-runner bug-report` prints the runner version, Go version, platform and CPU count. Include its output when you open an issue at [gitea/runner](https://gitea.com/gitea/runner/issues).
+17 -21
View File
@@ -1,10 +1,10 @@
---
sidebar_position: 6
sidebar_position: 7
---
# Proxies
The runner reads the usual proxy variables from its own environment and passes them on to every job:
Set proxy variables in the runner's environment. Use `Environment=` in a systemd unit, `docker run -e`, or `env:` in Kubernetes:
```sh
http_proxy=http://proxy.example:3128
@@ -12,35 +12,31 @@ https_proxy=http://proxy.example:3128
no_proxy=gitea.internal,.example.local
```
Set them where the process is started — `Environment=` in a systemd unit, `docker run -e`, or `env:` in Kubernetes. They are used for the runner's own requests and are given to jobs in both lower and upper case.
The runner uses these variables for its own requests. It passes them to every job and service container in lowercase and uppercase. Dockerfile actions receive them as build arguments.
## What is exempted automatically
The runner adds the following to `no_proxy` so jobs reach them directly:
These are added to `no_proxy` for jobs, so they are always reached directly:
- the cache server
- The runner's cache server
- `localhost`, `127.0.0.1` and `::1`
- the job's service containers
- the Docker daemon, when it is reached over `tcp://`
- The job's service containers
- The Docker daemon, when reached over `tcp://`
The Gitea instance is **not** added. Add it to `no_proxy` yourself if it should be reached directly.
Add Gitea to `no_proxy` if jobs should reach it directly. The runner does not add it automatically.
## Overriding per job or per runner
## Changing the proxy for jobs
| Scope | Where to set it |
| --- | --- |
| one step | the step's `env:` |
| one job | the job's `container.env` |
| the whole runner | `runner.envs` in the config; a `no_proxy` set there is added to the list above instead of replacing it |
| One step | Set the step's `env:`. |
| The rest of a job | Write lowercase and uppercase variable names to `$GITHUB_ENV`. |
| Every job | Set `runner.envs`. Values in `no_proxy` are added to the list above. |
Setting proxy variables at workflow or job level (outside `container.env`) has no effect.
Proxy values set at the workflow or job level, or in `container.env`, cannot replace the runner's proxy values.
## The Docker daemon needs its own setting
## Docker daemon
Images are pulled by the daemon, not by the runner, so the daemon needs its own proxy configuration. In the `dind` images the daemon shares the container's environment and picks the variables up; for any other daemon see [the Docker documentation](https://docs.docker.com/engine/daemon/proxy/). The runner logs a warning at startup when it has a proxy configured and the daemon does not.
The Docker daemon pulls images and needs its own proxy settings. See [the Docker documentation](https://docs.docker.com/engine/daemon/proxy/). The Docker daemon bundled with the `dind` images reads the runner's proxy variables. The runner warns at startup if it has a proxy but the Docker daemon does not.
Dockerfile actions are built with these variables passed as build arguments, so their `RUN` steps can reach the network.
## Credentials
## Credentials in proxy URLs
A password inside a proxy URL is masked in job logs, but any step can still read it: the step is given the proxy URL in its environment. Prefer a proxy that does not need credentials, or one that authenticates by source address.
The runner masks passwords in proxy URLs in job logs, but every step can read them from its environment. Prefer a proxy that authenticates by source address.
+134 -15
View File
@@ -20,13 +20,13 @@ Usage:
gitea-runner [command]
Available Commands:
bug-report Print information useful when filing a bug report
cache-server Start a cache server for the cache action
config Generate, read and edit config files
daemon Run as a runner daemon
exec Run workflow locally.
help Help about any command
register Register a runner to the server
bug-report Print information useful when filing a bug report
cache-server Start a cache server for the cache action
config Generate, read and edit config files
daemon Run as a runner daemon
exec Run workflow locally.
help Help about any command
register Register a runner to the server
Flags:
-c, --config config Config file path. config subcommands fall back to config.yaml in the working directory or next to the executable
@@ -38,7 +38,7 @@ Use "gitea-runner [command] --help" for more information about a command.
## register
Registers the runner against a Gitea instance and writes the registration file. Interactive unless `--no-interactive` is given; the token can also come from `--token-file` or the `GITEA_RUNNER_REGISTRATION_TOKEN` environment variable. See [Registering a runner](../registration.md).
Registers the runner with a Gitea instance and writes the registration file. The token comes from `--token`, `--token-file` or the `GITEA_RUNNER_REGISTRATION_TOKEN` environment variable, and the command asks for the other settings unless `--no-interactive` is given. See [Registration](../registration.md).
```text
Register a runner to the server
@@ -91,8 +91,8 @@ Usage:
Flags:
--artifact-server-addr string Defines the address where the artifact server listens
--artifact-server-path string Defines the path where the artifact server stores uploads and retrieves downloads from. If not specified the artifact server will not start. (default ".")
--artifact-server-port string Defines the port where the artifact server listens (will only bind to localhost). (default "34567")
--artifact-server-path string Defines the path where the artifact server stores uploads and retrieves downloads from, outside the workspace that jobs copy. Defaults to a temporary directory removed on exit.
--artifact-server-port string Defines the port where the artifact server listens (default "34567")
--container-architecture string Architecture which should be used to run containers, e.g.: linux/amd64. If not specified, will use host default architecture. Requires Docker server API Version 1.41+. Ignored on earlier Docker server platforms.
--container-cap-add stringArray kernel capabilities to add to the workflow containers (e.g. --container-cap-add SYS_PTRACE)
--container-cap-drop stringArray kernel capabilities to remove from the workflow containers (e.g. --container-cap-drop SYS_PTRACE)
@@ -135,7 +135,7 @@ Global Flags:
## cache-server
Runs only the cache server, so several runners can share one cache. `--dir`, `--host` and `--port` override the matching `cache.*` keys; every other setting, `cache.external_secret` included, has to come from the config file. See [Caching](../cache.md#sharing-a-cache-between-runners).
Runs only the cache server, so several runners can share one cache. `--dir`, `--host` and `--port` override the matching `cache.*` keys. Every other setting, `cache.external_secret` included, has to come from the config file. See [Caching](../cache.md#sharing-a-cache-between-runners).
```text
Start a cache server for the cache action
@@ -153,18 +153,137 @@ Global Flags:
-c, --config config Config file path. config subcommands fall back to config.yaml in the working directory or next to the executable
```
## generate-config
## config
Prints the commented example configuration on stdout, which is the starting point for a config file: `gitea-runner generate-config > config.yaml`.
Writes, reads and edits config files. `config init` writes a minimal `config.yaml`, `config set`, `add` and `remove` change it in place, and `config generate` prints every option. See [Configuration](../configuration.md).
```text
Generate, read and edit config files
Usage:
gitea-runner config [flags]
gitea-runner config [command]
Available Commands:
add Append values to a list config key
generate Print the example config, which documents every option
get Print the value of a config key
init Write a minimal config file
remove Remove values from a list config key
runner-file Print the registration file path used by the runner
set Set the value of a config key
Flags:
-h, --help help for config
Global Flags:
-c, --config config Config file path. config subcommands fall back to config.yaml in the working directory or next to the executable
Use "gitea-runner config [command] --help" for more information about a command.
```
### config add
```text
Append values to a list config key
Usage:
gitea-runner config add <key> <value>... [flags]
Flags:
-h, --help help for add
Global Flags:
-c, --config config Config file path. config subcommands fall back to config.yaml in the working directory or next to the executable
```
### config generate
```text
Print the example config, which documents every option
Usage:
gitea-runner generate-config [flags]
gitea-runner config generate [flags]
Flags:
-h, --help help for generate-config
-h, --help help for generate
Global Flags:
-c, --config config Config file path. config subcommands fall back to config.yaml in the working directory or next to the executable
```
### config get
```text
Print the value of a config key
Usage:
gitea-runner config get <key> [flags]
Flags:
-h, --help help for get
Global Flags:
-c, --config config Config file path. config subcommands fall back to config.yaml in the working directory or next to the executable
```
### config init
```text
Write a minimal config file, leaving every option at its default.
Without --config it writes config.yaml in the working directory.
Usage:
gitea-runner config init [flags]
Flags:
-f, --force overwrite an existing config file
-h, --help help for init
Global Flags:
-c, --config config Config file path. config subcommands fall back to config.yaml in the working directory or next to the executable
```
### config remove
```text
Remove values from a list config key
Usage:
gitea-runner config remove <key> <value>... [flags]
Flags:
-h, --help help for remove
Global Flags:
-c, --config config Config file path. config subcommands fall back to config.yaml in the working directory or next to the executable
```
### config runner-file
```text
Print the registration file path used by the runner
Usage:
gitea-runner config runner-file [flags]
Flags:
-h, --help help for runner-file
Global Flags:
-c, --config config Config file path. config subcommands fall back to config.yaml in the working directory or next to the executable
```
### config set
```text
Set the value of a config key
Usage:
gitea-runner config set <key> <value>... [flags]
Flags:
-h, --help help for set
Global Flags:
-c, --config config Config file path. config subcommands fall back to config.yaml in the working directory or next to the executable
+25 -10
View File
@@ -7,10 +7,8 @@ description: The commented example configuration of the runner, generated from t
{/* Generated by update_runner_docs.sh from the gitea/runner sources, do not edit. */}
This is the output of `gitea-runner generate-config`. It is safe to use unmodified,
and it is the authoritative list of every option the runner understands. See
[Configuration](../configuration.md) for what the options mean and how the file is
loaded.
Every option the runner understands, with its default, as printed by `gitea-runner config generate`.
See [Configuration](../configuration.md) for how to write and load a config file.
```yaml
# Every option with its default value, all commented out. Read this file, do not copy it.
@@ -76,9 +74,9 @@ runner:
#workdir_cleanup_age: 24h
# Cadence for the idle cleanup pass. Besides the directories above, on runners that use
# docker it removes the per-job networks and volumes of jobs this runner did not live to
# tear down, which would otherwise hold a subnet of the daemon address pool and the job's
# workspace contents until the host is rebuilt. On runners with kubernetes labels it
# removes such jobs' pods and Secrets.
# tear down once unused and 10 minutes old, which would otherwise hold a subnet of the
# daemon address pool and the job's workspace contents until the host is rebuilt. On
# runners with kubernetes labels it removes such jobs' pods and Secrets.
#idle_cleanup_interval: 10m
# The base interval for periodic log flush to the Gitea instance.
# Logs may be sent earlier if the buffer reaches log_report_batch_size
@@ -91,6 +89,9 @@ runner:
# Flush logs immediately when the buffer reaches this many rows.
# This ensures bursty output (e.g., npm install) is delivered promptly.
#log_report_batch_size: 100
# The lowest severity of the runner's own messages (not step output) that are sent to the
# job log between steps: trace, debug, info, warn, error. Default if omitted: follow log.level.
#log_report_level: info
# The interval for reporting task state (step status, timing) to the Gitea instance.
# State is also reported on step transitions (start/stop) and after each log flush.
#state_report_interval: 5s
@@ -103,11 +104,12 @@ runner:
# it replaces https://github.com with the value here, which is useful for some special network environments.
#github_mirror: ''
# Extra HTTP headers for runner API requests to Gitea, e.g. reverse proxy authentication.
# Not used for action clones, artifacts, job summaries or jobs. Runner headers take precedence.
# Not used for action downloads, artifacts, job summaries or jobs. Runner headers take precedence.
# Dropped on redirects to another hostname, or to HTTP if the instance URL uses HTTPS.
#extra_headers:
# X-Proxy-Token: 'example'
# When true (the default), fetch only the requested ref of an action repository (e.g. actions/checkout@v4) at depth 1 instead of cloning every branch's full history.
# When true (the default), clone only the requested ref at depth 1 instead of every branch's full history. Actions download as
# archives, so this applies to reusable workflows and to actions whose host serves no archive of them.
# Set to false to clone the full history.
#action_shallow_clone: true
# When true (the default), inject the ACT=true environment variable into jobs.
@@ -205,7 +207,8 @@ cache:
# secret_key_file: "/run/secrets/cache-s3-secret-key"
# When true, reuse a cached action instead of fetching from the remote on every job.
# A moved tag (e.g. a re-tagged "v6") or an updated branch stays at the cached commit
# until its cache entry expires or is manually removed.
# until its cache entry expires or is manually removed. A cached action whose files changed size
# since it was downloaded is still fetched again, unless an older runner cached it without sizes.
#offline_mode: false
# Serve the actions cache service v2 API. The actions that use it fall back to v1 on any host
# they do not take for GitHub, so reaching it means editing that check out of their own bundle,
@@ -316,6 +319,18 @@ kubernetes:
#pod_template: {}
# Pod templates merged over pod_template, in runs-on order, for jobs whose runs-on has the label they are keyed by.
#pod_templates: {}
# Have job pods fetch directories, such as actions, and copies of 64 KiB or more gzipped from the runner over the pod network,
# instead of the runner streaming them through the API server. An init container copies gitea-runner from image into each job pod.
# api: through the API server (default). auto: over the pod network, through the API server for a job pod that
# cannot reach the runner. direct: over the pod network only, so a broken setup fails jobs instead of quietly
# costing API server traffic again.
#transfer:
# mode: api
# # Address and port the runner listens on and job pods reach it at, the outbound IP and a free port when empty.
# host: ""
# port: 0
# # Image with gitea-runner on its PATH, the runner pod's image when it has a single container and runs in namespace.
# image: ""
# Optional local task-admission checks. Disabled by default. When enabled, low
# disk space or a failing script pauses new task fetching; existing jobs continue.
+23 -53
View File
@@ -2,84 +2,54 @@
sidebar_position: 2
---
# Registering a runner
# Registration
A runner has to be registered before it can pick up jobs: registration is what tells the runner where to fetch jobs from, and what gives the Gitea instance a stable identity for the runner.
Register a runner once with a Gitea instance to get its identity and credentials.
## Obtain a registration token
## Registration token
Registration tokens are issued by the Gitea instance and can be scoped to the whole instance, an organization/user, or a single repository. See [Actions runners](/usage/actions/runner) for where to find them in the UI and via the API.
Gitea issues registration tokens for the whole instance, an organization, a user, or a single repository. A runner accepts jobs within the token's scope. To find a token, see [Gitea Runner](/usage/actions/runner). A token can register any number of runners until it is reset.
One token can register any number of runners and stays valid until it is reset in the UI or through the API. Instance administrators can also hand Gitea a fixed token with `GITEA_RUNNER_REGISTRATION_TOKEN` / `GITEA_RUNNER_REGISTRATION_TOKEN_FILE` at startup, which is what makes disposable runners practical.
## Interactive registration
## Registering
```bash
gitea-runner register
# or with a config file
gitea-runner -c config.yaml register
gitea-runner register --instance https://gitea.example.com --token <token>
```
The runner asks for:
- the instance URL, e.g. `https://gitea.com/` or `http://192.168.8.8:3000/` — use the instance's `ROOT_URL`, not `localhost`, when Gitea and the runner are in different containers or hosts;
- the registration token;
- the runner name, defaulting to the hostname;
- the [labels](labels.md), defaulting to the built-in list, or to `runner.labels` when the config file sets it.
## Non-interactive registration
The runner asks for its name. It also asks for its [labels](labels.md) unless you set `runner.labels`. To skip the prompts, use `--no-interactive` and pass the values as flags:
```bash
gitea-runner register --no-interactive \
--instance <instance_url> \
--token <registration_token> \
--name <runner_name> \
--labels <runner_labels>
--instance https://gitea.example.com \
--token <token> \
--name my-runner \
--labels ubuntu-latest:docker://docker.gitea.com/runner-images:ubuntu-latest
```
The token can be kept off the command line, where it would show up in the process list and in shell history:
Set `--instance` to the instance's `ROOT_URL`. `localhost` does not work from job containers.
- `--token-file <path>` reads it from a file, e.g. a Docker or Kubernetes secret;
- the `GITEA_RUNNER_REGISTRATION_TOKEN` environment variable is used when neither flag is given.
To keep the token out of shell history and the process list, pass it with `--token-file <path>` or through the `GITEA_RUNNER_REGISTRATION_TOKEN` environment variable.
## The registration file
A successful registration writes a `.runner` file (`runner.file` in the config) into the current working directory. It holds the runner's identity and its API credentials, so:
Registration writes the runner's credentials to `.runner` in the working directory, or to the path in `runner.file`:
- do not edit it by hand, and do not copy it to a second machine;
- back it up or put it on a persistent volume, otherwise a recreated runner registers as a *new* runner and leaves a stale entry behind — and fails outright if the token has been reset in the meantime;
- if it is lost or corrupted, delete it and register again.
Each runner process needs its own registration file. Two processes sharing one file are indistinguishable to Gitea and cancel each other's jobs, so the runner takes an advisory lock on `<runner.file>.lock` and refuses to start when another process already holds it. The lock is released by the operating system when the process exits, including after a crash. When the lock cannot be created at all — for example on a read-only mount — the runner logs a warning and starts without the guard.
- Keep it on persistent storage. If the file is lost, register again as a new runner.
- Give each runner process its own registration file. A second process using the same file refuses to start.
- Treat it as a secret.
## Ephemeral runners
An ephemeral runner accepts exactly one job and then exits. Once a job has been assigned, its credentials are revoked, so it cannot poll for more work before the job's untrusted code runs; it can still report progress until the job finishes.
This is how organization-wide or instance-wide runners can be offered without trusting every repository that may use them, provided each runner is a fresh VM or container.
An ephemeral runner accepts exactly one job, then exits. Its credentials are revoked as soon as the job is assigned, so the job cannot use them to accept more work. Start each ephemeral runner in a fresh VM or container when running jobs from repositories you do not fully trust.
```bash
gitea-runner register --ephemeral
gitea-runner register --no-interactive --ephemeral --instance <instance_url> --token <registration_token>
gitea-runner register --ephemeral --instance https://gitea.example.com --token <token>
```
With the Docker images, set `GITEA_RUNNER_EPHEMERAL=1` instead; no `/data` volume is needed, since the credentials are single-use:
With the Docker images, set `GITEA_RUNNER_EPHEMERAL=1`. To start a runner for each job, listen for Gitea's `workflow_job` [webhook](/usage/repository/webhooks), which fires when a job is queued.
```bash
docker run -d --name my_runner \
-e GITEA_INSTANCE_URL=<instance_url> \
-e GITEA_RUNNER_REGISTRATION_TOKEN=<registration_token> \
-e GITEA_RUNNER_EPHEMERAL=1 \
-v /var/run/docker.sock:/var/run/docker.sock \
docker.io/gitea/runner:nightly
```
`gitea-runner daemon --once` also exits after one job, but its credentials stay valid.
Because a fresh registration is required for every job, ephemeral runners are usually started on demand from the `workflow_job` webhook, which fires when a job is queued.
## Removing a runner
`--ephemeral` is stricter than `daemon --once`: `--once` also stops after one job, but its credentials stay valid for as long as the runner is registered.
## Re-registering and unregistering
Running `register` again in a directory that already has a registration file asks whether to overwrite it. To retire a runner, delete it in the Gitea UI (or via the API) and remove its registration file; the daemon shuts itself down once the server no longer knows it.
Changing labels does not require re-registration — see [Labels](labels.md#registration-versus-config-labels).
Delete the runner in Gitea's runner settings, then delete its registration file. The runner exits when it detects that Gitea has removed its registration.
+11 -38
View File
@@ -1,10 +1,10 @@
---
sidebar_position: 8
sidebar_position: 9
---
# Upgrading
A runner upgrade is a binary or image replacement: stop the daemon, swap it, start it again. The registration file stays valid across versions, so a runner keeps its identity and does not have to be registered again.
Stop the runner, replace the binary or image, and start it again. The registration file stays valid, so you do not need to register the runner again.
```bash
sudo systemctl stop gitea-runner
@@ -12,42 +12,15 @@ sudo install -m 0755 gitea-runner-<version>-linux-amd64 /usr/local/bin/gitea-run
sudo systemctl start gitea-runner
```
With `runner.shutdown_timeout` set, `stop` lets the jobs in flight finish first; without it, they are cancelled and Gitea reschedules them.
Stopping the runner cancels running jobs unless `runner.shutdown_timeout` gives them time to finish. The runner logs a warning for unknown configuration keys and ignores them. Run `gitea-runner config generate` to list all configuration options.
Config files are read leniently: keys the running version does not know are reported as a warning and ignored, so one file can be shared by runners of different versions. Compare your file with `gitea-runner generate-config` after an upgrade to pick up new options.
## Breaking changes in 5.0
## 3.0
- **Git 2.34.1 or later is required** on the runner and in job images that use `builtin:checkout`.
- **`builtin:checkout` works like `actions/checkout`.** It removes untracked and ignored files unless `clean: false` is set. It fails if the triggering tag moved after the workflow started.
- **Configuration values expand `${NAME}`** from the runner's environment. The configuration fails to load if `NAME` is unset. Write `$${NAME}` for a literal `${NAME}`, for example in a shell command in a Kubernetes `pod_template`.
- **Jobs require `runs-on`.** A job without `runs-on` fails, as it does on GitHub. Jobs that call a reusable workflow are exempt.
- **OpenTelemetry metrics follow the semantic conventions**, which changed their names, units and attributes. See [telemetry.md](https://gitea.com/gitea/runner/src/branch/main/docs/telemetry.md#metrics). On `/metrics`, `gitea_runner_client_errors_total` gained a `code` label, and `gitea_runner_job_total` counts jobs stopped by `runner.timeout` as `timeout`.
- **Unprivileged runners drop more workflow options.** Unless `container.privileged` is set, `--log-driver`, `--log-opt`, `--oom-score-adj` and `--oom-kill-disable` are removed from a job's `container.options` with a warning.
### Breaking
- **Container options from workflows are filtered.** While `container.privileged` is disabled, options in a job's `container.options` that would escape the container are stripped with a warning: `--pid`, `--ipc`, `--uts`, `--cgroupns`, `--userns`, `--cap-add`, `--security-opt`, `--device`, `--device-cgroup-rule`, `--gpus`, `--volumes-from`, `--runtime`, `--cgroup-parent`, `--sysctl`. Workflows that relied on them need a runner with privileged mode enabled.
- **One process per registration file.** The daemon takes an advisory lock on `<runner.file>.lock` and refuses to start when another process already uses that file. Setups that started several runners from one directory must give each its own `runner.file` (or its own working directory).
- **Cache service v2 is served and enabled by default.** `actions/[email protected]` and later, and the stock `actions/upload-artifact` / `download-artifact` from `v4.4.0` on, reach it through a patch the runner applies to the action's own bundle. Jobs are pointed at the runner's cache server as their results service, which then forwards artifact calls to Gitea. Set `cache.v2: false` to keep to v1.
### Also new
- [Job hooks](hooks/job-hooks.md) (`runner.hooks.job_started` / `job_completed`) running inside the job environment.
- [Proxy variables](proxy.md) propagated to jobs, service containers and Dockerfile action builds.
- Secrets are masked in the log even when a job prints them in an encoded form.
- The tool cache can be relocated, and runner-managed paths can be mounted over, via `container.options`.
## 2.0
### Breaking
- **`DOCKER_USERNAME` / `DOCKER_PASSWORD` are no longer implicit pull credentials.** They used to be attached to every image pull, which sent private-registry credentials to Docker Hub for public images. They are ordinary secrets now. Migrate to:
- `container.credentials` (and service `credentials`) in the workflow for private images;
- a `docker login` performed on the runner host, or a [job hook](hooks/job-hooks.md), for private `uses: docker://...` actions.
- **`container.force_pull` now defaults to `false`** in the generated example config, so images already present are reused unless you ask for a pull.
- **No environment variable overrides for the config.** `GITEA_DEBUG`, `GITEA_TRACE`, `GITEA_RUNNER_CAPACITY`, `GITEA_RUNNER_FILE`, `GITEA_RUNNER_ENVIRON` and `GITEA_RUNNER_ENV_FILE` are ignored; use a config file. The Docker images' [entrypoint variables](installation/docker.md#entrypoint-environment-variables) are unaffected.
### Also new
- [Post-task script](hooks/post-task-script.md) (`runner.post_task_script`) for host housekeeping between jobs.
- [Health checks](monitoring.md#local-health-checks) (`health_check.*`) that pause task admission on low disk space or a failing script.
- `register --token-file`, and `GITEA_RUNNER_LABELS` honoured by `daemon`, so labels can change without re-registering.
- `jobs.<job_id>.timeout-minutes` and `jobs.<job_id>.continue-on-error` support, job summaries, shallow action clones (`runner.action_shallow_clone`), `ssh://` action URLs, IPv4/IPv6 options for auto-created networks, `--platform` and `--pull` in `container.options`, `cache.external_secret_file`, a GitHub-style "Set up job" log section, and pre/post entrypoints of Docker actions.
## Downgrading
Downgrading a runner is possible — the registration file format has not changed — but a config file written for a newer version may carry keys the older one ignores, and features such as cache service v2 stop being served, so jobs that came to rely on them fail. Test a downgrade with a spare runner before doing it on a busy pool.
Release notes for every version are on the [release page](https://gitea.com/gitea/runner/releases).
@@ -38,7 +38,7 @@ Use "gitea-runner [command] --help" for more information about a command.
## register
Registers the runner against a Gitea instance and writes the registration file. Interactive unless `--no-interactive` is given; the token can also come from `--token-file` or the `GITEA_RUNNER_REGISTRATION_TOKEN` environment variable. See [Registering a runner](../registration.md).
Registers the runner with a Gitea instance and writes the registration file. The token comes from `--token`, `--token-file` or the `GITEA_RUNNER_REGISTRATION_TOKEN` environment variable, and the command asks for the other settings unless `--no-interactive` is given. See [Registration](../registration.md).
```text
Register a runner to the server
@@ -132,7 +132,7 @@ Global Flags:
## cache-server
Runs only the cache server, so several runners can share one cache. `--dir`, `--host` and `--port` override the matching `cache.*` keys; every other setting, `cache.external_secret` included, has to come from the config file. See [Caching](../cache.md#sharing-a-cache-between-runners).
Runs only the cache server, so several runners can share one cache. `--dir`, `--host` and `--port` override the matching `cache.*` keys. Every other setting, `cache.external_secret` included, has to come from the config file. See [Caching](../cache.md#sharing-a-cache-between-runners).
```text
Start a cache server for the cache action
@@ -7,10 +7,8 @@ description: The commented example configuration of the runner, generated from t
{/* Generated by update_runner_docs.sh from the gitea/runner sources, do not edit. */}
This is the output of `gitea-runner generate-config`. It is safe to use unmodified,
and it is the authoritative list of every option the runner understands. See
[Configuration](../configuration.md) for what the options mean and how the file is
loaded.
Every option the runner understands, with its default, as printed by `gitea-runner generate-config`.
See [Configuration](../configuration.md) for how to write and load a config file.
```yaml
# Example configuration file, it's safe to copy this as the default config file without any modification.
@@ -5,7 +5,7 @@ sidebar_position: 2
# Install with Docker
The official images are published on [Docker Hub](https://hub.docker.com/r/gitea/runner/tags) as `docker.io/gitea/runner`.
`latest` is the newest release and the `3` tag follows the newest `3.x` release; `nightly` is built from the `main` branch, and every release is also tagged with its exact version.
The `3` tag follows the newest `3.x` release; `nightly` is built from the `main` branch, and every release is also tagged with its exact version.
In the container the registration and the daemon are combined: the entrypoint registers the runner on first start (when no registration file exists yet) and then execs `gitea-runner daemon`.
@@ -15,9 +15,9 @@ All flavours contain the same `gitea-runner` binary and differ only in how a Doc
| Tag | Base image | Docker daemon | Supervisor | Runs as |
| --- | --- | --- | --- | --- |
| `latest`, `3`, `3.0`, `<version>` | `alpine` | none, you provide one | `tini` | `root` |
| `latest-dind`, `3-dind` | `docker:dind` | bundled, needs `--privileged` | `s6` | `root` |
| `latest-dind-rootless`, `3-dind-rootless` | `docker:dind-rootless` | bundled, rootless | `s6` | `rootless` (UID 1000) |
| `3`, `3.5`, `<version>` | `alpine` | none, you provide one | `tini` | `root` |
| `3-dind` | `docker:dind` | bundled, needs `--privileged` | `s6` | `root` |
| `3-dind-rootless` | `docker:dind-rootless` | bundled, rootless | `s6` | `rootless` (UID 1000) |
The rootless flavour's UID is fixed at 1000 by the upstream base image, and its daemon always listens on `/run/user/1000/docker.sock`, so `--user 1001` does not work. To talk to a *host* rootless daemon under another UID, use the basic flavour and bind-mount that daemon's socket instead.
@@ -38,7 +38,7 @@ Use "gitea-runner [command] --help" for more information about a command.
## register
Registers the runner against a Gitea instance and writes the registration file. Interactive unless `--no-interactive` is given; the token can also come from `--token-file` or the `GITEA_RUNNER_REGISTRATION_TOKEN` environment variable. See [Registering a runner](../registration.md).
Registers the runner with a Gitea instance and writes the registration file. The token comes from `--token`, `--token-file` or the `GITEA_RUNNER_REGISTRATION_TOKEN` environment variable, and the command asks for the other settings unless `--no-interactive` is given. See [Registration](../registration.md).
```text
Register a runner to the server
@@ -135,7 +135,7 @@ Global Flags:
## cache-server
Runs only the cache server, so several runners can share one cache. `--dir`, `--host` and `--port` override the matching `cache.*` keys; every other setting, `cache.external_secret` included, has to come from the config file. See [Caching](../cache.md#sharing-a-cache-between-runners).
Runs only the cache server, so several runners can share one cache. `--dir`, `--host` and `--port` override the matching `cache.*` keys. Every other setting, `cache.external_secret` included, has to come from the config file. See [Caching](../cache.md#sharing-a-cache-between-runners).
```text
Start a cache server for the cache action
@@ -153,18 +153,121 @@ Global Flags:
-c, --config config Config file path. config subcommands fall back to config.yaml in the working directory or next to the executable
```
## generate-config
## config
Prints the commented example configuration on stdout, which is the starting point for a config file: `gitea-runner generate-config > config.yaml`.
Writes, reads and edits config files. `config init` writes a minimal `config.yaml`, `config set`, `add` and `remove` change it in place, and `config generate` prints every option. See [Configuration](../configuration.md).
```text
Generate, read and edit config files
Usage:
gitea-runner config [flags]
gitea-runner config [command]
Available Commands:
add Append values to a list config key
generate Print the example config, which documents every option
get Print the value of a config key
init Write a minimal config file
remove Remove values from a list config key
set Set the value of a config key
Flags:
-h, --help help for config
Global Flags:
-c, --config config Config file path. config subcommands fall back to config.yaml in the working directory or next to the executable
Use "gitea-runner config [command] --help" for more information about a command.
```
### config add
```text
Append values to a list config key
Usage:
gitea-runner config add <key> <value>... [flags]
Flags:
-h, --help help for add
Global Flags:
-c, --config config Config file path. config subcommands fall back to config.yaml in the working directory or next to the executable
```
### config generate
```text
Print the example config, which documents every option
Usage:
gitea-runner generate-config [flags]
gitea-runner config generate [flags]
Flags:
-h, --help help for generate-config
-h, --help help for generate
Global Flags:
-c, --config config Config file path. config subcommands fall back to config.yaml in the working directory or next to the executable
```
### config get
```text
Print the value of a config key
Usage:
gitea-runner config get <key> [flags]
Flags:
-h, --help help for get
Global Flags:
-c, --config config Config file path. config subcommands fall back to config.yaml in the working directory or next to the executable
```
### config init
```text
Write a minimal config file, leaving every option at its default.
Without --config it writes config.yaml in the working directory.
Usage:
gitea-runner config init [flags]
Flags:
-f, --force overwrite an existing config file
-h, --help help for init
Global Flags:
-c, --config config Config file path. config subcommands fall back to config.yaml in the working directory or next to the executable
```
### config remove
```text
Remove values from a list config key
Usage:
gitea-runner config remove <key> <value>... [flags]
Flags:
-h, --help help for remove
Global Flags:
-c, --config config Config file path. config subcommands fall back to config.yaml in the working directory or next to the executable
```
### config set
```text
Set the value of a config key
Usage:
gitea-runner config set <key> <value>... [flags]
Flags:
-h, --help help for set
Global Flags:
-c, --config config Config file path. config subcommands fall back to config.yaml in the working directory or next to the executable
@@ -7,10 +7,8 @@ description: The commented example configuration of the runner, generated from t
{/* Generated by update_runner_docs.sh from the gitea/runner sources, do not edit. */}
This is the output of `gitea-runner generate-config`. It is safe to use unmodified,
and it is the authoritative list of every option the runner understands. See
[Configuration](../configuration.md) for what the options mean and how the file is
loaded.
Every option the runner understands, with its default, as printed by `gitea-runner config generate`.
See [Configuration](../configuration.md) for how to write and load a config file.
```yaml
# Every option with its default value, all commented out. Read this file, do not copy it.
@@ -0,0 +1,107 @@
---
sidebar_position: 5
---
# Caching
Every runner starts its own cache server, so `actions/cache` works without any configuration. Cache entries are local to that runner: two runners do not share a cache unless you make them.
## Cache service v2
`actions/[email protected]` and later can use the *cache service v2* API. The runner serves it from the same store as v1, on by default, and it also works behind a shared cache server. Turn it off with:
```yaml
cache:
v2: false
```
Those actions refuse any host they do not take for GitHub. Rather than misreport the server URL, the runner edits that check out of the action's own JavaScript bundle and keeps the untouched copy beside it; a bundle it does not recognise is left alone and keeps to v1. The same edit lets the stock `actions/upload-artifact` and `actions/download-artifact` work from `v4.4.0` on, without the `gitea-upload-artifact` fork.
Because `ACTIONS_RESULTS_URL` names one origin that has to serve the whole results API, the cache server also forwards the artifact half of it to the Gitea instance the job belongs to. Jobs are therefore pointed at the cache server, which is what makes clients such as `docker buildx` find the cache service instead of receiving a 404 from Gitea.
## Where cache blobs are stored
```yaml
cache:
enabled: true
dir: /var/lib/gitea-runner/cache # default: $HOME/.cache/actcache
```
The directory grows with use; entries are evicted as they expire, so give it a filesystem with room to spare and monitor it like any other build cache.
## Dockerized runners
When the runner itself runs in a container and creates a network per job, the address it detects for its own cache server is often unreachable from the job containers. `actions/cache` then fails with:
```text
Failed to restore: getCacheEntry failed: connect ETIMEDOUT IP:PORT
```
Pin the address and the port the job containers should use, and make that endpoint reachable:
1. take an address of the host that job containers can reach, and a free port on it;
2. configure them:
```yaml
cache:
enabled: true
dir: ""
host: "192.168.8.17"
port: 8088
```
3. publish the port when starting the runner container:
```bash
docker run -d --name gitea-runner -p 8088:8088 ... docker.io/gitea/runner:4
```
Putting the runner and the job containers on one shared `container.network` instead works too, and then the auto-detected address is reachable.
## Sharing a cache between runners
Run one dedicated cache server that every runner points at.
1. Config for the cache server host:
```yaml
cache:
dir: /data/actcache
port: 8088
external_secret: "replace-with-a-strong-random-secret"
# external_secret_file: /run/secrets/cache-secret # or keep it out of this file
```
2. Start it:
```bash
gitea-runner -c cache-server-config.yaml cache-server
```
3. On every runner:
```yaml
cache:
external_server: "http://cache-host:8088/"
external_secret: "replace-with-a-strong-random-secret" # must match the server
```
The secret authenticates runners against the shared server and must be identical on all of them; generate one with `openssl rand -hex 32`. Setting both `external_secret` and `external_secret_file` is an error.
`cache-server` accepts `--dir`, `--host` and `--port`, which override the corresponding `cache.*` keys. Every other setting, `external_secret` included, has to come from the config file.
### Alternatives
- **Shared filesystem** — mount the same NFS/CIFS share on every runner and point `cache.dir` at it. Simpler, but repositories are less isolated from each other than behind a cache server.
- **Object storage** — mount S3 or MinIO as a FUSE filesystem, e.g. with [s3fs](https://github.com/s3fs-fuse/s3fs-fuse) or [goofys](https://github.com/kahing/goofys), and set `cache.dir` to the mount point.
## Action repository cache
Actions pulled by `uses:` are cached too, and by default refreshed on every job so a moved tag is picked up. To pin them to what has already been fetched:
```yaml
cache:
offline_mode: true
```
A re-tagged `v6` or an updated branch then stays at the cached commit until its entry expires or is removed. Combined with `runner.action_shallow_clone` (on by default, fetching only the requested ref at depth 1), this keeps job startup fast on runners with limited bandwidth.
@@ -0,0 +1,138 @@
---
sidebar_position: 3
---
# Configuration
The runner is configured with a single YAML file. It is optional: without one, the built-in defaults apply, which are the same as an empty YAML document and safe to run with.
```bash
gitea-runner generate-config > config.yaml
gitea-runner -c config.yaml register
gitea-runner -c config.yaml daemon
```
`-c` / `--config` is a global flag and is accepted by every command that loads configuration (`register`, `daemon`, `cache-server`). The generated file is fully commented and is reproduced in [Example configuration](reference/config-example.md).
:::warning No environment variable overrides
The runner process is configured only through the YAML file. Earlier releases let a few variables (`GITEA_DEBUG`, `GITEA_TRACE`, `GITEA_RUNNER_CAPACITY`, `GITEA_RUNNER_FILE`, `GITEA_RUNNER_ENVIRON`, `GITEA_RUNNER_ENV_FILE`) override parts of the config; those overrides have been removed.
The variables understood by the Docker images belong to their [entrypoint](installation/docker.md#entrypoint-environment-variables), not to the runner, and `GITEA_RUNNER_LABELS` / `GITEA_RUNNER_REGISTRATION_TOKEN` are read by the corresponding CLI flags only.
:::
Values with a duration type accept Go duration strings such as `30s`, `10m`, `3h`.
## `log`
Controls the runner's own log, not how step output is streamed to the UI.
| Option | Default | Description |
| --- | --- | --- |
| `log.level` | `info` | `trace`, `debug`, `info`, `warn`, `error`, `fatal` or `panic`. `trace` and `debug` add the caller's `file:line`. |
## `runner`
| Option | Default | Description |
| --- | --- | --- |
| `file` | `.runner` | path of the registration file. Each runner process needs its own. |
| `capacity` | `1` | jobs executed concurrently. With an empty `container.network`, every concurrent docker job takes a subnet from the daemon's address pool, so a high capacity can exhaust it (see `default-address-pools` in the daemon config). |
| `envs` | – | extra environment variables given to every job. |
| `env_file` | `.env` | same, read from a file; ignored when empty or missing. |
| `timeout` | `3h` | maximum job duration. Gitea has its own timeout (3h by default) and may stop the job earlier. |
| `shutdown_timeout` | `0s` | how long a shutdown waits for running jobs before cancelling them. |
| `insecure` | `false` | skip TLS verification of the Gitea instance. |
| `fetch_timeout` | `5s` | timeout of a single job fetch. |
| `fetch_interval` | `2s` | base polling interval. |
| `fetch_interval_max` | `5s` | upper bound of the exponential backoff applied while idle. `0`, or the same value as `fetch_interval`, disables the backoff. |
| `labels` | see [Labels](labels.md) | labels used at registration, and by `daemon` when the flag is absent. |
| `github_mirror` | – | replaces `https://github.com` when actions are pulled and the instance's `DEFAULT_ACTIONS_URL` points at GitHub. |
| `action_shallow_clone` | `true` | fetch only the requested ref of an action repository at depth 1 instead of its full history. |
| `set_act_env` | `true` | inject `ACT=true` into jobs. Set to `false` so workflows gated on `if: ${{ !env.ACT }}` behave as they do on GitHub. |
| `allocate_pty` | `false` | allocate a pseudo-TTY per step. Enable only when a job needs an interactive terminal; tools like `docker build` then write redrawing progress frames into the log. |
| `workdir_cleanup_age` | `24h` | age at which stale task workspaces and orphaned host-mode scratch directories are removed while idle. |
| `idle_cleanup_interval` | `10m` | cadence of the idle cleanup pass. Setting either this or `workdir_cleanup_age` to `0` disables all idle cleanup. |
| `post_task_script` | – | host script run after each task's cleanup, see [Post-task script](hooks/post-task-script.md). |
| `post_task_script_timeout` | `5m` | hard limit for that script. |
| `hooks.job_started` / `hooks.job_completed` | – | scripts run inside the job environment, see [Job hooks](hooks/job-hooks.md). |
Log and state reporting can be tuned when the UI updates too slowly or the instance sees too many requests:
| Option | Default | Description |
| --- | --- | --- |
| `log_report_interval` | `5s` | base interval of the periodic log flush. |
| `log_report_max_latency` | `3s` | maximum time a single log row waits. Only has an effect below `log_report_interval`. |
| `log_report_batch_size` | `100` | flush immediately once this many rows are buffered, so bursty output arrives promptly. |
| `state_report_interval` | `5s` | interval of task state reports. State is also sent on every step transition. |
| `report_close_timeout` | `10s` | per-attempt deadline for the final log and state flush of a finished job. |
### Idle cleanup
While no job is running, the runner cleans up after earlier ones:
- stale task workspaces older than `workdir_cleanup_age` are removed when `container.bind_workdir` is enabled. Only purely numeric subdirectories of `container.workdir_parent` are treated as workspaces, and the path is assumed not to be shared with another runner;
- orphaned host-mode scratch directories are removed on the same schedule;
- per-job docker networks left behind by jobs the runner did not live to tear down are removed. They are recognised by the `com.gitea.runner.uuid` label carrying this runner's uuid, so leftovers of other runners on the same daemon are left alone. Without this, each leaked network keeps holding a subnet of the daemon's address pool.
## `cache`
See [Caching](cache.md) for the full picture, including shared cache servers.
| Option | Default | Description |
| --- | --- | --- |
| `enabled` | `true` | run the built-in cache server used by `actions/cache` and friends. |
| `dir` | `$HOME/.cache/actcache` | where cache blobs are stored. Ignored with `external_server`. |
| `host` | – | address job containers use to reach this runner's cache server. Empty means auto-detect; `0.0.0.0` is not valid. |
| `port` | `0` | port of the built-in server, `0` picks a free one. |
| `external_server` | – | URL of a shared `cache-server` to use instead of a local one. |
| `external_secret` | – | shared secret, required with `external_server`; must be identical everywhere. Generate with `openssl rand -hex 32`. |
| `external_secret_file` | – | read that secret from a file instead. Setting both is an error. |
| `offline_mode` | `false` | reuse a cached action instead of fetching it on every job. A moved tag or updated branch then stays at the cached commit until the entry expires or is removed. |
| `v2` | `true` | serve the cache service v2 API used by `actions/[email protected]` and later. |
## `container`
Applies to jobs that run in containers.
| Option | Default | Description |
| --- | --- | --- |
| `network` | – | network the job container joins: `host`, `bridge`, or a custom network name. Empty means the runner creates one per job. `network_mode` is still accepted for old configs. |
| `network_create_options.enable_ipv4` / `enable_ipv6` | Docker defaults | only apply to auto-created networks. IPv6 additionally requires `dockerd --ipv6`. |
| `privileged` | `false` | run job containers privileged; required for Docker-in-Docker inside jobs. |
| `options` | – | extra `docker run` options, e.g. `--add-host=my.gitea.url:host-gateway`. A volume declared here replaces the one the runner mounts on the same container path, which is how the tool cache can be kept on the host (`--volume /host/toolcache:/opt/hostedtoolcache`); its source must also be allowed by `valid_volumes`. |
| `workdir_parent` | `/workspace` | parent directory of a job's working directory inside the container. A leading `/` is trimmed and re-added. |
| `valid_volumes` | `[]` | volumes and bind mounts a job may mount, as [glob](https://github.com/gobwas/glob) patterns. `[]` forbids all, `['**']` allows all. |
| `docker_host` | – | override the docker host. Empty auto-detects it, `-` auto-detects it but does not mount the socket into job containers. |
| `force_pull` | `false` | pull images even when present. Images pinned by digest are never re-pulled, and a failed pull with a local copy available only warns. |
| `force_rebuild` | `false` | rebuild local action images even when present. |
| `require_docker` | `false` | always require a reachable daemon, even for host-only labels. |
| `docker_timeout` | `0s` | how long to wait for the daemon to become reachable. |
| `bind_workdir` | `false` | bind-mount the workspace from the host instead of using a docker volume, so jobs can bind-mount it by its own path into containers they start (`.:/app` in `docker compose`). Not needed when workflows use `GITEA_DOCKER_WORKSPACE`. The parent directory must then be mounted into the runner container. |
:::note Privileged mode and workflow container options
A workflow's own `jobs.<job_id>.container.options` are untrusted input. While `container.privileged` is disabled, the options that would break out of the container are stripped with a warning in the job log: `--pid`, `--ipc`, `--uts`, `--cgroupns`, `--userns`, `--cap-add`, `--security-opt`, `--device`, `--device-cgroup-rule`, `--gpus`, `--volumes-from`, `--runtime`, `--cgroup-parent` and `--sysctl`. They are honoured once privileged mode is enabled, because the operator has then opted into host access.
:::
## `host`
| Option | Default | Description |
| --- | --- | --- |
| `host.workdir_parent` | `$HOME/.cache/act/` | parent directory of a job's working directory for host-mode jobs. |
## `health_check` and `metrics`
Both are covered in [Monitoring](monitoring.md).
| Option | Default | Description |
| --- | --- | --- |
| `health_check.enabled` | `false` | pause fetching new tasks while the machine looks unhealthy. |
| `health_check.min_free_disk_space_mb` | `1024` | minimum free space on the filesystem holding the workspaces. |
| `health_check.script` | – | extra executable; a non-zero exit, a timeout or a start failure marks the runner unavailable. |
| `health_check.interval` | `30s` | how long a result is cached. |
| `health_check.timeout` | `10s` | maximum script runtime. |
| `metrics.enabled` | `false` | serve `/metrics`, `/healthz` and `/readyz`. |
| `metrics.addr` | `127.0.0.1:9101` | listen address. There is no authentication, so only expose it behind a firewall. |
| `metrics.readiness_grace` | `30s` | how long consecutive polling failures may last before `/readyz` returns 503. |
## Reloading
The runner reads its configuration at startup only. Restart the process after a change — with `shutdown_timeout` set, running jobs are given that much time to finish first.
@@ -0,0 +1,74 @@
---
sidebar_position: 1
---
# Job hooks
Job hooks are operator-provided scripts that run **inside the job environment**, before the job's first step and after its last one. They are the equivalent of GitHub's [job hooks](https://docs.github.com/en/actions/how-tos/manage-runners/self-hosted-runners/run-scripts):
```yaml
runner:
hooks:
job_started: /hooks/started.sh
job_completed: /hooks/completed.sh
```
| Setting | Runs |
| --- | --- |
| `runner.hooks.job_started` | before the job's first step, before any action is downloaded |
| `runner.hooks.job_completed` | after the job's last post step, while the job environment is still up |
`ACTIONS_RUNNER_HOOK_JOB_STARTED` and `ACTIONS_RUNNER_HOOK_JOB_COMPLETED` are read from the runner's environment (`runner.envs`, `runner.env_file`) when the settings are unset, so a configuration carried over from `actions/runner` keeps working. The settings take precedence. A workflow cannot point the runner at a different hook: those variables are only read from the runner's own environment, never from the job's.
Both hooks are **synchronous** and block the job while they run, and a non-zero exit from either one fails the job. There is no `continue-on-error` and no per-hook timeout — the job's `runner.timeout` is the only bound. Run anything long in the background from within the hook.
Use them for per-job setup that no workflow should have to carry: registry logins, mirror configuration, or masking runner-wide secrets with `::add-mask::`.
## Where they run
The hooks run where the job's steps run: inside the job container, or on the host in host mode. The paths are resolved *there*, so the script has to exist in the job image or on the host — a path that only exists on the runner host is not visible to a containerized job. For host-wide cleanup after the job environment is gone, use the [post-task script](post-task-script.md) instead.
:::note
This is a deliberate difference from `actions/runner`, which runs its job hooks on the host, outside any container the job declares. Running them where the steps run is what lets a hook prepare the environment the steps actually see.
:::
The script is run according to its extension:
| Extension | Command |
| --- | --- |
| `.sh` | `bash -e <path>` |
| `.ps1` | `pwsh -command . '<path>'` |
| anything else | the file itself, which needs its own shebang and executable bit |
As on GitHub, the shell flags applied to `run:` steps are **not** applied to a hook — set `pipefail` or anything else you want inside the script.
A hook path that does not exist inside the job environment fails the job with `No such file or directory`, naming the path.
### Docker-in-Docker and Docker-out-of-Docker
The hook is executed and its files are exchanged over the Docker API, addressed by container ID, so no path is translated between the runner and the daemon. Both setups work unchanged, but they differ in where the hook file has to be:
- **DinD** — the daemon has its own filesystem. Bake the hook into the job image; a path from the runner's filesystem is not visible to it.
- **DooD** — the job container is created by the host's daemon, so a bind mount in `container.options` is resolved against the **host**, not against the runner container. Either bake the hook into the job image, or mount a host directory and add it to `container.valid_volumes`.
## Environment
A hook sees the job's environment: the workflow, job and `container:` `env:`, the runner's `envs`, and the `GITHUB_*` context variables, with the same masking applied to its output as to a step's. The step-specific ones (`GITHUB_ACTION`, `GITHUB_OUTPUT`, `GITHUB_STATE`) are not set — a hook is not a step, so `::save-state::` and `::set-output::` have nowhere to go.
Its stdout is part of the job log, inside a collapsible group, and is scanned for workflow commands: `::add-mask::` registers a value to be masked for the rest of the job, `::set-env::` and `::add-path::` apply to the steps that follow.
`$GITHUB_ENV` and `$GITHUB_PATH` point at files that are read back after the hook exits, so the file-command form works too:
```bash
#!/bin/bash
echo "REGISTRY_TOKEN=$(fetch-token)" >> "$GITHUB_ENV"
echo "/opt/tooling/bin" >> "$GITHUB_PATH"
```
Both files are the hook's own, separate from the per-step ones, so nothing a hook writes is truncated by the first step.
## Recommendations
- Keep hooks **fast** and return the right exit code: they are on the critical path of every job, and nothing bounds them.
- Use **idempotent** operations, and expect `job_completed` to run after success, failure, and cancellation alike.
- Mask anything secret the hook prints or exports with `::add-mask::`.
@@ -0,0 +1,101 @@
---
sidebar_position: 2
---
# Post-task script
The post-task script is an optional host hook that runs **once after every task**, after the runner has finished its normal per-task cleanup. Typical uses are pruning Docker images, vacuuming ephemeral disks, or resetting VM state between jobs.
```yaml
runner:
# Path to an executable on the host. Empty or omitted disables the hook.
post_task_script: /usr/local/bin/gitea-post-task.sh
# Hard limit on script runtime. Default when post_task_script is set: 5m.
post_task_script_timeout: 2m
```
| Option | Default | Description |
| --- | --- | --- |
| `runner.post_task_script` | disabled | host path to the script or binary. Relative paths are resolved from the runner's working directory. |
| `runner.post_task_script_timeout` | `5m` when a script is set | maximum runtime before the runner kills the script and moves on. |
## When it runs
For each task, the order is:
1. the workflow runs (steps, actions, containers);
2. in-job cleanup (action `post:` steps, container stop and removal);
3. job outputs are reported to Gitea;
4. the bind-workdir workspace is removed, when `container.bind_workdir` is enabled;
5. **the post-task script**;
6. the final task acknowledgement to Gitea.
The script is **additive**: it does not replace any built-in cleanup. With `container.bind_workdir` enabled, the workspace directory has usually already been deleted before the script starts, but `GITEA_WORKSPACE` still names the path the job used.
## The runner stays offline until the script finishes
This is the most important operational detail. When the script starts, the runner **stops sending task heartbeats**, so from Gitea's perspective it is not available for new work until the script exits and the final task flush has been sent.
While the script runs:
- Gitea does not assign another task to this runner for the current job slot;
- the capacity slot stays occupied locally — with `capacity: 1`, no other task starts;
- a shutdown counts this phase as part of the in-flight task, so a slow script delays graceful shutdown.
If the script never exits, the runner stays in this state until `runner.post_task_script_timeout` elapses (default **5 minutes**), then kills it and proceeds. Set that timeout to what your housekeeping is allowed to take, and keep the script short and bounded.
## Environment variables
The script receives `runner.envs` / `runner.env_file` values plus:
| Variable | Description |
| --- | --- |
| `GITEA_TASK_ID` | numeric task ID |
| `GITEA_RUN_ID` | workflow run ID, when the server provides it |
| `GITEA_REPOSITORY` | repository slug (`owner/name`) |
| `GITEA_WORKSPACE` | workspace path the job used, which may already be deleted |
| `GITEA_JOB_RESULT` | `success`, `failure`, `cancelled`, `skipped` or `unknown` |
The environment is **not** a copy of the job container's. Even `PATH` is only present if `runner.envs` or `runner.env_file` defines it.
## Output and errors
- stdout and stderr go to the **runner process log**, prefixed with `post-task script stdout:` / `post-task script stderr:` — not to the job log;
- a non-zero exit is logged as a warning and does not change the job result already reported to Gitea;
- timeouts and start failures are warnings too; the runner still acknowledges the task.
## Interaction with other timeouts
| Timeout | Effect on the post-task script |
| --- | --- |
| `runner.post_task_script_timeout` | kills the script if it runs too long. The **only** timeout that bounds it. |
| `runner.timeout` | caps the task **up to** the script. The script detaches from the task deadline, so a job that nearly hit the runner timeout does not cut it short. |
| `runner.shutdown_timeout` | bounds how long a shutdown waits for the **task**. The script detaches from cancellation and may extend shutdown until its own timeout elapses. |
## Examples
Prune dangling Docker resources on Linux:
```sh
#!/bin/sh
set -eu
docker image prune -f
docker builder prune -f --filter 'until=24h'
```
On Windows, use a `.exe`, `.bat` or `.cmd` path; `.ps1` is not supported as the configured path, so wrap PowerShell in a batch file:
```bat
@echo off
powershell.exe -NoProfile -NonInteractive -ExecutionPolicy Bypass -File "%~dp0post-task.ps1"
```
`.sh` files on Windows need a Unix shell on `PATH`, unless `post_task_script` points at the interpreter itself.
## Notes
- `gitea-runner exec` does not load the runner YAML and never runs this hook.
- Use idempotent operations: the script runs after success, failure and cancellation alike.
- Watch the runner log when testing failure modes — a hung script, a non-zero exit, a missing executable.
- Bind-workdir idle cleanup (`runner.workdir_cleanup_age`) is separate from this hook and only runs while the runner is idle.
- For work that has to happen inside the job environment, use [job hooks](job-hooks.md) instead.
@@ -0,0 +1,49 @@
---
sidebar_position: 1
slug: /
---
# Gitea Runner
The [Gitea Runner](https://gitea.com/gitea/runner) executes the jobs of [Gitea Actions](/usage/actions/overview).
It polls a Gitea instance for queued jobs, runs their steps in a container or directly on the machine it is installed on, and streams the logs and the result back.
:::info
These pages describe the runner `4.x` series. Pick another release, or the development version, in the **Runner Version** dropdown.
:::
## Requirements
A runner needs a Gitea instance with Actions enabled, a [registration token](registration.md), and, for containerized jobs, a Docker daemon. Actions are enabled by default since Gitea 1.21; on older instances they have to be turned on:
```ini
[actions]
ENABLED=true
```
Other OCI engines that implement the Docker API may work, but are untested. Podman is not a supported configuration.
## Execution modes
A runner can run jobs in three different ways. The mode is not a global setting: it follows from the [labels](labels.md) the runner is registered with, so a single runner can offer both container and host labels.
| Mode | How jobs run | Docker daemon | Notes |
| --- | --- | --- | --- |
| Docker (recommended) | in a container created from the label's image | external, e.g. the host's `/var/run/docker.sock` | jobs are isolated from each other, but share the daemon |
| Docker-in-Docker | in a container created by a daemon that lives next to the runner | bundled in the `dind` / `dind-rootless` images | strongest isolation, more setup, needs `--privileged` |
| Host | directly on the machine, with the tools installed there | only needed for `docker://` actions and service containers | no isolation between jobs |
## Getting started
1. [Install the runner](installation/binary.md) as a binary, [in Docker](installation/docker.md), or [on Kubernetes](installation/kubernetes.md).
2. [Register it](registration.md) against your instance with a registration token.
3. [Configure it](configuration.md), and pick the [labels](labels.md) that decide which jobs it accepts.
4. Optionally set up a [shared cache](cache.md), [job hooks](hooks/job-hooks.md), a [post-task script](hooks/post-task-script.md), or [metrics and health checks](monitoring.md).
Every command and flag is listed in the [command line reference](reference/cli.md).
## Versioning and compatibility
The runner is released independently of Gitea and its version numbers are unrelated to the instance's. Gitea 1.21 or later is expected — older instances cannot accept the runner's label declaration — and individual features need a newer instance still, which is called out where they apply.
When moving between major runner versions, read [Upgrading](upgrade.md) first: `2.0.0`, `3.0.0` and `4.0.0` all contain breaking changes.
@@ -0,0 +1,153 @@
---
sidebar_position: 1
---
# Install from a binary
The runner is a single static binary called `gitea-runner`. It has no dependencies apart from a Docker daemon for containerized jobs.
## Download
- released builds: [dl.gitea.com/gitea-runner](https://dl.gitea.com/gitea-runner/) or the [release page](https://gitea.com/gitea/runner/releases)
- development builds of the `main` branch: [dl.gitea.com/gitea-runner/nightly](https://dl.gitea.com/gitea-runner/nightly/)
Each file is published next to a `.sha256` checksum and an `.xz` compressed variant:
```bash
VERSION=4.1.0 # any 4.x release, see the release page
curl -sSLO "https://dl.gitea.com/gitea-runner/$VERSION/gitea-runner-$VERSION-linux-amd64"
curl -sSLO "https://dl.gitea.com/gitea-runner/$VERSION/gitea-runner-$VERSION-linux-amd64.sha256"
sha256sum -c "gitea-runner-$VERSION-linux-amd64.sha256"
install -m 0755 "gitea-runner-$VERSION-linux-amd64" /usr/local/bin/gitea-runner
```
Check that the binary matches your platform:
```bash
gitea-runner --version
```
## Build from source
Building requires the Go version declared in the repository's `go.mod`:
```bash
git clone https://gitea.com/gitea/runner.git
cd runner
make build
```
## First run
```bash
gitea-runner generate-config > config.yaml # optional, defaults are safe
gitea-runner -c config.yaml register # see "Registering a runner"
gitea-runner -c config.yaml daemon
```
The `daemon` command runs in the foreground. It reads the registration file (`runner.file`, `.runner` by default) relative to its working directory, so keep the working directory stable across restarts.
## Run as a systemd service
Create an unprivileged user, install the binary, and register the runner as that user so the `.runner` file ends up in the service's working directory:
```bash
sudo useradd --system --home-dir /var/lib/gitea-runner --create-home gitea-runner
sudo install -d /etc/gitea-runner
sudo -u gitea-runner gitea-runner generate-config | sudo tee /etc/gitea-runner/config.yaml >/dev/null
cd /var/lib/gitea-runner
sudo -u gitea-runner gitea-runner register -c /etc/gitea-runner/config.yaml
```
Then install the unit as `/etc/systemd/system/gitea-runner.service`:
```ini
[Unit]
Description=Gitea Actions runner
Documentation=https://gitea.com/gitea/runner
After=network-online.target
Wants=network-online.target
# Uncomment when jobs use the local Docker daemon:
# After=docker.service
# Requires=docker.service
[Service]
Type=simple
ExecStart=/usr/local/bin/gitea-runner daemon --config /etc/gitea-runner/config.yaml
WorkingDirectory=/var/lib/gitea-runner
User=gitea-runner
Group=gitea-runner
Restart=on-failure
RestartSec=5s
# Allow running jobs to finish before the runner is stopped. Keep this in sync
# with runner.shutdown_timeout in the config.
TimeoutStopSec=3h
[Install]
WantedBy=multi-user.target
```
```bash
sudo systemctl daemon-reload
sudo systemctl enable --now gitea-runner
```
If jobs use the host's Docker daemon, the `gitea-runner` user also needs access to the daemon socket. Adding it to the `docker` group grants that access and is [equivalent to root on the host](https://docs.docker.com/engine/security/#docker-daemon-attack-surface).
Environment variables for the process — most importantly [proxy variables](../proxy.md) — belong in `Environment=` lines or a drop-in file, not in the runner config.
## Run as a launchd daemon (macOS)
macOS uses `launchd` instead of systemd. Daemons run as `root` by default; an unprivileged `_gitea-runner` user can be created with `dscl`. Install the following as `/Library/LaunchDaemons/com.gitea.runner.plist` and adjust the paths to your installation:
```xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>Label</key>
<string>com.gitea.runner</string>
<key>ProgramArguments</key>
<array>
<string>/usr/local/bin/gitea-runner</string>
<string>daemon</string>
<string>--config</string>
<string>/etc/gitea-runner/config.yaml</string>
</array>
<key>RunAtLoad</key>
<true/>
<key>KeepAlive</key>
<true/>
<key>WorkingDirectory</key>
<string>/var/lib/gitea-runner</string>
<key>StandardOutPath</key>
<string>/var/lib/gitea-runner/runner.log</string>
<key>StandardErrorPath</key>
<string>/var/lib/gitea-runner/runner.err</string>
<key>EnvironmentVariables</key>
<dict>
<key>PATH</key>
<string>/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin</string>
<key>HOME</key>
<string>/var/lib/gitea-runner</string>
</dict>
<key>UserName</key>
<string>_gitea-runner</string>
</dict>
</plist>
```
```bash
sudo launchctl load /Library/LaunchDaemons/com.gitea.runner.plist
```
On macOS and Windows hosts, jobs usually run with [host labels](../labels.md) and the tools installed on the machine.
## Windows
Install the `windows-amd64` binary and register it as a service with any service wrapper (for example `sc.exe` plus a wrapper such as [WinSW](https://github.com/winsw/winsw), or a scheduled task at boot). The runner itself has no service-installer subcommand.
Keep in mind for Windows hosts:
- `runner.post_task_script` accepts `.exe`, `.bat` and `.cmd` paths; `.ps1` is not supported as the configured path.
- host-mode jobs are terminated as a process tree, so tools that daemonize are not left behind.
@@ -0,0 +1,147 @@
---
sidebar_position: 2
---
# Install with Docker
The official images are published on [Docker Hub](https://hub.docker.com/r/gitea/runner/tags) as `docker.io/gitea/runner`.
The `4` tag follows the newest `4.x` release; `nightly` is built from the `main` branch, and every release is also tagged with its exact version.
In the container the registration and the daemon are combined: the entrypoint registers the runner on first start (when no registration file exists yet) and then execs `gitea-runner daemon`.
## Image flavours
All flavours contain the same `gitea-runner` binary and differ only in how a Docker daemon is made available to jobs.
| Tag | Base image | Docker daemon | Supervisor | Runs as |
| --- | --- | --- | --- | --- |
| `4`, `4.1`, `<version>` | `alpine` | none, you provide one | `tini` | `root` |
| `4-dind` | `docker:dind` | bundled, needs `--privileged` | `s6` | `root` |
| `4-dind-rootless` | `docker:dind-rootless` | bundled, rootless | `s6` | `rootless` (UID 1000) |
The rootless flavour's UID is fixed at 1000 by the upstream base image, and its daemon always listens on `/run/user/1000/docker.sock`, so `--user 1001` does not work. To talk to a *host* rootless daemon under another UID, use the basic flavour and bind-mount that daemon's socket instead.
## Basic flavour
The default image ships no daemon of its own, so jobs that use `docker://` images need one from outside the container — usually the host's socket:
```bash
docker run -d --name my_runner \
-e GITEA_INSTANCE_URL=<instance_url> \
-e GITEA_RUNNER_REGISTRATION_TOKEN=<registration_token> \
-e GITEA_RUNNER_NAME=<runner_name> \
-v $PWD/data:/data \
-v /var/run/docker.sock:/var/run/docker.sock \
docker.io/gitea/runner:4
```
This flavour does not need `--privileged`. The trade-off is that jobs share the host's daemon and can therefore see its other containers and images. A job that can reach the socket can also read the reusable `GITEA_RUNNER_REGISTRATION_TOKEN` from the runner container's `docker inspect` output.
## Docker-in-Docker
The `dind` flavour bundles its own daemon, so no socket has to be mounted:
```bash
docker run -d --name my_runner --privileged \
-e GITEA_INSTANCE_URL=<instance_url> \
-e GITEA_RUNNER_REGISTRATION_TOKEN=<registration_token> \
-v $PWD/data:/data \
docker.io/gitea/runner:4-dind
```
`s6` starts `dockerd` first and the runner service waits for it before registering. Use `4-dind-rootless` to run both the daemon and the runner as an unprivileged user; rootless Docker's usual limitations around networking, cgroups and storage drivers apply.
## Volumes
Two different pieces of state are worth persisting, and neither implies the other:
- `/data` is the runner's working directory. It holds the `.runner` registration file and, optionally, the config file. Without it, a recreated container registers itself again as a new runner, leaving a stale entry in Gitea, and fails outright if the token has been reset in the meantime.
- the Docker daemon's data root holds the images pulled for jobs. It is **not** under `/data`: for `dind` it is `/var/lib/docker` inside the container, for `dind-rootless` it is `/home/rootless/.local/share/docker`. Give it its own volume, or every new container re-pulls the job images.
## Entrypoint environment variables
The entrypoint ([`scripts/run.sh`](https://gitea.com/gitea/runner/src/branch/main/scripts/run.sh)) understands:
| Variable | Meaning |
| --- | --- |
| `GITEA_INSTANCE_URL` | instance to register against, e.g. `https://gitea.example.com/` |
| `GITEA_RUNNER_REGISTRATION_TOKEN` | registration token; unset before the daemon starts |
| `GITEA_RUNNER_REGISTRATION_TOKEN_FILE` | file to read the token from, for Docker/Kubernetes secrets |
| `GITEA_RUNNER_NAME` | runner name, defaults to the container hostname |
| `GITEA_RUNNER_LABELS` | labels, passed to both `register` and `daemon` |
| `GITEA_RUNNER_EPHEMERAL` | any non-empty value registers the runner as [ephemeral](../registration.md#ephemeral-runners) |
| `GITEA_RUNNER_ONCE` | any non-empty value runs a single job, then exits |
| `GITEA_MAX_REG_ATTEMPTS` | registration attempts before giving up, default `10` |
| `RUNNER_STATE_FILE` | registration file name inside `/data`, default `.runner` |
| `CONFIG_FILE` | config file inside the container, passed as `--config` |
These are entrypoint variables, not runner settings: the runner process itself is configured only through the [config file](../configuration.md).
Mount the config file when you need one:
```bash
docker run -v $PWD/config.yaml:/config.yaml -e CONFIG_FILE=/config.yaml ...
```
A config file can be generated with the image itself:
```bash
docker run --rm --entrypoint="" docker.io/gitea/runner:4 gitea-runner generate-config > config.yaml
```
## docker compose
```yaml
services:
runner:
image: docker.io/gitea/runner:4
restart: always
environment:
CONFIG_FILE: /config.yaml
GITEA_INSTANCE_URL: "${INSTANCE_URL}"
GITEA_RUNNER_REGISTRATION_TOKEN: "${REGISTRATION_TOKEN}"
GITEA_RUNNER_NAME: "${RUNNER_NAME}"
GITEA_RUNNER_LABELS: "${RUNNER_LABELS}"
volumes:
- ./config.yaml:/config.yaml
- ./data:/data
- /var/run/docker.sock:/var/run/docker.sock
```
When Gitea runs in the same compose project, depend on its health check so the runner does not try to register before the instance answers:
```yaml
depends_on:
gitea:
condition: service_healthy
restart: true
```
The rootless Docker-in-Docker variant needs a few extra options:
```yaml
services:
runner:
image: docker.io/gitea/runner:4-dind-rootless
restart: always
privileged: true
security_opt:
# for hosts running AppArmor (Ubuntu, Debian), whose default profile blocks
# the user namespace changes the bundled daemon needs
- apparmor=rootlesskit
volumes:
- ./data/runner:/data
environment:
- GITEA_INSTANCE_URL=<instance_url>
- GITEA_RUNNER_REGISTRATION_TOKEN=<registration_token>
- DOCKER_HOST=unix:///var/run/user/1000/docker.sock
# slirp4netns gives significantly better network throughput than vpnkit
- DOCKERD_ROOTLESS_ROOTLESSKIT_NET=slirp4netns
- DOCKERD_ROOTLESS_ROOTLESSKIT_MTU=65520
```
## Cache from a dockerized runner
A runner in a container creates a separate network per job by default, so the address it detects for its own cache server is often unreachable from job containers and `actions/cache` fails with a connection timeout. Set `cache.host` and `cache.port` explicitly and publish that port, or put the job containers on a shared network — see [Caching](../cache.md#dockerized-runners).
More deployment examples live in the [`examples`](https://gitea.com/gitea/runner/src/branch/main/examples) directory of the runner repository.
@@ -0,0 +1,49 @@
---
sidebar_position: 3
---
# Install on Kubernetes
Ready-to-adapt manifests live in [`examples/kubernetes`](https://gitea.com/gitea/runner/src/branch/main/examples/kubernetes) of the runner repository, and a Helm chart is maintained at [gitea/helm-actions](https://gitea.com/gitea/helm-actions).
## Choosing a manifest
| Example | Shape | Docker daemon |
| --- | --- | --- |
| `dind-docker.yaml` | `Deployment` with a native sidecar (`initContainer` with `restartPolicy: Always`, needs Kubernetes 1.29+) | privileged `docker:dind` sidecar, socket shared through an `emptyDir` |
| `statefulset-dind.yaml` | `StatefulSet` with `volumeClaimTemplates` | same as above |
| `rootless-docker.yaml` | `Deployment` with a single container | bundled rootless daemon of the `dind-rootless` image, reached over `tcp://localhost:2376` with TLS |
Prefer the `StatefulSet` variant when you scale past one replica: each pod then gets a stable identity and its own volume, so it keeps its `.runner` registration across restarts and reschedules instead of registering itself again as a new runner.
## Two volumes, two purposes
- `/data` — the runner's working directory, holding the `.runner` registration file and optionally the config file.
- the daemon's data root — `/var/lib/docker` for the `dind` sidecar, `/home/rootless/.local/share/docker` for `dind-rootless`. It holds the images pulled for jobs and is *not* under `/data`. Dropping it still works, but every recreated pod re-pulls all job images.
With the rootless image, both volumes must be writable by UID/GID 1000, which is what `securityContext.fsGroup: 1000` in the example is for.
## Registration token
The examples read the token from a `Secret`:
```yaml
env:
- name: GITEA_INSTANCE_URL
value: http://gitea-http.gitea.svc.cluster.local:3000
- name: GITEA_RUNNER_REGISTRATION_TOKEN
valueFrom:
secretKeyRef:
name: runner-secret
key: token
```
The token stays valid for further registrations until it is reset, but each registration creates another runner entry. For pods that are recreated without their volume, use the instance-wide token configured on the Gitea side, and expect stale runner entries — or start an [ephemeral runner](../registration.md#ephemeral-runners) per job.
## Privileges
Docker-in-Docker needs `securityContext.privileged: true`, which lets a malicious job break out of the container. Weigh that against the alternatives:
- the rootless flavour, which reduces but does not remove the exposure;
- pointing the basic flavour at a daemon outside the cluster;
- keeping such runners on a dedicated node pool and only granting them to trusted repositories.
@@ -0,0 +1,70 @@
---
sidebar_position: 4
---
# Labels
Labels decide **which jobs a runner accepts** and **how it runs them**. A job's `runs-on` value is matched against the runner's label names; the first match wins and selects the execution environment for that job.
A label is written as:
```text
<name>[:<schema>[:<args>]]
```
| Part | Meaning |
| --- | --- |
| `name` | the name a workflow refers to in `runs-on`, e.g. `ubuntu-latest` |
| `schema` | either `docker` or `host`, defaulting to `host` when omitted |
| `args` | only used by the `docker` schema: the image the job runs in |
Two schemas are supported:
- `docker://<image>` — the job runs in a container created from `<image>`:
```text
ubuntu-latest:docker://docker.gitea.com/runner-images:ubuntu-latest
```
- `host` — the job's steps run directly on the machine, with the tools installed there:
```text
macos:host
```
So a runner registered with
```text
ubuntu-latest:docker://docker.gitea.com/runner-images:ubuntu-latest,macos:host
```
runs `runs-on: ubuntu-latest` jobs in the `runner-images:ubuntu-latest` container and `runs-on: macos` jobs directly on the host.
Names may themselves contain a colon, for example `pool:e57e18d4-10d4-406f-93bf-60f127221bdd`; only `host` and `docker` are treated as schemas.
If a job's `runs-on` matches none of the runner's labels, the job still runs, in the default `docker.gitea.com/runner-images:ubuntu-latest` image. Images maintained for this purpose are listed at [gitea/runner-images](https://gitea.com/gitea/runner-images); community images such as the [act images](https://github.com/nektos/act/blob/master/IMAGES.md) work too.
:::note
A runner that only exposes `host` labels still needs access to a Docker daemon whenever a job uses a `docker://` action or a service container. `host` labels only change where the job's own steps run.
:::
## Registration versus config labels
Labels are chosen at registration time (`--labels`, or the interactive prompt) and stored in the registration file. Afterwards they can be changed without re-registering, and the most explicit source wins:
```text
--labels / GITEA_RUNNER_LABELS > runner.labels in the config file > labels in the .runner file
```
- during `register`, `runner.labels` from the config file takes precedence and the `--labels` flag is ignored;
- `daemon --labels` (which defaults to `GITEA_RUNNER_LABELS`) overrides the labels of an already registered runner;
- whenever the resulting labels differ from those in the registration file, they are written back to it and re-declared to the instance on startup;
- labels that fail to parse are skipped with a warning instead of stopping the runner.
Labels can also be edited in the Gitea UI under the runner's settings.
## Choosing images
- Pick an image that already contains the tools your workflows expect. The default images are small; a job that installs a toolchain on every run is usually better served by a purpose-built image.
- Pin images by tag or digest for reproducible jobs. An image pinned by digest is never re-pulled, even with `container.force_pull` enabled.
- Use distinct names such as `linux_amd64:host` or `windows:host` for host labels, so a workflow written for GitHub's `ubuntu-latest` does not accidentally run unsandboxed on your machine.
@@ -0,0 +1,93 @@
---
sidebar_position: 7
---
# Monitoring and health
## Prometheus metrics
```yaml
metrics:
enabled: true
addr: "127.0.0.1:9101"
readiness_grace: 30s
```
With `metrics.enabled`, the runner serves three endpoints on `metrics.addr`:
| Endpoint | Purpose |
| --- | --- |
| `/metrics` | Prometheus exposition of the runner's own metrics |
| `/healthz` | liveness: the process is up |
| `/readyz` | task admission: `503` while a [health check](#local-health-checks) reports the machine as unavailable, once polling has been failing for longer than `readiness_grace`, or after the runner has been deleted on the instance |
There is **no authentication** on this listener. The default address binds to localhost only; expose it more widely (`":9101"`) only behind a firewall or a scrape-only network.
All series are prefixed with `gitea_runner_`, among them:
| Metric | Meaning |
| --- | --- |
| `gitea_runner_info` | always `1`, with the version and runner name as labels |
| `gitea_runner_uptime_seconds` | seconds since the daemon started |
| `gitea_runner_capacity` / `gitea_runner_job_running` / `gitea_runner_job_capacity_utilization_ratio` | configured capacity, jobs in flight, and their ratio |
| `gitea_runner_job_total` | jobs by result (`success`, `failure`, `cancelled`, `skipped`, `unknown`) |
| `gitea_runner_job_duration_seconds` | job duration histogram |
| `gitea_runner_poll_fetch_total` / `gitea_runner_poll_fetch_duration_seconds` | task fetches by result (`task`, `empty`, `error`) and their latency |
| `gitea_runner_poll_backoff_seconds` | last polling backoff interval |
| `gitea_runner_report_log_total` / `gitea_runner_report_state_total` | log and state reports by result |
| `gitea_runner_report_log_buffer_rows` | log rows buffered but not yet sent |
| `gitea_runner_client_errors_total` | RPC errors by method |
Useful things to alert on: `gitea_runner_poll_fetch_total{result="error"}` rising (the instance is unreachable or the runner was deleted), a `capacity_utilization_ratio` pinned at `1` (the pool is too small), and a growing `report_log_buffer_rows` (log delivery is falling behind).
## OpenTelemetry tracing
Setting `OTEL_EXPORTER_OTLP_ENDPOINT` in the runner's environment sends a trace of every job, with a span per step, to an OTLP collector over `http/protobuf`:
```sh
OTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:4318
```
Jobs receive `TRACEPARENT`, so instrumented tools in them join the job's trace. See [docs/telemetry.md](https://gitea.com/gitea/runner/src/branch/main/docs/telemetry.md) for headers, sampling and the security notes.
## Local health checks
Health checks let a runner take itself out of rotation when the machine it runs on is not fit for work — most commonly when the disk is full.
```yaml
health_check:
enabled: false
min_free_disk_space_mb: 1024
script: ''
interval: 30s
timeout: 10s
```
- while a check fails, the runner stops fetching **new** tasks; jobs already running are unaffected and finish normally;
- no check runs while a job is active — the last result is reused until the runner is idle again;
- `min_free_disk_space_mb` is measured on the filesystem holding the runner's workspaces, and defaults to 1024 MiB when omitted or zero;
- `script` is any executable. A non-zero exit, a timeout, or a failure to start marks the runner unavailable. Its result is cached for `interval` and it is killed after `timeout`;
- recovery is automatic and logged (`runner local health recovered, resuming task polling`), and the state is reflected by `/readyz`.
## Logs
The runner logs to stderr; `log.level` controls the verbosity, with `debug` and `trace` adding `file:line` to each line. Under systemd the log ends up in the journal (`journalctl -u gitea-runner`), in Docker in `docker logs`.
The runner log is not the job log: step output is streamed to Gitea and is tuned with the `runner.log_report_*` settings, while things like [post-task script](hooks/post-task-script.md) output only ever appear in the runner log.
## Reporting a problem
`gitea-runner bug-report` prints the version, Go version, OS/architecture and CPU count — paste its output into an issue at [gitea/runner](https://gitea.com/gitea/runner/issues):
```bash
gitea-runner bug-report
```
```text
Runner version: 4.1.0
Go version: go1.26.5
OS/Arch: linux/amd64
NumCPU: 8
```
To reproduce a workflow locally, without a Gitea instance and without touching the runner's registration, use `gitea-runner exec` — see the [command line reference](reference/cli.md#exec).
Loaded 100 of 156 files, more files were not shown because too many files have changed in this diff. Show more