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]>
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]>
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]>
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]>
Document the OpenID Connect tokens that Gitea Actions jobs can request since https://github.com/go-gitea/gitea/pull/39052, so they can sign in to cloud providers without long-lived secrets.
- Add an OpenID Connect page covering how to trust Gitea, request a token and match its claims
- Document the `id-token` permission scope and its maximum
- Translate both for zh-cn and zh-tw, which still listed `id-token` as unsupported
Co-authored-by: silverwind <[email protected]>
Co-authored-by: silverwind <[email protected]>
Reviewed-on: https://gitea.com/gitea/docs/pulls/526
Reviewed-by: silverwind <[email protected]>
Co-authored-by: Max P. <[email protected]>
Follow-up to #588.
- rename `DocusaurusSidebarItem` -> `SidebarSourceItem`, `fromDocusaurus` -> `fromSidebarSource`, `docusaurusSidebars` -> `sidebarSources`
- reword comments and READMEs that described behaviour by comparison with docusaurus; fix the stale note in `sites/docs/src/config/api.ts`
- drop `.docusaurus/` from `.gitignore`
No functional change. `pnpm check` has 0 errors and `pnpm build` still builds 6690 pages.
<!-- cloudflare-preview --> Preview: https://pr-589.docs-gitea-com.pages.dev
Reviewed-on: https://gitea.com/gitea/docs/pulls/589
Reviewed-by: silverwind <[email protected]>
The v28 release (#564) added the `prepare-awesome-v%` Makefile rule, but the checks and publish workflows still only prepare awesome.md up to 1.27, so version-28/awesome.md is not generated in CI. Both workflows now call `prepare-awesome-v28`.
The preview step is skipped when Cloudflare secrets are unavailable, which is the case for pull requests from forks (#557 and #565 have no preview line, while same-repo PRs like #533 and #563 do). The skip is now a visible notice. Previewing fork PRs would need a separate trusted workflow and is not part of this change.
Reviewed-on: https://gitea.com/gitea/docs/pulls/579
Reviewed-by: bircni <[email protected]>
Co-authored-by: Lunny Xiao <[email protected]>
Bump the v28 docs to the 28.1.0 release, following the 1.27.3 bump (#532):
- point the `28` version variables (`version`, `sourceVersion`, `dockerVersion`, `displayVersion`) at 28.1.0
- update the v28 entry in `update_api_docs.sh` to `28.1.0`
- regenerate `static/swagger-28.json` and `static/openapi3-28.json` from the `v28.1.0` tag
---------
Co-authored-by: Lunny Xiao <[email protected]>
Reviewed-on: https://gitea.com/gitea/docs/pulls/578
Reviewed-by: TheFox0x7 <[email protected]>
Co-authored-by: bircni <[email protected]>
This PR contains the following updates:
| Package | Change | [Age](https://docs.renovatebot.com/merge-confidence/) | [Confidence](https://docs.renovatebot.com/merge-confidence/) |
|---|---|---|---|
| [pnpm](https://github.com/pnpm/pnpm/tree/main/pnpm) ([source](https://github.com/pnpm/pnpm/tree/HEAD/pnpm/npm/pnpm)) | [`12.6.0` → `12.8.2`](https://renovatebot.com/diffs/npm/pnpm/12.6.0/12.8.2) |  |  |
| [sharp](https://sharp.pixelplumbing.com) ([source](https://github.com/lovell/sharp), [changelog](https://github.com/lovell/sharp/blob/main/docs/src/content/docs/changelog.md)) | [`0.35.4` → `0.35.5`](https://renovatebot.com/diffs/npm/sharp/0.35.4/0.35.5) |  |  |
| [starlight-openapi](https://github.com/HiDeoo/starlight-openapi) ([source](https://github.com/HiDeoo/starlight-openapi/tree/HEAD/packages/starlight-openapi)) | [`0.26.2` → `0.26.3`](https://renovatebot.com/diffs/npm/starlight-openapi/0.26.2/0.26.3) |  |  |
---
### Release Notes
<details>
<summary>pnpm/pnpm (pnpm)</summary>
### [`v12.8.2`](https://github.com/pnpm/pnpm/releases/tag/v12.8.2): pnpm 12.8.2
[Compare Source](https://github.com/pnpm/pnpm/compare/v12.8.1...v12.8.2)
pnpm 12.8.2 fixes a startup crash on Linux ppc64le and `UnknownIssuer` errors on systems without CA certificates. `pnpm run` no longer installs before every script on CI when `autoDedupe` is enabled, and resolution and hoisted installs on macOS are faster.
##### Patch Changes
##### Platforms and environments
- Fixed pnpm crashing on startup on Linux ppc64le [#​16380](https://github.com/pnpm/pnpm/issues/16380).
- Fixed installs failing with `UnknownIssuer` on Linux systems without CA certificates, such as `node:24-slim`, when `NODE_EXTRA_CA_CERTS` is set. The extra certificates now extend the bundled CA roots [#​16365](https://github.com/pnpm/pnpm/issues/16365).
- pnpm now creates its store operation locks and other per-user lock files in `$XDG_RUNTIME_DIR` when it points to a directory only the user can write to. Otherwise, pnpm still uses `/tmp` on Linux and macOS. Sandboxes that block writes to `/tmp` can point `XDG_RUNTIME_DIR` at a writable directory [#​16390](https://github.com/pnpm/pnpm/issues/16390).
- POSIX bin shims and the `pnpm`, `pn`, `pnpx`, and `pnx` launchers now run inside a Nix build, where the system default path holds none of the utilities they call. Installing again replaces the shims already in `node_modules` [#​16377](https://github.com/pnpm/pnpm/issues/16377).
- In a project that pins another pnpm version, pnpm now passes a command with an option it does not know to the pinned version. Before, pnpm rejected the option before switching, so `pnpm install --auto-dedupe` failed with "Unknown option" even though the pinned pnpm supports it [#​16353](https://github.com/pnpm/pnpm/issues/16353).
##### Installing and resolving dependencies
- `pnpm install --frozen-lockfile` now fails when `Cargo.lock` does not satisfy a dependency requirement in `Cargo.toml`. The error names the crate and the version the lockfile holds [#​16355](https://github.com/pnpm/pnpm/issues/16355).
- `pnpm install` returns "Already up to date" again in a workspace with injected workspace dependencies and a shared lockfile. Since 12.7.0 every repeat install in such a workspace ran the full install and copied the injected projects again.
- With `injectWorkspacePackages: true`, a fresh `pnpm install` now records a workspace dependency as `link:` when its injected copy differs from the project only by an optional peer that peer-dependent dedupe merges. It was recorded as a peer-suffixed `file:` copy [#​16354](https://github.com/pnpm/pnpm/issues/16354).
- `pnpm dedupe --check` now passes right after `pnpm dedupe` when deduplication merges variants of a package that differ only in their peers. A lockfile key whose peer suffix named a merged variant now names the variant that replaced it [#​16356](https://github.com/pnpm/pnpm/issues/16356).
- When `minimumReleaseAge` hides the version that `latest` points to, pnpm now falls back to a prerelease of the same major before a stable version of an older major. A stable version of the same major is still preferred. Before, while a new `1.0.0` was too new, `latest` fell back to an old `0.0.1` even though `1.0.0-beta.4` had been `latest` until then [#​16388](https://github.com/pnpm/pnpm/issues/16388).
- Git-hosted dependencies now respect `pmOnFail`. If it is set to anything other than `download`, a git-hosted dependency that pins a pnpm version is prepared by the running pnpm, and pnpm does not download the pinned version [#​16376](https://github.com/pnpm/pnpm/issues/16376).
- pnpmfile hooks such as `readPackage` now run once for a dependency that several packages request at the same time. They could run twice for it before.
- `childConcurrency` now defaults to 5, the documented value. It used to be capped at 4 and to follow the host's CPU count.
##### Running scripts
- `pnpm run` and `pnpm exec` no longer install dependencies before every script on CI when `autoDedupe` is enabled. `pnpm install --frozen-lockfile` now keeps the deduplication record left by an earlier install [#​16374](https://github.com/pnpm/pnpm/issues/16374).
- On macOS and Linux, lifecycle scripts and `pnpm run` now always get `PATH` from the `PATH` variable. When the environment also held a `Path` variable, a script sometimes got `Path`'s value, and failed with `node: not found` [#​16308](https://github.com/pnpm/pnpm/issues/16308).
- `pnpm run` now exits after a `SIGTERM` in a container where pnpm is PID 1 and the script runs pnpm again, as `"start": "pnpm serve"` does. Since 12.6.0 it kept waiting after the script had shut down, until the container runtime killed it.
##### Other commands and settings
- `pnpm config get globalShims`, `pnpm shim list`, and global installs no longer read `globalShims` from a project's `pnpm-workspace.yaml`. Only the global config file, the pnpm home's own `pnpm-workspace.yaml`, and `PNPM_CONFIG_GLOBAL_SHIMS` set it, so a repository cannot choose which globally installed packages get project-aware shims.
- `pnpm config set --location=project` refuses a machine-level setting such as `stateDir` or `scope` with `ERR_PNPM_CONFIG_SET_NOT_A_PROJECT_SETTING`, which names where the setting belongs. `pnpm config delete` still clears such a key from a project's `pnpm-workspace.yaml`.
- `pnpm deploy` no longer fails with `ERR_PNPM_DEPLOY_AMBIGUOUS_PEER` in a workspace with `injectWorkspacePackages: true` when a workspace package lists its peer dependency as a dev dependency too [#​16375](https://github.com/pnpm/pnpm/issues/16375).
- `pnpm deploy` no longer copies the workspace root's `packageManager` and `devEngines.packageManager` fields into the deployed `package.json` [#​16403](https://github.com/pnpm/pnpm/issues/16403).
- `pnpm publish` now includes bare `README` files and README files with Markdown extensions such as `readme.markdown` in registry metadata [#​12704](https://github.com/pnpm/pnpm/issues/12704).
- `pnpm store prune` now removes the packages that only expired `pnpm dlx` cache entries used. They were left in the store until the next `pnpm store prune` [#​16383](https://github.com/pnpm/pnpm/discussions/16383).
##### Performance
- Sped up dependency resolution in large workspaces, and when many dependencies request different ranges of the same package. Resolution also uses less memory.
- Sped up `pnpm install` with `nodeLinker: hoisted` on macOS when the lockfile is re-resolved, such as with `autoDedupe` enabled [#​16397](https://github.com/pnpm/pnpm/issues/16397).
- Sped up extracting package tarballs.
- `pnpm install` without `--frozen-lockfile` is faster on some machines in projects with a `pnpm-workspace.yaml`. Those installs linked with one worker thread per core, half of what a frozen install uses.
- On Windows, warm `pnpm install --frozen-lockfile` runs are 4-5% faster on 4- and 8-core machines. pnpm now links with one worker thread per core on Windows, between 4 and 16. This changes frozen installs and installs in projects without a `pnpm-workspace.yaml` on machines with 3 to 15 cores.
<!-- 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.8.1`](https://github.com/pnpm/pnpm/releases/tag/v12.8.1): pnpm 12.8.1
[Compare Source](https://github.com/pnpm/pnpm/compare/[email protected])
pnpm 12.8.1 fixes `pnpm install --frozen-lockfile` rejecting lockfiles with injected workspace packages that have peers, restores the executable bit on files of local directory dependencies, makes `pnpm dedupe` converge, and uses less CPU on many-core machines.
##### Patch Changes
- `pnpm install --frozen-lockfile` no longer rejects a freshly generated lockfile when an injected workspace package has peer dependencies [#​16332](https://github.com/pnpm/pnpm/issues/16332).
- Executable files in a `file:` directory dependency or an injected workspace package keep their executable bit again. Since 12.8.0, pnpm installed these files without the permissions they have in their project.
- `pnpm dedupe` now reaches a stable lockfile when a package's peer suffix is long enough to be hashed. Before, each run could switch that package's key between the hashed and the spelled-out suffix, so `pnpm dedupe --check` always failed [#​16331](https://github.com/pnpm/pnpm/issues/16331).
- `pnpm install --frozen-lockfile`, the default in CI, now uses less CPU on machines with more than 8 cores. Warm installs on many-core Windows machines got up to 10% faster. Frozen installs now link with at most 16 worker threads.
- `verifyDepsBeforeRun` no longer reports dependencies as outdated after a filtered install just because `pnpm-lock.yaml` has a newer modification time. It checks the lockfile against the packages that install put in place. Before, `pnpm run` reinstalled the whole workspace with lifecycle scripts on, for example after a Docker `COPY` brought in a lockfile with a newer mtime [#​16322](https://github.com/pnpm/pnpm/issues/16322).
After a filtered install, `verifyDepsBeforeRun` now also checks that the install put the selected projects' dependencies in place. A `node_modules` directory alone no longer counts as proof.
- `pnpm run` and `pnpm exec` no longer install a project that has never been installed and has nothing to install. Such a project declares no dependencies, no peer dependencies that `autoInstallPeers` would fetch, and no install lifecycle scripts. The command now runs without writing `node_modules` or `pnpm-lock.yaml` [#​16313](https://github.com/pnpm/pnpm/issues/16313).
- `pnpm update -g --latest` now upgrades globally installed packages beyond their saved version ranges [#​16320](https://github.com/pnpm/pnpm/issues/16320).
<!-- 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.8.0`](https://github.com/pnpm/pnpm/releases/tag/v12.8.0): pnpm 12.8
[Compare Source](https://github.com/pnpm/pnpm/compare/[email protected])
pnpm 12.8.0 warns when `pnpm pack` or `pnpm publish` would ship a `.env` file that `files` does not list, installs `sharedWorkspaceLockfile: false` workspaces concurrently, applies every setting passed as `--config.<name>=<value>`, and no longer leaves the Windows terminal stuck after Ctrl+C in a script.
##### Minor Changes
- `pnpm pack` and `pnpm publish` now warn when the tarball includes a `.env` or `.env.*` file that the `files` field of `package.json` does not list. Templates such as `.env.example` are not reported. List the file in `files` to publish it on purpose, or exclude it in `.npmignore` or `.gitignore` [#​7826](https://github.com/pnpm/pnpm/issues/7826).
- `pnpm pack` now honors `--silent`, `--reporter=silent`, and `--loglevel=silent` to hide the tarball contents and summary. With `--json`, lifecycle script output and the final JSON output remain visible [#​10297](https://github.com/pnpm/pnpm/issues/10297).
##### Patch Changes
##### Installing packages
- Installing through a `pnpr` server now records the pnpmfile checksum in the lockfile, so a later `pnpm install --frozen-lockfile` accepts that lockfile [#​14460](https://github.com/pnpm/pnpm/issues/14460). A frozen install through the pnpr server now fails if the pnpmfile changed. If the pnpmfile defines a `readPackage`, `afterAllResolved` or `preResolution` hook or custom resolvers, pnpm resolves dependencies locally and prints a warning that the pnpr server was not used.
Installing through a `pnpr` server also links a workspace project at the directory its `publishConfig.directory` names. A server that does not forward the setting makes the install fail with `ERR_PNPM_PNPR_PUBLISH_DIRECTORY_MISMATCH`, so pnpm never writes a lockfile that points at the wrong directory. The server rejects a `publishConfig.directory` that points outside its project.
- Installing a git-hosted dependency that has to be built no longer fails when that dependency's own dependencies have build scripts nobody approved. pnpm skips those builds while preparing the dependency, as it does without `strictDepBuilds` [#​9764](https://github.com/pnpm/pnpm/issues/9764).
- A git-hosted dependency that is a pnpm workspace with no committed lockfile is now detected as a pnpm project [#​14011](https://github.com/pnpm/pnpm/issues/14011).
- `pnpm install --dev` and `pnpm fetch --dev` now install the optional dependencies of devDependencies, such as the platform binaries of Biome and oxlint. The project's own `optionalDependencies` are still skipped [#​9678](https://github.com/pnpm/pnpm/issues/9678).
- `pnpm install --offline` and `pnpm add --offline` now resolve a version range to the newest matching version whose tarball is already in the store. They used to pick the newest version in the cached metadata and fail with `ERR_PNPM_NO_OFFLINE_TARBALL` when its tarball was missing [#​10715](https://github.com/pnpm/pnpm/issues/10715).
- If an offline install fails because the registry metadata cache uses the layout from before pnpm 11.27 and 12.4, the error now names the older mirror on disk and explains that one online install repopulates the cache. The error also carries the `ERR_PNPM_NO_OFFLINE_META` code. `pnpm cache prune --help` now says that pnpm 11.26 and earlier, and pnpm 12.3 and earlier, depend on the directories it removes [#​15656](https://github.com/pnpm/pnpm/issues/15656).
- Running `pnpm install` now refreshes dependencies when a package declared with a local `file:` directory changes its dependencies [#​4623](https://github.com/pnpm/pnpm/issues/4623).
- A repeat `pnpm install` now keeps its fast up-to-date check when an override replaces a declared local `file:` dependency [#​12892](https://github.com/pnpm/pnpm/issues/12892).
- `pnpm install` now removes an optional dependency from `node_modules` if its install script fails. Code that checks whether the package is installed no longer finds a package that cannot load [#​8756](https://github.com/pnpm/pnpm/issues/8756).
- With `nodeLinker: hoisted`, `pnpm install` now restores a workspace project's `node_modules` after it was deleted. Before, the install printed "Already up to date" and left the project without the dependencies nested under it. On Windows, the install also no longer fails with "Access is denied" when another project's copy of a shared dependency links to the deleted directory.
- Under `nodeLinker: hoisted`, `pnpm install` now clears orphaned package directories that an interrupted or failed install leaves in a project's `node_modules`. A directory recorded by the previous install is removed, while an unrecorded directory is moved to `node_modules/.ignored`. A copy already in `.ignored` is never overwritten [#​13676](https://github.com/pnpm/pnpm/issues/13676).
- Concurrent installs no longer fail when they replace the same stale hoisted dependency link. Virtual store cleanup now keeps the temporary lockfiles that concurrent installs write.
##### Resolving and linking dependencies
- `pnpm install` no longer aborts on a failed allocation of many gigabytes when peer dependency ranges combine overlapping `||` alternatives [#​15867](https://github.com/pnpm/pnpm/issues/15867).
- `pnpm install` no longer fails when a package from the registry declares a `file:` dependency on a directory inside itself, such as `"@​types/css-tree": "file:./typings/css-tree"`. pnpm links that dependency to the directory inside the package, as npm and Yarn do. The lockfile records it as `link:<root>/typings/css-tree` [#​9141](https://github.com/pnpm/pnpm/issues/9141).
- An `npm:` alias written by `overrides` now stays in place when a change elsewhere makes pnpm re-resolve the aliased dependency. Before, pnpm could look up the alias name at the aliased version, which failed with `ERR_PNPM_NO_MATCHING_VERSION` or locked an unrelated package [#​16309](https://github.com/pnpm/pnpm/issues/16309).
- A peer dependency no longer resolves to two different versions for one package. This happened when the package peer-depends on another package and on one of that package's peers, and it is installed deeper than a direct dependency of the package that provides them [#​12098](https://github.com/pnpm/pnpm/issues/12098).
- An optional peer dependency is no longer resolved from another workspace project's package when the project provides one of that package's own peers at a version it rejects. This avoids bogus unmet peer errors [#​13989](https://github.com/pnpm/pnpm/issues/13989).
- `pnpm dedupe` no longer changes the lockfile on every run when a nested peer dependency is provided through an npm alias [#​15709](https://github.com/pnpm/pnpm/issues/15709).
- With `resolutionMode: time-based` and `minimumReleaseAge` both set, `pnpm install` no longer reports a subdependency as too new when only the time-based cutoff excludes it. Such subdependencies used to fail a strict install with `ERR_PNPM_NO_MATURE_MATCHING_VERSION`, or were added to `minimumReleaseAgeExclude` [#​13569](https://github.com/pnpm/pnpm/issues/13569). A transitive dependency that has no matching version published before the time-based cutoff now resolves to the lowest matching version allowed by `minimumReleaseAge`. pnpm picks a version younger than `minimumReleaseAge` only if no older version matches [#​16298](https://github.com/pnpm/pnpm/issues/16298).
- `pnpm install` retries registry metadata fetches that fail with a timeout, a dropped connection, or an interrupted response body before it applies `trustPolicy` or `minimumReleaseAge`. A transient fetch failure is not reported as `TRUST_DOWNGRADE` or `MINIMUM_RELEASE_AGE_VIOLATION` [#​12031](https://github.com/pnpm/pnpm/issues/12031).
- pnpm's built-in package compatibility database no longer applies to a project's own manifest. A project named like a published package, such as `vue-loader`, no longer gains dependencies on `pnpm install` or `pnpm update`. User-configured `packageExtensions` still apply to project manifests [#​11700](https://github.com/pnpm/pnpm/issues/11700).
- Packages in an external `virtualStoreDir` can resolve the project's direct dependencies selected by `hoistPattern`. Run `pnpm install --force` to repair an existing installation [#​5652](https://github.com/pnpm/pnpm/issues/5652).
- `pnpm install` now links the executables of auto-installed peer dependencies into the workspace root's `node_modules/.bin`, including after a frozen-lockfile reinstall [#​8511](https://github.com/pnpm/pnpm/issues/8511).
##### Lockfiles and frozen installs
- `pnpm install --frozen-lockfile` now works on a detached HEAD when `gitBranchLockfile` is enabled. The install reads the lockfiles of the local and remote-tracking branches that contain the checked-out commit. It still writes the shared `pnpm-lock.yaml` [#​7672](https://github.com/pnpm/pnpm/issues/7672).
- `pnpm install --frozen-lockfile` now accepts a lockfile that has no importer entry for a workspace package without dependencies. Such a package added after the lockfile was written made the install fail with `ERR_PNPM_PACKAGE_MANAGER_NO_IMPORTER` [#​15875](https://github.com/pnpm/pnpm/issues/15875).
- `pnpm install` now fails with `ERR_PNPM_LOCKFILE_MISSING_DEPENDENCY` when an importer references a dependency version that has no snapshot entry. Before, the install succeeded and left a `node_modules` symlink pointing at a missing virtual-store directory [#​14764](https://github.com/pnpm/pnpm/issues/14764).
- `pnpm install` on CI now fails on an outdated lockfile when `preferFrozenLockfile` is explicitly set to `true`. Setting it to `true` used to let CI update the lockfile [#​9072](https://github.com/pnpm/pnpm/pull/9072).
- With `gitBranchLockfile` enabled, each emoji or other character outside the Basic Multilingual Plane in a branch name now becomes `!!` in the lockfile name. Before, each such character became one `!`.
##### Workspaces and filtering
- `pnpm install` in a workspace with `sharedWorkspaceLockfile: false` now installs projects concurrently, up to `workspaceConcurrency` at a time [#​14480](https://github.com/pnpm/pnpm/issues/14480). A project is resolved, fetched, and written to its virtual store without waiting for the workspace projects it depends on. It waits for them only before it links its dependencies and runs its lifecycle scripts, so its scripts still run after theirs. A project with a `preinstall` or `pnpm:devPreinstall` script, or with an injected or `file:` workspace dependency, waits for its workspace dependencies before it starts.
The installs of the projects also share their package metadata, lockfile verification, and store caches, so they use less CPU and memory when several projects depend on the same packages. An install with a pnpmfile no longer starts an extra Node.js process when the pnpmfile has no `preResolution` hook.
- With `enableGlobalVirtualStore` and `sharedWorkspaceLockfile: false`, each project now keeps its current lockfile and its hidden hoisted dependencies in its own `node_modules/.pnpm`. Before, every project wrote them to the workspace root's `node_modules/.pnpm`, so each repeat install treated the other projects' packages as its own and relinked them [#​14480](https://github.com/pnpm/pnpm/issues/14480).
- `pnpm rebuild`, `pnpm approve-builds`, and `pnpm ignored-builds` now work on the current project's `node_modules` when they run inside a project of a workspace with `sharedWorkspaceLockfile: false`. They used to read the workspace root's `node_modules`, so `pnpm rebuild` did not rebuild the project's dependencies and created a second virtual store at the workspace root [#​9402](https://github.com/pnpm/pnpm/issues/9402).
- `pnpm install` no longer creates a `node_modules` symlink inside the `publishConfig.directory` of a workspace package linked with `linkDirectory`. A build tool that cleaned its output directory through that symlink deleted the files of the package's dependencies. `pnpm install` also removes a symlink that an earlier install left there [#​16226](https://github.com/pnpm/pnpm/issues/16226). It also no longer fails with `ERR_PNPM_CMD_SHIM_RESOLVE_PATH` when such a package has a `bin` field and its `publishConfig.directory` does not exist yet.
- `pnpm install` no longer fails for an injected workspace dependency whose package publishes from a `publishConfig.directory` that its own `prepare` script builds. The injected copy now picks up that directory once `prepare` finishes building it. `pnpm install --frozen-lockfile` no longer reports the dependency as outdated while the directory has not been built yet [#​7811](https://github.com/pnpm/pnpm/issues/7811).
- An in-place edit to the source of an injected workspace package now shows up in its injected copy, unless a build writes to that package or `packageImportMethod` is set. pnpm hardlinks such packages under the default import method [#​4410](https://github.com/pnpm/pnpm/issues/4410). Scripts listed in `syncInjectedDepsAfterScripts` now update injected dependencies while they run, so a watcher on the injected package, such as a dev server, sees each change before the script exits.
- With `sharedWorkspaceLockfile: false`, an injected workspace package that has lifecycle scripts is now hard linked into the projects that depend on it. Before, pnpm left a plain copy, so later edits to the package did not reach those projects [#​9828](https://github.com/pnpm/pnpm/issues/9828).
- `injectWorkspacePackages` now hard links a workspace dependency declared with a relative path, such as `workspace:../foo`, the same way it already does for `workspace:*` [#​10446](https://github.com/pnpm/pnpm/issues/10446).
- Workspace discovery prunes dot-prefixed directories, so a `packages` pattern such as `**` no longer matches projects inside `.cache` and other hidden directories [#​16250](https://github.com/pnpm/pnpm/issues/16250).
- `pnpm import` in a workspace now keeps the versions pinned by a `yarn.lock` inside a workspace project [#​4385](https://github.com/pnpm/pnpm/issues/4385).
##### Store and caches
- Files imported from the store now follow the umask of the install that writes them. Installing with a umask of `077` no longer leaves imported files readable by the group and others [#​3807](https://github.com/pnpm/pnpm/issues/3807).
- `pnpm install` keeps the owner, group, and mode of files already in a shared store, including `index.db`. New store files and directories inherit the store directory's group-write bit. When that directory is setgid, new files inherit its group. pnpm does not change a file's owner or group [#​12765](https://github.com/pnpm/pnpm/issues/12765).
- When `pnpm install` repairs a store file that was modified through a hard link in `node_modules`, the repair now keeps the file's inode on Linux and macOS, so hard-linked copies in other projects are healed at the same time. On Windows the repair still replaces the file, so other projects are healed on their next install [#​3445](https://github.com/pnpm/pnpm/issues/3445).
- `pnpm install` now reports a full store at once when writing package files fails. It no longer retries the tarball [#​8581](https://github.com/pnpm/pnpm/issues/8581).
- pnpm now warns when it cannot hard link packages from an existing store in the pnpm home directory and falls back to a store on the project's filesystem. This can happen when the project is on another filesystem, such as a bind-mounted workspace in a container. The warning names both stores and suggests setting `storeDir` [#​14505](https://github.com/pnpm/pnpm/issues/14505).
- The side-effects cache now restores the symlinks that a build script creates inside a package. A warm install used to replace each of them with a copy of its target [#​12859](https://github.com/pnpm/pnpm/issues/12859).
After upgrading, every package with a build script is built once more.
- The global virtual store and the side-effects cache now key built packages by the Node.js version that the root project's `devEngines.runtime` or `engines.runtime` pins. That is the Node.js their build scripts run with. A dependency that declares its own `engines.runtime` no longer changes the key for every other package.
- With `enableGlobalVirtualStore`, an install into a fresh `node_modules` no longer runs the build scripts of a dependency whose global virtual store slot an earlier install already built. `pnpm rebuild` still runs them [#​14480](https://github.com/pnpm/pnpm/issues/14480).
- Concurrent installs that share a global virtual store now run a package's build in its shared slot one at a time. A failed build leaves the slot in place and marks it for the next install to rebuild [#​15568](https://github.com/pnpm/pnpm/issues/15568).
- A warm `pnpm install` reuses on-disk package metadata for five minutes when the registry does not send an ETag. Registries that send an ETag, including the public npm registry, still revalidate with a conditional request. `pnpm update` still fetches current metadata [#​13976](https://github.com/pnpm/pnpm/issues/13976).
- pnpm no longer revalidates cached registry metadata when the registry sends `Cache-Control: max-age=0`, `no-cache`, or `no-store`. It downloads the metadata again, so a version newly published to such a registry is visible on the next install [#​13487](https://github.com/pnpm/pnpm/issues/13487).
- `pnpm install` honors `Cache-Control` for dependencies named with an `http:` or `https:` tarball URL. A fresh response is taken from the store with no request, and a stale one is revalidated with `If-None-Match` [#​15648](https://github.com/pnpm/pnpm/issues/15648).
##### Patched dependencies
- `pnpm install` now repairs a `pnpm-lock.yaml` whose `(patch_hash=<hash>)` dependency paths disagree with its `patchedDependencies` map, including paths that lack the hash their patch calls for. Before, pnpm accepted such a lockfile as up to date and kept the old patched files. `pnpm install --frozen-lockfile` now fails on such a lockfile with `ERR_PNPM_INCONSISTENT_PATCH_HASH`. It fails with `ERR_PNPM_UNCHECKABLE_PATCH_HASH` when a patch hash in the lockfile is malformed, or when the lockfile lacks the package version or patch entry that the check needs [#​15336](https://github.com/pnpm/pnpm/pull/15336).
- `pnpm install` with `nodeLinker: hoisted` now applies a patch once to each copy of a patched dependency in a workspace. Before, a copy that several workspace projects shared could receive the patch twice and end up with the patched content duplicated [#​7565](https://github.com/pnpm/pnpm/issues/7565).
- `pnpm install` and `pnpm fetch` now fail with `ERR_PNPM_PATCH_NOT_FOUND` when a patch file listed in `patchedDependencies` does not exist [#​5268](https://github.com/pnpm/pnpm/issues/5268).
- `engineStrict` now checks the patched `package.json` when a `patchedDependencies` entry changes `engines`. A patch that relaxes `engines.node` no longer fails the install against the published range [#​9603](https://github.com/pnpm/pnpm/issues/9603).
- `pnpm patch` now applies the existing patch file to the edit directory of a git-hosted dependency, as it already does for packages from the registry [#​9699](https://github.com/pnpm/pnpm/issues/9699).
##### Adding, updating, and removing dependencies
- `pnpm add <dir>` now warns when the added directory declares peer dependencies, as `pnpm link` does. The directory is saved as a `link:` dependency, and its peers are not resolved from the project that adds it. Use the `file:` protocol to have them resolved [#​5523](https://github.com/pnpm/pnpm/issues/5523).
- `pnpm add --save-types` no longer adds a `@types/*` package whose resolved version is deprecated. DefinitelyTyped publishes such stubs for packages that ship their own types, such as `@types/typescript` for `typescript` [#​15636](https://github.com/pnpm/pnpm/issues/15636).
- `pnpm version`, `pnpm add`, and `pnpm pkg set` keep JSON5 style when they update `package.json5`. ASCII identifier keys stay unquoted, strings keep JSON5 quotes, and indented files keep trailing commas [#​15717](https://github.com/pnpm/pnpm/issues/15717).
##### Running scripts and commands
- `pnpm run` and `pnpm exec` no longer install dependencies automatically when the root `package.json` still keeps `overrides`, `packageExtensions`, `patchedDependencies`, or `ignoredOptionalDependencies` in its `pnpm` field. pnpm no longer reads that field, so the install rewrote the lockfile without those settings. The command now fails and asks to move the settings to `pnpm-workspace.yaml` [#​16278](https://github.com/pnpm/pnpm/issues/16278).
- When `verifyDepsBeforeRun` triggers an install before a filtered `pnpm run` or `pnpm exec`, pnpm now installs only the selected projects and their dependencies. A later filtered command also installs a selected project that an earlier filtered install skipped [#​11865](https://github.com/pnpm/pnpm/issues/11865).
- `pnpm -r run /regexp/` now honors the `tasks` `dependsOn` declared for each script the selector matches, like running the script by name does. Matched scripts that depend on each other run in order. Each matched script runs once [#​15596](https://github.com/pnpm/pnpm/issues/15596).
- `pnpm run` exits with the code of a script that handles Ctrl+C and shuts down. A script that finished cleanly is not reported as a lifecycle failure. The commands after it in the same script still run [#​9945](https://github.com/pnpm/pnpm/issues/9945).
- pnpm no longer hangs after a lifecycle script exits while a process it started in the background keeps the script's output open. pnpm stops reading that output one second after the script exits [#​5730](https://github.com/pnpm/pnpm/issues/5730).
- `pnpm run` and lifecycle scripts use the configured `scriptShell`, including Git Bash on Windows, when `shellEmulator` is also enabled. `shellEmulator` still runs scripts when `scriptShell` is not set. Extra arguments passed to `pnpm run` are quoted for the shell that runs the script, so a Windows path stays intact [#​14719](https://github.com/pnpm/pnpm/issues/14719).
- With `enableGlobalVirtualStore`, dependency build scripts now see the workspace root's `node_modules/.bin`, as they do with a local virtual store. A `postinstall` script that runs `node` finds the Node.js installed by `devEngines.runtime` and no longer fails with "command not found" on machines without a system Node.js [#​15652](https://github.com/pnpm/pnpm/issues/15652). Dependency build scripts also see the bins of privately hoisted dependencies.
- Dependency install scripts now find the node-gyp bundled with pnpm when pnpm runs through a symlink, such as `node_modules/.bin/pnpm` or the `pnpm` that `npm install -g pnpm` links. They used to fail with `node-gyp: command not found` on macOS [#​15694](https://github.com/pnpm/pnpm/issues/15694).
- `pnpm run` and lifecycle scripts now set `npm_config_node_gyp` to the bundled `node-gyp` entry point. Tools that read the variable resolve the same `node-gyp` pnpm builds with. An `npm_config_node_gyp` value the environment already sets is kept as is [#​16270](https://github.com/pnpm/pnpm/issues/16270).
- Scripts now see the `npm_command` environment variable that npm sets. It holds `run-script` when the command runs a script, and the command's own name otherwise [#​16265](https://github.com/pnpm/pnpm/issues/16265).
- Commands run from a POSIX shell through a dependency's own `node_modules/.bin`, such as `node_modules/vite/node_modules/.bin/esbuild`, no longer fail with `MODULE_NOT_FOUND` [#​10189](https://github.com/pnpm/pnpm/issues/10189).
- `pnpx --version` and `pnpm dlx --version` now print the pnpm version. Other unknown options before the command are reported as errors. Before, pnpm tried to download a package named after the option [#​16259](https://github.com/pnpm/pnpm/issues/16259).
- `pnpm dlx` now keeps the virtual store of its cached installs in `node_modules/.pnpm`, like every other install [#​13955](https://github.com/pnpm/pnpm/issues/13955). `pnpm pack-app` now names the manifest of its runtime install directory `pnpm-pack-app-<target>`.
##### Publishing, packing, and deploying
- `pnpm pack` and `pnpm publish` now ship a file that the `files` field names even when another entry excludes the directory holding it. For example, `["**", "!dist", "dist/index.d.ts"]` ships `dist/index.d.ts` [#​16213](https://github.com/pnpm/pnpm/issues/16213).
- `pnpm pack` prunes a directory that a `files` field exclusion names, such as `!**/test`, excluding the directory and its contents from the packed package [#​15738](https://github.com/pnpm/pnpm/issues/15738).
- `pnpm publish` now waits at least 5 minutes for the registry to answer a publish request, like npm. This fixes "409 Conflict - Failed to save packument" errors when the registry is slow to answer [#​11454](https://github.com/pnpm/pnpm/issues/11454).
- `pnpm deploy --prod` no longer fails with `ERR_PNPM_OUTDATED_LOCKFILE` when the deployed project declares a `devEngines.runtime` with `onFail: download`. The runtime stays out of the deployed `node_modules` with the rest of the dev dependencies [#​15703](https://github.com/pnpm/pnpm/issues/15703).
- `pnpm deploy` with a shared lockfile now copies workspace dependencies into the deploy directory, even when `packageImportMethod` is set to `hardlink`. Before, their files were hard-linked to the workspace sources, so editing a source file also changed the deployed copy [#​12176](https://github.com/pnpm/pnpm/issues/12176).
- `pnpm deploy --legacy` no longer leaves broken links to nested local dependencies of workspace packages [#​9575](https://github.com/pnpm/pnpm/issues/9575).
##### Configuration and pnpmfile hooks
- Every setting pnpm supports can now be set with `--config.<name>=<value>` on the command line, not only the ones whose command also carries a matching flag. Before, `pnpm install --config.frozen-lockfile=true` dropped the setting and rewrote `pnpm-lock.yaml` as though the install had not been frozen [#​16276](https://github.com/pnpm/pnpm/issues/16276).
- Settings given on the command line, such as `--registry` and `--store-dir`, now take precedence over the values a pnpmfile `updateConfig` hook sets [#​14063](https://github.com/pnpm/pnpm/issues/14063).
- `pnpm config set --location=project` and `pnpm config delete --location=project`, run from a package inside a workspace, now write settings that belong in `pnpm-workspace.yaml` to the workspace root's `pnpm-workspace.yaml`. Before, they created a new `pnpm-workspace.yaml` in the current package, which made that package the workspace root. Settings stored in `.npmrc` are still written to the current directory [#​13757](https://github.com/pnpm/pnpm/issues/13757).
- pnpm now reads the workspace directory override from `PNPM_CONFIG_WORKSPACE_DIR`, like other settings. `NPM_CONFIG_WORKSPACE_DIR` still works as a fallback [#​16275](https://github.com/pnpm/pnpm/issues/16275).
- pnpm now fails with `ERR_PNPM_AUTH_INVALID_BASE64` when a registry's `_password` in `.npmrc` is not valid base64. Before, it sent the value as the raw password. A `username` or `_password` left empty, for example by an unset environment variable, now supplies no credential [#​16273](https://github.com/pnpm/pnpm/issues/16273).
- `proxy=false` now turns proxying off even when `HTTP_PROXY`, `HTTPS_PROXY`, or `ALL_PROXY` is set. pnpm no longer sends requests through a proxy named only in `ALL_PROXY`.
- `pnpm install` now runs the install hooks of a config dependency plugin's pnpmfile, including `readPackage`, `afterAllResolved`, and custom resolvers. Its pnpmfile is also counted in `pnpmfileChecksum`. Before, only the plugin's `updateConfig` hook ran, so a plugin could not change the resolved dependencies.
- A pnpmfile `fetchers` hook now runs once per package on a fresh install when it handles a resolution with a custom `type` or delegates a git-hosted one to the same subdirectory [#​15584](https://github.com/pnpm/pnpm/issues/15584). These packages were fetched a second time for installation, so the installed files could come from a different archive than the one their dependencies were read from. The hook also no longer runs twice when a `resolvers` hook returns a tarball resolution without a manifest [#​15025](https://github.com/pnpm/pnpm/issues/15025).
- `pnpm install` now re-fetches a package from a custom resolver when the `integrity` of its resolution changes, with or without `enableGlobalVirtualStore`. It used to update the lockfile but keep the old files in `node_modules` [#​15670](https://github.com/pnpm/pnpm/issues/15670).
- `pnpm install` now rejects invalid results from a `readPackage` hook. A hook that returns a non-object value fails with `ERR_PNPM_BAD_READ_PACKAGE_HOOK_RESULT` [#​15730](https://github.com/pnpm/pnpm/issues/15730). A hook that sets a dependency range to a value other than a string, such as `undefined`, fails with an error that names the dependency, the package and the pnpmfile. Delete the property to remove a dependency [#​15705](https://github.com/pnpm/pnpm/issues/15705).
##### Global packages, pnpm versions, and runtimes
- `pnpm update --global` now reinstalls the global packages that pnpm 10 installed into the previous global directory, `<global-dir>/5`, so their commands are linked into the pnpm home `bin` directory again and `pnpm list --global` lists them. Once every package is migrated, pnpm deletes the previous directory and the commands pnpm 10 linked into the pnpm home [#​11528](https://github.com/pnpm/pnpm/issues/11528).
- A signal sent to pnpm, such as `SIGTERM`, now reaches the pnpm that pnpm switches to because of `packageManager` or `devEngines.packageManager`, and the one that `pnpm with` runs. The signal used to be dropped, so scripts running under that pnpm never got to shut down [#​9948](https://github.com/pnpm/pnpm/issues/9948).
- On arm64 musl Linux, such as Alpine on ARM, switching to a pinned pnpm older than 12 now runs the JavaScript `pnpm` package. The standalone executable of those versions crashed at startup on that platform [#​10443](https://github.com/pnpm/pnpm/issues/10443).
- Global shims such as `node` now work when pnpm runs through a relative symlink, as with a Homebrew install. They were copies of that symlink and did not resolve from the global bin directory [#​15691](https://github.com/pnpm/pnpm/issues/15691).
- `pnpm env remove --global` deletes Node.js versions that pnpm installed into its own store, including when another tool installed pnpm [#​8357](https://github.com/pnpm/pnpm/issues/8357).
- `pnpm self-update` no longer suggests a downgrade when `minimumReleaseAge` holds back the registry's `latest` release. It now says that release is still within the cutoff [#​12006](https://github.com/pnpm/pnpm/issues/12006).
##### Windows
- Interrupting a script with Ctrl+C on Windows no longer leaves the terminal stuck [#​14860](https://github.com/pnpm/pnpm/issues/14860). A script that runs through a batch shim, as `vite dev` does through `vite.CMD`, made cmd.exe wait forever on its "Terminate batch job (Y/N)?" answer, and every following keystroke went to that prompt. pnpm now ends a cmd.exe script shell once it has sat for a second after the interrupt with nothing running under it. A script that takes longer to shut down is still waited for. A second Ctrl+C ends the script's shell at once.
- On Windows, `pnpm run` now passes the arguments after the script name to the script as typed. Before, `cmd` expanded `%VAR%` in them and backslashes arrived doubled. Line breaks still arrive as the two characters `\n`, because `cmd` cannot pass them. The command line pnpm prints for the script quotes the arguments the same way on every platform [#​16257](https://github.com/pnpm/pnpm/issues/16257).
- The Windows `pnpm.exe` runs on a clean Windows install that does not have the Visual C++ Redistributable. It used to exit immediately on startup because that runtime was missing [#​15723](https://github.com/pnpm/pnpm/issues/15723).
- On Windows, the `.cmd` command shims in `node_modules/.bin` now keep a `%` in the project path. Before, cmd.exe expanded it as a variable reference, so the command received a mangled `NODE_PATH` [#​15716](https://github.com/pnpm/pnpm/issues/15716). Command shims also run tools whose paths contain non-ASCII characters [#​6999](https://github.com/pnpm/pnpm/issues/6999), including the PowerShell shims in Windows PowerShell 5.1 [#​16217](https://github.com/pnpm/pnpm/issues/16217).
- Bin shims in `node_modules/.bin` run from Cygwin on Windows again. The shims passed a `/cygdrive/c/...` path to the Windows `node` found on `PATH`, so Node.js failed with `Cannot find module 'C:\cygdrive\c\...'` [#​12845](https://github.com/pnpm/pnpm/issues/12845).
- On Windows, installing pnpm with npm inside a project now writes `node_modules/.bin` shims that run `pnpm.exe`. A global install with `npm install --location=global` now gets the same shims as `npm install -g` [#​15688](https://github.com/pnpm/pnpm/issues/15688).
- `pnpm install` no longer fails with `ERR_PNPM_WORKSPACE_INVALID_GLOB` on Windows for a wildcard pattern such as `plugins/*/*` in `pnpm-workspace.yaml` when the workspace is on a different drive than the pnpm cache or state directory [#​16239](https://github.com/pnpm/pnpm/issues/16239).
- On Windows, `pnpm install` no longer skips a dependency's build script on a later install when the package ships an executable file and the script changes nothing inside the package directory [#​15667](https://github.com/pnpm/pnpm/issues/15667).
- `pnpm setup` no longer writes the `pn.ps1`, `pnpx.ps1`, and `pnx.ps1` PowerShell wrappers. It also removes the ones an earlier setup wrote. PowerShell now runs `pn`, `pnpx`, and `pnx` through their `.cmd` wrappers, like `pnpm` itself. Before, these aliases failed with a "not digitally signed" error wherever the execution policy blocks unsigned scripts [#​8444](https://github.com/pnpm/pnpm/issues/8444).
- `pnpm setup` on Windows no longer panics when an unrelated environment variable has a name containing a non-ASCII character. It skips that variable [#​15684](https://github.com/pnpm/pnpm/issues/15684).
- On Windows, `pnpm setup` repairs the `PNPM_HOME` registry type left by older pnpm versions, even when the configured directory has not changed.
- On Windows, the `ERR_PNPM_BAD_ENV_FOUND` error of `pnpm setup` now shows the value `PNPM_HOME` is currently set to. Before, it showed the directory pnpm wanted to set.
- On Windows, pnpm expands nested `%VAR%` references in `PNPM_HOME` and the other directory environment variables it uses for its home, store, cache, state, and config directories. pnpm fails with an error when a `%VAR%` reference remains after expansion [#​13236](https://github.com/pnpm/pnpm/issues/13236).
- On Windows, if the global bin directory is not in `PATH` and a `PATH` entry still contains an unexpanded variable such as `%PNPM_HOME%`, the error now names that entry. A variable referenced from the user `Path` must be set to a full path and stored as a plain string (`REG_SZ`) for the entry to expand [#​5283](https://github.com/pnpm/pnpm/issues/5283).
##### Inspecting dependencies
- `pnpm audit` and `pnpm audit signatures` now fail with an error when the lockfile contains unresolvable dependency references [#​13638](https://github.com/pnpm/pnpm/issues/13638).
- `pnpm licenses list` now reports the actual on-disk package locations when using `nodeLinker: hoisted` or `shamefully-hoist: true` [#​8589](https://github.com/pnpm/pnpm/issues/8589). With `--json`, its `paths` array now includes every installed copy of a package, including hoisted copies and isolated installations with different peer dependencies.
- `pnpm root` now prints the configured `modulesDir`. It used to print `node_modules` regardless of the setting. A project's own `modulesDir` from `packageConfigs` is printed too [#​9113](https://github.com/pnpm/pnpm/issues/9113).
##### Output and messages
- With the default and append-only reporters, installs with `--loglevel warn` or `--loglevel error` now print the full output of a failed install script. The output of successful scripts, including the root project's own install hooks, stays hidden. With `--loglevel warn`, pnpm also prints ignored build script warnings.
- When a dependency fails to resolve, the error now shows the cause. For example, a Node.js runtime download behind a proxy that re-signs TLS now reports `invalid peer certificate: UnknownIssuer` [#​9556](https://github.com/pnpm/pnpm/issues/9556).
- When installing a git dependency over SSH fails with `Permission denied (publickey)`, pnpm suggests checking the loaded keys with `ssh-add -l`. Resolving an SSH URL that refuses the key also shows a local HTTPS rewrite that leaves the recorded URL alone [#​13743](https://github.com/pnpm/pnpm/issues/13743).
- Lockfile verification now fails with `ERR_PNPM_TARBALL_URL_MISMATCH`, `ERR_PNPM_TARBALL_REVISION_MISMATCH`, or `ERR_PNPM_MISSING_NAMED_REGISTRY` when every rejected entry failed that check. These failures were reported as the generic `ERR_PNPM_LOCKFILE_RESOLUTION_VERIFICATION`.
- The lockfile verification error now suggests relaxing the policy that flagged an entry only if a fresh resolution still fails and you trust the affected packages. Errors from checks that no policy controls, such as a missing tarball integrity, no longer suggest relaxing a policy [#​14411](https://github.com/pnpm/pnpm/issues/14411).
- `pnpm install` no longer prints an extra `Progress:` line after the progress line is marked `done` [#​16184](https://github.com/pnpm/pnpm/issues/16184).
<!-- 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.7.0`](https://github.com/pnpm/pnpm/releases/tag/v12.7.0): pnpm 12.7
[Compare Source](https://github.com/pnpm/pnpm/compare/v12.6.0...v12.7.0)
pnpm 12.7.0 ships with `.nvmrc` and `.node-version` support in the global `node` shim, `pnpm install --allow-build`, `pnpm publish --publish-wait-timeout`, and `pnpm-workspace.yaml` created from the `workspaces` field. `pnpm install --force` no longer installs optional dependencies built for other platforms. This release also carries security fixes for bin shims on Nix, for lifecycle scripts of packages in a `storeDir` inside the workspace, and for `userAgent` placeholders in `pnpm-workspace.yaml`.
##### Minor Changes
- `pnpm install --force` now keeps skipping optional dependencies whose `os`, `cpu` or `libc` do not match the host. It still refetches every package and lifts `engineStrict`. The new `forceIgnoresPlatform` setting restores the previous behaviour, installing optional dependencies of every platform under `--force` [#​6133](https://github.com/pnpm/pnpm/issues/6133).
- The global `node` shim created by pnpm now uses the Node.js version from the nearest `.nvmrc` or `.node-version` file when the project does not declare a Node.js runtime in `devEngines.runtime` or `engines.runtime` [#​4471](https://github.com/pnpm/pnpm/issues/4471). The nearest directory with a Node.js runtime declaration decides the version. Within one directory, `package.json` takes precedence over `.node-version`, which takes precedence over `.nvmrc`. An `.nvmrc` value that only nvm can act on, such as `system` or a custom alias, is ignored.
- `pnpm install` now supports the `--allow-build` option to selectively allow or deny package lifecycle scripts and record them in `pnpm-workspace.yaml` [#​15388](https://github.com/pnpm/pnpm/issues/15388).
- Added `pnpm publish --publish-wait-timeout <milliseconds>` to wait for published versions and their tarballs to become available from the registry. Set `publishWaitTimeout` in `pnpm-workspace.yaml` to configure a default. A value of `0` disables the check.
Recursive publishing confirms availability before publishing dependent packages. If confirmation times out, the command fails.
When `pnpm publish -r --report-summary` fails after some uploads were accepted, the summary file now lists those packages.
- `pnpm install` now creates `pnpm-workspace.yaml` from the `workspaces` field of the root `package.json` when the repository has no `pnpm-workspace.yaml`. The projects the field lists are linked on that same install. An existing `pnpm-workspace.yaml` is never changed. With `--ignore-workspace`, no file is created. If the `workspaces` field later differs from `packages` in `pnpm-workspace.yaml`, pnpm prints a warning [#​2255](https://github.com/pnpm/pnpm/issues/2255).
- When a project pins a pnpm version or a runtime that another pnpm process is installing at that moment, pnpm now waits a few seconds and then installs and runs a private copy of its own. It used to wait up to five minutes and then use the shared install directory without the lock. The private copy is removed once the command has run. `pnpm store prune` removes any private copy that a killed process left behind [#​15413](https://github.com/pnpm/pnpm/issues/15413).
- pnpm now keeps the blank lines between entries of `package.json` when it updates the file, for example on `pnpm add` [#​5602](https://github.com/pnpm/pnpm/issues/5602).
##### Patch Changes
##### Security
- pnpm no longer expands environment variables in a `userAgent` set in a project's `pnpm-workspace.yaml`. A `userAgent` with a placeholder in that file is now ignored. Before this fix, pnpm sent the variable's value to the configured registry [#​15415](https://github.com/pnpm/pnpm/issues/15415).
- On Nix, a dependency's bin named like a system utility such as `sed` can no longer redirect a POSIX bin shim or the `pnpm`, `pn`, `pnpx`, and `pnx` launchers. The shims and launchers now ignore `node_modules` and relative `PATH` entries while they locate their own files. Installing again replaces the shims already in `node_modules` [#​14883](https://github.com/pnpm/pnpm/issues/14883).
- pnpm no longer treats manifests inside its store, cache, state, or modules directories as workspace projects. Before, a `storeDir` inside the workspace could let lifecycle scripts of packages in the store run without `allowBuilds` approval [#​15033](https://github.com/pnpm/pnpm/issues/15033).
- Packages that run a lifecycle script are no longer hard-linked into the virtual store, so a build script can no longer rewrite the workspace source of an injected package or the store copy it was imported from [#​15483](https://github.com/pnpm/pnpm/issues/15483).
##### Installing packages
- Fixed `pnpm install`, `pnpm add`, `pnpm remove`, and `pnpm peers check` running out of memory when many packages share a missing peer dependency. This mostly affected projects with `autoInstallPeers: false` [#​15362](https://github.com/pnpm/pnpm/issues/15362).
- pnpm no longer hangs for up to 5 minutes after a pnpm process was killed while setting up the pnpm version pinned in `packageManager` or `devEngines` [#​15360](https://github.com/pnpm/pnpm/issues/15360), [#​15393](https://github.com/pnpm/pnpm/issues/15393). The killed process left behind a lock that every later pnpm command in the project waited on. pnpm now detects that the process holding a lock is gone and takes the lock over at once. The same applies to the locks pnpm takes while installing a managed runtime or writing the global bin directory. Two pnpm processes that are both still running keep waiting for each other as before.
- Requests to a registry or tarball server whose TLS certificate fails verification now fail at once. Such requests were retried for more than a minute without any output [#​9134](https://github.com/pnpm/pnpm/issues/9134).
- On macOS, pnpm now falls back to its bundled CA roots when system trust evaluation is unavailable, such as in a sandbox or when macOS cannot create an SSL policy for a registry connection. Installs failed or crashed on the first registry request in that case. Custom `ca` certificates are now honored directly [#​15329](https://github.com/pnpm/pnpm/issues/15329), [#​14461](https://github.com/pnpm/pnpm/issues/14461).
- `pnpm install` now caps concurrent connections to a proxy at 50 sockets by default [#​15280](https://github.com/pnpm/pnpm/issues/15280). It also immediately retries transient connection resets when downloading package archives.
- `pnpm install` now reuses a package already present in the store when an existing lockfile entry satisfies the dependency, avoiding registry requests that fail without authorization [#​2522](https://github.com/pnpm/pnpm/issues/2522).
- Installing or adding dependencies no longer fails when a previously installed local tarball file was deleted from disk [#​8367](https://github.com/pnpm/pnpm/issues/8367).
- `pnpm install` now installs the new version of a local tarball dependency whose file was replaced at the same path [#​2437](https://github.com/pnpm/pnpm/issues/2437). `pnpm install --frozen-lockfile` rejects such a changed tarball, even when the previous archive contents are in the store [#​1889](https://github.com/pnpm/pnpm/issues/1889).
- `pnpm install` now fetches committed submodules of git dependencies [#​1470](https://github.com/pnpm/pnpm/issues/1470).
- `pnpm install` now applies patches produced by `pnpm patch-commit` when an edit removes the trailing lines of a file along with its newline. The install no longer fails with `ERR_PNPM_INVALID_PATCH` ("expected end of hunk") [#​12451](https://github.com/pnpm/pnpm/issues/12451).
- `pnpm install` now preserves existing `node_modules` directories when a cross-device move reports `EXDEV` [#​14504](https://github.com/pnpm/pnpm/issues/14504).
- `pnpm install` no longer fails when writing the workspace state file encounters an error. Failures to update the state file now emit a warning instead of aborting the install [#​14550](https://github.com/pnpm/pnpm/issues/14550).
- Interrupting `pnpm install` with Ctrl+C or SIGTERM no longer leaves a temporary lockfile (`.pnpm-lock.yaml.*.tmp`) behind in the project [#​1418](https://github.com/pnpm/pnpm/issues/1418).
- `pnpm install` now relinks a direct dependency whose link in `node_modules` points to a missing target. Before, it reported "Already up to date" and left the broken link [#​9758](https://github.com/pnpm/pnpm/issues/9758).
- `pnpm install` uses less CPU when it links packages from a warm store. On Windows, a warm install could take several times longer than with pnpm 11 [#​15439](https://github.com/pnpm/pnpm/issues/15439).
- `pnpm install` now runs `node --version` once per run. A workspace whose projects keep their own lockfiles (`sharedWorkspaceLockfile: false`) previously ran the probe once or twice for every project, and on macOS the concurrent launches waited on each other, so a project could wait several seconds before its linking started.
- A repeat `pnpm install --frozen-lockfile` with `nodeLinker: hoisted` in a workspace no longer re-links `node_modules` when nothing changed.
- Custom fetcher hooks no longer run a second time during installation when an archive was already fetched during dependency resolution [#​15025](https://github.com/pnpm/pnpm/issues/15025).
- Fixed a package resolved by a `resolvers` pnpmfile hook installing without its own dependencies. This happened when the hook returned no `manifest` and a `fetchers` hook handled the resolution [#​15552](https://github.com/pnpm/pnpm/issues/15552).
- `pnpm install --prod` and other installs that skip `devDependencies` no longer run the `pnpm:devPreinstall` script [#​7065](https://github.com/pnpm/pnpm/issues/7065). They skip `prepare` lifecycle scripts too, as do installs given package arguments.
- `pnpm prune --prod` and production installs now remove devDependencies when `lockfile: false` is configured [#​2677](https://github.com/pnpm/pnpm/issues/2677).
- `pnpm install --prod`, `pnpm fetch --prod` and `pnpm deploy --prod` no longer install a devDependency that is only there to satisfy an optional peer dependency of a production dependency. `pnpm list`, `pnpm why`, `pnpm licenses`, `pnpm sbom` and `pnpm audit` leave it out of `--prod` results too. The same applies to `--dev`. A peer that is not optional is still installed and audited [#​15344](https://github.com/pnpm/pnpm/issues/15344).
- `pnpm install` no longer skips optional dependencies that the Node.js version locked for a `devEngines.runtime` range supports, when the range uses `onFail: download`. An explicitly set `nodeVersion` still takes priority [#​14628](https://github.com/pnpm/pnpm/issues/14628).
- `pnpm fetch` now also installs the pnpm version that `pnpm-lock.yaml` pins, when it differs from the running pnpm. A later `pnpm install --offline` that switches to the pinned version no longer fails because that version is missing from the store [#​11808](https://github.com/pnpm/pnpm/issues/11808).
- A dependency that ships a `binding.gyp` and sets `gypfile: false` no longer gets the `node-gyp rebuild` install script pnpm synthesizes for it. Such a dependency needs no `allowBuilds` entry and is no longer listed under "Ignored build scripts".
- `pnpm install` no longer adds `allowBuilds` placeholder entries to `pnpm-workspace.yaml` when it runs in CI or without a terminal. Interactive installs still add them [#​11574](https://github.com/pnpm/pnpm/issues/11574).
- pnpm now detects the same CI environments as pnpm 11, including AWS CodeBuild, which does not set `CI`. On these services `pnpm install` uses a frozen lockfile by default and fails with `ERR_PNPM_OUTDATED_LOCKFILE` when the lockfile is outdated.
##### Resolving and linking dependencies
- Installing through a pnpr server now installs a project's peer dependencies when `autoInstallPeers` is enabled. A project that declared only peer dependencies failed with `ERR_PNPM_OUTDATED_LOCKFILE` or skipped its peers [#​14833](https://github.com/pnpm/pnpm/issues/14833).
- pnpm now installs a dependency that a package also declares as an optional peer dependency, for example `lightningcss` in some vite builds. The dependency was missing from `node_modules`, so the package failed to import it [#​8912](https://github.com/pnpm/pnpm/issues/8912).
- Removal overrides such as `"parent>peer": "-"` now prevent optional peers from being installed from another workspace package [#​15008](https://github.com/pnpm/pnpm/issues/15008).
- Removing an entry from `overrides` now re-resolves the packages it targeted. A version the override had locked is no longer kept just because the declared range still accepts it [#​4587](https://github.com/pnpm/pnpm/issues/4587).
- `packageExtensions` and `overrides` entries with a ranged selector (such as `@<X` or `@*`) no longer match a dependency that has no `package.json`, such as a local directory dependency [#​15007](https://github.com/pnpm/pnpm/issues/15007).
- Trim leading and trailing whitespace from dependency override selectors in `pnpm.overrides` [#​6356](https://github.com/pnpm/pnpm/issues/6356).
- With `trustPolicy: no-downgrade`, pnpm now resolves the newest matching version that is not a trust downgrade. Previously a dependency failed with `ERR_PNPM_TRUST_DOWNGRADE` even when an older version satisfied its range. `pnpm self-update` picks its target version the same way. A request for an exact version still fails [#​14176](https://github.com/pnpm/pnpm/issues/14176).
- `pnpm install` now re-resolves a dependency when its manifest range is updated from a prerelease to a stable version. The lockfile previously retained the prerelease version and caused `--frozen-lockfile` to fail [#​15528](https://github.com/pnpm/pnpm/issues/15528).
- `pnpm install --ignore-pnpmfile` no longer removes `pnpmfileChecksum` from an up-to-date `pnpm-lock.yaml`. `pnpm install --frozen-lockfile --ignore-pnpmfile` no longer fails with `ERR_PNPM_LOCKFILE_CONFIG_MISMATCH` when the lockfile records a `pnpmfileChecksum`. A command that resolves dependencies with the pnpmfile ignored still writes the lockfile without it [#​10944](https://github.com/pnpm/pnpm/issues/10944).
- `pnpm install` and `pnpm peers check` now use local tarball packages' actual versions when checking peer dependencies. Compatible packages no longer fail with `strictPeerDependencies` enabled.
- `pnpm peers check` and the install-time peer dependency check now resolve peer dependencies from the workspace root when `resolvePeersFromWorkspaceRoot` is enabled [#​14982](https://github.com/pnpm/pnpm/issues/14982).
- `autoDedupe` and `pnpm dedupe` now move transitive dependencies to the version a `catalog:` dependency pins, as they already did for versions written directly in `package.json`. Previously they could move those dependencies to a higher version and keep both versions in the lockfile.
- `pnpm dedupe` now produces a stable lockfile when a dependency's range matches both a direct dependency and an `npm:` alias of the same package. The dependency resolves to the version of the direct dependency. Repeated runs previously alternated between two lockfiles [#​15588](https://github.com/pnpm/pnpm/issues/15588).
- Merging lockfiles now preserves recorded configuration fields such as `overrides`, `neverBuiltDependencies`, `patchedDependencies`, `packageExtensionsChecksum`, `settings`, and `catalogs` [#​8366](https://github.com/pnpm/pnpm/issues/8366).
- A lockfile entry whose resolution is unchanged now keeps its recorded `deprecated` message [#​5772](https://github.com/pnpm/pnpm/issues/5772).
- pnpm no longer writes a package's legacy array-form `engines`, such as `["node >= 0.8"]`, to the lockfile. It was recorded as an object keyed by index, such as `{'0': node >= 0.8}` [#​4518](https://github.com/pnpm/pnpm/issues/4518).
- Tarball URLs recorded in the lockfile now strip default HTTP and HTTPS ports (`:80` and `:443`) [#​15539](https://github.com/pnpm/pnpm/issues/15539).
- `node_modules/.package-map.json` no longer contains entries that point at directories that do not exist. Such entries appeared for packages installed only with peer dependencies, most visibly with `enableGlobalVirtualStore` [#​14938](https://github.com/pnpm/pnpm/issues/14938).
- With `nodeLinker: hoisted`, `hoistWorkspacePackages` now links each workspace project that `hoistPattern` or `publicHoistPattern` selects into the root `node_modules`, unless a hoisted package or a root dependency already uses its name. The project's bins are linked into the root `node_modules/.bin` [#​7553](https://github.com/pnpm/pnpm/issues/7553).
- With `nodeLinker: hoisted`, `pnpm install` now removes the commands of the packages it removes from `node_modules/.bin`, such as a nested copy deduped into the root `node_modules` [#​7568](https://github.com/pnpm/pnpm/issues/7568).
- `pnpm install` no longer puts a dependency's bin on `PATH` for that dependency's own lifecycle scripts before the bin's file exists. pnpm links such a bin after the dependency's build has run. It also removes such a bin left by an earlier install. This fixes installing the `node` package on Windows [#​15501](https://github.com/pnpm/pnpm/issues/15501).
- Dependencies and executable binaries are now correctly linked and accessible for workspace packages using `publishConfig.directory` and `publishConfig.linkDirectory` [#​8338](https://github.com/pnpm/pnpm/issues/8338).
- Bin linking leaves workspace and linked dependency files outside node\_modules unchanged. Already executable bin files no longer receive redundant permission changes.
##### Workspaces and filtering
- `pnpm install` now finds workspace projects reached through a symlink, such as a `packages` directory that links to a folder outside the workspace. It installs their dependencies, and the links in their `node_modules` resolve [#​1044](https://github.com/pnpm/pnpm/issues/1044).
- A dependency declared with `catalog:` now counts as a workspace dependency when its catalog entry points at a workspace project, for example `workspace:*` [#​15587](https://github.com/pnpm/pnpm/issues/15587). With `linkWorkspacePackages` enabled, so does an `npm:` alias of a workspace project, such as `"math-alias": "npm:math@^1.0.0"`. `pnpm -r run` runs that project first. `--filter <pkg>...` selects it.
- A `workspace:` dependency now resolves to a workspace project whose version is not valid semver, such as `1` or `1.0`. `workspace:*`, `workspace:^`, and `workspace:~` match it. A range identical to the version also matches it [#​4567](https://github.com/pnpm/pnpm/issues/4567).
- A `workspace:` dependency with an exact version now resolves to a workspace project whose version carries SemVer build metadata. For example, `workspace:0.5.6-next.3` matches a project at `0.5.6-next.3+f60facc` [#​6483](https://github.com/pnpm/pnpm/issues/6483).
- Secondary dependencies now prefer the version resolved by the local project's direct dependencies over versions from sibling workspace projects [#​7191](https://github.com/pnpm/pnpm/issues/7191).
- `pnpm install` now re-resolves a workspace project's auto-installed peer dependency when another workspace project changes its specifier for that package to one that excludes the locked version but still overlaps the peer range. The peer then resolves to the version a fresh install would pick [#​11800](https://github.com/pnpm/pnpm/issues/11800).
- `pnpm install --frozen-lockfile` now fails with `ERR_PNPM_OUTDATED_LOCKFILE` when `pnpm-lock.yaml` lists a workspace project whose directory or manifest file is missing. The install used to report success without installing that project's dependencies [#​7667](https://github.com/pnpm/pnpm/issues/7667).
- `pnpm install -r` now installs every workspace project when `recursiveInstall` is set to `false` in `pnpm-workspace.yaml` [#​7504](https://github.com/pnpm/pnpm/issues/7504).
- `pnpm install` with `--filter` now installs only the dependencies of the selected projects when using `nodeLinker: hoisted` [#​8882](https://github.com/pnpm/pnpm/issues/8882).
- `pnpm install` now updates an injected workspace dependency after that package's own dependencies change, when `shared-workspace-lockfile` is `false` [#​7209](https://github.com/pnpm/pnpm/issues/7209).
- `pnpm install` now copies the output of a workspace package's own `prepare`, `install`, or `postinstall` script into the injected copies of that package. Before, the injected copies kept only the files that existed before the script ran. `syncInjectedDepsAfterScripts` now also works when `modulesDir` is set [#​9464](https://github.com/pnpm/pnpm/issues/9464).
- `syncInjectedDepsAfterScripts` now copies files into injected dependencies when `node_modules` is on another filesystem than the package sources. The sync previously failed with a cross-device link error and made the script run exit with an error [#​14703](https://github.com/pnpm/pnpm/issues/14703).
- A `modulesDir` with several path segments, such as `www/modules`, now puts each workspace project's dependencies in `<project>/www/modules` on both fresh and frozen installs, and `pnpm bin` prints `<project>/www/modules/.bin` [#​15484](https://github.com/pnpm/pnpm/issues/15484).
- With `nodeLinker: hoisted`, pnpm now installs the root project's dependencies into a custom `modulesDir` instead of `node_modules`. With a custom `modulesDir`, the virtual store and its `lock.yaml` now default to `<modulesDir>/.pnpm`.
- A repeat `pnpm install` in a workspace with a custom `modulesDir` now takes the up-to-date fast path. Before, pnpm looked for each workspace project's dependencies in `node_modules` and ran a full install every time.
- pnpm now warns when a workspace install covers a project that has its own `pnpm-workspace.yaml`. The nested file's settings, such as `patchedDependencies`, do not apply when the outer workspace installs that project. pnpm reads settings only from the `pnpm-workspace.yaml` at the workspace root [#​11724](https://github.com/pnpm/pnpm/issues/11724).
- The `[<since>]` filter selector now compares against the commit where the current branch forked from `<since>`. Projects changed only by newer commits on `<since>` are no longer selected. Uncommitted changes are still included. In a shallow clone without that commit, pnpm compares against `<since>` directly, as before [#​9907](https://github.com/pnpm/pnpm/issues/9907).
- `--filter "[<since>]"` now selects workspace packages when dependency versions change in a catalog in `pnpm-workspace.yaml` [#​8718](https://github.com/pnpm/pnpm/issues/8718). It also selects projects that files were moved out of when git detects the move as a rename [#​15481](https://github.com/pnpm/pnpm/issues/15481).
- `--filter` now evaluates selectors in order, so later inclusion filters can re-include packages that an earlier exclusion filter excluded [#​9354](https://github.com/pnpm/pnpm/issues/9354).
##### Adding, updating, and removing dependencies
- `pnpm add` now saves changes to `package.json` before running lifecycle scripts, so a postinstall script failure leaves the added dependency in `package.json` [#​8627](https://github.com/pnpm/pnpm/issues/8627).
- `pnpm add` now saves the requested exact version when adding a dependency, even when the manifest already contains a version range [#​6040](https://github.com/pnpm/pnpm/issues/6040).
- `pnpm add <pkg>@​<version>` and `pnpm update <pkg>@​<version>` now move the catalog entry onto the named version when the entry's range already covers it. For example, `^7.22.17` becomes `^7.29.6`, the same way `pnpm update <pkg>` moves an entry to the version it resolves [#​13715](https://github.com/pnpm/pnpm/issues/13715).
- `pnpm add` and `pnpm install` keep an empty `peerDependencies`, `dependencies`, `devDependencies`, or `optionalDependencies` field that was already in `package.json`. pnpm still drops such a field when it removes the last entry itself, as `pnpm remove` does [#​5096](https://github.com/pnpm/pnpm/issues/5096).
- `pnpm update` now keeps a version range whose shape has no save prefix, such as `<= 3.0.0` or `>=1.0.0 <2.0.0`, when the updated version still satisfies it. Before, `<= 3.0.0` became `^3.0.0` [#​6714](https://github.com/pnpm/pnpm/issues/6714).
- `pnpm update <pkg>` now moves a package off a locked version the registry no longer serves, such as an unpublished release. The lockfile check for supply-chain policies such as `minimumReleaseAge` used to reject that version before the update could replace it [#​9953](https://github.com/pnpm/pnpm/issues/9953).
- `pnpm update --prod` no longer installs devDependencies when run in a project installed with `--prod` [#​8038](https://github.com/pnpm/pnpm/issues/8038).
- `pnpm update --interactive --workspace` now allows external dependencies to be updated.
- `pnpm outdated` and `pnpm update` now apply `minimumReleaseAge` to GitHub Actions. `minimumReleaseAgeExclude` entries match action names such as `actions/checkout` [#​13923](https://github.com/pnpm/pnpm/issues/13923).
- `pnpm remove` now accepts `--trust-lockfile` and `--no-trust-lockfile` to control supply-chain policy checks while removing a package [#​14406](https://github.com/pnpm/pnpm/issues/14406).
- `pnpm unlink` now removes the `link:` dependency that `pnpm link <dir>` added to `package.json`. The linked package is removed from `node_modules` and the lockfile. A `link:` dependency to another directory is kept [#​4219](https://github.com/pnpm/pnpm/issues/4219).
- `pnpm install` now prunes unreferenced catalog entries from `pnpm-workspace.yaml` when `catalogPrune: true` is configured [#​15273](https://github.com/pnpm/pnpm/issues/15273).
- `minimumReleaseAgeExcludePrune` and `trustPolicyExcludePrune` now work in workspaces with `shared-workspace-lockfile=false`. Once every project has been installed, pnpm drops an entry only if no project lockfile records it. Undecided `allowBuilds` entries are pruned the same way [#​14612](https://github.com/pnpm/pnpm/issues/14612).
- Exclude entries that pnpm writes to `pnpm-workspace.yaml` now match the file's list indentation and dominant quote style [#​15571](https://github.com/pnpm/pnpm/issues/15571), [#​15079](https://github.com/pnpm/pnpm/issues/15079).
- `pnpm import` now converts dependencies that use Yarn's `patch:` protocol. The dependency keeps the version it patches, and the patch file is added to `patchedDependencies` in `pnpm-workspace.yaml`. If the patch file is missing, pnpm prints a warning and imports the dependency without the patch [#​10278](https://github.com/pnpm/pnpm/issues/10278).
- `pnpm import` in a workspace now keeps the versions pinned by the root `yarn.lock`, `package-lock.json`, or `npm-shrinkwrap.json` when another workspace project's range allows a newer version. Before, the root project got the newest version in its range [#​4385](https://github.com/pnpm/pnpm/issues/4385).
- `pnpm patch`, `pnpm patch-commit`, and `pnpm patch-remove` now work in a project of a workspace with `sharedWorkspaceLockfile: false`. `pnpm patch` failed there with `ERR_PNPM_PATCH_NO_LOCKFILE` after a successful install. The reinstall after committing or removing a patch left the project's own `node_modules` unchanged [#​9926](https://github.com/pnpm/pnpm/issues/9926).
- `pnpm patch-commit` now resolves default patch directory locations when passed a package name or package specifier (such as `pnpm patch-commit <pkg>` or `pnpm patch-commit <pkg>@​<version>`).
- `pnpm patch-commit` now updates the lockfile snapshot and prunes removed dependencies when the patch modifies `package.json` [#​6866](https://github.com/pnpm/pnpm/issues/6866).
- `pnpm patch-commit` now falls back to copying package files when hard linking fails.
##### Running scripts and commands
- A script that pnpm runs without a terminal now ends when pnpm itself is killed. Killing pnpm's process group, as Playwright's `webServer` does to stop the command it started, used to leave the script running and holding the caller's output pipes open [#​15555](https://github.com/pnpm/pnpm/issues/15555).
- `pnpm --filter <project> <command>` and `pnpm -r <command>` now run a command installed in the selected projects' dependencies when none of them has a script by that name. This matches `pnpm <command>` in a single project. `pnpm run` with `--filter` or `-r` still reports the missing script [#​10151](https://github.com/pnpm/pnpm/issues/10151).
- `pnpm exec` and `pnpm dlx` now set `npm_execpath`, `INIT_CWD`, `npm_node_execpath`, and `NODE` in child environments when Node.js is available. Stale inherited `NODE` and `npm_node_execpath` variables are cleared when Node.js cannot be found on PATH [#​7037](https://github.com/pnpm/pnpm/issues/7037). Scripts that `pnpx` and `pnx` run now get pnpm itself as `npm_execpath`. A script that ran `$npm_execpath install` there ran `pnpm dlx install`.
- `pnpm exec` now sets the `PWD` environment variable to the directory the command runs in. Shells and tools that read `PWD` now report the logical path of a workspace package reached through a symlink [#​1550](https://github.com/pnpm/pnpm/issues/1550).
- A script that runs `pnpm run` no longer adds duplicate `node_modules/.bin` and `node-gyp-bin` entries to `PATH` [#​5352](https://github.com/pnpm/pnpm/issues/5352).
- Concurrent `pnpm run` and `pnpm exec` commands now serialize their dependency installs [#​14551](https://github.com/pnpm/pnpm/issues/14551).
- `pnpm run` and `pnpm exec` with `verifyDepsBeforeRun` now accept a moved project whose store is on the project's volume. Before, the check reported that the workspace structure had changed whenever the default store was not on the home volume.
- `verifyDepsBeforeRun` checks now account for project-specific `packageConfigs` overrides in workspaces with `sharedWorkspaceLockfile: false` [#​15545](https://github.com/pnpm/pnpm/issues/15545).
- `pnpm restart` now runs the "stop" and "start" scripts when the package has no "restart" script. Previously it ran "stop" and then failed with "Missing script: restart" [#​4750](https://github.com/pnpm/pnpm/issues/4750).
- `pnpm dlx` now keeps a separate cache entry for each Node.js major version. A package built under one Node.js major version, such as a native addon, is no longer reused under another [#​8611](https://github.com/pnpm/pnpm/issues/8611).
- `pnpm pipeline` no longer fails when run in a project outside a Git work tree or on a system without `git`. Tasks in those projects run without caching, and pnpm prints a warning explaining why [#​15601](https://github.com/pnpm/pnpm/issues/15601).
- A `runtime:` version range that contains `||` or a space, such as a `devEngines.runtime` version of `^22.18.0 || ^24.0.0`, now installs the requested runtime. pnpm used to install the npm package with the same name, such as `node` [#​14817](https://github.com/pnpm/pnpm/issues/14817).
- When the configured `scriptShell` does not exist, running a script now fails with an error that names the shell. Previously pnpm printed only an exit code or the package directory [#​7562](https://github.com/pnpm/pnpm/issues/7562).
- A script killed by a signal now fails with an error that names the signal, such as `Command failed with signal SIGKILL.` [#​9821](https://github.com/pnpm/pnpm/issues/9821).
##### Publishing, packing, and deploying
- `pnpm publish` now resolves `workspace:` dependencies from workspace manifests when `node_modules` is not installed. Previously, publishing without `node_modules` failed with `ERR_PNPM_CANNOT_RESOLVE_WORKSPACE_PROTOCOL` [#​6567](https://github.com/pnpm/pnpm/issues/6567).
- `pnpm publish` now honors `publishConfig["@​scope:registry"]` for a package in that scope. It takes precedence over the registry set for the same scope in `.npmrc` and over `publishConfig.registry` [#​12071](https://github.com/pnpm/pnpm/issues/12071).
- `pnpm pack` and `pnpm publish` now include bundled dependencies when using the isolated linker. This covers workspace packages and the dependencies of each bundled package. Bundled dependencies are also included when `publishConfig.directory` selects a build directory [#​1643](https://github.com/pnpm/pnpm/issues/1643).
- `pnpm pack`, `pnpm deploy`, and installs of local directory dependencies now keep symlinks that point to files or directories included in the package. `pnpm pack` leaves out symlinks that point outside the package [#​8208](https://github.com/pnpm/pnpm/issues/8208).
- `pnpm pack` now preserves file executable permissions in the packed tarball when source files are executable on disk.
- `pnpm publish` and `pnpm pack` now report a missing `version` or `name` field on a workspace dependency. Previously, pnpm reported that the dependency was not installed [#​4164](https://github.com/pnpm/pnpm/issues/4164).
- `pnpm publish` and `pnpm pack` now report an error when a bin script has a shebang line ending with CRLF [#​7311](https://github.com/pnpm/pnpm/issues/7311).
- `pnpm deploy` now copies the `packageManager` and `devEngines.packageManager` fields of the workspace root `package.json` into the deployed `package.json`, unless the deployed project pins a package manager itself [#​9079](https://github.com/pnpm/pnpm/issues/9079).
- `pnpm deploy` now puts the virtual store at `virtualStoreDir`, resolved against the deploy directory. A shared-lockfile deploy records `virtualStoreDir` in the deployed `pnpm-workspace.yaml`. With the global virtual store enabled or an absolute `virtualStoreDir`, the deploy still uses `node_modules/.pnpm` [#​8787](https://github.com/pnpm/pnpm/issues/8787).
- `pnpm deploy` now respects `--package-import-method` passed on the command line and reports the package import method correctly [#​7593](https://github.com/pnpm/pnpm/issues/7593).
- `pnpm deploy` does not run the `prepare` scripts of the deployed project [#​7282](https://github.com/pnpm/pnpm/issues/7282).
- `pnpm deploy --legacy` no longer rewrites the source workspace's `node_modules/.pnpm-workspace-state-v1.json` to describe only the deployed project [#​15352](https://github.com/pnpm/pnpm/issues/15352).
##### Manifests and configuration files
- pnpm now reads and updates `package.json5` project manifests. Manifest updates retain comments, and workspace discovery prefers `package.json`, then `package.json5`, then `package.yaml` [#​15129](https://github.com/pnpm/pnpm/issues/15129). `pnpm pack` includes exactly one `package.json` in the archive when the project uses an alternative manifest format, even when `.npmignore` or `files` excludes the source file.
- Git-hosted dependencies that use a `package.yaml` or `package.json5` manifest now honor its `files` field [#​7906](https://github.com/pnpm/pnpm/issues/7906).
- Fixed `pnpm version` failing on projects using a `package.yaml` manifest.
Fixed `pnpm init` creating an extra `package.json` when `package.yaml` is already present.
- `pnpm version` now applies pending bumps to private workspace packages. A private package's changelog is written to its committed `CHANGELOG.md`, also when `versioning.changelog.storage` is `registry` [#​13736](https://github.com/pnpm/pnpm/issues/13736), [#​13519](https://github.com/pnpm/pnpm/issues/13519).
- `pnpm init` now supports the `--bare` option. It creates a `package.json` file with only the required fields [#​15538](https://github.com/pnpm/pnpm/issues/15538).
- The `reporter` setting is now honored when it is configured in `pnpm-workspace.yaml`, the global configuration, or the `PNPM_CONFIG_REPORTER` environment variable. Configured `reporter: silent` makes silent output the default. An explicit `--reporter` takes precedence [#​4879](https://github.com/pnpm/pnpm/issues/4879).
- `.npmrc` and `pnpm-workspace.yaml` files now support npm's `${VAR?}` placeholder. It expands to the value of `VAR`, or to an empty string without a warning when `VAR` is unset [#​14404](https://github.com/pnpm/pnpm/issues/14404).
- pnpm now expands environment variables in `_auth.authToken` values loaded from global `config.yaml` and `pnpm_config__auth`.
- pnpm now keeps the configured default registry when `_auth` holds credentials for several registries and some of those registries serve package scopes.
Lockfile verification checks a tarball hosted on a scoped registry against that registry's metadata, unless the package's own scope has a registry assigned [#​15530](https://github.com/pnpm/pnpm/issues/15530).
- pnpm now parses the first setting in a `.npmrc` that starts with a UTF-8 byte order mark. Previously, the leading byte order mark caused the first line's key to be ignored [#​15353](https://github.com/pnpm/pnpm/issues/15353).
- pnpm now prints a warning when a `.npmrc`, `auth.ini`, or the file set by `npmrcAuthFile` exists but cannot be read. The settings in such a file were ignored without any message. A `.npmrc` that contains invalid UTF-8 is now read [#​5065](https://github.com/pnpm/pnpm/issues/5065).
- `pnpm config set` and `pnpm config delete` now preserve comments and repeated keys such as `ca=` in `.npmrc` [#​14851](https://github.com/pnpm/pnpm/issues/14851).
- `pnpm login` now logs back in to an existing user on registries without web login, such as verdaccio. The classic login request sends the username and password as basic auth, as `npm login` does [#​12055](https://github.com/pnpm/pnpm/issues/12055).
- Commands that do not use the store no longer create a temporary file in the project directory when they load their settings. These include `pnpm view`, `pnpm config`, `pnpm root`, `pnpm bin`, `pnpm exec`, `pnpm run`, the script shortcuts such as `pnpm test`, and the registry commands such as `pnpm whoami`, `pnpm dist-tag`, and `pnpm search`. `pnpm exec`, `pnpm run`, and the script shortcuts still create one in projects that declare `configDependencies`.
##### Global packages, pnpm versions, and runtimes
- Global commands such as `pnpm add --global`, `pnpm list --global`, and `pnpm bin --global` now run with the pnpm you invoked, even in a project that pins another pnpm version. Previously, a pin with `onFail: "download"` switched them to the pinned pnpm, and a pinned pnpm 10 or older failed because its global bin directory was not in `PATH` [#​14531](https://github.com/pnpm/pnpm/issues/14531).
- `pnpm add -g`, `pnpm update -g`, and `pnpm remove -g` no longer fail with `ERR_PNPM_PACKAGE_MANIFEST_IO_ERROR` when another global package's link into the store dangles, for example after the store was pruned. The pnpm install script failed the same way on such a machine.
- `pnpm update --global` now skips a global package installed from a `file:` path that no longer exists, prints a warning, and updates the remaining global packages. Previously the whole update failed with `ERR_PNPM_LINKED_PKG_DIR_NOT_FOUND` [#​12533](https://github.com/pnpm/pnpm/issues/12533).
- `pnpm self-update` run in a project that pins pnpm through `packageManager` or `devEngines.packageManager` now also updates the global pnpm, as it does outside a project [#​14747](https://github.com/pnpm/pnpm/issues/14747).
- `pnpm self-update` no longer leaves the previous pnpm in the global packages when it was installed as `@pnpm/exe`. `pnpm ls -g` now lists a single pnpm [#​14709](https://github.com/pnpm/pnpm/issues/14709). `pnpm setup` now installs pnpm under the package name `pnpm` too, so both commands leave the same shims in the global bin directory. After a self-update on Windows, PowerShell ran pnpm through `pnpm.cmd` and asked "Terminate batch job (Y/N)?" on Ctrl+C [#​15567](https://github.com/pnpm/pnpm/issues/15567).
- `pnpm setup` failed with `Text file busy (os error 26)` when `$PNPM_HOME/bin` already held `pn`, `pnpx`, or `pnx` as links to the running pnpm executable. It now replaces those files and completes [#​15494](https://github.com/pnpm/pnpm/issues/15494).
- `pnpm setup` no longer deletes aliases and other lines that sit between a `# pnpm` comment and the pnpm block in a shell startup file [#​7067](https://github.com/pnpm/pnpm/issues/7067).
- When pnpm switches to the version a project pins, the `minimumReleaseAge` approvals for that version are now added to `minimumReleaseAgeExclude` in the project's `pnpm-workspace.yaml`. A project without that file gets one. Global commands leave the project's settings unchanged [#​15396](https://github.com/pnpm/pnpm/issues/15396).
- `pnpm env remove` now cleans up dangling Node.js executables and shims. Surviving global commands remain intact.
- Node.js runtime resolution now supports Windows ARM64. Node.js 20 and newer resolve native `win-arm64` builds, and older versions fall back to `win-x64` under emulation [#​7123](https://github.com/pnpm/pnpm/issues/7123).
##### Windows and WSL
- On Windows, `pnpm clean` and installs no longer fail immediately when another process uses a package in `node_modules`. pnpm waits up to a minute for an open file. It waits up to 5 seconds for a running program [#​15081](https://github.com/pnpm/pnpm/issues/15081).
- `pnpm install` in WSL now waits out Windows file locks on a Windows drive such as `/mnt/c`, as it already does on Windows. Before, an antivirus or indexer scan holding a file open could fail the install with `EACCES` [#​6155](https://github.com/pnpm/pnpm/issues/6155).
- On Windows, pnpm now retries saving `pnpm-lock.yaml` for up to a minute while another process holds the file open. The save used to fail at once with `EPERM`, `EBUSY`, or "Access is denied" [#​9461](https://github.com/pnpm/pnpm/issues/9461).
- pnpm now escapes trailing dots and spaces in `node_modules/.pnpm` directory names. Windows strips these characters, so a dependency such as `"parent-pkg": "file:../"` created a directory that could not be deleted or failed to install [#​8101](https://github.com/pnpm/pnpm/issues/8101).
- On Windows, bin shims run from Git Bash, MSYS2, or Cygwin now pass `NODE_PATH` to Node.js as Windows paths. A project installed from cmd or PowerShell gave its bins a `NODE_PATH` under the Git install directory when they ran from Git Bash. Installing again replaces the shims already in `node_modules` [#​3360](https://github.com/pnpm/pnpm/issues/3360).
- Fixed scripts failing with errors such as `'an-compile' is not recognized` when `scriptShell` is set to `cmd.exe` on Windows [#​7181](https://github.com/pnpm/pnpm/issues/7181).
- On Windows, the error for a `node_modules` directory that pnpm cannot move out of the way now names the directory and says that a file in it is probably in use by another process [#​7505](https://github.com/pnpm/pnpm/issues/7505).
##### Inspecting dependencies
- `pnpm audit` and `pnpm audit signatures` now check only the dependencies of the projects selected by `--filter`, `--filter-prod`, or `--workspace-root`. The filter used to be ignored, so a filtered audit reported the whole workspace [#​10982](https://github.com/pnpm/pnpm/issues/10982).
- `pnpm audit` now lists at least one dependency path from every workspace project that depends on a vulnerable package. Before, a project whose dependency was reached through more than 100 paths filled the path list, and other projects that depend on the same package were left out [#​12200](https://github.com/pnpm/pnpm/issues/12200).
- `pnpm audit --fix` now prunes redundant overrides when one vulnerable range is a subset of another for the same package [#​8577](https://github.com/pnpm/pnpm/issues/8577).
- Running `pnpm list` inside a workspace package without `--recursive` or a filter now lists only the current package [#​14494](https://github.com/pnpm/pnpm/issues/14494). `pnpm licenses list` does the same. Use `--recursive` or `--filter` to list the licenses of other workspace projects [#​5689](https://github.com/pnpm/pnpm/issues/5689).
- `pnpm list --only-projects` now prints every project selected with `--filter` or `--recursive`, including a project that has no workspace dependencies [#​9770](https://github.com/pnpm/pnpm/issues/9770). It also lists the workspace projects when `sharedWorkspaceLockfile` is `false` [#​7151](https://github.com/pnpm/pnpm/issues/7151), and a project that sets `publishConfig.directory` [#​10635](https://github.com/pnpm/pnpm/issues/10635). It no longer reports packages in `node_modules` that are missing from the lockfile [#​9528](https://github.com/pnpm/pnpm/issues/9528).
- `pnpm licenses list` failed or reported nothing in a workspace with `sharedWorkspaceLockfile: false`. It now reads the lockfile of each selected project [#​10140](https://github.com/pnpm/pnpm/issues/10140).
- With `nodeLinker: hoisted`, `pnpm licenses list` reported every license as `Unknown` and listed paths under `node_modules/.pnpm` that do not exist. It now reads each package from the directory where the hoisted linker placed it [#​8589](https://github.com/pnpm/pnpm/issues/8589).
- `pnpm outdated` and `pnpm -r outdated` now fail with `ERR_PNPM_NO_PACKAGE_IN_DEPENDENCIES` when a requested package selector does not match any dependency in the inspected projects [#​2319](https://github.com/pnpm/pnpm/issues/2319).
- `pnpm -r outdated --json` now includes every outdated workspace dependency when multiple projects depend on different versions or dependency types of the same package. Such a package is keyed by its current version and dependency type, for example `[email protected] (dev)` [#​7693](https://github.com/pnpm/pnpm/issues/7693).
- `pnpm sbom` filtered to a single workspace project now takes the `author`, `description`, `license`, `repository`, and `bugs` fields from the workspace root `package.json` when the project does not declare them. A field the project declares is never taken from the root, even when it is blank or `null` [#​14882](https://github.com/pnpm/pnpm/issues/14882).
- `pnpm store status` no longer reports a package as modified when it has build or postinstall scripts, peer dependencies, or skipped optional dependencies [#​15383](https://github.com/pnpm/pnpm/issues/15383). When packages were mutated, it now lists only those packages and no longer suggests running `pnpm install --force` [#​919](https://github.com/pnpm/pnpm/issues/919).
- `pnpm peers check` and the `ERR_PNPM_PEER_DEP_ISSUES` error now group peer dependency issues under the workspace project they were found in [#​15351](https://github.com/pnpm/pnpm/issues/15351).
##### Output and messages
- The error for an incompatible pnpm-lock.yaml now reports the lockfileVersion the file was generated with and the lockfileVersion the current pnpm supports [#​848](https://github.com/pnpm/pnpm/issues/848).
- When the registry stops sending data for longer than `fetchTimeout`, pnpm now reports that the metadata or tarball request timed out. Previously the error did not mention the timeout [#​3646](https://github.com/pnpm/pnpm/issues/3646).
- Fatal peer dependency errors and their hints are now written to stderr [#​5419](https://github.com/pnpm/pnpm/issues/5419).
- `pnpm run` with `--loglevel` set to `warn`, `error`, or `silent` (or the same `loglevel` setting) no longer prints the `$ <command>` line before a script, nor the summary of the install that `verifyDepsBeforeRun` runs first. Both are info-level output [#​8944](https://github.com/pnpm/pnpm/issues/8944).
- `pnpm run` and `pnpm exec` now print `No projects matched the filters in "<workspace>"` when `--filter` selects no project [#​8408](https://github.com/pnpm/pnpm/issues/8408).
- `pnpm dedupe` now counts each package reused from the store once in its progress output [#​15303](https://github.com/pnpm/pnpm/issues/15303).
- `pnpm add` now warns when replacing an existing dependency with a specifier pointing to a different source [#​14869](https://github.com/pnpm/pnpm/issues/14869).
- `pnpm link` now warns when linking a package that declares one or more peer dependencies, explaining that the linked dependency will not resolve peer dependencies from the target `node_modules` and suggesting the `file:` protocol instead.
- `pnpm import` now warns when package.json lists projects in a "workspaces" array and there is no "pnpm-workspace.yaml". Without that file, the import writes a lockfile for the root project only [#​5240](https://github.com/pnpm/pnpm/issues/5240).
- Bash completion now completes script names that contain a colon, such as `pnpm run test:u` to `pnpm run test:unit` [#​5482](https://github.com/pnpm/pnpm/issues/5482).
<!-- 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>
<details>
<summary>lovell/sharp (sharp)</summary>
### [`v0.35.5`](https://github.com/lovell/sharp/releases/tag/v0.35.5)
[Compare Source](https://github.com/lovell/sharp/compare/v0.35.4...v0.35.5)
<https://github.com/lovell/sharp-libvips/releases/tag/v1.3.4>
- Add upper bounds check on length of `linear` and GIF `delay` arrays.
- Improve error handing when WebAssembly fallback also fails.
[#​4593](https://github.com/lovell/sharp/pull/4593)
[@​lazerg](https://github.com/lazerg)
- TypeScript: Allow multi-frame options for JXL output.
[#​4602](https://github.com/lovell/sharp/pull/4602)
[@​ramin-010](https://github.com/ramin-010)
- TypeScript: Remove non-existent named export.
[#​4604](https://github.com/lovell/sharp/issues/4604)
- Increase accepted dimensions when extending an image.
[#​4605](https://github.com/lovell/sharp/issues/4605)
- Improve gain map support for `extract` and `rotate` operations.
[#​4606](https://github.com/lovell/sharp/issues/4606)
- Tests: Ensure composite tests pass on big endian platforms.
[#​4609](https://github.com/lovell/sharp/issues/4609)
</details>
<details>
<summary>HiDeoo/starlight-openapi (starlight-openapi)</summary>
### [`v0.26.3`](https://github.com/HiDeoo/starlight-openapi/blob/HEAD/packages/starlight-openapi/CHANGELOG.md#0263)
[Compare Source](https://github.com/HiDeoo/starlight-openapi/compare/[email protected]@0.26.3)
##### Patch Changes
- [#​181](https://github.com/HiDeoo/starlight-openapi/pull/181) [`e81882f`](https://github.com/HiDeoo/starlight-openapi/commit/e81882ffd4a85b1c113fd477422ee6b524715d58) Thanks [@​HiDeoo](https://github.com/HiDeoo)! - Fixes a potential spacing issue with heading anchor links.
</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](https://github.com/renovatebot/renovate).
<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0My4xOTEuMiIsInVwZGF0ZWRJblZlciI6IjQzLjE5MS4yIiwidGFyZ2V0QnJhbmNoIjoibWFpbiIsImxhYmVscyI6W119-->
<!-- cloudflare-preview --> Preview: https://pr-575.docs-gitea-com.pages.dev
Reviewed-on: https://gitea.com/gitea/docs/pulls/575
Reviewed-by: silverwind <[email protected]>
Co-authored-by: Renovate Bot <[email protected]>
The Docker Compose example currently mounts the PostgreSQL data directory to `/var/lib/postgresql/data`.
With PostgreSQL 18, the official PostgreSQL Docker image expects the volume to be mounted at `/var/lib/postgresql` instead. Mounting `/var/lib/postgresql/data` causes the container to fail during startup because the image detects PostgreSQL data at an unused mount point.
This change updates the example to use the recommended `/var/lib/postgresql` mount path for PostgreSQL 18 and newer.
---------
Co-authored-by: silverwind <[email protected]>
Reviewed-on: https://gitea.com/gitea/docs/pulls/570
Reviewed-by: silverwind <[email protected]>
Co-authored-by: JWheelMaker <[email protected]>
The cheat sheet lists `/img/repo_default.png` as the `REPOSITORY_AVATAR_FALLBACK_IMAGE` default, but public files moved under `/assets` in Gitea 1.15 (https://github.com/go-gitea/gitea/pull/15219), so that path 404s. Like `app.example.ini` in https://github.com/go-gitea/gitea/pull/39514, document the default as empty, falling back to Gitea's builtin repository avatar, and note that a custom value is used as-is without the sub-path. Applies to all documented versions and translations.
_Written by Claude Code (Opus 5.5)._
Reviewed-on: https://gitea.com/gitea/docs/pulls/569
Reviewed-by: wxiaoguang <[email protected]>
Co-authored-by: silverwind <[email protected]>
Documents the settings changed by https://github.com/go-gitea/gitea/pull/39426: `EGRESS_MODE`, the `[migrations]` `ALLOWED_HOST_LIST`/`BLOCKED_HOST_LIST` replacing `ALLOWED_DOMAINS`, `BLOCKED_DOMAINS` and `ALLOW_LOCALNETWORKS`, and the host list syntax with ports, CGNAT and reserved addresses. Also fixes the `socks://` examples, Go only speaks `socks5://`.
_Written by Claude._
---------
Co-authored-by: Lunny Xiao <[email protected]>
Co-authored-by: TheFox0x7 <[email protected]>
Reviewed-on: https://gitea.com/gitea/docs/pulls/557
Reviewed-by: Lunny Xiao <[email protected]>
Co-authored-by: silverwind <[email protected]>
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.
---------
Co-authored-by: Lunny Xiao <[email protected]>
Reviewed-on: https://gitea.com/gitea/docs/pulls/555
Reviewed-by: Lunny Xiao <[email protected]>
Co-authored-by: Gitea Bot <[email protected]>
This PR contains the following updates:
| Package | Change | [Age](https://docs.renovatebot.com/merge-confidence/) | [Confidence](https://docs.renovatebot.com/merge-confidence/) |
|---|---|---|---|
| [@astrojs/starlight](https://starlight.astro.build) ([source](https://github.com/withastro/starlight/tree/HEAD/packages/starlight)) | [`0.42.0` → `0.42.1`](https://renovatebot.com/diffs/npm/@astrojs%2fstarlight/0.42.0/0.42.1) |  |  |
| [pnpm](https://github.com/pnpm/pnpm/tree/main/pnpm) ([source](https://github.com/pnpm/pnpm/tree/HEAD/pnpm/npm/pnpm)) | [`12.4.0` → `12.4.2`](https://renovatebot.com/diffs/npm/pnpm/12.4.0/12.4.2) |  |  |
| [yaml](https://eemeli.org/yaml/) ([source](https://github.com/eemeli/yaml)) | [`2.9.0` → `2.9.1`](https://renovatebot.com/diffs/npm/yaml/2.9.0/2.9.1) |  |  |
---
### Release Notes
<details>
<summary>withastro/starlight (@​astrojs/starlight)</summary>
### [`v0.42.1`](https://github.com/withastro/starlight/blob/HEAD/packages/starlight/CHANGELOG.md#0421)
[Compare Source](https://github.com/withastro/starlight/compare/@astrojs/[email protected]...@astrojs/[email protected])
##### Patch Changes
- [#​4198](https://github.com/withastro/starlight/pull/4198) [`96a44d4`](https://github.com/withastro/starlight/commit/96a44d4f296b512febc6e631a587103ec5297872) Thanks [@​mayank99](https://github.com/mayank99)! - Removes the `popover` attribute from the sidebar pane on desktop viewports. This ensures the desktop sidebar doesn't stay in a "hidden" popover visibility state.
</details>
<details>
<summary>pnpm/pnpm (pnpm)</summary>
### [`v12.4.2`](https://github.com/pnpm/pnpm/releases/tag/v12.4.2): pnpm 12.4.2
[Compare Source](https://github.com/pnpm/pnpm/compare/v12.4.1...v12.4.2)
pnpm 12.4.2 includes security fixes for executable shims and GitHub Actions links, more reliable installs, faster peer dependency checks in workspaces, and Python lockfiles that work across compatible targets.
#### Patch Changes
##### Security
- Dependency executables can no longer take over another package's POSIX bin shim through its shell helpers. Reinstall dependencies to replace existing shims [#​14837](https://github.com/pnpm/pnpm/issues/14837).
On Cygwin, MSYS2, and WSL, shims still use `PATH` for Windows path conversion, so dependency executables can still redirect them there.
- GitHub Actions homepage links no longer expose server credentials. GitHub server URLs now require HTTPS, with HTTP allowed only for loopback hosts.
##### Installing packages
- pnpm no longer crashes at startup on FreeBSD and other Unix-like platforms. Platforms other than Windows and macOS use `~/.local/share/pnpm/store` by default [#​14859](https://github.com/pnpm/pnpm/issues/14859).
- `pnpm install` on Windows no longer fails with `ERR_PNPM_PACKAGE_MANAGER_REMOVE_MODULES_DIR` when clearing `node_modules` containing linked dependencies, such as when changing `nodeLinker` [#​14790](https://github.com/pnpm/pnpm/issues/14790).
- `pnpm install <pkg>` now accepts `--prod` and `--dev`, including `--prod=false` [#​14868](https://github.com/pnpm/pnpm/issues/14868).
- `pnpm install` and `pnpm update` now honor `--ignore-workspace` in nested projects excluded from the surrounding workspace. The flag also skips that workspace's settings during the `packageManager` check [#​14809](https://github.com/pnpm/pnpm/issues/14809).
- `pnpm install` on macOS no longer reuses stale files for `file:` tarball or git-hosted tarball dependencies.
- `pnpm install` in a single-project directory now detects `package.json` edits made while the previous install was finishing [#​14890](https://github.com/pnpm/pnpm/issues/14890).
- `pnpm install --frozen-lockfile` now removes packages no longer reachable from any project in `pnpm-lock.yaml`. This also prevents repeated lifecycle script execution and unnecessary installs before `pnpm run` and `pnpm exec` with `verifyDepsBeforeRun` [#​14891](https://github.com/pnpm/pnpm/issues/14891).
##### Resolving and updating dependencies
- Node.js runtime resolution now reports network failures from unofficial-builds.nodejs.org. These failures previously omitted musl builds from `pnpm-lock.yaml`, making its contents depend on network access [#​14813](https://github.com/pnpm/pnpm/issues/14813).
- `pnpm install` now rejects invalid `peerDependencies` specifiers with `ERR_PNPM_INVALID_PEER_DEPENDENCY_SPECIFICATION`. A value such as `"foo": "[email protected]"` previously created a broken directory link [#​14791](https://github.com/pnpm/pnpm/issues/14791).
- `pnpm deploy` now writes plain registry versions in the deployed `package.json`, without peer dependency suffixes. The lockfile retains peer bindings, and npm aliases retain their target package names [#​14873](https://github.com/pnpm/pnpm/issues/14873).
- `pnpm add <git repository>` now names repositories without a `package.json` as `@owner/repo`, allowing dependencies on equally named repositories from different owners [#​14870](https://github.com/pnpm/pnpm/issues/14870).
- Peer dependency resolution now deduplicates packages whose child dependency resolves an optional peer in only some workspace projects, such as `next` with `styled-jsx`'s optional `babel-plugin-macros` peer [#​14800](https://github.com/pnpm/pnpm/issues/14800).
- `pnpm update` now settles the lockfile in one run when an upgrade removes the package providing an optional peer dependency [#​14895](https://github.com/pnpm/pnpm/issues/14895).
- `pnpm update --no-save` now preserves override-applied specifiers for dependencies it is not updating, preventing subsequent frozen installs from failing with `ERR_PNPM_OUTDATED_LOCKFILE` [#​14836](https://github.com/pnpm/pnpm/issues/14836).
- `pnpm update --no-save` now succeeds under `minimumReleaseAgeStrict` when every resolved version is old enough [#​14835](https://github.com/pnpm/pnpm/issues/14835).
##### Performance
- Workspace installs and `pnpm peers check` are faster when projects depend on each other, fixing a slowdown introduced in 12.3.0. Unmet peer dependencies of workspace packages are now reported only under projects that link them directly [#​14906](https://github.com/pnpm/pnpm/issues/14906).
- Hoisted installs use less memory when packages are cached. Frozen-lockfile hoisted installs on macOS are also faster when reusable package directories are cached.
##### Python projects
- `pnpm install --frozen-lockfile` now reuses `pylock.toml` across compatible Python targets, including after kernel updates. Reuse requires unchanged requirements, index, and `requires-python`, compatible wheels, and a locked dependency graph matching the target's markers [#​14843](https://github.com/pnpm/pnpm/issues/14843).
The lockfile's `environments` marker now includes only the interpreter version and marker variables used by the dependency graph. Without `--frozen-lockfile`, pnpm warns and resolves again when the locked graph no longer matches the target.
- Python resolution no longer fails on malformed `Requires-Python` values, such as the trailing comma in `openpyxl` 3.0.x. pnpm treats these releases as declaring no interpreter range [#​14910](https://github.com/pnpm/pnpm/issues/14910).
- `pnpm add pypi:...` now rejects unsupported `--save-prefix` values before editing the manifest or resolving dependencies.
##### Workspaces and scripts
- Scripts listed in `syncInjectedDepsAfterScripts` no longer fail with `ERR_PNPM_INJECTED_DEPS_SYNC_READ_DIR` when the lockfile contains an injected package copy that no project depends on.
- `shellEmulator` now expands `${VAR}`, `${VAR:-default}`, and `${VAR:+alternative}` in scripts [#​14814](https://github.com/pnpm/pnpm/issues/14814).
- Cargo and Python project discovery now honors `!` exclusions in `pnpm-workspace.yaml` `packages`, skipping both parsing and generated source configuration for excluded projects [#​14844](https://github.com/pnpm/pnpm/issues/14844).
- `pnpm --filter "./packages/{app,lib}"` now selects either alternative. Brace alternatives can nest, span path separators, and combine with other wildcards.
- GitHub Actions updates now stop if an action reference changes during version resolution, and preserve unrelated workflow edits.
##### CLI and output
- `pn`, `pnpx`, and `pnx` now run the pnpm installed alongside them, even when that directory is absent from `PATH` or another pnpm comes first [#​14803](https://github.com/pnpm/pnpm/issues/14803).
- `pnpm --version` now reports failures to install or record a project's pinned pnpm, then prints the running CLI's version. It also honors `--store-dir` and `--store` [#​14831](https://github.com/pnpm/pnpm/issues/14831).
- `pnpm self-update` no longer reinstalls the active version when it was installed by the standalone installation script [#​14823](https://github.com/pnpm/pnpm/issues/14823).
- `pnpm t` and `pnpm tst` work again as aliases for `pnpm test`.
- `pnpm sbom` now emits valid repository URLs in CycloneDX `externalReferences[].url` and SPDX `homepage`. Shorthands such as `vercel/ms` become `git+https` URLs, embedded credentials are removed, and invalid repository values are omitted [#​14773](https://github.com/pnpm/pnpm/issues/14773).
<!-- 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.4.1`](https://github.com/pnpm/pnpm/releases/tag/v12.4.1): pnpm 12.4.1
[Compare Source](https://github.com/pnpm/pnpm/compare/v12.4.0...v12.4.1)
pnpm 12.4.1 fixes installs that failed on filesystems refusing hard links or clones, on Android, and under `nodeLinker: hoisted`. Repeat installs are faster.
#### Patch Changes
##### Installing packages
- `pnpm install` no longer fails with `Operation not permitted` when the filesystem refuses a hard link or a copy-on-write clone [#​14722](https://github.com/pnpm/pnpm/issues/14722). Under `packageImportMethod: auto` and `clone-or-copy`, pnpm copies the file instead. EdenFS checkouts, which have no hard links, and rootless containers, which refuse the clone syscall, both hit this. An explicit `packageImportMethod: hardlink` or `clone` still reports the error.
pnpm also copies a package file whose store entry has reached the filesystem's limit on names for one file, 1024 on NTFS and 65000 on ext4. Such a file failed the install under `packageImportMethod: hardlink`, and under `auto` it stopped pnpm hard linking for the rest of the install.
- `pnpm install` no longer writes a package file through a symlink left at the path it is importing to. Copying such a file overwrote whatever the link pointed at, and created that file when the link pointed nowhere. An executable package file also made the link's target executable.
- Fixed `pnpm install` and `pnpm dlx` on Android. Registry requests crashed because pnpm found no system CA certificates, so pnpm uses bundled ones there [#​14777](https://github.com/pnpm/pnpm/issues/14777). Imports also failed with "Permission denied" on filesystems that deny hard links and reflinks, and now fall back to copying [#​14780](https://github.com/pnpm/pnpm/issues/14780).
- `pnpm install` no longer fails with "Invalid cross-device link" while preserving a package's nested `node_modules` directory during a Docker build [#​14758](https://github.com/pnpm/pnpm/issues/14758).
- `pnpm install` no longer fails on a package tarball that carries a file at the archive root, such as the `._*` entries macOS `tar` adds [#​14701](https://github.com/pnpm/pnpm/issues/14701). The file is installed at the root of the package.
A `file:` tarball packed without the usual `package/` directory is now recorded under the name and version from its own `package.json`. It was recorded under the alias the dependency was given, at version 0.0.0.
- Under `nodeLinker: hoisted`, `pnpm install` no longer re-imports packages that are already in place. A repeat install replaced the whole `node_modules` tree and reported `Packages: +N`. A package is still imported when its directory is missing, when its `package.json` no longer carries the installed version, when it is a `file:` dependency, and when it is patched. Lifecycle scripts no longer run again for a package left in place, and `pnpm rebuild` and a change to `allowBuilds` still reach it.
- `pnpm install` now runs a dependency's build scripts again when its side-effects cache entry has no files to restore [#​14717](https://github.com/pnpm/pnpm/issues/14717). Such builds were skipped and nothing was put in their place, so a script whose whole effect lands outside its own package directory, such as a git hook installer, never took effect. pnpm no longer publishes empty artifacts to the shared side-effects cache either.
##### Resolving and linking dependencies
- `pnpm install`, `pnpm add`, and `pnpm dedupe` now apply `ignoredOptionalDependencies` [#​14729](https://github.com/pnpm/pnpm/issues/14729). Matching optional dependencies are left out of the lockfile and are not installed. pnpm 12 installed them whenever it resolved dependencies from scratch.
- `pnpm install` no longer links a transitive dependency to a workspace package when `linkWorkspacePackages` is `true` and the dependency is declared with a plain version range [#​14781](https://github.com/pnpm/pnpm/issues/14781). Enabling `preferWorkspacePackages` does not change this. Set `linkWorkspacePackages: deep` to link them.
- `pnpm install` no longer leaves dangling dependency links in workspace packages located above the workspace root [#​14726](https://github.com/pnpm/pnpm/issues/14726).
- `pnpm install` and `pnpm add` no longer leave a dangling symlink in `node_modules` when a project starts depending directly on a package that the lockfile holds only as a transitive dependency with resolved peer dependencies [#​14714](https://github.com/pnpm/pnpm/issues/14714).
- `pnpm dedupe` now keeps a compatible auto-installed peer when another workspace project depends on a newer major [#​14697](https://github.com/pnpm/pnpm/issues/14697). Repeated runs alternated between compatible and incompatible peer versions.
- `pnpm peers check` no longer reports a peer dependency declared as `workspace:^`, `workspace:~`, or a bare `workspace:` as unmet [#​14770](https://github.com/pnpm/pnpm/issues/14770). pnpm reported these as unmet whatever version the linked workspace project supplied.
##### Performance
- Sped up repeat installs [#​14540](https://github.com/pnpm/pnpm/issues/14540). pnpm checks the store's files only for the packages it links into `node_modules`, instead of every package in the lockfile. Creating the command shims in `node_modules/.bin` makes about 1,500 fewer filesystem calls in a 76 project workspace. Installs that use the global virtual store read their slot paths from the cache directory instead of deriving them every time. Verifying a large lockfile also allocates less memory.
- Sped up `pnpm install` in Cargo workspaces with many member crates. Repeated installs reuse verified Cargo checksum metadata.
- Installing several packages from the same Git repository and commit now downloads the source once per install [#​14725](https://github.com/pnpm/pnpm/issues/14725). Each package still runs its prepare scripts in its own copy of the checkout.
##### Running scripts and tasks
- pnpm now passes Ctrl+C on to the script or command it started and waits for it to shut down [#​14723](https://github.com/pnpm/pnpm/issues/14723). pnpm exited first, so a script that was still writing landed on the shell prompt.
- `pnpm run "/pattern/" --no-bail` now lets every matched script finish after one of them fails [#​14718](https://github.com/pnpm/pnpm/issues/14718). The command exits with `ERR_PNPM_RUN_FAILED`, and its message lists the scripts that failed in the order they were selected.
- `pnpm pipeline` no longer fails on a project that tracks a symlink, such as a `CLAUDE.md` pointing at `AGENTS.md` [#​14692](https://github.com/pnpm/pnpm/issues/14692). Changing a symlinked input's target invalidates that task's cache, and `pnpm pipeline --no-cache` no longer hashes task inputs.
##### Commands
- `pnpm add -g`, `pnpm update -g`, and `pnpm remove -g` no longer change global bins or install directories after reading only part of an installed package group [#​13796](https://github.com/pnpm/pnpm/issues/13796). If any declared package manifest is missing, malformed, or unreadable, pnpm now fails before it activates or removes anything and leaves the existing global installation intact.
- `pnpm dedupe` now processes every workspace project by default, including workspaces that keep a separate lockfile per project [#​14732](https://github.com/pnpm/pnpm/issues/14732). Workspace filters select which projects it processes, and `--fail-if-no-match` exits with an error when no project matches.
- `pnpm update <name>@​<version>` now keeps the range operator the manifest declares [#​14745](https://github.com/pnpm/pnpm/issues/14745). Running `pnpm update [email protected]` on `"react": "^19.2.8"` writes `"react": "^19.3.0"`. A `jsr:` entry keeps its `jsr:` prefix, and a plain `pnpm update` now moves a `jsr:` range the way it moves an npm range.
- `pnpm --filter` directory selectors now support `?` wildcards and character classes such as `[ab]`. A `*` or `?` wildcard no longer selects a directory whose name starts with a dot, as on pnpm 11.
- `pnpm deploy --legacy` now prefers the dependency versions pinned in the source workspace lockfile when they still satisfy the deployed project's range [#​13857](https://github.com/pnpm/pnpm/issues/13857).
- `pnpm sbom` now leaves out a package's author field when the manifest author name is empty or contains only whitespace [#​14685](https://github.com/pnpm/pnpm/issues/14685). In a filtered or split workspace run, only a project with no `author` field inherits the workspace root's author.
`pnpm sbom --sbom-format spdx` now writes `creationInfo.created` with whole seconds, such as `2026-09-08T10:38:21Z` [#​14684](https://github.com/pnpm/pnpm/issues/14684). The fractional seconds it carried were rejected by strict SPDX consumers.
##### Configuration
- The `updateConfig` pnpmfile hook now receives the resolved configuration, including settings that came from `.npmrc`, the command line, or a default [#​14676](https://github.com/pnpm/pnpm/issues/14676). Scoped registries are reported under `registriesByScope`, and a hook may rewrite that map to change where packages are fetched from. Registry credentials are reported under `configByUri`, as pnpm 11 reports them. An unset setting is left out rather than reported as `null`.
- `pnpm audit --fix` and the `minimumReleaseAgeStrict` approval prompt now keep the comments in `minimumReleaseAgeExclude` when they append an entry to it in `pnpm-workspace.yaml`. The rest of the list is left as written, and the `trustPolicyExcludePrune` and `minimumReleaseAgeExcludePrune` cleanups keep the comments of the entries they retain.
`pnpm install` and `pnpm dedupe` now run those cleanups too [#​14759](https://github.com/pnpm/pnpm/issues/14759). Only `pnpm add`, `pnpm update`, and `pnpm remove` pruned the entries that the freshly written lockfile no longer resolves.
- `pnpm config set --global node-download-mirrors` no longer rejects the key [#​13611](https://github.com/pnpm/pnpm/issues/13611). The global config file already accepted `nodeDownloadMirrors`, but the command refused to write it.
- `NO_PROXY` entries that start with a dot, such as `.npmjs.org`, now bypass the proxy for the domain and its subdomains [#​14686](https://github.com/pnpm/pnpm/issues/14686).
- pnpm no longer creates a project `pnpm-lock.yaml` when `devEngines.packageManager.onFail` is `download` and lockfile writing is off through `lockfile: false` or `--no-lockfile` [#​14728](https://github.com/pnpm/pnpm/issues/14728). pnpm still switches to the pinned version.
- pnpm now writes `node_modules/.package-map.json` only when `nodeExperimentalPackageMap` is enabled. Nothing reads the file without that setting, and an install that stops writing the map removes the one a previous install left.
##### Windows
- `pnpm pipeline` no longer fails with intermittent access denied errors when concurrent tasks save their cache entries on Windows.
- Windows filesystem operations now retry permission errors for up to one second [#​14682](https://github.com/pnpm/pnpm/issues/14682). A permanent permission error delayed the failure by a minute. Sharing and lock violations keep their one minute retry budget.
##### Messages and output
- pnpm now warns when the root `package.json` declares a non-empty `workspaces` array and the project has no `pnpm-workspace.yaml` [#​2255](https://github.com/pnpm/pnpm/issues/2255). Such an install linked no project and said nothing about why.
- `ERR_PNPM_PACKAGE_MANAGER_REMOVE_MODULES_DIR` now names the file or directory in `node_modules` that pnpm could not clean up. It reported only the underlying OS error, such as "Access is denied (os error 5)".
- `pnpm --help` no longer describes pnpm as experimental.
<!-- 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>
<details>
<summary>eemeli/yaml (yaml)</summary>
### [`v2.9.1`](https://github.com/eemeli/yaml/releases/tag/v2.9.1)
[Compare Source](https://github.com/eemeli/yaml/compare/v2.9.0...v2.9.1)
- Limit recursive merge aliases ([#​685](https://github.com/eemeli/yaml/issues/685), [#​713](https://github.com/eemeli/yaml/issues/713))
- Simplify line unfolding during quoted string parsing ([#​714](https://github.com/eemeli/yaml/issues/714))
</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](https://github.com/renovatebot/renovate).
<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0My4xOTEuMiIsInVwZGF0ZWRJblZlciI6IjQzLjE5MS4yIiwidGFyZ2V0QnJhbmNoIjoibWFpbiIsImxhYmVscyI6W119-->
<!-- cloudflare-preview --> Preview: https://pr-554.docs-gitea-com.pages.dev
Reviewed-on: https://gitea.com/gitea/docs/pulls/554
Reviewed-by: silverwind <[email protected]>
Co-authored-by: Renovate Bot <[email protected]>
Fixes https://github.com/go-gitea/gitea/issues/39310
The docker install pages tell readers to use `:nightly` / `:nightless-rootless` for the latest development version, but those tags are no longer published — the registry only carries `main-nightly` and `main-nightly-rootless` (plus the per-release-branch `1.x-nightly` tags):
```
$ curl -s https://docker.gitea.com/v2/gitea/tags/list?n=1000 | tr ',' '\n' | grep -E '"(main-)?nightly(-rootless)?"'
"main-nightly"
"main-nightly-rootless"
```
Anyone who followed the rootless page has been pinned to an image that has not been rebuilt since 2025-08-23.
Changes:
- point the docker and rootless docker install pages at `:main-nightly` / `:main-nightly-rootless`, in English and in the zh-cn/zh-tw translations
- use `1.27-nightly` instead of the long gone `1.16-nightly` as the release branch example (the oldest one still published is `1.19-nightly`)
- fix the zh-cn/zh-tw claim that `:latest` installs a development version, and mention the nightly tags there the way the English page does
- render `@dockerVersion@` as `main-nightly` for the next version, so the compose snippets on the unreleased docs no longer reference `gitea:nightly`
Not touched: the 1.22–1.26 versioned docs carry the same stale tag, but those versions are already out of maintenance — happy to include them if preferred.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
<!-- cloudflare-preview --> Preview: https://pr-550.docs-gitea-com.pages.dev
---------
Co-authored-by: bircni <[email protected]>
Reviewed-on: https://gitea.com/gitea/docs/pulls/550
Reviewed-by: silverwind <[email protected]>
Co-authored-by: Lunny Xiao <[email protected]>
Several cron tasks in the cheat sheet listed `ENABLE_SUCCESS_NOTICE` or `NO_SUCCESS_NOTICE`. Gitea reads neither, only `NOTICE_ON_SUCCESS`. `NO_SUCCESS_NOTICE` was removed in https://github.com/go-gitea/gitea/pull/19221.
1. Replace both dead keys with `NOTICE_ON_SUCCESS` (default `false`).
2. Fix "Notice if not success" descriptions, which inverted the key's meaning.
3. List the key for `cleanup_hook_task_table`, `cleanup_actions`, `cleanup_action_runs` and `gc_lfs`, which honor it but omitted it.
Matches https://github.com/go-gitea/gitea/pull/39352.
Written by Claude.
Reviewed-on: https://gitea.com/gitea/docs/pulls/552
Reviewed-by: Lunny Xiao <[email protected]>
Co-authored-by: silverwind <[email protected]>
Two unrelated but small fixes, closes#258 and closes#32.
**The reference does not name its types (#258)**
Since the api reference is rendered from the openapi 3.0 document, a field of type `ReviewStateType` is rendered as the bare string enum the `$ref` points at: starlight-openapi dereferences everything and the schema has no `title`, so the name of the type is nowhere on the page. Searching the current reference for `ReviewStateType` returns nothing, which is what the issue reports (the plugin also has no route for `components/schemas`, so there is no page per type either).
`normalizeSchema()` now titles every named schema of an openapi 3.0 document with its own name, which the plugin renders next to the type of a field:
- before: `state` · `string` · Allowed values: APPROVED …
- after: `state` · `ReviewStateType` · `string` · Allowed values: APPROVED …
The go doc comments start with the name of the type, so the description would repeat it ("ChangeFilesOptions — ChangeFilesOptions options for creating …"); a leading `<name> ` is stripped from the description.
For the record, how the three generations of documents describe that type:
| Versions | Type name | Allowed values |
| --- | --- | --- |
| 1.22 – 1.25 (swagger, `ReviewStateType` definition without `enum`) | shown | missing |
| 1.26 – 1.27 (swagger, enum inlined upstream) | missing | shown |
| 1.27, next (openapi 3.0, with this change) | shown | shown |
The released swagger documents are frozen, so 1.22 – 1.26 keep what they have; from 1.27 on the reference answers the question the issue asks.
**Gmail needs an app password (#32)**
The configuration in the guide is still correct after Google turned off password-only access, because it already asks for an app password. What was missing is why there is no alternative: Gitea's mailer authenticates with `CRAM-MD5`, `PLAIN` or `LOGIN` and has no XOAUTH2, so "Sign in with Google" is not an option. Added that, together with the two things people run into — an app password needs 2-step verification and a Workspace administrator can disable app passwords for the domain — and the `587` + `smtp+starttls` variant.
Verified by building the api and the next docs: the type names show up on the operation pages, the descriptions are not duplicated, and 1.26 and older are unchanged. `pnpm check` passes.
<!-- cloudflare-preview --> Preview: https://pr-541.docs-gitea-com.pages.dev
---------
Co-authored-by: bircni <[email protected]>
Reviewed-on: https://gitea.com/gitea/docs/pulls/541
Reviewed-by: bircni <[email protected]>
Co-authored-by: Lunny Xiao <[email protected]>
Closes#243.
The `sha` of the contents API is compared against the ID of the blob the commit has at that path, and for a file tracked by Git LFS that blob is the pointer file. Sending the `lfs_oid` or a checksum of the file content fails with `sha does not match`, and nothing in the documentation said so.
Adds a section to the LFS page: use the `sha` from `GET /repos/{owner}/{repo}/contents/{filepath}`, not `lfs_oid`, and note that the API does turn the content into an LFS object by itself when `.gitattributes` tracks the path (`modifyFile()` stores the object and commits the pointer), so nothing else has to be done differently.
The swagger comments upstream are being fixed in https://github.com/go-gitea/gitea/pull/38915, which will reach `/api/next/` through the scheduled job and the released reference with 1.28. The documents of 1.22 – 1.27 are frozen, so the guide is the only place where readers of the current release can find it.
<!-- cloudflare-preview --> Preview: https://pr-542.docs-gitea-com.pages.dev
Reviewed-on: https://gitea.com/gitea/docs/pulls/542
Reviewed-by: bircni <[email protected]>
Co-authored-by: Lunny Xiao <[email protected]>
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.
Reviewed-on: https://gitea.com/gitea/docs/pulls/531
Reviewed-by: Lunny Xiao <[email protected]>
Co-authored-by: Gitea Bot <[email protected]>
## Summary
Adds Sealos to the cloud-provider installation page for the current and next
Gitea documentation.
## Why
Sealos provides a public, community-maintained one-click Gitea template. The
entry fits alongside the existing Cloudron, Linode, alwaysdata, and Exoscale
options.
## Proposed files
- `docs/installation/on-cloud-provider.md`
- `versioned_docs/version-1.27/installation/on-cloud-provider.md`
## Validation
- Sealos App Store: https://sealos.io/products/app-store/gitea
- Template source: https://github.com/labring-actions/templates/tree/kb-0.9/template/gitea
- Gitea image: `docker.gitea.com/gitea:1.27.0-rootless`
- Live deployment validated on 2026-07-24
- Verified administrator initialization, login, repository creation, commits,
issues, release uploads, persistence, and S3 upload, read, and delete flows
## Maintenance
I maintain the Sealos template and can update the documentation when its
deployment path changes. Deployment-specific support is handled in the
[Sealos templates repository](https://github.com/labring-actions/templates/issues).
## AI assistance
This proposal was prepared with OpenAI Codex assistance. I reviewed the
deployment evidence and proposed content and will personally handle review
feedback.
---------
Co-authored-by: Lunny Xiao <[email protected]>
Reviewed-on: https://gitea.com/gitea/docs/pulls/525
Reviewed-by: Lunny Xiao <[email protected]>
Co-authored-by: yangchuansheng <[email protected]>
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.
Reviewed-on: https://gitea.com/gitea/docs/pulls/528
Reviewed-by: Lunny Xiao <[email protected]>
Co-authored-by: Gitea Bot <[email protected]>
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.
Reviewed-on: https://gitea.com/gitea/docs/pulls/522
Reviewed-by: Lunny Xiao <[email protected]>
Co-authored-by: Gitea Bot <[email protected]>
The docs site is served by Cloudflare Pages now, so the AWS deployment path is redundant.
- Drop the AWS CLI installation step.
- Drop the `configure-aws-credentials` step and the `aws s3 sync` + CloudFront invalidation step.
- Keep only the Cloudflare Pages deploy, and simplify the `_headers` / `_redirects` comment since it no longer needs to explain S3 ordering.
The AWS_* secrets are no longer used by this workflow and can be removed from the repository settings.
Reviewed-on: https://gitea.com/gitea/docs/pulls/518
Reviewed-by: silverwind <[email protected]>
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.
Reviewed-on: https://gitea.com/gitea/docs/pulls/519
Reviewed-by: Lunny Xiao <[email protected]>
Co-authored-by: Gitea Bot <[email protected]>
The `checks` workflow deploys a Cloudflare Pages preview to the `pr-<number>` branch of the `docs-gitea-com` project for every pull request, but nothing ever removed it. Closed and merged pull requests therefore keep an alias and a growing list of deployments around.
This adds a `cleanup preview` workflow that runs when a pull request is closed (merged or not), lists the deployments of the project through the Cloudflare API and deletes the ones whose trigger branch is `pr-<number>`. `force=true` is used so the deployment the branch alias points at is removed as well.
Pull requests from forks do not receive the secrets, so the cleanup is skipped there, exactly like the preview deployment itself.
---------
Co-authored-by: bircni <[email protected]>
Reviewed-on: https://gitea.com/gitea/docs/pulls/517
Reviewed-by: bircni <[email protected]>
Co-authored-by: Lunny Xiao <[email protected]>
Five documentation fixes that do not depend on anything upstream.
**PostgreSQL version in the docker guide** (closes#224)
The compose example pinned `postgres:14`, the oldest version Gitea still tests against, and PostgreSQL does not upgrade its data directory on its own, so every new deployment that copies the example is one major upgrade behind from day one. The example now uses `postgres:18`, with a note about why the major version is pinned and a pointer to the supported range.
**SELinux** (closes#211)
`setcap 'cap_net_bind_service=+ep'` is what the docs suggest for binding to a port below 1024, but it is denied under SELinux depending on the label of the binary, and it is lost on every upgrade because the capability lives in an extended attribute of the file. A new page describes granting the capability through the systemd unit instead (`AmbientCapabilities`, plus `PrivateUsers=false` when the sandboxing gets in the way), the `semanage fcontext` / `restorecon` labels for the binary and the data directory, and how to read the denials with `ausearch` and `audit2allow`. It is linked from the binary and source installation pages and from the `gitea web` notes.
**OAuth2 and OpenID Connect** (closes#106)
The authentication page documented LDAP, PAM, SMTP, FreeIPA, SPNEGO and the reverse proxy, but not that Gitea can log users in against an external OAuth2 or OpenID Connect provider, which made it look like the only OAuth2 support is Gitea acting as the provider. The new section covers the fields of the authentication source, the callback URL to register at the provider, the group and claim mappings, and the `[oauth2_client]` settings, and separates it from the older OpenID 2.0 sign-in.
**IIS reverse proxy** (closes#39)
Application Request Routing rewrites the host of the `Location` header back to the internal one while "Reverse rewrite host in response headers" is on, which sends the browser to `127.0.0.1:3000` after a sign-in. The IIS section now says to turn it off and where the setting lives.
**ACME in containers** (closes#292)
`ACME_DIRECTORY` defaults to the relative path `https`, which is resolved against the working directory of the process. In the official image that is `/app/gitea`, outside the `/data` volume, so a recreated container asks the CA for a new certificate and runs into the Let's Encrypt limit of 5 certificates per week for the same domains. The HTTPS page now warns about it and shows an absolute path inside the volume; the cheat sheet entry points at the warning. The default itself is being changed in https://github.com/go-gitea/gitea/pull/35851, the note stays useful for every released version.
Only `docs/` is touched, the released versions keep their content.
<!-- cloudflare-preview --> Preview: https://pr-513.docs-gitea-com.pages.dev
Reviewed-on: https://gitea.com/gitea/docs/pulls/513
Reviewed-by: bircni <[email protected]>
Co-authored-by: Lunny Xiao <[email protected]>
Closes#37.
Gitea generates an OpenAPI 3.0 document since 1.27 (`templates/swagger/v1_openapi3_json.tmpl`, renamed to `templates/swagger/v1-openapi3.generated.json` on main, served by an instance at `/openapi3.v1.json`), but this site never picked it up and still published Swagger 2.0 only. That is what the issue is about: code generators such as `openapi-python-client` reject the document with "You may be trying to use a Swagger document; this is not supported by this project".
**What this does**
- `update_api_docs.sh` downloads the OpenAPI 3.0 document next to the Swagger 2.0 one, rewrites the same placeholders (they sit in `servers` instead of `basePath`) and writes `static/openapi3-latest.json` and `static/openapi3-<minor>.json`. Versions that do not have one (1.26 and older) are skipped with a message and keep their Swagger 2.0 document.
- The OpenAPI 3.0 document is rendered where it exists (`/api/` and `/api/next/`), the older versions keep rendering Swagger 2.0. Both documents describe the same api and carry the same operation ids, so no url of an operation page changes; the `basePath` normalization is now only applied to the Swagger 2.0 documents, since a full url in `servers` is valid.
- The overview page of each version links both documents for download, which is what the issue actually needs: `https://docs.gitea.com/openapi3-27.json` for the current release, `https://docs.gitea.com/openapi3-latest.json` for gitea main.
- The scheduled workflow now refreshes and proposes both documents of gitea main.
**Verification**
- `pnpm dlx @redocly/cli lint static/openapi3-latest.json`: valid, only content warnings that come from upstream (missing 4xx responses, ambiguous paths).
- `openapi-python-client generate --path static/openapi3-latest.json`, the tool from the issue, generates a client. The only warning left is `POST /markdown/raw`, whose request body is `text/plain`.
- Operation ids of `static/swagger-27.json` and `static/openapi3-27.json` are identical (481 on both sides), so the operation pages keep their urls.
- Built the api product and compared a rendered operation page against the Swagger 2.0 one: same sections, the request body is now marked required where the document says so.
The released Swagger 2.0 documents are kept: they are the only description 1.26 and older ever produced, and some tooling still expects them.
<!-- cloudflare-preview --> Preview: https://pr-514.docs-gitea-com.pages.dev
Reviewed-on: https://gitea.com/gitea/docs/pulls/514
Reviewed-by: bircni <[email protected]>
Closes#146.
The published site runs pagefind, not docsearch: `PUBLIC_DOCSEARCH_APP_ID` and `PUBLIC_DOCSEARCH_API_KEY` are not set, so `astro.config.mjs` falls back to `pagefind: true`, and a page of docs.gitea.com ships starlight's `#starlight__search` with the `/pagefind/` bundle. The version scoping we have is only implemented for docsearch (`docsearch:product` / `docsearch:version` meta tags plus `facetFilters` in `src/config/docsearch.ts`), so nothing consumes it today.
Pagefind builds one index for the whole site, partitioned only by the html language, and nothing tags the pages with a version, so a search started in the 1.23 docs answers with every version of the same page and the latest one usually wins. That is what the issue reports; the original cause (the docusaurus search plugin) is gone, this one replaced it.
**What this does**
- `src/components/MarkdownContent.astro` wraps the content in two `data-pagefind-filter` elements, `product` and `version`, so the filters land in the index. One filter per element on purpose: pagefind reads the whole attribute as a single `name:value` pair, a comma separated value ends up as one filter named `product` with the value `docs,version:1.26`.
- The same scope is written to a `gitea:search-filters` meta tag next to the docsearch ones (`src/lib/search.ts`).
- Starlight builds the search ui from a build time configuration and has no option for per page filters, so its `@pagefind/default-ui` import is redirected to a subclass in `src/lib/pagefind-ui.ts` by the `gitea-pagefind-filters` plugin in `astro.config.mjs`, the same approach the `gitea-openapi-overview` plugin already uses. The subclass selects the filters of the page the modal was opened on.
- A "Search all versions" checkbox below the search input drops the filters again and labels each result with the version (and the product for the api and the runner) it comes from, so the duplicates are distinguishable. Translated into 简体中文 and 繁體中文.
- Pagefind's own filter panel is hidden, it is redundant next to the checkbox and starlight does not style it. The checkbox gets its native rendering back, pagefind resets everything it renders with `all: unset`.
- A `gitea-pagefind-filters-check` integration fails the build if the redirect did not run, so a starlight upgrade that moves the import cannot silently bring back an unscoped search.
`@pagefind/default-ui` is added to `sites/docs/package.json`; it was only reachable as a transitive dependency of starlight, and the version is the one already in the lockfile.
**Verification**
Built the whole site and drove the search with a headless browser against `astro preview` (the search does not run in `pnpm dev`):
| Page | Query | Results |
| --- | --- | --- |
| `/1.23/usage/actions/comparison/` | email | only `/1.23/...` |
| `/usage/actions/overview/` | email | only the version served at the root |
| `/runner/registration/` | label | only `/runner/...` |
| `/api/operations/tags/issue/` | label | only `/api/...` |
| `/zh-cn/1.26/usage/actions/overview/` | email | only `/zh-cn/1.26/...` |
| `/1.23/...` with the checkbox ticked | email | every version, results labelled `... · API 1.25`, `... · next` |
`pnpm check` passes. The docsearch path is unchanged: when the credentials are set the plugin takes over, the redirect does not run and the check integration skips.
<!-- cloudflare-preview --> Preview: https://pr-512.docs-gitea-com.pages.dev
Reviewed-on: https://gitea.com/gitea/docs/pulls/512
## Problem
`https://docs.gitea.com/help/seek-help` returns 404. It is still linked as the
*Support* item of the blog.gitea.com navbar, and it is not the only url in that
state: `/usage/webhooks`, `/usage/agit`, `/usage/protected-tags` and
`/usage/repo-mirror` are linked from gitea itself and 404 as well.
## Root cause
Not a rendering problem of the new site: those pages were moved years ago.
`docs/help/seek-help.md` became `docs/help/support.md` in 2023 (`slug: support`),
and the flat `/usage/<page>`, `/advanced/<page>`, `/developers/<page>` layout of
1.19 and 1.20 was reorganized into `usage/repository/`, `administration/` and
`development/`.
The old urls kept working because the site was published to S3 with
`aws s3 sync` **without** `--delete`: every page built before a move stayed in
the bucket and CloudFront kept serving it. The archived copy of
`/help/seek-help` still carries `last-modified: Wed, 12 Jul 2023` - the file was
six years stale but reachable.
The Astro deployment publishes to Cloudflare Pages, which only serves what the
current build emits, so all of those leftovers disappeared at once.
I crawled the urls the Wayback Machine has for `docs.gitea.com` and checked them
against the live site: 335 non-removed-version urls 404 today, and they are all
of this kind.
## Fix
301s in `cloudflare/_redirects`, from every legacy url that has a current
equivalent to that page, generated for the root, the `zh-cn` and `zh-tw`
prefixes and the `next` (develop) version. Also the runner docs that were
published under a full version number (`/runner/0.2.11/`, `/runner/1.0.8/`) and
`/runner/next/`, which is `/runner/develop/` now.
Checked while generating them, against a full local build:
- no rule shadows a page that exists today - the zh translations of `usage/agit`
and `usage/profile-readme` were never moved, so they are still served at the
old path and are left alone
- every target resolves to a page that exists, in the language and version the
rule redirects within
- no duplicated source, 417 static and 10 dynamic rules, well inside the
Cloudflare Pages limits (2100 / 100)
## Out of scope
- `/contributing/*`, `/developers/guidelines-*` and `/developers/hacking-on-gitea`
404 too, but those pages moved to the gitea repository, there is nothing on
docs.gitea.com to point them at.
- The `aliases:` frontmatter of the sources (`/en-us/seek-help`, `/webhooks`,
...) is a leftover of the Hugo site. Docusaurus ignored it and so does the
Astro build; those urls have been 404 for years and are not resurrected here.
- The blog.gitea.com navbar should link to `/help/support` directly, that needs a
pull request against gitea/blog.
Reviewed-on: https://gitea.com/gitea/docs/pulls/507
Reviewed-by: bircni <[email protected]>
Co-authored-by: Lunny Xiao <[email protected]>
Replace [#307](https://gitea.com/gitea/docs/issues/307)
Closes [#237](https://gitea.com/gitea/docs/issues/237)
## Summary
Rebuilds docs.gitea.com with [Astro](https://astro.build) and [Starlight](https://starlight.astro.build), replacing Docusaurus. Every
published url keeps working and no content file was moved: the site is a new rendering layer over the existing `docs/`, `versioned_docs/`, `i18n/`, `runner-docs/` and `static/swagger-*.json` trees.
For the first time the API reference is part of the site rather than a Redoc
bundle: all seven swagger documents are rendered into real pages, one per
operation, so they are linkable, crawlable and searchable.
## Why
- the Docusaurus build needs an 8 GB heap and about 2 minutes for 2478 pages;
the Astro build produces 5751 pages, API reference included, in about 100
seconds
- the API reference was a single client rendered Redoc page per version, absent
from the site search and from search engines
- sidebars, version lists and language lists were configured in three different
places and drifted apart
## What is in here
- **`packages/content-loader`** — the product × version × language matrix
(`products.ts`) and an Astro content loader that reads the existing markdown
trees directly. The matrix is the single source of truth: content loading,
sidebars, the version and language pickers, the version banner, the search
facets and the API schemas are all derived from it.
- **Content compatibility** — frontmatter `slug`, `sidebar_position` and
`sidebar_label` keep working, `_category_.json` still drives sidebar labels,
order and the generated category pages, `@version@` style release variables are
still substituted, `:::note` admonitions become Starlight asides and the 2778
relative `*.md` links are rewritten to urls while the site is built.
- **API reference** — `starlight-openapi` renders the seven swagger documents.
Operation urls are hyphenated (`/api/operations/list-admin-workflow-jobs/`),
the sidebar shows the operations of the version being read with a coloured
HTTP method badge, and the overview page links to one page per tag instead of
repeating all 484 operations.
- **Navigation** — a version picker that follows the reader to the same page in
another version, a language picker that only offers the languages the current
product is published in, and product links for Docs, API, Runner and
Enterprise.
- **Theme** — the light and dark palettes of about.gitea.com, mapped onto the
Starlight variables.
- **Search** — Pagefind by default; Algolia DocSearch takes over when
`PUBLIC_DOCSEARCH_APP_ID` and `PUBLIC_DOCSEARCH_API_KEY` are set. Every page
carries `docsearch:product`, `docsearch:version` and `docsearch:language` meta
tags, so a search stays inside what is being read.
`cloudflare/docsearch-crawler.json` holds the crawler configuration.
## Bugs fixed along the way
- `GET /user/applications/oauth2` and `GET /user/applications/oauth2/{id}` have
operation ids that only differ in case, so they collapsed onto the same url
and one of the two pages was silently dropped. They are now
`user-get-oauth2-application` and `user-get-oauth2-application-by-id`.
- The API overview and every tag page were all titled "Overview".
## Url compatibility
The routes of both builds were compared page by page during the migration with
`scripts/url-diff.mjs`:
```
identical: 2368, missing: 0, accepted: 110, added: 3292
```
The 110 accepted ones are all redirected in `cloudflare/_redirects`: the
localized copies of the English-only API and Runner docs, the Docusaurus search
page, and `/1.27/`, `/runner/3/`, `/api/1.27/` which are aliases of the versions
served at the product root. The added ones are the API operation and tag pages
plus the routes the Starlight language fallback serves in English when a
translation is missing — Docusaurus answered those with a 404.
## Workflows
- `checks` builds the site and type checks it on every pull request
- `Build and Publish Docs site` publishes `sites/docs/dist` to S3/CloudFront and
to Cloudflare Pages, and copies `cloudflare/_headers` and
`cloudflare/_redirects` into the deployment
- `update swagger files` and `update runner reference` are unchanged, they only
touch content
- `make cut-version PRODUCT=docs VERSION=1.28` replaces
`docusaurus docs:version`
## Removed
`docusaurus.config.js`, the swizzled theme under `src/`, and the Docusaurus UI
translations (`i18n/*/code.json`, `i18n/*/docusaurus-theme-classic/`). The
strings those carried — the product names, the footer column titles and the
outdated translation notice — were ported to
`sites/docs/src/config/strings.ts`; Starlight ships the rest of its interface in
both Chinese locales. The documentation content itself is untouched.
## Follow ups
- nine relative links are broken in the sources and reported by every build, the
same ones Docusaurus warned about; `GITEA_DOCS_STRICT_LINKS=true` turns them
into an error once they are fixed
- `cloudflare/worker.js` has to be deployed for `/enterprise/` to keep resolving
- the Algolia index has to be created and crawled before the search credentials
are set
## Testing
```shell
make install
make serve-fast # english, the version served at the root
make serve # the whole matrix
make build # 5751 pages
make check # 0 errors
make serve-built # build and serve, the only way to try the search locally
```
## Screenshots
<img width="1371" alt="image.png" src="attachments/69acdd77-cc89-4635-a8bd-1163a34afa86">
<img width="1810" alt="image.png" src="attachments/76212416-4825-4b4d-bf59-3e549900c96f">
<img width="1789" alt="image.png" src="attachments/7be84cda-5abe-48bd-8839-7ae1ee7806e7">
---------
Co-authored-by: bircni <[email protected]>
Reviewed-on: https://gitea.com/gitea/docs/pulls/496
Reviewed-by: bircni <[email protected]>
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 |
|---|---|---|---|---|---|---|
| [@easyops-cn/docusaurus-search-local](https://github.com/easyops-cn/docusaurus-search-local) ([source](https://github.com/easyops-cn/docusaurus-search-local/tree/HEAD/packages/docusaurus-search-local)) | [`0.55.2` → `0.55.3`](https://renovatebot.com/diffs/npm/@easyops-cn%2fdocusaurus-search-local/0.55.2/0.55.3) |  |  | dependencies | patch | |
| [pnpm](https://pnpm.io) ([source](https://github.com/pnpm/pnpm/tree/HEAD/pnpm11/pnpm)) | [`11.17.0` → `11.20.0`](https://renovatebot.com/diffs/npm/pnpm/11.17.0/11.20.0) |  |  | packageManager | minor | `11.21.0` |
| [pnpm/action-setup](https://github.com/pnpm/action-setup) ([changelog](https://github.com/pnpm/action-setup/compare/0ebf47130e4866e96fce0953f49152a61190b271..0977fd99725f1db4007ccb2928dbb4e90d06cc86)) | `0ebf471` → `0977fd9` | | | action | digest | |
---
### Release Notes
<details>
<summary>easyops-cn/docusaurus-search-local (@​easyops-cn/docusaurus-search-local)</summary>
### [`v0.55.3`](https://github.com/easyops-cn/docusaurus-search-local/releases/tag/v0.55.3)
[Compare Source](https://github.com/easyops-cn/docusaurus-search-local/compare/v0.55.2...v0.55.3)
##### Bug Fixes
- trigger a release for refining search icon for [#​577](https://github.com/easyops-cn/docusaurus-search-local/issues/577) ([9a7eb91](https://github.com/easyops-cn/docusaurus-search-local/commit/9a7eb91393f389838cc31f45e4f4737dc4bb325e))
</details>
<details>
<summary>pnpm/pnpm (pnpm)</summary>
### [`v11.20.0`](https://github.com/pnpm/pnpm/releases/tag/v11.20.0): pnpm 11.20
[Compare Source](https://github.com/pnpm/pnpm/compare/v11.19.0...v11.20.0)
#### Minor Changes
- **Security fix.** Affects projects using `namedRegistries` on pnpm 11.1.0–11.19.x. It is **semi-breaking** for those projects — see "If you use named registries" below.
The lockfile recorded no marker for which registry a package came from. Packages were keyed by `name@version` alone, and entry lookup went through `refToRelative(ref, name)`, so a dependency you declared against one registry could be satisfied by an entry that was actually resolved from another. When two registries served the same name and version, both collapsed onto a single `packages:` entry and whichever resolved first decided the tarball every consumer got.
That is a package-substitution risk: a package you expect from your private registry could be installed from a different registry that publishes the same name and version, and the lockfile recorded nothing that would let you tell.
Packages resolved from a named registry are now recorded under registry-qualified keys (`<name>@​<registryName>:<version>`, e.g. `foo@work:1.0.0`), so each registry gets its own entry and the lockfile pins which one a dependency came from.
The lockfile format version is unchanged. Registry-qualified keys appear only for packages resolved from a named registry, so a project that does not use `namedRegistries` sees no difference, and older pnpm versions keep reading the file.
##### If you use named registries
Your next non-frozen install re-keys those entries, which shows up as a lockfile diff. Commit it — that diff is the fix being applied. Review it: an entry that moves to a registry you did not expect is worth investigating.
Everyone working on the project should be on this version or newer before you do. An older pnpm reads the re-keyed lockfile fine — frozen installs are unaffected — but it does not produce registry-qualified keys itself, so any install that updates the lockfile writes those entries back to the old shape, and the next install on a current pnpm re-qualifies them. The result is a lockfile that flips back and forth, and while it is in the old shape the project is exposed again. Because the lockfile format version is deliberately unchanged, pnpm cannot detect this and warn you about it.
There is no setting to keep the old behavior: the old shape is the vulnerability.
Tarball URLs that follow the standard registry layout are no longer written to the lockfile for named-registry packages; they are recomputed from the `namedRegistries` setting on demand.
To use named registries, map your aliases in `pnpm-workspace.yaml`:
```yaml
namedRegistries:
work: https://npm.enterprise.example.com/
```
##### New built-in `npmjs:` alias
`npmjs:` now resolves to `https://registry.npmjs.org/` with no configuration, alongside the existing `gh:` alias for GitHub Packages. It pins a dependency to the public registry even when `registry` points elsewhere, such as an internal proxy:
```json
{ "dependencies": { "left-pad": "npmjs:^1.3.0" } }
```
`npm:` cannot do this — it is the alias protocol (`npm:<name>@​<range>`) and resolves through whatever `registry` points at.
**If you mirror or proxy npmjs, point the alias at your mirror:**
```yaml
namedRegistries:
npmjs: https://npm.internal.example.com/
```
Built-in registry URLs are also the prefixes a lockfile's recorded tarball URL is matched against when pnpm verifies a package. Without the override, an entry whose tarball URL is on `registry.npmjs.org` is verified against the public registry rather than your mirror. This only affects lockfiles that record such URLs — a canonical URL for your configured registry is omitted from the lockfile and unaffected — and only when a tarball-URL, `minimumReleaseAge`, or `trustPolicy` check runs. Overriding the alias is the same escape hatch GHES users already have for `gh`.
Every alias the lockfile references must stay in `namedRegistries`: reading an entry whose alias is gone fails with `ERR_PNPM_MISSING_NAMED_REGISTRY` rather than silently falling back to the default registry, since that would fetch a different package. Renaming an alias re-resolves the packages that used it.
Named registry aliases that shadow a reserved dependency specifier prefix (`file`, `link`, `workspace`, `runtime`, `npm`, `jsr`, ...) are now rejected with `ERR_PNPM_RESERVED_NAMED_REGISTRY_NAME` instead of being silently shadowed by the corresponding resolver.
`pnpm licenses` and `pnpm sbom` now keep the two artifacts apart as well: license records carry the registry alias, and SBOM components carry the purl `repository_url` qualifier.
#### Patch Changes
- An empty `http-proxy`, `https-proxy`, `proxy`, or `no-proxy` value — from the `.npmrc`, `pnpm-workspace.yaml`, the CLI, or the `HTTP_PROXY` / `HTTPS_PROXY` / `PROXY` / `NO_PROXY` environment variables — no longer fails the install with `ERR_PNPM_INVALID_PROXY`. Empty settings read as unset, so a shell exporting `HTTP_PROXY=` disables the proxy, and an empty `proxy=` in the `.npmrc` no longer suppresses `HTTPS_PROXY` [#​13533](https://github.com/pnpm/pnpm/issues/13533).
`proxy=false` in the `.npmrc` or `proxy: false` in `pnpm-workspace.yaml` now turns proxying off instead of being read as a proxy host named `false`. `false` and `null` on `https-proxy` / `http-proxy` / `no-proxy` read as unset, and on the command line they are ordinary host names, since a flag carries its value verbatim.
- The env lockfile no longer pins `@pnpm/exe` alongside `pnpm` when the wanted pnpm version is 12 or newer. From v12 the unscoped `pnpm` package is itself the native executable, so `@pnpm/exe` is not published for it and resolving it would fail. The engine identity check now verifies the native binary through whichever package ships it.
- `lexCompare` and `nerfDart` are now published as `@pnpm/text.ordinal-comparator` and `@pnpm/config.registry-auth-key`. Use these instead of `@pnpm/util.lex-comparator` and `@pnpm/config.nerf-dart`.
- Fixed the order in which pnpm matches a lockfile's recorded tarball URL against known registry URLs. Two registry URLs of equal length were previously ordered arbitrarily, so which one a tarball URL matched could differ between runs.
- Dependency resolution is faster: package metadata is now filtered once per packument instead of once per dependency edge when `minimumReleaseAge` is active, and parsed semver versions and ranges are reused instead of re-parsed on every comparison.
- Security: `pnpm rebuild` now refuses a lockfile whose `packages` key carries a path traversal in the package name (e.g. `../../../[email protected]`), instead of running that package's lifecycle scripts and linking its bins in a directory outside the virtual store. Such a name is rejected with `ERR_PNPM_INVALID_DEPENDENCY_NAME`.
<!-- 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>
</tr>
<tr>
<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>
</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://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>
<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>
</tr>
<tr>
<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>
<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>
</tr>
</tbody>
</table>
<!-- sponsors end -->
### [`v11.19.0`](https://github.com/pnpm/pnpm/releases/tag/v11.19.0): pnpm 11.19
[Compare Source](https://github.com/pnpm/pnpm/compare/v11.18.0...v11.19.0)
#### Minor Changes
- `pnpm login` no longer requires an interactive terminal when the registry supports web-based login: without a TTY it prints the authentication URL (skipping the QR code and the "Press ENTER to open the URL in your browser" prompt) and polls the registry until the browser approval completes. Only the classic username/password login still fails with `ERR_PNPM_LOGIN_NON_INTERACTIVE` in a non-interactive terminal.
- The `save-prefix` setting now accepts `=`: newly added dependencies are saved with an explicit `=` operator (`=1.2.3`) instead of the setting being silently treated as the default `^`.
#### Patch Changes
- `allowBuilds` entries can now approve git-hosted packages that pnpm downloads as a tarball, such as `github:` dependencies (which are fetched from `codeload.github.com` rather than cloned), by their repository URL without the resolved commit hash. This matches the hashless `git+` matching already supported for cloned git dependencies. For example:
```yaml
allowBuilds:
"foo@git+https://github.com/org/foo.git": true
```
This approves the package whether pnpm clones it or downloads a tarball, so the entry no longer has to be updated every time the pinned commit changes. GitLab and Bitbucket tarball downloads are matched the same way. Approving or denying a specific resolved commit by its full tarball dep path continues to work.
- `pnpm outdated --include-github-actions` no longer blocks on an interactive git credential prompt when a workflow uses a private action repo.
- Prevented `minimumReleaseAge` from replacing `latest` with a SemVer-greater version than the registry tag target [#​13034](https://github.com/pnpm/pnpm/issues/13034).
- Fixed empty `bundledDependencies` and `bundleDependencies` arrays causing nondeterministic lockfile changes. See [#​13123](https://github.com/pnpm/pnpm/issues/13123).
- The install summary no longer prints `(X is available)` when the registry's `dist-tags.latest` is still held back by the active `minimumReleaseAge` policy. The hint only ever names the actual latest tag, so an immature latest suppresses the hint instead of advertising the version pnpm just refused to install [#​11698](https://github.com/pnpm/pnpm/issues/11698).
- `pnpm update` keeps the explicit `=` operator of an exact version pin: a dependency saved as `=3.5.1` now updates to `=3.5.2` instead of the bare `3.5.2`. See [#​13168](https://github.com/pnpm/pnpm/issues/13168).
- Preserve a workspace dependency's `link:` entry when a run does not target it — e.g. `pnpm update <other-pkg>` (with or without `--recursive`), or a plain install after a root/catalog dependency change — with `injectWorkspacePackages`, instead of spuriously rewriting it to a peer-suffixed `file:` protocol. See [#​10433](https://github.com/pnpm/pnpm/issues/10433).
- Workspace dependencies declared with a relative path (e.g. `"foo": "workspace:../foo"`) are no longer silently dropped from the workspace projects graph, so `--filter` selection and the topological order of recursive commands take them into account.
<!-- 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>
</tr>
<tr>
<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>
</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://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>
<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>
</tr>
<tr>
<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>
<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>
</tr>
</tbody>
</table>
<!-- sponsors end -->
### [`v11.18.0`](https://github.com/pnpm/pnpm/releases/tag/v11.18.0): pnpm 11.18
[Compare Source](https://github.com/pnpm/pnpm/compare/v11.17.0...v11.18.0)
#### Minor Changes
- Fixed an installed optional dependency being left without one of its own required dependencies. When a package reached through `optionalDependencies` is installable on the current system but one of its regular `dependencies` is not, a lockfile-based install skipped that dependency and installed the parent anyway, so importing the parent failed with `MODULE_NOT_FOUND`. The dependency is now installed, and an install-check warning reports the incompatibility. A dependency is still only skipped when every path to it is optional, or when the package that pulls it in was itself skipped [#​13286](https://github.com/pnpm/pnpm/issues/13286).
- `pnpm setup` now appends `PNPM_HOME` and the global bin directory to the GitHub Actions environment files (`GITHUB_ENV` and `GITHUB_PATH`), so later steps in the same job can run `pnpm add --global` and other global commands [#​9191](https://github.com/pnpm/pnpm/issues/9191).
- Added support for `publishConfig.name`, which publishes a package under a different name than the one its manifest carries in the workspace. It is for a project whose published name is already taken by a sibling project, which otherwise has to be renamed by a build step just before publishing. Only the published artifact is renamed — dependents, `pnpm-lock.yaml`, and release tooling keep addressing the project by its manifest name — and the new name reaches the packed manifest, the tarball filename, and everything that addresses the package at the registry: the already-published check of `pnpm publish -r`, its registry selection, and the release-planning probes of `pnpm change status` and `pnpm version -r` [#​13345](https://github.com/pnpm/pnpm/issues/13345).
- `pnpm self-update` no longer takes any instruction from the project it is run in:
- pnpm is fetched through the same trusted registry and auth configuration used when switching pnpm versions, so a project `.npmrc` or `pnpm-workspace.yaml` can no longer redirect the download or attach credentials to it, and the project's default `.pnpmfile.(c|m)js` is no longer loaded. Pnpmfiles from trusted sources (the `pnpmfile` setting, the global pnpmfile, config dependencies) still apply.
- The `minimumReleaseAge` settings in `pnpm-workspace.yaml` no longer affect `self-update`. They still govern the project's own dependencies; for `self-update` the cooldown now comes from the built-in default, your global config, a `PNPM_CONFIG_*` environment variable, or a command-line flag. This fixes `self-update` failing inside a workspace that raises the cutoff while succeeding everywhere else, and stops a repository from either waiving the cooldown or keeping you on an outdated pnpm by raising it.
- The same applies to the `trustPolicy` settings and to `ci`: a project can no longer weaken the trust check that guards the pnpm download, nor re-enable the confirmation prompt that a CI run suppresses.
When `self-update` refuses a version that is younger than the cutoff, an interactive run now offers to update anyway; non-interactive runs still fail. CI never prompts, even on a runner that attaches a TTY.
#### Patch Changes
- Fixed `pnpm licenses list` to report every version when the same package is installed under multiple aliases [pnpm/pnpm#13438](https://github.com/pnpm/pnpm/issues/13438).
- Sort `pnpm dedupe --check` snapshot changes for stable output across pnpm implementations.
- Strip Unicode formatting characters from registry- and manifest-derived terminal output.
- Speed up installs after compatible catalog or direct dependency range changes by retaining the locked version without resolving the dependency graph again.
- Speed up installs after safe override changes by reusing unambiguous compatible dependency resolutions, pruning obsolete dependencies, applying independent replacements and removals together, and handling parent-scoped `"-"` overrides without full lockfile resolution.
- Installing a local `file:` directory dependency with the global virtual store enabled no longer fails with `TypeError: Cannot read properties of undefined (reading 'split')` [#​13335](https://github.com/pnpm/pnpm/issues/13335).
Local directory dependencies — `file:` directories and injected workspace packages — now get a global-virtual-store slot of their own per project. They used to share one slot across every project that depended on a directory of the same name, so a project could end up linked to another project's copy of the dependency.
- The `Workspace` column of `pnpm update --interactive` now falls back to the project's path when its `name` is only whitespace, as it already did for a missing or empty one — all three render an equally blank label otherwise.
- Checking GitHub Actions dependencies for updates is now opt-in for every command. Neither `pnpm outdated` nor `pnpm update` reads the workflow files unless `--include-github-actions` is passed or `update.githubActions` is set to `true` in `pnpm-workspace.yaml`. Reading them runs `git ls-remote` against every referenced repository, which fails in environments where GitHub is not reachable the way pnpm assumes (a GitHub Enterprise Server, a custom certificate authority, or an offline network) [#​13254](https://github.com/pnpm/pnpm/issues/13254).
`pnpm outdated` accepts the `--include-github-actions` option too.
- `pnpm update --interactive` now measures its table in terminal columns rather than in characters. A package name, workspace name, or version containing wide characters (CJK, most emoji) no longer knocks its row's columns out of line with the rest of the group, and a wide character in a version no longer aborts the command with `Subject parameter value width cannot be greater than the container width` [#​13357](https://github.com/pnpm/pnpm/issues/13357).
- The `Workspace` column of `pnpm update --interactive` is more informative in two cases. A dependency outdated at the same version in several workspace projects is offered as one choice, since selecting it updates every project — that choice now names all of them instead of only the first. And a workspace project without a `name` is now labelled with its path rather than left blank, so several unnamed projects can be told apart.
- An auto-installed *optional* peer is no longer hoisted at a version the workspace root's own dependency on that package excludes. `resolvePeersFromWorkspaceRoot` already made the workspace root's specifier decide which version a missing *required* peer is installed at; the optional-peer picker ignored it and always took the highest version present anywhere in the graph. In a workspace whose root pins `postcss: 8.5.10`, an importer that depends on `webpack` and declares no `postcss` of its own got `[email protected]` hoisted for `terser-webpack-plugin`'s optional `postcss` peer, leaving two `[email protected]` instances in the graph [#​13320](https://github.com/pnpm/pnpm/issues/13320).
- `overrides` now also govern peers that pnpm auto-installs. Previously an override only rewrote dependencies declared in a manifest, so a peer nobody declares — installed because `autoInstallPeers` is on — resolved against its declared peer range and could bring in a second copy of the very package the override pinned. For example, with `overrides: { react: npm:[email protected] }` and a lone `lucide-react` dependency, pnpm installed `[email protected]`; it now installs the pinned `[email protected]` [#​13320](https://github.com/pnpm/pnpm/issues/13320).
- Under `resolvePeersFromWorkspaceRoot`, a workspace root dependency declared with `link:` or `file:` (or the path form of `workspace:`, such as `workspace:../pkg`) now satisfies another project's missing peer dependency at the linked package's own version, instead of being hoisted as a path. Those specifiers are relative to the project that declares them, so the same specifier reached a different directory — or none — from the project the peer was hoisted into, leaving a broken link. The root now has the same authority over the peer as it has when it declares the package with a version range [#​13373](https://github.com/pnpm/pnpm/issues/13373).
- Installs through a pnpr server now apply the project's whole verification policy. `minimumReleaseAgeExclude`, `minimumReleaseAgeIgnoreMissingTime`, `trustPolicy`, `trustPolicyExclude`, `trustPolicyIgnoreAfter`, and `trustLockfile` were ignored, so excluded packages were still held back and a lockfile containing them could be rejected.
`trustPolicy: no-downgrade` no longer fails with `TRUST_POLICY_INCOMPATIBLE_WITH_PNPR` when a pnpr server is configured.
`--frozen-lockfile` and `--no-prefer-frozen-lockfile` are now honored on the pnpr path, instead of resolving and rewriting the lockfile anyway. Since `frozenLockfile` defaults to `true` on CI, a CI install through a pnpr server now fails on an out-of-date lockfile rather than updating it.
- Workspace installs through a pnpr server no longer crash with `Cannot read properties of undefined (reading 'filter')` after linking, when `minimumReleaseAge` is active [#​13275](https://github.com/pnpm/pnpm/issues/13275).
- Fixed `pnpm dedupe` updating valid catalog resolutions when another matching version exists in the lockfile.
- `pnpm -r run "/pattern/" --no-bail` no longer exits zero when one of a project's matched scripts fails and a later one passes. The run summary carries a single status per project, and the passing script overwrote the recorded failure.
- Restored the store block a first install prints, naming how packages were materialized and where the stores live [#​13315](https://github.com/pnpm/pnpm/issues/13315):
```text
Packages are hard linked from the content-addressable store to the virtual store.
Content-addressable store is at: ~/.local/share/pnpm/store/v11
Virtual store is at: node_modules/.pnpm
```
- The root project's `pnpm:devPreinstall` script now runs before resolution and linking, as it does in pnpm 11. It is skipped under `--ignore-scripts`, `--lockfile-only` and `--dry-run`, by `pnpm fetch` and `pnpm rebuild`, and by a repeat install that is already up to date. Workspaces that use the hook to prepare state the install depends on — such as [next.js](https://github.com/vercel/next.js), which generates a placeholder `next` bin with it — were left with dependents linked against files that were never created [#​13313](https://github.com/pnpm/pnpm/issues/13313).
- Prevented `pnpm dedupe --check` from removing an incompatible `node_modules` directory.
- `pnpm update --workspace` no longer links dependencies the user never named:
- Running it with `updateConfig.ignoreDependencies` configured no longer fails with `ERR_PNPM_WORKSPACE_PACKAGE_NOT_FOUND` for a dependency that is only published to the registry. Such dependencies keep their specifiers, as they already did when no dependencies were ignored.
- Passing package selectors that match no direct dependency no longer falls back to linking every workspace dependency.
<!-- 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>
</tr>
<tr>
<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>
</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://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>
<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>
</tr>
<tr>
<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>
<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>
</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](https://github.com/renovatebot/renovate).
<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0My4xOTEuMiIsInVwZGF0ZWRJblZlciI6IjQzLjE5MS4yIiwidGFyZ2V0QnJhbmNoIjoibWFpbiIsImxhYmVscyI6W119-->
<!-- cloudflare-preview --> Preview: https://pr-500.docs-gitea-com.pages.dev
---------
Co-authored-by: Lunny Xiao <[email protected]>
Reviewed-on: https://gitea.com/gitea/docs/pulls/500
Reviewed-by: Lunny Xiao <[email protected]>
Co-authored-by: Renovate Bot <[email protected]>
Pull requests now get a Cloudflare Pages preview deployment after the site build succeeds, and the preview URL is written back as the last line of the pull request description.
- `concurrency` cancels the previous preview build when a pull request is pushed again.
- After `make build`, the workflow copies `cloudflare/_headers` into `build/` (same as the production deployment, so the file is never published to S3/CloudFront) and runs `wrangler pages deploy build --project-name docs-gitea-com --branch pr-<number>`, then extracts the `*.pages.dev` URL from the wrangler output.
Notes:
- Pull requests from forks do not receive the secrets, in that case the preview steps are skipped with a log message and the build check still runs.
- Requires `CLOUDFLARE_ACCOUNT_ID` / `CLOUDFLARE_API_TOKEN` (Pages edit permission) and `DEPLOY_TOKEN` (pull request write permission).
<!-- cloudflare-preview --> Preview: https://pr-498.docs-gitea-com.pages.dev
Reviewed-on: https://gitea.com/gitea/docs/pulls/498
Reviewed-by: Zettat123 <[email protected]>
The scheduled `update swagger files` job has failed on **every** run since it was added (I checked the run list on gitea.com, ~250 consecutive failures). Two reasons:
1. Upstream moved the file: `go-gitea/gitea` main no longer has `templates/swagger/v1_json.tmpl`, it is now `templates/swagger/v1-swagger.generated.json`, and the placeholders changed from `{{.SwaggerAppVer}}` / `{{.SwaggerAppSubUrl}}` to `0.0.0+GITEA-API-APP-VERSION` / `/GITEA-API-APP-SUBURL/api/v1`. Because the script used `curl --silent` without `--fail`, the 404 body was written into `static/swagger-latest.json`.
2. `main` is protected (`required_approvals=1`), so the job's `git push` to main could never succeed.
Changes:
- `update_api_docs.sh`: `set -euo pipefail`, download with `--fail` (a broken download now aborts instead of committing garbage), try the new generated json first and fall back to the old template for released tags, handle the 1.28+/1.24+/<1.24 placeholder variants, and add a `--latest-only` flag.
- `Makefile`: new `update-api-docs-latest` target for the cron job; `clean` no longer deletes the committed `static/swagger-*.json` files (deleting them breaks `make build`).
- `.gitea/workflows/update_swagger.yaml`: every 12h (plus `workflow_dispatch`) it regenerates only `static/swagger-latest.json`, verifies no placeholder is left and the file is valid JSON, force pushes to `bot/update-swagger-latest` and opens a pull request via the API (HTTP 409 = a PR is already open, it just now points at the new commit). It skips entirely when the bot branch already carries the same file, so no PR churn.
- `README.md`: document the two make targets and the automation.
`secrets.DEPLOY_TOKEN` must belong to a real user with `write:repository` so the PR checks are triggered; the job fails with a clear message if it is empty.
Tested locally in a scratch clone: `make update-api-docs-latest` produces a valid `dev` spec with no placeholders left, `make update-api-docs` still regenerates all released versions, and the workflow shell logic was simulated against a local bare repo for all three paths (open PR / bot branch already up to date / main already up to date), including a shallow clone to confirm `fetch-depth: 1` force pushes work.
Fixes#454
Reviewed-on: https://gitea.com/gitea/docs/pulls/481
Reviewed-by: techknowlogick <[email protected]>
The `usage/actions/runner` has duplicated runner documentations as `/runner`. This PR remove the duplicated content and leave obtain registry token in `usage/actions/runner` and runner install/configurations in `/runner`.
Reviewed-on: https://gitea.com/gitea/docs/pulls/479
Reviewed-by: silverwind <[email protected]>
Fixes https://gitea.com/gitea/docs/issues/386
`gitea.event` was documented as `{...}` only. Adds an "Event payload" section listing the commonly used fields per event, including `gitea.event.schedule`, and notes where Gitea's payloads deviate from GitHub's.
Also corrects the trigger event table:
1. `pull_request` takes `synchronize`, not `synchronized`
1. `workflow_run` also emits `in_progress`
1. `schedule`, `workflow_call` and `pull_request_target` were missing
Reviewed-on: https://gitea.com/gitea/docs/pulls/478
Reviewed-by: Lunny Xiao <[email protected]>
Co-authored-by: silverwind <[email protected]>
Documents the WebSocket endpoint `/-/ws` added in https://github.com/go-gitea/gitea/pull/36965 and fixes the examples that could not serve it.
1. Nginx: adds the missing `proxy_http_version 1.1`, without it Nginx talks HTTP/1.0 upstream and Gitea rejects the handshake.
1. Apache HTTPD: `upgrade=websocket` on the existing `ProxyPass` rule, handled by `mod_proxy_http` since 2.4.48.
1. Nginx Proxy Manager: use the `Websockets Support` toggle, a duplicate `proxy_http_version` stops Nginx from starting.
1. IIS: the `WebSocket Protocol` Windows feature is required.
---------
Co-authored-by: silverwind <[email protected]>
Co-authored-by: silverwind <[email protected]>
Co-authored-by: mohammad rahimi <[email protected]>
Reviewed-on: https://gitea.com/gitea/docs/pulls/463
Reviewed-by: Lunny Xiao <[email protected]>
Reviewed-by: silverwind <[email protected]>
Co-authored-by: mohammad rahimi <[email protected]>
This PR contains the following updates:
| Package | Change | [Age](https://docs.renovatebot.com/merge-confidence/) | [Confidence](https://docs.renovatebot.com/merge-confidence/) |
|---|---|---|---|
| [pnpm](https://pnpm.io) ([source](https://github.com/pnpm/pnpm/tree/HEAD/pnpm11/pnpm)) | [`11.11.0` → `11.15.0`](https://renovatebot.com/diffs/npm/pnpm/11.11.0/11.15.0) |  |  |
---
### Release Notes
<details>
<summary>pnpm/pnpm (pnpm)</summary>
### [`v11.15.0`](https://github.com/pnpm/pnpm/releases/tag/v11.15.0): pnpm 11.15
[Compare Source](https://github.com/pnpm/pnpm/compare/v11.14.0...v11.15.0)
##### Minor Changes
- Optional peer dependencies declared only via `peerDependenciesMeta` (for example `debug`'s `supports-color` peer) are now resolved from a satisfying version already present in the dependency graph, the same way explicitly declared optional peer dependencies are. Previously such peers were only resolved this way when the package's metadata was read back from the lockfile, so an unrelated dependency change could rewrite peer resolutions across the whole lockfile.
##### Patch Changes
- Updated `adm-zip` to prevent crafted ZIP archives from causing excessive memory allocation.
- `pnpm version -r` no longer writes a versioning-ledger entry with no consumed intents as a bare `intents:` key, which the next run failed to read with `ERR_PNPM_INVALID_VERSIONING_LEDGER`. Empty intent lists are now written as `intents: []`, and the ledger reader accepts the bare form left by earlier releases.
- Fixed pnpr workspace resolution to preserve project names and versions for `workspace:` dependencies.
<!-- 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>
</tr>
<tr>
<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>
</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://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>
<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>
</tr>
<tr>
<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>
<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>
</tr>
</tbody>
</table>
<!-- sponsors end -->
### [`v11.14.0`](https://github.com/pnpm/pnpm/releases/tag/v11.14.0): pnpm 11.14
[Compare Source](https://github.com/pnpm/pnpm/compare/v11.13.1...v11.14.0)
#### Minor Changes
- `peerDependencies` now accept dependency specifiers that carry a scheme — a named-registry spec (`<registry>:<version>`), an `npm:` alias, or a `file:`/git/URL spec — instead of rejecting them with `ERR_PNPM_INVALID_PEER_DEPENDENCY_SPECIFICATION` [#​13095](https://github.com/pnpm/pnpm/issues/13095). Such a peer is matched against the semver range carried by the specifier (`work:5.x.x` is checked as `5.x.x`, `npm:bar@^5` as `^5`), or against `*` when it carries no version, while the original specifier still selects the package to auto-install. Bare `name@version` values, which are almost always a mistake, are still rejected.
- Added `pnpm doctor`, which diagnoses the pnpm installation and the environment it runs in: the versions and install method, whether the global bin directory is on `PATH`, whether the store and cache are writable, which link strategies (reflink, hardlink, symlink) the store's filesystem supports, registry connectivity, and an offline `file:` install that exercises the resolve/store/link path end to end. Each check reports how to fix what it finds, and the command exits non-zero when any check fails.
Use `--offline` to skip the checks that need network access, `--json` for machine-readable output, and `--benchmark` to time the filesystem and install checks.
- Added support for executing multiple scripts matching a RegExp passed to `pnpm run` (e.g., `pnpm run "/^build:.*/"`), running matched scripts in deterministic lexicographical order. Restored the `--sequential` (`-s`) CLI option for `pnpm run`, which forces `workspaceConcurrency` to 1 so that matched scripts run sequentially one by one across and within packages.
#### Patch Changes
- Fixed `pnpm install` failing with `ERR_PNPM_LOCKFILE_IS_SYMLINK` when `pnpm-lock.yaml` is a symlink, as build sandboxes such as Bazel and Nix stage it [#​13073](https://github.com/pnpm/pnpm/issues/13073). Reading a lockfile through a symlink is allowed again, and an install that leaves the lockfile unchanged no longer rewrites it, so `--frozen-lockfile` no longer needs to write at all. Writing a *changed* lockfile through a symlink is still refused, as that would redirect the write onto the symlink's target.
- Fixed frozen installs incorrectly treating equivalent Git dependency specifiers as a stale lockfile. See [#​13039](https://github.com/pnpm/pnpm/issues/13039).
- `pnpm owner ls` now reports authentication and authorization failures (401/403) as dedicated errors that include the registry's response body, matching `pnpm owner add`/`rm`, instead of a generic `Failed to fetch owners` message.
- Recover from a metadata cache entry that disappears (concurrent cache cleanup, antivirus) after the registry has already answered the conditional request with `304 Not Modified`. The metadata is re-requested once without cache validators instead of failing the install with `ERR_PNPM_CACHE_MISSING_AFTER_304`.
- A project pinned to a broken pnpm release via `packageManager` or `devEngines.packageManager` now reports which release is broken and what to do about it, instead of failing inside the installer. `pnpm self-update` already refused these releases; the version switch does too.
- Prevent broken-lockfile errors from including snippets of the lockfile's contents.
- `pnpm self-update` now checks that the version it installed can run before making it the active pnpm. A release that installs but cannot execute is discarded with an error instead of replacing a working installation.
- Fixed an out-of-memory regression when workspace projects concurrently resolve a package with large registry metadata [pnpm/pnpm#13077](https://github.com/pnpm/pnpm/issues/13077).
- Fixed `pnpm update` rewriting exact version pins that use the `=` operator (for example `=3.5.1`) to a caret range (`^3.5.1`). Exact pins are now preserved and written back as the bare version. See [#​12745](https://github.com/pnpm/pnpm/issues/12745).
<!-- 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>
</tr>
<tr>
<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>
</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://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>
<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>
</tr>
<tr>
<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>
<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>
</tr>
</tbody>
</table>
<!-- sponsors end -->
### [`v11.13.1`](https://github.com/pnpm/pnpm/releases/tag/v11.13.1): pnpm 11.13.1
[Compare Source](https://github.com/pnpm/pnpm/compare/v11.13.0...v11.13.1)
#### Patch Changes
- Fixed `pnpm pack` applying workspace-root ignore rules when a workspace package has its own `.npmignore` file.
- Keep the interactive `minimumReleaseAge` approval prompt visible during `pnpm install`. The progress reporter now pauses its redraws while a prompt is waiting for input instead of overwriting it, so the install no longer hangs on a question the user cannot see [#​13019](https://github.com/pnpm/pnpm/issues/13019).
- Fixed `pnpm self-update` failing to link native platform binaries stored in sibling global virtual store slots.
### [`v11.13.0`](https://github.com/pnpm/pnpm/releases/tag/v11.13.0): pnpm 11.13
[Compare Source](https://github.com/pnpm/pnpm/compare/v11.12.0...v11.13.0)
#### Minor Changes
- Added `versioning.epics` to `pnpm-workspace.yaml`. An epic ties a group of member packages to a lead package, constraining every member's major version to a band derived from the lead's major: while the lead is on major `M`, members live in `M*100 … M*100+99`. Members move independently inside the band (patch, minor, and a `major` intent that stays in-band); a bump that would carry a member past the band ceiling is rejected until the lead advances its own major. When a release plan takes the lead to a new stable major, every member re-bases to the band floor in the same plan. Membership is matched with pnpm's package selectors — name globs, `./`-prefixed directory globs, and `!`-prefixed negations.
- Added the `team` command for managing organization teams and team memberships on the registry, with create, destroy, add, rm, and ls subcommands and support for --otp, --parseable, and --json flags.
- Added native workspace release management [#​12952](https://github.com/pnpm/pnpm/issues/12952): the new `pnpm change` command records change intents as changesets-compatible `.changeset/*.md` files (`pnpm change status` shows the pending release plan), and the bare `pnpm version -r` consumes them — bumping versions across the workspace with dependent propagation through `workspace:` ranges, fixed groups, a `maxBump` cap, `--filter` narrowing, and `--dry-run` — writing changelogs, and recording consumed intents in a committed ledger that keeps cherry-picks and merge-backs between release branches safe. Packages can be moved onto per-package release lanes with the new `pnpm lane <name> --filter <pkg>` command and back with `pnpm lane main --filter <pkg>` (`pnpm lane` shows the membership), releasing `X.Y.Z-lane.N` prereleases from the same runs that release stable versions of the packages on the main lane. Configuration lives under the new `versioning` key of `pnpm-workspace.yaml` (`fixed`, `ignore`, `maxBump`, `lanes`, `changelog`). When two workspace projects publish the same name, intent files, `versioning.lanes`, and `versioning.fixed`/`ignore` may reference a project by its workspace-relative directory path (e.g. `"./pnpm/npm/pnpm"`) — the one additive extension to the changesets format, applied automatically by `pnpm change`.
Release changelogs default to `registry` storage (`versioning.changelog.storage`): no `CHANGELOG.md` is committed. Each release's section is composed at publish time and packed into the published tarball on top of the previously published version's changelog, and the consumed change intents are garbage-collected by a later `pnpm version -r` only once the registry confirms the version is published with its section. Set `versioning.changelog.storage: repository` to keep committed `CHANGELOG.md` files instead.
- Added a new override selector form with an empty range — `"pkg@": "<version>"` — called a convergence override. It rewrites a dependency edge only when its exact version satisfies the edge's declared range, so compatible consumers converge on one version while incompatible consumers keep their own resolution — now and for any dependent added in the future [#​12794](https://github.com/pnpm/pnpm/issues/12794).
```yaml
overrides:
"form-data@": 4.0.6
```
The value must be an exact version. When a full resolution detects that every declared range also admits a newer version, pnpm warns that the override is stale and names the version to converge on. Previously an empty range in an override selector was undocumented and behaved like a bare (unscoped) override.
#### Patch Changes
- A `tokenHelper` set in the global pnpm `auth.ini` is no longer rejected as project-level configuration. The guard that blocks `tokenHelper` from a project `.npmrc` only treated `~/.npmrc` as a trusted source, so a helper written to `auth.ini` (for example by `pnpm config set`) failed on every command and could not even be removed with `pnpm config delete`. A `tokenHelper` in a workspace or project `.npmrc` is still rejected.
- `pnpm cache delete` now removes a package's metadata from every metadata cache directory (`metadata`, `metadata-full`, and `metadata-full-filtered`), instead of only the one the current resolution mode reads. Previously a package cached under a different mode (e.g. `metadata-full-filtered`) was left behind. Closes [#​12753](https://github.com/pnpm/pnpm/issues/12753).
- Fixed an injected workspace dependency (`injectWorkspacePackages: true`) incorrectly staying as `file:` instead of deduping back to `link:` when an unrelated, ordinary shared dependency resolved to a peer-suffixed variant for the target project's own copy but not for the injected occurrence. See [#​10433](https://github.com/pnpm/pnpm/issues/10433).
- `pnpm deploy` now supports workspaces that use catalogs.
- Fixed `pnpm deploy` with a shared lockfile so local `file:` tarball dependencies keep their package name in the generated deploy lockfile. This prevents warm-store deploys from failing with `ERR_PNPM_UNEXPECTED_PKG_CONTENT_IN_STORE` when the tarball filename includes the version.
- Options that follow `create`, `exec`, or `test` appearing as a subcommand of another command are now parsed instead of being silently treated as positional parameters. For example, `pnpm team create @​org:team --registry <url>` previously ignored the `--registry` option and sent the request to the default registry.
- `pnpm add -g`, `pnpm update -g`, `pnpm setup`, and the self-updater no longer fail with `ERR_PNPM_MISSING_TIME` when `trustPolicy: no-downgrade` or `resolutionMode: time-based` is set in the global config [#​12883](https://github.com/pnpm/pnpm/issues/12883). The decision to fetch full registry metadata now lives in one place, and the `no-downgrade` trust policy always requests full metadata (matching the self-updater), since the trust evidence it checks is missing from abbreviated metadata even on registries that include the `time` field.
- `pnpm list` and `pnpm why` no longer crash with `EMFILE: too many open files` when a project has a large number of unsaved dependencies (packages present in `node_modules` but not in the lockfile). The reads of those packages are now concurrency-limited.
- The published `pnpm` package no longer declares `dependencies` or `devDependencies`. Because the CLI bundles its runtime dependencies into `dist/node_modules`, those fields are dropped when packing, so `npm install` of the tarball no longer tries to resolve internal-only packages such as `@pnpm/test-ipc-server`. Closes [#​12955](https://github.com/pnpm/pnpm/issues/12955).
- Fixed `pnpm publish --otp` and `pnpm publish --batch --otp` to send the configured OTP to the registry.
- `pnpm publish` again sends the package's README to the registry as metadata, so registries can render it on the package page. The readme is always included in the published metadata (matching the npm CLI), while the `embed-readme` setting continues to control only whether the readme is written into the `package.json` inside the tarball. This restores the behavior that was lost when publishing became fully native. Closes [#​12966](https://github.com/pnpm/pnpm/issues/12966).
- Fixed the dependency status check wrongly reporting "up to date" when a `package.json`, `.pnpmfile.cjs`, or patch file was edited in the same second as the previous install, on filesystems that record mtimes at whole-second resolution (for example ext4 with 128-byte inodes). The optimistic repeat-install fast path and `verify-deps-before-run` compared mtimes strictly, so a same-second edit whose mtime rounded down looked unchanged and re-resolution was skipped. Such a file's whole second is now treated as possibly-modified, falling through to the content check; behavior on sub-second filesystems is unchanged.
- Retry package metadata requests when a registry or proxy returns `304 Not Modified` to an unconditional request, preventing false `ERR_PNPM_CACHE_MISSING_AFTER_304` failures [pnpm/pnpm#12882](https://github.com/pnpm/pnpm/issues/12882).
If the retry also returns `304`, report `ERR_PNPM_META_NOT_MODIFIED_WITHOUT_CACHE` instead.
- Fixed `pnpm update` removing transitive lockfile entries when `dedupePeerDependents` is disabled and the selected package is absent [pnpm/pnpm#12456](https://github.com/pnpm/pnpm/issues/12456).
- Limit modern deploy lockfiles and localized virtual stores to dependencies reachable from the selected dependency groups.
- A `tokenHelper` command is now given a 60-second time limit. A helper that hangs (deadlock, stuck I/O) is killed and reported as an error instead of leaving the command waiting forever.
- Fixed orphaned child processes on Windows when pnpm exits on an error while commands spawned by `pnpm exec` or `pnpm dlx` are still running (for example, when one project's command fails during `pnpm --recursive exec`). The PIDs of these commands are now recorded when they are spawned and their whole process trees are terminated with `taskkill` on an error exit. Previously the cleanup relied on enumerating the system process list, which is so slow on Windows that the enumeration hit its timeout and the cleanup was silently skipped [#​12406](https://github.com/pnpm/pnpm/issues/12406).
- `pnpm pack` now respects workspace-root `.npmignore` and `.gitignore` files when packing workspace packages.
<!-- 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>
</tr>
<tr>
<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>
</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://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>
<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>
</tr>
<tr>
<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>
<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>
</tr>
</tbody>
</table>
<!-- sponsors end -->
### [`v11.12.0`](https://github.com/pnpm/pnpm/releases/tag/v11.12.0): pnpm 11.12
[Compare Source](https://github.com/pnpm/pnpm/compare/v11.11.0...v11.12.0)
#### Minor Changes
- [`a897ef7`](https://github.com/pnpm/pnpm/commit/a897ef7): Custom fetchers exported from a pnpmfile can now delegate by returning a `{ delegate: <resolution> }` envelope: pnpm rewrites the package's resolution to the delegated shape and runs the built-in fetcher on it. This is the portable delegation form that also works in pacquet, where `cafs` and `fetchers` cannot be passed to the hook. Related to [pnpm/pnpm#11685](https://github.com/pnpm/pnpm/issues/11685).
#### Patch Changes
- [`2b02764`](https://github.com/pnpm/pnpm/commit/2b02764): The changed-packages filter (`--filter "...[<since>]"`) no longer allows an option-like `<since>` value (such as `--output=<path>`) to be interpreted as a git option — git now rejects it as a bad revision. The repository root is also resolved to the nearest `.git` entry, so the filter works in a git worktree checked out inside another repository's tree.
- [`43711ce`](https://github.com/pnpm/pnpm/commit/43711ce): `pnpm outdated` no longer checks the registry for dependencies that are resolved from local `link:`, `file:`, or `workspace:` references in the lockfile [#​12827](https://github.com/pnpm/pnpm/issues/12827).
- [`3c6718b`](https://github.com/pnpm/pnpm/commit/3c6718b): Fixed a deadlock in peer dependency resolution: `pnpm install` hung forever when a peer dependency cycle spanned a project's own dependencies and auto-installed peer providers, for example when installing `[email protected].3` [#​12921](https://github.com/pnpm/pnpm/issues/12921).
- [`252f15e`](https://github.com/pnpm/pnpm/commit/252f15e): Fixed peer dependency auto-install picking a version the peer range rejects. In a workspace with several projects, a package declaring a peer dependency with a semver range (for example `^1.0.0`) could get the highest version found anywhere in the workspace (for example a `2.0.0` resolved for another project) instead of a version that satisfies the range. Peers are now deduplicated onto the highest preferred version that satisfies the declared range, and when none does, the range is resolved from the registry.
Also fixed re-resolving with an existing lockfile hoisting a different peer version than a fresh install of the same manifest: root dependencies reused from the lockfile were invisible to peer hoisting, so a peer that a root dependency provides could be bound to another version.
- [`a38adda`](https://github.com/pnpm/pnpm/commit/a38adda): `pnpm self-update <version>` now installs the requested pnpm version when it matches the currently running version but is missing from the global self-update directory.
- [`6a85968`](https://github.com/pnpm/pnpm/commit/6a85968): `pnpm stage list` now stops paginating after a fail-safe cap of 1000 pages, so a misbehaving registry cannot keep the command looping forever.
- [`eee7c9a`](https://github.com/pnpm/pnpm/commit/eee7c9a): `verify-deps-before-run` no longer spawns a `pnpm install` when pnpm is executed in a directory that has no `package.json`. A mistyped command run outside a project (for example `pnpm witch 10 login`) used to crash with a confusing error from the spawned install; now it fails with the regular "no package.json found" 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>
</tr>
<tr>
<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>
</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://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>
<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>
</tr>
<tr>
<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>
<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>
</tr>
</tbody>
</table>
<!-- sponsors end -->
</details>
---
### Configuration
📅 **Schedule**: (UTC)
- Branch creation
- At any time (no schedule defined)
- 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.
🔕 **Ignore**: Close this PR and you won't be reminded about this update again.
---
- [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check this box
---
This PR has been generated by [Mend Renovate](https://github.com/renovatebot/renovate).
<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0My4xOTEuMiIsInVwZGF0ZWRJblZlciI6IjQzLjE5MS4yIiwidGFyZ2V0QnJhbmNoIjoibWFpbiIsImxhYmVscyI6W119-->
---------
Co-authored-by: Lunny Xiao <[email protected]>
Reviewed-on: https://gitea.com/gitea/docs/pulls/469
Reviewed-by: silverwind <[email protected]>
Co-authored-by: Renovate Bot <[email protected]>
This PR contains the following updates:
| Package | Change | [Age](https://docs.renovatebot.com/merge-confidence/) | [Confidence](https://docs.renovatebot.com/merge-confidence/) |
|---|---|---|---|
| [pnpm](https://pnpm.io) ([source](https://github.com/pnpm/pnpm/tree/HEAD/pnpm11/pnpm)) | [`11.10.0` → `11.11.0`](https://renovatebot.com/diffs/npm/pnpm/11.10.0/11.11.0) |  |  |
---
### Release Notes
<details>
<summary>pnpm/pnpm (pnpm)</summary>
### [`v11.11.0`](https://github.com/pnpm/pnpm/blob/HEAD/pnpm11/pnpm/CHANGELOG.md#11110)
[Compare Source](https://github.com/pnpm/pnpm/compare/v11.10.0...v11.11.0)
##### Minor Changes
- [`508b8c2`](https://github.com/pnpm/pnpm/commit/508b8c2): Added the `pnpm access` command for managing package access and visibility on the registry, supporting listing packages and collaborators, getting and setting package status and MFA requirements, and granting or revoking team access.
##### Patch Changes
- [`c70e33e`](https://github.com/pnpm/pnpm/commit/c70e33e): Allow `allowBuilds` entries for git-hosted packages to match by repository URL without pinning the resolved commit hash. This lets trusted git repositories keep running their build scripts after branch updates without approving each new commit, while package-name-only rules still do not approve git-hosted artifacts.
- [`3067e4f`](https://github.com/pnpm/pnpm/commit/3067e4f): Reduced peak memory usage during cold-cache dependency resolution. The metadata fetch is memoized for the whole resolution phase, and it was retaining each package's raw registry response body (used only to mirror the response to disk) for that entire time. The memoized cache now holds a body-less copy, so the raw body only lives as long as the call that writes the disk mirror. On large graphs that fetch full metadata (e.g. with `minimumReleaseAge` or `trustPolicy` enabled) this cuts peak RSS by roughly 30%, back in line with pnpm 10. The resolved lockfile is unchanged.
- [`51300fd`](https://github.com/pnpm/pnpm/commit/51300fd): Prevent a crafted `pnpm-lock.yaml` from writing package content outside the virtual store. A dependency path key whose name reconstructs to a path-traversal sequence (e.g. `../../../tmp/[email protected]`) is now rejected by the isolated (virtual-store) linker and the Plug'n'Play resolver map, matching the containment already applied to the hoisted linker. Under the global virtual store, a traversal in the version-derived path segment (e.g. a snapshot `version: "../../x"`) is now rejected at `formatGlobalVirtualStorePath`, the single point every global-virtual-store slot path funnels through — closing the same escape in the isolated linker, the resolver's dependency-graph builder, and the config-dependency installer.
- [`f8058eb`](https://github.com/pnpm/pnpm/commit/f8058eb): Reject symlinked `pnpm-lock.yaml` files when reading or writing the env lockfile document.
- [`9318a11`](https://github.com/pnpm/pnpm/commit/9318a11): Allow `registries` and `namedRegistries` to be configured in the global `config.yaml` file.
- [`51300fd`](https://github.com/pnpm/pnpm/commit/51300fd): Fixed a path traversal vulnerability where a dependency whose manifest `name` was a scoped path traversal (e.g. `@x/../../../<path>`) could be written outside `node_modules` to an attacker-controlled location during `pnpm install`, even with `--ignore-scripts`. The isolated linker now validates the package name before using it as a directory name, matching the existing protection in the hoisted linker.
- [`14332f0`](https://github.com/pnpm/pnpm/commit/14332f0): Fail instead of silently removing an optional dependency's locked entries from `pnpm-lock.yaml` when the registry cannot resolve it. Previously, when registry metadata lacked a version that the lockfile already pinned (for example, a mirror that had not synced a recent release yet), `pnpm install` and `pnpm dedupe` silently dropped the optional dependency's entries — emptying maps such as the platform binaries of `@napi-rs/canvas` — so the lockfile differed between machines and frozen installs on other hosts had nothing to link [#​12853](https://github.com/pnpm/pnpm/issues/12853).
- [`fecfe83`](https://github.com/pnpm/pnpm/commit/fecfe83): Fixed peer dependency resolution with `autoInstallPeers` when a workspace package depends on a version of a package that a transitive dependency's self-contained closure also provides for itself. The peer providers that are attached to the root project for reuse are no longer peer-resolved a second time in the root context, so packages inside such a closure no longer get their peers bound to the root project's incompatible version [#​4993](https://github.com/pnpm/pnpm/issues/4993).
- [`5a4daec`](https://github.com/pnpm/pnpm/commit/5a4daec): `${...}` environment-variable placeholders in the `httpProxy`, `httpsProxy`, `noProxy`, `proxy`, and `noproxy` settings are no longer expanded when these settings come from a project's `pnpm-workspace.yaml`. They now receive the same protection already applied to `registry`, `namedRegistries`, and `pnprServer`.
- [`d1da02e`](https://github.com/pnpm/pnpm/commit/d1da02e): `pnpm publish` no longer prints credentials when the target registry is configured with inline `user:pass@` credentials (e.g. `registry=https://user:[email protected]/`). They are now redacted both from the "publishing to registry" line and from the OIDC (trusted publishing) failure messages.
- [`dcfc611`](https://github.com/pnpm/pnpm/commit/dcfc611): `pnpm self-update` now honors `trustPolicy=no-downgrade`. It resolves the target pnpm version against full registry metadata, so it refuses to switch to a version whose supply-chain trust evidence is weaker than an earlier-published one, the same way a regular install does.
- [`a8ad82d`](https://github.com/pnpm/pnpm/commit/a8ad82d): Register the `pn` alias in generated shell completion scripts.
- [`25bd5c3`](https://github.com/pnpm/pnpm/commit/25bd5c3): Fixed standalone installer downgrades from pnpm v12 to v11.
- [`23996e9`](https://github.com/pnpm/pnpm/commit/23996e9): `pnpm runtime set <name> <version>` now validates its arguments: the name must be `node`, `deno`, or `bun`, and the version must not contain a comma. Previously these were interpolated straight into a `pnpm add` selector, where an unsupported name or a comma (e.g. `node 22,is-positive`) could be misread as a list of packages or a local directory and install unintended packages or bins.
</details>
---
This PR has been generated by [Mend Renovate](https://github.com/renovatebot/renovate).
<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0My4xOTEuMiIsInVwZGF0ZWRJblZlciI6IjQzLjE5MS4yIiwidGFyZ2V0QnJhbmNoIjoibWFpbiIsImxhYmVscyI6W119-->Reviewed-on: https://gitea.com/gitea/docs/pulls/465
Reviewed-by: Lunny Xiao <[email protected]>
Co-authored-by: Renovate Bot <[email protected]>
This PR contains the following updates:
| Package | Change | [Age](https://docs.renovatebot.com/merge-confidence/) | [Confidence](https://docs.renovatebot.com/merge-confidence/) |
|---|---|---|---|
| [pnpm](https://pnpm.io) ([source](https://github.com/pnpm/pnpm/tree/HEAD/pnpm11/pnpm)) | [`11.9.0` → `11.10.0`](https://renovatebot.com/diffs/npm/pnpm/11.9.0/11.10.0) |  |  |
---
### Release Notes
<details>
<summary>pnpm/pnpm (pnpm)</summary>
### [`v11.10.0`](https://github.com/pnpm/pnpm/blob/HEAD/pnpm11/pnpm/CHANGELOG.md#11100)
[Compare Source](https://github.com/pnpm/pnpm/compare/v11.9.0...v11.10.0)
##### Minor Changes
- [`e2e3c81`](https://github.com/pnpm/pnpm/commit/e2e3c81): Added the `issues` command as an alias of `bugs`, so `pnpm issues` opens the package's bug tracker URL in the browser.
- [`8491f8e`](https://github.com/pnpm/pnpm/commit/8491f8e): Added the `prefix` command which prints the current package prefix directory (or global prefix directory if `-g` / `--global` is used).
- [`3425e80`](https://github.com/pnpm/pnpm/commit/3425e80): Added an `_auth` setting for configuring registry authentication as a single structured (URL-keyed) value. It can be set in the **global** pnpm config (`config.yaml`) or, for CI, via the `pnpm_config__auth` environment variable. The env form sidesteps the GitHub Actions / bash / zsh limitation that broke the existing `pnpm_config_//host/:_authToken=…` form (env var names containing `/`, `:`, or `.` are silently dropped). Closes [#​12314](https://github.com/pnpm/pnpm/issues/12314).
The value is keyed by registry URL so each secret is explicitly bound to the host that may receive it. Registry URL keys must use `http` or `https` and must not include credentials, query strings, or fragments:
```sh
export pnpm_config__auth='{"https://registry.npmjs.org":{"@​":{"authToken":"npm-token"},"@​org":{"authToken":"org-token"}}}'
```
The equivalent in the global `config.yaml`:
```yaml
_auth:
https://registry.npmjs.org:
"@​":
authToken: npm-token
"@​org":
authToken: org-token
```
Within each registry URL, `@` means registry-wide/default credentials and package scopes like `@org` bind credentials to that scope on the same host. The only supported credential field is `authToken` (maps to `_authToken` / bearer auth); the deprecated `basicAuth` / `username` + `password` forms are intentionally not accepted here.
Each entry also infers a trusted registry route: `@` routes the default registry (and `pnpm add <pkg>` resolves there), and `@org` routes that scope. Because the credential and destination host arrive in one trusted value, repo-controlled `pnpm-workspace.yaml` or project `.npmrc` cannot redirect the token to a different host. `_auth` is honored **only** from the env var and the global config — it is ignored in a project `pnpm-workspace.yaml` / `.npmrc`, so repo-controlled config can never supply registry auth. Precedence: CLI flags (`--registry`, `--@​scope:registry`) > `pnpm_config__auth` > global `config.yaml` `_auth` > `pnpm-workspace.yaml`.
Both `pnpm_config__auth` (lowercase, documented form) and `PNPM_CONFIG__AUTH` (all-caps, the shell convention some CI runners apply) are honored. If both are set, lowercase wins unless it is empty, in which case uppercase is used. The env var wins over the global `config.yaml` `_auth` on a conflicting key. `tokenHelper` is not supported in `_auth`. Parsing is strict: a malformed value (bad JSON, wrong shape, invalid registry URL or scope, an unsupported credential field) fails fast with an error rather than being silently dropped.
**Pacquet parity note:** the pacquet (Rust) port supports the same single credential field as the TS CLI: `authToken`.
- [`a33eeec`](https://github.com/pnpm/pnpm/commit/a33eeec): `pnpm self-update` and `packageManager` version-switching can now install and link pnpm v12 (the Rust port), published with equal content under both the `pnpm` and `@pnpm/exe` names on the `next-12` dist-tag. Its native binaries ship as `@pnpm/exe.<platform>-<arch>` packages, which pnpm's built-in installer links directly — no Node.js launcher, so the command pays no Node startup cost. v12 is initialized exactly like `@pnpm/exe`, including per-platform global-virtual-store hashing. From v12 onward the install converges on the unscoped `pnpm` package (the Rust exe) — even when updating from the SEA `@pnpm/exe` build.
- [`1dd12bd`](https://github.com/pnpm/pnpm/commit/1dd12bd): When resolving through a pnpr install-accelerator server, pnpm no longer forwards its own upstream registry credentials in the resolve request. Only the `Authorization` header identifying the caller to pnpr is sent. The pnpr server now selects upstream credentials from its own route policy (operator-configured upstream credential aliases), so private dependencies resolve through a pnpr-managed alias the caller is authorized to use, rather than by sending the client's registry tokens to the server.
- [`1e81761`](https://github.com/pnpm/pnpm/commit/1e81761): Expose web authentication `authUrl` and `doneUrl` in JSON error output when OTP is required in a non-interactive terminal [#​12724](https://github.com/pnpm/pnpm/issues/12724).
##### Patch Changes
- [`2f389d6`](https://github.com/pnpm/pnpm/commit/2f389d6): Added the Node.js release team's new signing key (Stewart X Addison, `655F3B5C1FB3FA8D1A0CA6BDE4A7D232B936D2FD`) to the embedded Node.js release keys, so runtimes whose `SHASUMS256.txt` is signed by the new releaser verify successfully.
- [`acbdb94`](https://github.com/pnpm/pnpm/commit/acbdb94): Fixed shell tab completion not suggesting workspaces after the `-F` alias for `--filter` option.
- [`dcabb78`](https://github.com/pnpm/pnpm/commit/dcabb78): Fixed `pnpm up -r <pkg>` bumping unrelated packages that have open semver ranges. Previously, any update mutation nullified the lockfile-derived `preferredVersions` globally, so packages with `^x.y.z` ranges could re-resolve to newer compatible versions even though the user only asked to update a specific package. The install layer now always seeds `preferredVersions` from the lockfile, and caller-supplied preferred versions (such as the vulnerability penalties of `pnpm audit --fix`) layer on top of the seed instead of replacing it. The targeted package still bumps: the per-resolve `updateRequested` flag makes the resolver ignore the target's own lockfile pins.
Closes [#​10662](https://github.com/pnpm/pnpm/issues/10662).
- [`d539172`](https://github.com/pnpm/pnpm/commit/d539172): Fixed pnpm pack and pnpm publish failing when prepack generates files that are included in the package and postpack cleans them up.
- [`be6505a`](https://github.com/pnpm/pnpm/commit/be6505a): Hardened global package management:
- On Windows, removing or updating a global package now also cleans up the `node.exe` flavor of a bin, so a stale `node.exe` no longer survives on `PATH` after uninstall, and a new global install no longer silently overwrites an existing `node.exe`.
- `pnpm add -g pnpm@<version>` (and `@pnpm/exe@<version>`) is now rejected like the bare `pnpm` form, pointing to `pnpm self-update`.
- Dependency aliases read from a global package's manifest are validated before being joined onto `node_modules` paths, preventing a tampered manifest from escaping the install directory.
- Each global install group is created in its own freshly-made directory (no longer reusing a colliding or pre-existing path).
- Removing or updating a global package no longer unlinks a bin that belongs to a different globally installed package.
- [`25c7388`](https://github.com/pnpm/pnpm/commit/25c7388): pnpm now rejects `jsr:` specifiers whose package name is not a valid npm package name — an empty scope or name (e.g. `jsr:@​scope/`), path separators inside the name, or any other shape `validate-npm-package-name` rejects — with `ERR_PNPM_INVALID_JSR_PACKAGE_NAME` instead of silently converting them into a malformed `@jsr/...` npm package name.
- [`25c7388`](https://github.com/pnpm/pnpm/commit/25c7388): pnpm now rejects named-registry specifiers (e.g. `gh:`) whose package name is not a valid npm package name — an empty scope (e.g. `gh:@​/bar`), path separators inside the name (e.g. `gh:@​scope/../name`), or any other shape `validate-npm-package-name` rejects — with `ERR_PNPM_INVALID_NAMED_REGISTRY_PACKAGE_NAME` instead of passing the name through to registry URLs and metadata cache file paths.
- [`96da7c5`](https://github.com/pnpm/pnpm/commit/96da7c5): node-gyp's `gyp_main.py` and `gyp` entrypoints are now packed with the executable bit in the `pnpm` and `@pnpm/exe` tarballs. Without it, building native addons from source could fail with a permission error.
- [`99982b9`](https://github.com/pnpm/pnpm/commit/99982b9): Sped up resolution and reduced memory use against registries that ignore npm's abbreviated metadata format and always return the full package document (for example, Azure DevOps Artifacts). pnpm now strips such documents down to the abbreviated field set before caching them. Resolution output is unchanged, and registries that honor the abbreviated format (such as the npm registry) pay no extra cost.
- [`11a7fdd`](https://github.com/pnpm/pnpm/commit/11a7fdd): Sped up offline and `--prefer-offline` resolution on large workspaces (e.g. `pnpm dedupe --offline`, `pnpm install --offline`). Package metadata loaded from the local cache is now kept in memory, so each package's metadata is parsed once per command instead of once per dependent that references it.
- [`2c7369d`](https://github.com/pnpm/pnpm/commit/2c7369d): `pnpm pack-app` now rejects `--entry` / `pnpm.app.entry` and `--output-dir` / `pnpm.app.outputDir` values that are absolute paths or escape the project directory via `..` (or a symlink that resolves outside it), and refuses to write the produced executable when its target path already exists as a symlink (or other non-regular file). This prevents a repository-controlled `package.json` from embedding host files (such as an SSH key) into the produced executable, writing build artifacts outside the project, or overwriting an arbitrary file through a committed symlink. The new error codes are `ERR_PNPM_PACK_APP_ENTRY_OUTSIDE_PROJECT`, `ERR_PNPM_PACK_APP_OUTPUT_DIR_OUTSIDE_PROJECT`, and `ERR_PNPM_PACK_APP_OUTPUT_FILE_NOT_REGULAR`.
When ad-hoc signing macOS targets, `pnpm pack-app` now runs the system `codesign` by absolute path and resolves `ldid` to a location outside the project, so a repository-controlled `node_modules/.bin` on `PATH` cannot hijack the signer.
- [`ce5d5a5`](https://github.com/pnpm/pnpm/commit/ce5d5a5): Relative paths in `patchedDependencies` are now resolved against the lockfile directory when computing patch file hashes, so running `pnpm install` from a subdirectory no longer fails with `ENOENT` looking for the patch file in the wrong location [#​12762](https://github.com/pnpm/pnpm/pull/12762).
- [`ebb4096`](https://github.com/pnpm/pnpm/commit/ebb4096): `pnpm peers` no longer reports a conflict for a missing peer dependency that is ignored via `pnpm.peerDependencyRules.ignoreMissing`.
- [`dcabb78`](https://github.com/pnpm/pnpm/commit/dcabb78): Fixed a prototype-pollution hazard when seeding preferred versions: a dependency named `__proto__` in a manifest or in `pnpm-lock.yaml` could write through `Object.prototype` (or crash the install) while the preferred-versions map was being built. The maps are now null-prototype objects, so crafted package names land as plain keys.
- [`f38e696`](https://github.com/pnpm/pnpm/commit/f38e696): Hardened `pnpm deploy --force` so it refuses unsafe deploy targets such as workspace roots, parent directories, out-of-workspace paths, and symlinked target parents.
- [`806c3ec`](https://github.com/pnpm/pnpm/commit/806c3ec): pnpm no longer warns about ignored project-level auth settings when `PNPM_CONFIG_NPMRC_AUTH_FILE` points at the project `.npmrc` — setting it to that file is an explicit opt-in to trusting it, so auth env variables in it are expanded [pnpm/pnpm#12480](https://github.com/pnpm/pnpm/issues/12480).
- [`991405e`](https://github.com/pnpm/pnpm/commit/991405e): Restore differential rendering (`ansi-diff`) to fix duplicated output lines introduced by [#​12351](https://github.com/pnpm/pnpm/issues/12351).
- [`c121235`](https://github.com/pnpm/pnpm/commit/c121235): Fixed the topological order of `--filter`ed commands (`pnpm run`, `pnpm exec`, `pnpm publish`, `pnpm pack`, `pnpm rebuild`) when the selected projects depend on each other only transitively through projects that were not selected. Previously such selected projects could run concurrently or in the wrong order; now a project always runs after the selected projects it transitively depends on, while projects without a real dependency relationship still run concurrently. This now also holds for prod-only filters (`--filter-prod`), which resolve order through the production dependency graph so transitive production dependencies are respected without pulling back the dev dependencies the filter drops, and for selections that mix `--filter` with `--filter-prod` [#​8335](https://github.com/pnpm/pnpm/issues/8335).
- [`d539172`](https://github.com/pnpm/pnpm/commit/d539172): `pnpm pack` and `pnpm publish` no longer follow a symlinked workspace `LICENSE` file when injecting it into a package that has no license of its own. Following the symlink could pack bytes from outside the workspace into the published tarball.
- [`dcabb78`](https://github.com/pnpm/pnpm/commit/dcabb78): Fixed `pnpm up <pkg>` producing a different result than a fresh install of the same manifests would. The resolver now distinguishes `updateRequested` (true only for packages that match the user's update target) from the broader `update` flag, and for the targeted package ignores only its own lockfile-derived preferred-version pins — so the target re-resolves exactly as if its lockfile entries were deleted and `pnpm install` ran. Preferred versions a fresh install applies (manifest pins, versions propagated down the dependency chain, and the vulnerability-avoidance penalties of `pnpm audit --fix`) stay in effect, so an update never installs duplicate versions that a reinstall from scratch would not reproduce. When a preferred version holds the update target below the newest version its range admits, pnpm now prints a warning explaining that reaching the newer version everywhere requires an override.
- [`dcabb78`](https://github.com/pnpm/pnpm/commit/dcabb78): `pnpm update <dep>@​<version>` now prints a warning when `<dep>` is only present as a transitive dependency: the requested version cannot be applied there (updates resolve the target the way a fresh install would), and the warning recommends adding the version to `pnpm.overrides` instead, which is the mechanism that does pin transitive dependencies. Closes [#​12744](https://github.com/pnpm/pnpm/issues/12744).
- [`a6c4d5f`](https://github.com/pnpm/pnpm/commit/a6c4d5f): When a dependency cannot be found in the registry (404) or the registry has no matching version, and a workspace project with the same name exists only at non-matching versions, the error now reports the available workspace versions (`ERR_PNPM_NO_MATCHING_VERSION_INSIDE_WORKSPACE`) instead of the raw registry failure [pnpm/pnpm#1379](https://github.com/pnpm/pnpm/issues/1379). Other registry failures (authorization, network, server errors) still propagate unchanged. The pacquet (Rust) resolver applies the same behavior.
</details>
---------
Co-authored-by: Lunny Xiao <[email protected]>
Reviewed-on: https://gitea.com/gitea/docs/pulls/460
Reviewed-by: Lunny Xiao <[email protected]>
Co-authored-by: Renovate Bot <[email protected]>
This PR contains the following updates:
| Package | Change | [Age](https://docs.renovatebot.com/merge-confidence/) | [Confidence](https://docs.renovatebot.com/merge-confidence/) |
|---|---|---|---|
| [pnpm](https://pnpm.io) ([source](https://github.com/pnpm/pnpm/tree/HEAD/pnpm)) | [`11.7.0` → `11.9.0`](https://renovatebot.com/diffs/npm/pnpm/11.7.0/11.9.0) |  |  |
---
### Release Notes
<details>
<summary>pnpm/pnpm (pnpm)</summary>
### [`v11.9.0`](https://github.com/pnpm/pnpm/releases/tag/v11.9.0): pnpm 11.9
[Compare Source](https://github.com/pnpm/pnpm/compare/v11.8.0...v11.9.0)
#### Minor Changes
- [`bae694f`](https://github.com/pnpm/pnpm/commit/bae694f): Some registries generate tarballs on-demand and cannot provide an integrity checksum in their package metadata. In that case pnpm now computes the integrity from the downloaded tarball and stores it in the lockfile, so the entry is verifiable on subsequent installs instead of being written without an integrity (which would fail the next install). This also applies to `--lockfile-only`: the tarball is downloaded so its integrity can be computed. A lockfile entry that is still missing its integrity is rejected as a `ERR_PNPM_MISSING_TARBALL_INTEGRITY` lockfile verification violation (the install fails closed) rather than being silently re-fetched.
- [`6c35a43`](https://github.com/pnpm/pnpm/commit/6c35a43): Added `--exclude-peers` to `pnpm sbom`. With `auto-install-peers` (the default), peer dependencies resolve into the lockfile and are otherwise indistinguishable from the package's own dependencies. The flag drops peer dependencies (and any transitive subtree reachable only through them) from the SBOM. CycloneDX 1.7 has no scope or relationship that expresses "consumer-provided peer", so omission is the only spec-clean handling. The flag name matches `pnpm list --exclude-peers`; note the SBOM flag prunes a peer's exclusive subtree, which is stricter than `pnpm list` (which only hides leaf peers).
#### Patch Changes
- [`25a829e`](https://github.com/pnpm/pnpm/commit/25a829e): `pnpm audit --fix` now writes a single combined `minimumReleaseAgeExclude` entry per package (e.g. `[email protected].1 || 0.21.1`) instead of one entry per version, matching the format documented for the setting. Existing per-version entries in `pnpm-workspace.yaml` are merged into the combined form rather than left as duplicates. Installs that auto-collect immature versions into `minimumReleaseAgeExclude` now report the same combined entries, so the "Added N entries" message matches what is written to the manifest [#​12534](https://github.com/pnpm/pnpm/issues/12534).
- [`1cbb5f2`](https://github.com/pnpm/pnpm/commit/1cbb5f2): Fixed non-deterministic peer resolution that could add or remove an optional transitive peer — for example `@babel/core`, reached through `styled-jsx` — from a package's peer-dependency suffix across otherwise identical installs, churning the lockfile and causing intermittent `pnpm dedupe --check` failures in CI. When a package's children are resolved by one occurrence (the "owner") and reused by a deeper consumer, whether that consumer inherited the owner's missing peers depended on whether the owner's resolution had finished yet — a race under concurrent resolution. The decision is now a function of the dependency graph's structure rather than resolution-completion order.
- [`d577eea`](https://github.com/pnpm/pnpm/commit/d577eea): Fixed a Windows flakiness in `pnpm dlx` where a failed install could surface a spurious `EBUSY: resource busy or locked` error. The cleanup of a partially-populated dlx cache is now best-effort with retries and no longer masks the original error.
- [`ec7cf70`](https://github.com/pnpm/pnpm/commit/ec7cf70): Shortened the `pnpm dlx` cache path so deep dependency trees no longer overflow Windows' `MAX_PATH`, which could make a dependency's lifecycle script fail with `spawn cmd.exe ENOENT`.
- [`05b95ab`](https://github.com/pnpm/pnpm/commit/05b95ab): Fixed `pnpm` hanging (and crashing with an unhandled promise rejection) when a non-retryable network error such as `SELF_SIGNED_CERT_IN_CHAIN` occurs while fetching from a registry. The error is now rejected through the returned promise instead of being thrown inside the detached retry callback.
- [`d3f68e2`](https://github.com/pnpm/pnpm/commit/d3f68e2): Fix a `pnpm audit` performance regression on lockfiles that contain dependency cycles. The reachable-vulnerability pruning added in pnpm 11.5.1 only memoized acyclic subtrees, so any node whose subtree touched a cycle — together with all of its ancestors — was recomputed on every query, making the path walk quadratic. Reachability is now computed once per node using Tarjan's strongly-connected-components algorithm, so cyclic graphs are handled in linear time [#​12212](https://github.com/pnpm/pnpm/issues/12212).
The audit path walk also no longer recurses, so a deeply nested dependency graph can no longer overflow the call stack, and the install path to each finding is tracked without per-node copying, keeping memory linear in the graph depth.
- [`322f88f`](https://github.com/pnpm/pnpm/commit/322f88f): Fix failed optional dependency updates so they don't rewrite unrelated dependency specs [#​11267](https://github.com/pnpm/pnpm/issues/11267).
- [`1488db1`](https://github.com/pnpm/pnpm/commit/1488db1): When `enableGlobalVirtualStore` is toggled on for a project that was previously installed without it, stale hoisted symlinks under `node_modules/.pnpm/node_modules` are now replaced instead of being left pointing at the old per-project virtual store location [#​9739](https://github.com/pnpm/pnpm/issues/9739).
- [`6545793`](https://github.com/pnpm/pnpm/commit/6545793): Fixed `pnpm install --ignore-workspace` overwriting the `allowBuilds` map in `pnpm-workspace.yaml`. The ignored builds of a package with a build script were auto-populated into `allowBuilds` even though `--ignore-workspace` was passed, clobbering committed `true`/`false` values with the `set this to true or false` placeholder [#​12469](https://github.com/pnpm/pnpm/issues/12469).
- [`fbdc0eb`](https://github.com/pnpm/pnpm/commit/fbdc0eb): Fixed `minimumReleaseAgeExclude` and `trustPolicyExclude` so multiple exact-version entries for the same package behave the same as a single `||` disjunction entry. Previously only the first matching rule's versions were honored, so a config like `[[email protected], [email protected]]` could still flag `[email protected]` as violating `minimumReleaseAge`, while `[[email protected] || 2.5.6]` worked as expected [#​12463](https://github.com/pnpm/pnpm/issues/12463).
- [`fa7004b`](https://github.com/pnpm/pnpm/commit/fa7004b): The in-memory package metadata cache is now populated on the exact-version disk fast path, so repeated resolutions of the same package within one install no longer re-read and re-parse the on-disk metadata. In large monorepos this brings the time for adding a new package down from minutes to seconds. The in-memory cache key now also includes the registry, so a package of the same name served by two different registries in a single install can no longer share a cache slot and resolve the wrong tarball.
- [`0a154b1`](https://github.com/pnpm/pnpm/commit/0a154b1): Fixed `pnpm patch` dropping the package name (and leaking internal option fields) when the patched dependency resolves to a single git-hosted version.
- [`4d3fe4b`](https://github.com/pnpm/pnpm/commit/4d3fe4b): The pnpr resolver endpoints moved under the reserved `/-/pnpr` namespace: `POST /v1/resolve` is now `POST /-/pnpr/v0/resolve` and `POST /v1/verify-lockfile` is now `POST /-/pnpr/v0/verify-lockfile`. The capability handshake at `GET /-/pnpr` advertises protocol version `0` to match. This keeps every pnpr-proprietary route in npm's reserved namespace, so it can never collide with a package path.
- [`0ec878d`](https://github.com/pnpm/pnpm/commit/0ec878d): Removing a runtime dependency now removes the matching `devEngines.runtime` or `engines.runtime` entry that was materialized from it. Blank runtime selectors are normalized to `latest`.
- [`17e7f2c`](https://github.com/pnpm/pnpm/commit/17e7f2c): `pnpm sbom` now emits a CycloneDX `issue-tracker` external reference for components (and the root) whose `package.json` declares a `bugs` URL. Email-only `bugs` entries are skipped, since the reference requires a URL.
- [`a84d2a1`](https://github.com/pnpm/pnpm/commit/a84d2a1): Add `@pnpm/resolving.tarball-url`, which builds and recognizes the canonical npm tarball URL of a package. It vendors `getNpmTarballUrl` (previously the external `get-npm-tarball-url` package) and adds `isCanonicalRegistryTarballUrl`, the predicate the lockfile writer uses to decide whether a tarball URL is derivable from name+version+registry (and can therefore be omitted from `pnpm-lock.yaml`).
Exposing `isCanonicalRegistryTarballUrl` lets a custom resolver (pnpmfile `resolvers`) fronting a proxy that serves tarballs on a non-canonical path (e.g. an ephemeral `localhost:<port>`) rewrite the resolved tarball to the canonical form, so nothing host-specific is persisted to the lockfile. Previously this logic was private to `@pnpm/lockfile.utils`.
Two correctness fixes are included while consolidating the logic: the scoped-package unescape now handles uppercase `%2F` as well as `%2f` (percent-encoding is case-insensitive), and protocol-insensitive comparison strips only a leading `http(s)://` scheme instead of splitting on the first `://` (which could truncate URLs containing a later `://`).
- [`852d537`](https://github.com/pnpm/pnpm/commit/852d537): Lockfile verification no longer reports a registry metadata fetch failure (for example a `403`/`401` on a private registry, or a network error) as `ERR_PNPM_TARBALL_URL_MISMATCH`. When the registry can't be reached to verify an entry, the install now aborts with the registry's own fetch error (such as `ERR_PNPM_FETCH_403`, which already explains the authentication situation) instead of mislabeling a transport failure as lockfile tampering. Registry fetch errors no longer leak basic-auth credentials embedded in the registry URL (`https://user:pass@host/`) into their message.
<!-- 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>
</tr>
<tr>
<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>
</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://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>
<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>
</tr>
<tr>
<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>
<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>
</tr>
</tbody>
</table>
<!-- sponsors end -->
### [`v11.8.0`](https://github.com/pnpm/pnpm/releases/tag/v11.8.0): pnpm 11.8
[Compare Source](https://github.com/pnpm/pnpm/compare/v11.7.0...v11.8.0)
#### Minor Changes
- [`c112b61`](https://github.com/pnpm/pnpm/commit/c112b61): Added a `--dry-run` option to `pnpm install`. It runs a full dependency resolution and reports what an install would change, but writes nothing to disk (no lockfile, no `node_modules`) and always exits with code 0. This mirrors the preview semantics of `npm install --dry-run` [#​7340](https://github.com/pnpm/pnpm/issues/7340).
- [`179ebc4`](https://github.com/pnpm/pnpm/commit/179ebc4): `pnpm run --no-bail` now exits with a non-zero exit code when any of the executed scripts fail, while still running every matched script to completion. This makes the exit-code behavior of `--no-bail` consistent between recursive and non-recursive runs (recursive runs already failed at the end). Previously, a non-recursive `pnpm run --no-bail` always exited with code 0, even when a script failed [#​8013](https://github.com/pnpm/pnpm/issues/8013).
- [`0474a9c`](https://github.com/pnpm/pnpm/commit/0474a9c): Added support for generating Node.js package maps at `node_modules/.package-map.json` during isolated and hoisted installs. Added the `node-experimental-package-map` setting to inject the generated map into pnpm-managed Node.js script environments, and the `node-package-map-type` setting to choose between `standard` and `loose` package maps.
- [`dcededc`](https://github.com/pnpm/pnpm/commit/dcededc): `pnpm sbom` now marks components reachable only through `devDependencies` with CycloneDX `scope: "excluded"` and the `cdx:npm:package:development` property. The `excluded` scope documents "component usage for test and other non-runtime purposes", which matches the semantics of a devDependency; the property is the CycloneDX npm-taxonomy marker emitted by `@cyclonedx/cyclonedx-npm`, so both modern (scope) and existing (property) consumers are covered. Components reachable at runtime (including installed `optionalDependencies`) omit `scope` and default to `required`.
- [`1495cb0`](https://github.com/pnpm/pnpm/commit/1495cb0): Added per-package SBOM generation with `--out` and `--split` flags. Use `--out out/%s.cdx.json` to write one SBOM per workspace package to individual files, or `--split` for NDJSON output to stdout. When `--filter` selects a single package, the SBOM root component now uses that package's metadata. Workspace inter-dependencies (`workspace:` protocol) and their transitive dependencies are included. Author, repository, and license fall back to the root manifest when the package doesn't define them.
- [`293921a`](https://github.com/pnpm/pnpm/commit/293921a): feat(view): support searching project manifest upward when package name is omitted
When running `pnpm view` without a package name, the command now searches
upward for the nearest project manifest (`package.json`, `package.yaml`, or `package.json5`) and uses its `name` field.
If the manifest exists but lacks a `name` field, an error is thrown.
This change also replaces the `find-up` dependency with `empathic` for
improved performance and consistency across workspace tools.
#### Patch Changes
- [`29ab905`](https://github.com/pnpm/pnpm/commit/29ab905): Fixed `pnpm update` overriding the version range policy of a named catalog whose name parses as a version (e.g. `catalog:express4-21`). The `catalog:` reference carries no pinning of its own, so the prefix from the catalog entry (such as `~`) is now preserved instead of being widened to `^` [#​10321](https://github.com/pnpm/pnpm/issues/10321).
- [`bee4bf4`](https://github.com/pnpm/pnpm/commit/bee4bf4): Security: validate config dependency names and versions from the env lockfile (`pnpm-lock.yaml`) before using them to build filesystem paths. A committed lockfile with a traversal-shaped `configDependencies` name (such as `../../PWNED`) or version (such as `../../../PWNED`) could previously cause `pnpm install` to create symlinks or write package files outside `node_modules/.pnpm-config` and the store. Names must now be valid npm package names and versions must be exact semver versions; the same validation is applied to optional subdependencies of config dependencies, and to the legacy workspace-manifest format before any lockfile is written. See [GHSA-qrv3-253h-g69c](https://github.com/pnpm/pnpm/security/advisories/GHSA-qrv3-253h-g69c).
- [`96bdd57`](https://github.com/pnpm/pnpm/commit/96bdd57): Fix `link:` workspace protocol switching to `file:` after `pnpm rm` is run from inside a workspace package whose target workspace dependency has its own dependencies, when `injectWorkspacePackages: true` is set. Follow-up to [#​10575](https://github.com/pnpm/pnpm/pull/10575), which fixed the same symptom for workspace packages without dependencies.
- [`302a2f7`](https://github.com/pnpm/pnpm/commit/302a2f7): No longer warn about using both `packageManager` and `devEngines.packageManager` when the two fields pin the same package manager at the same version with the same integrity hash (e.g. both `[email protected]+sha512.…`). Previously the hash was stripped from the legacy `packageManager` field but not from `devEngines.packageManager`, so even identical specifications looked like a mismatch [#​12028](https://github.com/pnpm/pnpm/issues/12028).
The warning still fires on any genuine divergence, and several cases now state the specific reason instead of a single generic message: a different package manager, a different version, or contradictory integrity hashes for the same version.
- [`3f0fb21`](https://github.com/pnpm/pnpm/commit/3f0fb21): Fixed the progress line showing leftover characters from external processes that write to the terminal between progress updates (e.g. an SSH passphrase prompt would leave a fragment like `added 0sa':`). The interactive reporter now redraws each frame in place, erasing to the end of the display before reprinting, so any such remnants are cleared [#​12350](https://github.com/pnpm/pnpm/issues/12350).
- [`564619f`](https://github.com/pnpm/pnpm/commit/564619f): Fixed `pnpm approve-builds` reporting "no packages awaiting approval" when a build-script dependency whose approval was revoked (e.g. after `git stash` drops the `allowBuilds` from `pnpm-workspace.yaml`) is re-added. The revoked packages are now correctly recorded in `.modules.yaml` so `approve-builds` can find them. [#​12221](https://github.com/pnpm/pnpm/issues/12221)
- [`3d1fd20`](https://github.com/pnpm/pnpm/commit/3d1fd20): Skip the redundant "target bin directory already contains an exe called node" warning on Windows when the existing `node.exe` already matches the target (same hard link or identical content) [pnpm/pnpm#12203](https://github.com/pnpm/pnpm/issues/12203).
- [`1b02b47`](https://github.com/pnpm/pnpm/commit/1b02b47): Fix macOS Gatekeeper blocking native binaries (`.node`, `.dylib`, `.so`) by removing the `com.apple.quarantine` extended attribute after importing them from the store.
When pnpm imports files from its content-addressable store into `node_modules`, macOS preserves extended attributes, including `com.apple.quarantine`. If this xattr is present on a store blob (e.g. it was first written under a Gatekeeper-enabled app such as a Git client), it propagates to `node_modules`, and Gatekeeper blocks the native binary from loading even though pnpm already verified the file's integrity against the lockfile.
After importing a package, pnpm now strips `com.apple.quarantine` from its native binaries, matching Homebrew's behaviour of dropping quarantine from verified downloads. The cleanup is macOS-only, runs in a single batched `xattr` call per package, is restricted to native binaries (other files are untouched), and is non-fatal (it logs a warning on unexpected errors).
Fixes [#​11056](https://github.com/pnpm/pnpm/issues/11056)
- [`61969fb`](https://github.com/pnpm/pnpm/commit/61969fb): Fix `pnpm install` with `optimisticRepeatInstall` incorrectly reporting `Already up to date` when `pnpm-lock.yaml` changed but project manifests did not. This affected workflows such as checking out or restoring only the lockfile [#​12100](https://github.com/pnpm/pnpm/issues/12100).
Also fixes `checkDepsStatus` to use the correct lockfile path when `useGitBranchLockfile` is enabled, so the optimistic fast-path and lockfile modification detection work with `pnpm-lock.<branch>.yaml` files instead of always stat'ing `pnpm-lock.yaml`. Merge-conflict detection now reads the resolved lockfile name as well, and with `mergeGitBranchLockfiles` enabled every `pnpm-lock.*.yaml` is scanned for modifications and conflicts. The git branch is now resolved by reading `.git/HEAD` directly (no process spawn) and uses the workspace directory rather than `process.cwd()`.
- [`5c12968`](https://github.com/pnpm/pnpm/commit/5c12968): Fix recursive updates of transitive dependencies when the update command mixes transitive dependency patterns with direct dependency selectors. For example, `pnpm up -r "@​babel/core" uuid` now updates matching transitive `@babel/core` dependencies even when `uuid` is a direct dependency selector [#​12103](https://github.com/pnpm/pnpm/issues/12103).
- [`9d79ba1`](https://github.com/pnpm/pnpm/commit/9d79ba1): Register the `pnpm update --no-save` flag in the CLI help and option parser.
- [`0474a9c`](https://github.com/pnpm/pnpm/commit/0474a9c): Fixed `pnpm import` for Yarn v2 lockfiles when `js-yaml` v4 is installed.
- [`9e0c375`](https://github.com/pnpm/pnpm/commit/9e0c375): Fixed `pnpm install` repeatedly prompting to remove and reinstall `node_modules` in a workspace package when `enableGlobalVirtualStore` is enabled. The post-install build step recorded a per-project `node_modules/.pnpm` virtual store directory in `node_modules/.modules.yaml`, overwriting the global `<storeDir>/links` value the install step had written. The next install then detected a virtual-store mismatch (`ERR_PNPM_UNEXPECTED_VIRTUAL_STORE`). The build step now derives the same global virtual store directory as the install step [#​12307](https://github.com/pnpm/pnpm/issues/12307).
- [`223d060`](https://github.com/pnpm/pnpm/commit/223d060): Document the `--cpu`, `--os` and `--libc` flags in the output of `pnpm install --help`. These flags were already supported but were only documented on the website [#​12359](https://github.com/pnpm/pnpm/issues/12359).
- [`e85aea2`](https://github.com/pnpm/pnpm/commit/e85aea2): Avoid reading `README.md` from disk when publishing if the publish manifest already provides a `readme` field. The README is now only read lazily, inside `createExportableManifest`, when it is actually needed.
- [`3188ae7`](https://github.com/pnpm/pnpm/commit/3188ae7): Fixed `pnpm peers check` to accept loose peer dependency ranges such as `>=3.16.0 || >=4.0.0-` when the installed peer version satisfies the range [#​12149](https://github.com/pnpm/pnpm/issues/12149).
- [`531f2a3`](https://github.com/pnpm/pnpm/commit/531f2a3): Fixed `pnpm update` rewriting a `workspace:` dependency that points at a local path (e.g. `workspace:../packages/foo/dist`) into a normalized `link:` or version-range specifier. Such specifiers are now preserved verbatim when the workspace protocol is preserved [#​3902](https://github.com/pnpm/pnpm/issues/3902).
- [`fe66535`](https://github.com/pnpm/pnpm/commit/fe66535): Fixed a lockfile non-convergence bug where an incremental install kept a duplicate transitive dependency that a fresh install would not produce. When a package is reused from the lockfile, its child edges are taken verbatim and bypass the preferred-versions walk, so a transitive dependency could stay pinned to an older version even after a direct dependency resolved to a higher version that satisfies the same range. The resolver now refreshes such a stale pin to the higher direct-dependency version during resolution — so the older version is never resolved or fetched, and the incremental result converges to the fresh one.
- [`6d35338`](https://github.com/pnpm/pnpm/commit/6d35338): `pnpm install` detects changes inside local file dependencies again. The optimistic repeat-install fast path only tracks manifest and lockfile modification times, so edits inside a local dependency's directory (or a repacked local tarball) were reported as "Already up to date". Projects with local file dependencies (`file:` and bare local path or tarball specifiers, declared directly or through `pnpm.overrides`) now always run a full install, which refetches those dependencies, matching pnpm v10 behavior [#​11795](https://github.com/pnpm/pnpm/issues/11795).
- [`4ca9247`](https://github.com/pnpm/pnpm/commit/4ca9247): Preserve the existing Node.js runtime version prefix when resolving `node@runtime:<range>` to a concrete version.
- [`30c7590`](https://github.com/pnpm/pnpm/commit/30c7590): Create shorter CAFS temporary package directories to leave room for lifecycle scripts that create IPC socket paths under TMPDIR.
- [`13815ad`](https://github.com/pnpm/pnpm/commit/13815ad): Reporter output (warnings, progress) for `pnpm store` and `pnpm config` subcommands now goes to stderr instead of stdout. This fixes scripts that capture their stdout (e.g. `PNPM_STORE=$(pnpm store path)`, `pnpm config list --json | jq`) from getting warnings mixed into the result.
- [`1c05876`](https://github.com/pnpm/pnpm/commit/1c05876): Avoid relinking unchanged child dependencies and remove stale child links during warm installs.
- [`817f99d`](https://github.com/pnpm/pnpm/commit/817f99d): Fixed lockfile churn where a package's `transitivePeerDependencies` could be dropped (and shift between packages) when the package participates in a dependency cycle. A cycle re-entry resolves against truncated children, so it must not be cached as "pure"; otherwise sibling occurrences of the same package short-circuit and lose transitive peers depending on traversal order [#​5108](https://github.com/pnpm/pnpm/issues/5108).
- [`eba03e0`](https://github.com/pnpm/pnpm/commit/eba03e0): Fix `pnpm install` reporting "Already up to date" after a catalog entry in `pnpm-workspace.yaml` was reverted to a previous version. After an update modified a catalog, the workspace state cache stored the pre-update catalog versions, so reverting the entry back to its original version was not detected as an outdated state [#​12418](https://github.com/pnpm/pnpm/issues/12418).
- [`3b54d79`](https://github.com/pnpm/pnpm/commit/3b54d79): `pnpm update` now keeps lockfile `overrides` that resolve through a catalog in sync with the catalog. Previously, when an override referenced a catalog (e.g. `overrides: { foo: 'catalog:' }`) and `pnpm update` bumped that catalog entry, the lockfile's `catalogs` advanced while the resolved `overrides` kept the old version. The resulting lockfile was internally inconsistent, so a later `pnpm install --frozen-lockfile` failed with `ERR_PNPM_LOCKFILE_CONFIG_MISMATCH`.
- [`9d0a300`](https://github.com/pnpm/pnpm/commit/9d0a300): Fixed `pnpm version --recursive` so it honors the workspace selection. In recursive mode the version bump now applies to the packages resolved from the workspace filter (`selectedProjectsGraph`), matching the behavior of `pnpm publish --recursive`, instead of always bumping every workspace package [#​11348](https://github.com/pnpm/pnpm/issues/11348).
<!-- 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>
</tr>
<tr>
<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>
</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://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>
<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>
</tr>
<tr>
<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>
<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>
</tr>
</tbody>
</table>
<!-- sponsors end -->
</details>
---
### Configuration
📅 **Schedule**: (UTC)
- Branch creation
- At any time (no schedule defined)
- 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.
🔕 **Ignore**: Close this PR and you won't be reminded about this update again.
---
- [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check this box
---
This PR has been generated by [Mend Renovate](https://github.com/renovatebot/renovate).
<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0My4xOTEuMiIsInVwZGF0ZWRJblZlciI6IjQzLjE5MS4yIiwidGFyZ2V0QnJhbmNoIjoibWFpbiIsImxhYmVscyI6W119-->
Reviewed-on: https://gitea.com/gitea/docs/pulls/446
Reviewed-by: Lunny Xiao <[email protected]>
Co-authored-by: Renovate Bot <[email protected]>
Co-committed-by: Renovate Bot <[email protected]>
Adds a versioned docs snapshot for Gitea Runner v1.0.8 (latest stable),
updates lastVersion and navbar to point to the new version while keeping
0.2.11 in the dropdown for historical reference.
---------
Co-authored-by: Lunny Xiao <[email protected]>
Reviewed-on: https://gitea.com/gitea/docs/pulls/435
Reviewed-by: Lunny Xiao <[email protected]>
Co-authored-by: Nicolas <[email protected]>
Co-committed-by: Nicolas <[email protected]>
Adds documentation for the `DEFAULT_TITLE_SOURCE` setting.
- Added `DEFAULT_TITLE_SOURCE` to the config cheat sheet under `[repository.pull-request]`
- Added a "Default pull request title" section to the pull request usage docs explaining both modes (`first-commit` and `auto`)
---------
Co-authored-by: Nicolas <[email protected]>
Reviewed-on: https://gitea.com/gitea/docs/pulls/391
Reviewed-by: Nicolas <[email protected]>
Co-authored-by: 0xGREG <[email protected]>
Co-committed-by: 0xGREG <[email protected]>
Sync the zh-cn and zh-tw `config-cheat-sheet.md` translations with the English source for current and 1.26 versions.
Changes:
- Add 17 missing sections (`qos`, `mailer.override_header`, `markup.external-render-name`, `lfs_client`, `storage_minio`, `storage_azureblob`, `storage_override`, and 10 cron task subsections including the `Actions` cron tasks group)
- Add ~30 missing individual settings across existing sections
- Restructure storage sections to match English (separate Minio/Azure/Override subsections)
- Remove stale settings (`TEST_CONFLICTING_PATCHES_WITH_GIT_APPLY`, old inline MINIO duplicates, removed git timeout settings)
- Fix `MERMAID_MAX_SOURCE_CHARACTERS` default value (5000 -> 50000)
- Replace full-width Unicode punctuation (U+FF1A, U+FF08, U+FF09) with ASCII equivalents
All 4 i18n files now have 745 settings matching the English source.
---
This PR was written with the help of Claude Opus 4.6
Reviewed-on: https://gitea.com/gitea/docs/pulls/381
Reviewed-by: Lunny Xiao <[email protected]>
Co-authored-by: silverwind <[email protected]>
Co-committed-by: silverwind <[email protected]>
Add documentation for the new `REVERSE_PROXY_LOGOUT_REDIRECT` setting in the `[security]` section, which allows redirecting users to an external URL or relative path after logout when using reverse proxy authentication.
For Gitea 1.26, ref https://github.com/go-gitea/gitea/pull/36085.
---
This PR was written with the help of Claude Opus 4.6
Reviewed-on: https://gitea.com/gitea/docs/pulls/380
Reviewed-by: Lunny Xiao <[email protected]>
Co-authored-by: silverwind <[email protected]>
Co-committed-by: silverwind <[email protected]>
This PR contains the following updates:
| Package | Type | Update | Change |
|---|---|---|---|
| [pnpm/action-setup](https://github.com/pnpm/action-setup) | action | major | `v4` → `v5` |
---
### Release Notes
<details>
<summary>pnpm/action-setup (pnpm/action-setup)</summary>
### [`v5`](https://github.com/pnpm/action-setup/compare/v4...v5)
[Compare Source](https://github.com/pnpm/action-setup/compare/v4...v5)
</details>
---
### Configuration
📅 **Schedule**: Branch creation - At any time (no schedule defined), 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.
🔕 **Ignore**: Close this PR and you won't be reminded about this update again.
---
- [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check this box
---
This PR has been generated by [Renovate Bot](https://github.com/renovatebot/renovate).
<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0My43Ni41IiwidXBkYXRlZEluVmVyIjoiNDMuNzYuNSIsInRhcmdldEJyYW5jaCI6Im1haW4iLCJsYWJlbHMiOltdfQ==-->
---------
Co-authored-by: silverwind <[email protected]>
Reviewed-on: https://gitea.com/gitea/docs/pulls/365
Reviewed-by: silverwind <[email protected]>
Reviewed-by: Nicolas <[email protected]>
Co-authored-by: Renovate Bot <[email protected]>
Co-committed-by: Renovate Bot <[email protected]>
The standalone `environment-to-ini` tool was removed in https://github.com/go-gitea/gitea/pull/35735 and its functionality is now built into the Gitea binary. This PR:
- Removes outdated links to the removed `contrib/environment-to-ini` directory
- Updates the "Use environment variables to setup Gitea" section in the config cheat sheet
- Adds examples showing how env vars map to `app.ini` settings and the `__FILE` suffix for secrets
*This PR was authored by Claude.*
Reviewed-on: https://gitea.com/gitea/docs/pulls/355
Reviewed-by: wxiaoguang <[email protected]>
Reviewed-by: TheFox0x7 <[email protected]>
Fix act runner doc typos and errors and simplify some grammar.
Co-authored-by: Peter Ryszkiewicz <[email protected]>
Co-committed-by: Peter Ryszkiewicz <[email protected]>
Original Gitea's repository no longer contains "Environment to INI" document referenced in "Managing Deployments With Environment Variables" section.
This commit fixes the reference by pointing the link to the last commit where needed doc was still present.
Reviewed-on: https://gitea.com/gitea/docs/pulls/315
Reviewed-by: Lunny Xiao <[email protected]>
Co-authored-by: andriibratanin <[email protected]>
Co-committed-by: andriibratanin <[email protected]>
This adds explanations, that were missing regarding Token scopes and the packages feature requiring a token with said scope. Also added a missing explanation, that tokens can be created in the user interface, not just the API. The reason this PR is created was due to misunderstandings around the package feature, that arose during the discussion in https://github.com/go-gitea/gitea/issues/32048 .
This PR aims to clarify the misunderstood points around tokens, scopes and the package feature.
During the creation of this PR bug https://github.com/go-gitea/gitea/issues/32078 was found, the scopes are always reported as `null` during the response of the initial `POST` to create the token.
Co-authored-by: Wladislav ヴラド Artsimovich <[email protected]>
Co-authored-by: Lunny Xiao <[email protected]>
Reviewed-on: https://gitea.com/gitea/docs/pulls/72
Reviewed-by: Lunny Xiao <[email protected]>
Co-authored-by: FrostKiwi <[email protected]>
Co-committed-by: FrostKiwi <[email protected]>
The SMTPS protocol uses port 465, not 587. Port 587 is instead used for STARTTLS.
Helps getting administrators stuck in cryptic errors such as:
Failed to send a testing email to "[email protected]": could not initiate SMTP session: tls: first record does not look like a TLS handshake
Reviewed-on: https://gitea.com/gitea/docs/pulls/295
Reviewed-by: Lunny Xiao <[email protected]>
Co-authored-by: kairosys-dev <[email protected]>
Co-committed-by: kairosys-dev <[email protected]>
Fix JSON syntax error in token creation request
The previous JSON was malformed:
{"name":test_token","scopes":[...]}
The value of "name" was missing opening quotes, causing the error:
invalid character 't' looking for beginning of value
Fixed by enclosing the string in double quotes:
{"name":"test_token","scopes":[...]}
Reviewed-on: https://gitea.com/gitea/docs/pulls/269
Reviewed-by: techknowlogick <[email protected]>
Co-authored-by: desysoft <[email protected]>
Co-committed-by: desysoft <[email protected]>
Added instructions on how to download the signature file using wget, and fixed a typo in the wget commands that would have caused the command to fail when copied/pasted.
Reviewed-on: https://gitea.com/gitea/docs/pulls/262
Reviewed-by: Lunny Xiao <[email protected]>
Co-authored-by: kmanwar89 <[email protected]>
Co-committed-by: kmanwar89 <[email protected]>
Documentation for my changes to ssh commit signing in <https://github.com/go-gitea/gitea/pull/34341>.
* Renames "GPG Commit Signatures" to "GPG/SSH Commit Signatures", since the url is called just signing and creating a new page would duplicate a lot
* _"The default option and repository specific signing keys are not supported for ssh keys"_
* the original draft implementation did support this for ssh keys
* this warning should avoid people expecting this gitconfig style things to work for ssh keys, since verifying is disabled in this case (e.g. pre 1.25 undocumented behavior preserved).
Co-authored-by: Lunny Xiao <[email protected]>
Reviewed-on: https://gitea.com/gitea/docs/pulls/241
Reviewed-by: Lunny Xiao <[email protected]>
Co-authored-by: Christopher Homberger <[email protected]>
Co-committed-by: Christopher Homberger <[email protected]>
## Description
Fix step 1 of the [Database Preparation](https://docs.gitea.com/installation/database-prep) docs.
## Changes
When a user modifies the */etc/mysql/my.cnf* config file as instructed in step 1 of the database preparation guide, they get an error message from MySQL when trying to log in as the root user after saving changes.
```shell
mysql: [ERROR] Found option without preceding group in config file /etc/mysql/my.cnf at line 37.
mysql: [ERROR] Fatal error in defaults handling. Program aborted!
```
The solution is to include precede `bind-address` with a group like this.
```ini
[mysqld]
bind-address = 203.0.113.3
```
Reviewed-on: https://gitea.com/gitea/docs/pulls/173
Reviewed-by: Lunny Xiao <[email protected]>
Co-authored-by: b-nagaj <[email protected]>
Co-committed-by: b-nagaj <[email protected]>
I can not understand the old logic:
- download the web page (not the raw data) from GitHub
- replace `AppVer`
- remove the file???
It does nothing.
The correct logic:
- download the raw file from GitHub
- replace `AppVer` and `AppSubUrl`
- move the file to the correct place
As the script is not correct for a long time, added the support for all version since 1.19.
Fix#142
Reviewed-on: https://gitea.com/gitea/docs/pulls/150
Reviewed-by: Lunny Xiao <[email protected]>
Co-authored-by: yp05327 <[email protected]>
Co-committed-by: yp05327 <[email protected]>
On FAQ item: "Push Hook / Webhook / Actions aren't running"
The action suggested in the original answer is not enough to fix the issue when the hooks are not running. For example, when none of the hooks is running, and branches and tags are not in the database, re-running only the update/receive hooks will not have any effect.
This change adds two additional actions: sync branches and tags before running re-running the sync of update/receive hooks.
Also added a comment about filesystems mounted with `no-exec` option.
Co-authored-by: Lunny Xiao <[email protected]>
Reviewed-on: https://gitea.com/gitea/docs/pulls/130
Reviewed-by: Lunny Xiao <[email protected]>
Co-authored-by: vmmello <[email protected]>
Co-committed-by: vmmello <[email protected]>
#### Summary
This pull request adds detailed instructions for configuring ProtonMail's SMTP server in the `email-setup.md` documentation. The new section provides step-by-step guidance on generating SMTP tokens and configuring the `app.ini` file for ProtonMail.
#### Changes
- Added a new section under the "ProtonMail" heading in `email-setup.md`.
- Included instructions for generating SMTP tokens in ProtonMail settings.
- Provided an example configuration for the `app.ini` file to use ProtonMail's SMTP server.
Reviewed-on: https://gitea.com/gitea/docs/pulls/93
Reviewed-by: Lunny Xiao <[email protected]>
Reviewed-by: techknowlogick <[email protected]>
Co-authored-by: talltechy <[email protected]>
Co-committed-by: talltechy <[email protected]>
- Added detailed documentation for `cron.delete_repo_archives` settings.
- Documented configuration options including `ENABLED`, `RUN_AT_START`, `NOTICE_ON_SUCCESS`, and `SCHEDULE`.
- Ensured consistent formatting across cron job documentation.
This update improves clarity on how to configure periodic deletion of repository archives within Gitea.
`man 5 jail.conf`:
CONFIGURATION FILES FORMAT
*.conf files are distributed by Fail2Ban. It is recommended that *.conf files should remain unchanged to ease upgrades. If needed,
customizations should be provided in *.local files. For example, if you would like to enable the [ssh-iptables-ipset] jail specified
in jail.conf, create jail.local containing
Reviewed-on: https://gitea.com/gitea/docs/pulls/14
Reviewed-by: techknowlogick <[email protected]>
Co-authored-by: webracer999 <[email protected]>
Co-committed-by: webracer999 <[email protected]>
- Always use a relative markdown link so that we can jump even if we edit it in local env or read it in code hosting server.
Reviewed-on: https://gitea.com/gitea/docs/pulls/1
`overflow-wrap: anywhere` is a superior alternative to `word-wrap:
break-word` and we were already setting it in the class. I tested a few
cases, all look good.
This PR implemented object storages(LFS/Packages/Attachments and etc.)
for Azure Blob Storage. It depends on azure official golang SDK and can
support both the azure blob storage cloud service and azurite mock
server.
Replace #25458Fix#22527
- [x] CI Tests
- [x] integration test, MSSQL integration tests will now based on
azureblob
- [x] unit test
- [x] CLI Migrate Storage
- [x] Documentation for configuration added
------
TODO (other PRs):
- [ ] Improve performance of `blob download`.
---------
Co-authored-by: yp05327 <[email protected]>
Backport #31022 by @kemzeb
Syncs up docs associated to actions and deleted branch cleanup i.e. in
custom/app.example.ini and the config cheat sheet.
Co-authored-by: Kemal Zebari <[email protected]>
Backport #31003 by wxiaoguang
Fix#31002
1. Mention Make sure `Host` and `X-Fowarded-Proto` headers are correctly passed to Gitea
2. Clarify the basic requirements and move the "general configuration" to the top
3. Add a comment for the "container registry"
4. Use 1.21 behavior if the reverse proxy is not correctly configured
Co-authored-by: wxiaoguang <[email protected]>
Co-authored-by: KN4CK3R <[email protected]>
Backport #30744 by @lunny
Documentation building has encountered a problem like below. This is
because MDX syntax doesn't allow `{customPath}`, we have to use
\`{customPath}\`
```
Error: Can't render static file for pathname "/next/administration/config-cheat-sheet"
at generateStaticFile (/workspace/gitea/gitea-docusaurus/node_modules/@docusaurus/core/lib/ssg.js:119:15)
at runNextTicks (node:internal/process/task_queues:60:5)
at process.processImmediate (node:internal/timers:449:9)
at async /workspace/gitea/gitea-docusaurus/node_modules/p-map/index.js:57:22 {
[cause]: ReferenceError: CustomPath is not defined
at _createMdxContent (server.bundle.js:4406:106)
at MDXContent (server.bundle.js:10745:8)
at Uc (server.bundle.js:264171:44)
at Xc (server.bundle.js:264173:253)
at Z (server.bundle.js:264179:89)
at Yc (server.bundle.js:264182:98)
at $c (server.bundle.js:264181:140)
at Z (server.bundle.js:264179:345)
at Xc (server.bundle.js:264177:231)
at Z (server.bundle.js:264179:89)
```
Co-authored-by: Lunny Xiao <[email protected]>
Added to doc for rootless Docker installation: for SSH passthrough, the
ssh user (git) has to be able to run docker.
---------
Co-authored-by: techknowlogick <[email protected]>
Backport #30555 by xor-gate
Config section `[task]` has been deprecated in favor of `[queue.task]`
Co-authored-by: Jerry Jacobs <[email protected]>
Co-authored-by: wxiaoguang <[email protected]>
Backport #29314 by @scribblemaniac
This fixes a minor issue in the documentation for SSH Container
Passthrough for non-rootless installs. The non-rootless Dockerfile and
docker-compose do not set `USER`/`user` instructions so `docker exec`
will run as root by default. While running as root, gitea commands will
refuse to execute, breaking these approaches. For containers built with
the rootless instructions, `docker exec` will run as git by default so
this is not necessary in that case.
This issue was already discussed in #19065, but it does not appear this
part of the issue was ever added to the documentation.
Co-authored-by: scribblemaniac <[email protected]>
The command order of prepare-docs is incorrect.
We should prepare same version with different locale at once, otherwise the doc files for `zh-cn` in `i18n` folder will be incorrect.
Before: (files in current is as same as version 1.19)

After:

Co-authored-by: Lunny Xiao <[email protected]>
Reviewed-on: https://gitea.com/gitea/gitea-docusaurus/pulls/92
Reviewed-by: Lunny Xiao <[email protected]>
Reviewed-by: silverwind <[email protected]>
Co-authored-by: yp05327 <[email protected]>
Co-committed-by: yp05327 <[email protected]>
For some user (as me), documentation lack of precision about where to
store issue/pr template.
I propose an enhancement about this point. With bold exergue and
precision about server itself.
I've found some user with same interrogation as :
https://forum.gitea.com/t/issue-template-directory/3328
---------
Co-authored-by: 6543 <[email protected]>
Co-authored-by: Lunny Xiao <[email protected]>
Backport #28639 by @denyskon
According to [Debian
docs](https://wiki.debian.org/DebianRepository/UseThirdParty):
> The certificate MUST NOT be placed in /etc/apt/trusted.gpg.d or loaded
by apt-key add.
> ...
> If future updates to the certificate will be managed by an apt/dpkg
package as recommended below, then it SHOULD be downloaded into
/usr/share/keyrings using the same filename that will be provided by the
package. If it will be managed locally , it SHOULD be downloaded into
/etc/apt/keyrings instead.
> ...
> A sources.list entry SHOULD have the signed-by option set.
Co-authored-by: Denys Konovalov <[email protected]>
Backport #28626 by @hakito
Make it clear that this value is just a default value and that every
artifact can have it's own value.
Co-authored-by: Gerd Katzenbeisser <[email protected]>
Backport #28587, the only conflict is the test file.
The CORS code has been unmaintained for long time, and the behavior is
not correct.
This PR tries to improve it. The key point is written as comment in
code. And add more tests.
Fix#28515Fix#27642Fix#17098
Follow #28191
There are some misadded docs in #24914's backport PR #25016.
The following package registries are all supported in 1.20, so we should
not have docs in v1.19
- Alpine (#23714)
- CRAN (#22343)
- Debain (#24426)
- Go (#24687)
- RPM (#23380)
Backport #28160 by @yp05327
Since #27054, Actions are enabled by default. so we should also edit the
document. 😃
ps: I think this should be backport to 1.21.0.
Co-authored-by: yp05327 <[email protected]>
These are some things I changed to make local development easier, especially when having to update multiple versions of docs at once like the docusaurus v3 update.
i.e.
```makefile
GITEA_REMOTE := ~/code/code.gitea.io/gitea
GITEA_LATEST_BRANCH := docs-v3
GITEA_VERSION_BRANCH_PREFIX := docs-v3-
...
```
to run the Makefile against my local repo.
Reviewed-on: https://gitea.com/gitea/gitea-docusaurus/pulls/89
Co-authored-by: jolheiser <[email protected]>
Co-committed-by: jolheiser <[email protected]>
Backport #27946 by @lng2020
As title. Some attachments and file sizes can easily be larger than
these limits
Co-authored-by: Nanguan Lin <[email protected]>
Backport #27723 by @mpldr
This patchset changes the connection string builder to use net.URL and
the host/port parser to use the stdlib function for splitting host from
port. It also adds a footnote about a potentially required portnumber
for postgres UNIX sockets.
Fixes: #24552
Co-authored-by: Moritz Poldrack <[email protected]>
Backport #27844 by @nodiscc
This documents running `act-runner` as a systemd service under a
dedicated user account.
Co-authored-by: nodiscc <[email protected]>
Backport #27555 by @wolfogre
It should be OK to increase the default queue length since the default
type is "level".
IMO, the old default length (100) is a little too small. See
https://github.com/go-gitea/gitea/issues/27540#issuecomment-1754269491
IIRC, a larger length could lead to more memory usage only when the type
is "channel," but it's an obscure case. Otherwise, it's just a limit
(for "level" or "redis").
Co-authored-by: Jason Song <[email protected]>
Backport #27500 by @silverwind
Duplicate headers in a single Markdown document are problemlematic
because the auto-generated links won't be stable. Enable this rule with
no exceptions which is also the default of `markdownlint`. For example:
```md
# A
## Example
# B
## Example
```
Docasaurus will generated `example` and `example-1` links for this. If
the first heading is altered, the link `example` will unexpectedly move
to the second example heading.
Ref: https://github.com/go-gitea/gitea/pull/27461#discussion_r1347987659
Co-authored-by: silverwind <[email protected]>
Backport #27400 by @fantognazza
With this PR we added the possibility to configure the Actions timeouts
values for killing tasks/jobs.
Particularly this enhancement is closely related to the `act_runner`
configuration reported below:
```
# The timeout for a job to be finished.
# Please note that the Gitea instance also has a timeout (3h by default) for the job.
# So the job could be stopped by the Gitea instance if it's timeout is shorter than this.
timeout: 3h
```
---
Setting the corresponding key in the INI configuration file, it is
possible to let jobs run for more than 3 hours.
Signed-off-by: Francesco Antognazza <[email protected]>
Backport #27291 by @silverwind
Previously, the production build never output sourcemaps. Now we emit
one file for `index.js` because it is the most likely one where we need
to be able to better debug reported issues like
https://github.com/go-gitea/gitea/issues/27213. This will currently
increase the binary size of gitea by around 700kB which is what the
gzipped source map file has.
Also, I fixed the CSS sourcemap generation which was broken since the
introduction of lightningcss.
The chinese docs are machine-translated, please correct accordingly.
Co-authored-by: silverwind <[email protected]>
Backport #27126 by @Zettat123
Related to #27039
The `ref` property in Gitea Actions is different from GitHub Actions.
This PR improves the documentation to explain the difference.
Co-authored-by: Zettat123 <[email protected]>
Backport #26999
If the AppURL(ROOT_URL) is an HTTPS URL, then the COOKIE_SECURE's
default value should be true.
And, if a user visits an "http" site with "https" AppURL, they won't be
able to login, and they should have been warned. The only problem is
that the "language" can't be set either in such case, while I think it
is not a serious problem, and it could be fixed easily if needed.
Backport #26883
This change was caused by #26271, for configuration as below:
```
[attachment]
ENABLE = true
PATH = data/attachments
MAX_SIZE = 100
MAX_FILES = 5
```
Before #26271, the resolved path is ${AppWorkPath}/${attachments.PATH}
(such as `/var/lib/gitea/data/attachments`)
After #26271, the resolved path is ${AppDataPath}/${attachments.PATH}
(such as `/var/lib/gitea/data/data/attachments`)
Fix https://github.com/go-gitea/gitea/issues/26864 Follow
https://github.com/go-gitea/gitea/pull/26271
Backport #26382 by @jolheiser
This PR adds our matrix space to the support options and alphabetizes
the list.
I also considered adding our Mastodon, however that isn't as suitable as
the other options because it's just whoever has access to the account vs
a community chat/forum.
Signed-off-by: jolheiser <[email protected]>
Co-authored-by: John Olheiser <[email protected]>
Backport #26690 by @thomas-mc-work
… because it doesn't require a separate shell, spawning a process which
cost unnecessary resources and takes time.
Co-authored-by: Thomas McWork <[email protected]>
Backport #26654 by @lunny
copy and modified from #14572
> Whilst debating enforcing MFA within our team, I realised there isn't
a lot of context to the side effects of enabling it. Most of us use Git
over HTTP and would need to add a token.
I plan to add another PR that adds a sentence to the UI about needing to
generate a token when enabling MFA if HTTP is to be used.
Co-authored-by: Lunny Xiao <[email protected]>
Co-authored-by: techknowlogick <[email protected]>
Co-authored-by: silverwind <[email protected]>
Backport #26267 by @wxiaoguang
1. Fix the wrong document (add the missing `MODE=`)
2. Add a more friendly log message to tell users to add `MODE=` in their
config
Co-authored-by: wxiaoguang <[email protected]>
Closes#30Closes#31Closes#42
Rather than partially cloning, instead we clone once and then `clean` (to remove generated files), `reset` (to reset changed files), and `checkout` branches as needed. This should allow the outdated translation check to continue to work while also reducing build times significantly.
Before:

After:

Reviewed-on: https://gitea.com/gitea/gitea-docusaurus/pulls/52
Co-authored-by: John Olheiser <[email protected]>
Co-committed-by: John Olheiser <[email protected]>
Backport #26068 by @felixvictor
The setting `MAILER_TYPE` is deprecated.
According to the config cheat sheet, it should be `PROTOCOL`.
Co-authored-by: Felix Victor <[email protected]>
Co-authored-by: Lunny Xiao <[email protected]>
Backport #26096 by @thigg
related #1635#18159
This will probably be obsolete at some point, but it should not break
anything and it may help some users
Co-authored-by: thigg <[email protected]>
Close#46
Same as those in [blog](https://gitea.com/gitea/blog/pulls/272), but for docs and api
Ejected DocPage (unsafe to eject) and ApiDoc (safe to eject) for layout changes

Screenshots

Co-authored-by: techknowlogick <[email protected]>
Co-authored-by: techknowlogick <[email protected]>
Reviewed-on: https://gitea.com/gitea/gitea-docusaurus/pulls/47
Co-authored-by: HesterG <[email protected]>
Co-committed-by: HesterG <[email protected]>
- Do not use `target="_blank"`for gitea websites on navbar (same behavior as about.gitea.com)
- Translate "blog" on navbar for zh-cn
Before:

After:

Reviewed-on: https://gitea.com/gitea/gitea-docusaurus/pulls/44
Co-authored-by: HesterG <[email protected]>
Co-committed-by: HesterG <[email protected]>
Backport #25648
Replace #25580Fix#19453
The problem was: when users set "GITEA__XXX__YYY" , the "install page"
doesn't respect it.
So, to make the result consistent and avoid surprising end users, now
the "install page" also writes the environment variables to the config
file.
And, to make things clear, there are enough messages on the UI to tell
users what will happen.
There are some necessary/related changes to `environment-to-ini.go`:
* The "--clear" flag is removed and it was incorrectly written there.
The "clear" operation should be done if INSTALL_LOCK=true
* The "--prefix" flag is removed because it's never used, never
documented and it only causes inconsistent behavior.
The only conflict during backport is "ui divider" in
templates/install.tmpl
Backport #25771 by @leavesster
---
according `docker/rootless/usr/local/bin/docker-setup.sh` , in rootless
docker setup, ssh port is 2222.
and mysql database case should port same as PostgreSQL port
Co-authored-by: leavesster <[email protected]>
- Used custom api dropdown for changing label dynamically. Used method discussed in [this post](https://github.com/facebook/docusaurus/pull/7231). `ComponentTypes` is ejected safely.

- Fixed dropdowns missing on chrome docs page

- Fixed `:has` selector compatibility problem on firefox. After (Screenshots from firefox):




Reviewed-on: https://gitea.com/gitea/gitea-docusaurus/pulls/39
Co-authored-by: HesterG <[email protected]>
Co-committed-by: HesterG <[email protected]>
# Changes
- Added [redocusaurus](https://github.com/rohit-gohri/redocusaurus) package to display swagger api with reference to [woodpecker](https://github.com/woodpecker-ci/woodpecker/tree/master/docs)
- Change `sed` for swagger.json for proper version and baseurl.
- Add a dropdown for different api versions. (screenshot below)
- On API pages, only API version dropdown will show(hide locale dropdown, version dropdown for doc, and doc search)
- Allow build CSR API pages for development so it saves building time.
# Screenshots




Search:

Reviewed-on: https://gitea.com/gitea/gitea-docusaurus/pulls/32
Co-authored-by: HesterG <[email protected]>
Co-committed-by: HesterG <[email protected]>
Backport #25581 by @wolfogre
Resolve#24789
## ⚠️ BREAKING ⚠️
Before this, `DEFAULT_ACTIONS_URL` cound be set to any custom URLs like
`https://gitea.com` or `http://your-git-server,https://gitea.com`, and
the default value was `https://gitea.com`.
But now, `DEFAULT_ACTIONS_URL` supports only
`github`(`https://github.com`) or `self`(the root url of current Gitea
instance), and the default value is `github`.
If it has configured with a URL, an error log will be displayed and it
will fallback to `github`.
Actually, what we really want to do is always make it
`https://github.com`, however, this may not be acceptable for some
instances of internal use, so there's extra support for `self`, but no
more, even `https://gitea.com`.
Please note that `uses: https://xxx/yyy/zzz` always works and it does
exactly what it is supposed to do.
Although it's breaking, I belive it should be backported to `v1.20` due
to some security issues.
Follow-up on the runner side:
- https://gitea.com/gitea/act_runner/pulls/262
- https://gitea.com/gitea/act/pulls/70
Co-authored-by: Jason Song <[email protected]>
Close#4
Right now on MacOS, sed will produce backup files, because on Mac, If do something like below using bash script
```
$SED_INPLACE '1s/---/---\nisOutdated: true/' $file
```
It will become the following on executed, and a backup file will be produced:
```
sed -i ''\'''\''' '1s/---/---\nisOutdated: true/' ./docs/content/doc/help/faq.zh-cn.md
```
The way to fix this in this PR is to change the `SED_INPLACE` to a function. [Reference](https://github.com/pingcap/tipb/blob/aa19c2d12587e66b31069f790fc371b6be565504/generate-cpp.sh#L4) (Tested on my
Mac and backup files with suffix '' will not be produced after changing to function)
Reviewed-on: https://gitea.com/gitea/gitea-docusaurus/pulls/36
Reviewed-by: Lunny Xiao <[email protected]>
Co-authored-by: HesterG <[email protected]>
Co-committed-by: HesterG <[email protected]>
Backport #25256 by @thezzisu
In this pull request, the following changes are addressed:
- State user should create `config.yaml` before start container to avoid
errors.
- Provided instructions to deploy runners using docker compose.
Co-authored-by: Zisu Zhang <[email protected]>
Modified `loop_docs.sh` to take `version` and `locale` and params so do not need other `loop_docs*` scripts.
Need confirmation with version information for 1.20 and latest.
Like version in `static/20-swagger.json` and `static/latest-swagger.json`, and version used to replace `{{< version >}}`
Will wait for a stable version for 1.20

Reviewed-on: https://gitea.com/gitea/gitea-docusaurus/pulls/28
Reviewed-by: Lunny Xiao <[email protected]>
Co-authored-by: HesterG <[email protected]>
Co-committed-by: HesterG <[email protected]>
Backport #23911 by @lunny
Follow up #22405Fix#20703
This PR rewrites storage configuration read sequences with some breaks
and tests. It becomes more strict than before and also fixed some
inherit problems.
- Move storage's MinioConfig struct into setting, so after the
configuration loading, the values will be stored into the struct but not
still on some section.
- All storages configurations should be stored on one section,
configuration items cannot be overrided by multiple sections. The
prioioty of configuration is `[attachment]` > `[storage.attachments]` |
`[storage.customized]` > `[storage]` > `default`
- For extra override configuration items, currently are `SERVE_DIRECT`,
`MINIO_BASE_PATH`, `MINIO_BUCKET`, which could be configured in another
section. The prioioty of the override configuration is `[attachment]` >
`[storage.attachments]` > `default`.
- Add more tests for storages configurations.
- Update the storage documentations.
Co-authored-by: Lunny Xiao <[email protected]>
Co-authored-by: wxiaoguang <[email protected]>
Close#17
mdxa is another component that is safe to eject

And right now some links are using `master` as the branch(link will be redirect to `main`), so checked for that as well.
Update:
Change `master` to `main` in upstream, so also removed check for `master`
https://github.com/go-gitea/gitea/pull/25126
# After
For example:




Reviewed-on: https://gitea.com/gitea/gitea-docusaurus/pulls/27
Reviewed-by: Lunny Xiao <[email protected]>
Co-authored-by: HesterG <[email protected]>
Co-committed-by: HesterG <[email protected]>
- `git log` needs all commit history to compare latest commit of files, so changed `git clone --depth=1` to `git clone`. This will make the step take longer, so might be improved if there is a better way.
- `src/theme/MDXContent/index.js` is from [ejecting of the component](https://docusaurus.io/docs/swizzling#ejecting) inside `@docusaurus/theme-classic` plugin, and this one is safe to eject according to docusaurus


- [Outdated component style reference](https://mui.com/material-ui/react-alert/)
- Added [`Translate` component](https://docusaurus.io/docs/next/docusaurus-core#translate) to `Outdated` so it can be localized. [reference](https://docusaurus.io/docs/next/i18n/tutorial#translate-your-react-code)
- One way to check for the specific outdated documents:
search for `lastest commit timestamp` in [prepare nightly docs and prepare 1.19 docs steps](https://gitea.com/gitea/gitea-docusaurus/actions/runs/74)
# After
The Chinese documents that are outdated (latest commit is ealier than laster commit of English version):



Reviewed-on: https://gitea.com/gitea/gitea-docusaurus/pulls/25
Reviewed-by: Lunny Xiao <[email protected]>
Co-authored-by: HesterG <[email protected]>
Co-committed-by: HesterG <[email protected]>
Backport #24925, partially backport #24934
(`docs/content/doc/help/faq.zh-cn.md` file)
Part of backport has already done by #24942
Also backport #24881 to avoid "deadlock"
---------
Co-authored-by: Zettat123 <[email protected]>
Backport #24979
Changes:
1. Use uniform links types relative to doc folder (start with `doc/`)
2. According to [docusaurus
links](https://docusaurus.io/docs/markdown-features/links), if `<a>` is
used, the `href` is resolved as URL location, but not file location. So
need to use `[text]({{< relref "path" >}})` instead.
# Main changes and Some notices
- Chines Docs should be put into corresponding versions' folders under `i18n/zh-cn/docusaurus-plugin-content-docs`. See [translate-markdown-files](https://docusaurus.io/docs/i18n/tutorial#translate-markdown-files) for reference. And also [srs-docs](https://github.com/ossrs/srs-docs) is a project that can be used as a reference. So changed the related paths in `loop_docs*` scripts.
- The markdown files under `docs/<version>` and `i18n/zh-cn/docusaurus-plugin-content-docs/<version>` must have the same name to be referred as translated. Files inside docs are corresponding to `current` version.
For example, `docs/administration/mail-templates.md` and `i18n/zh-cn/docusaurus-plugin-content-docs/current/administration/mail-templates.md`. Filename of `mail-templates.md` must be the same (cannot be `mail-templates.zh-cn.md` and `mail-templates.us-en.md`)
So trim the `.en-us.md` and `.zh-cn.md` to `.md` to ensure the same names inside `loop_docs*` scripts.
- Used `npx docusaurus write-translations --locale zh-cn` to do sidebar, footer, and header translations, so no need to keep `sideBarCN` anymore. [reference](https://docusaurus.io/docs/cli#docusaurus-write-translations-sitedir)
- Local Tests for `en` and `zh-cn` are separeted, run the following on local respectly to test them
```bash
# test en version
npm run start
# test zh-cn version
npm run start -- --locale zh-cn
```
- If wants to test `en` and `zh-cn` both at the same time, run build and serve
```
npm run build
npm run serve
```
- Added some indexed page, for example:


Reviewed-on: https://gitea.com/gitea/gitea-docusaurus/pulls/13
Co-authored-by: HesterG <[email protected]>
Co-committed-by: HesterG <[email protected]>
Backport #23923 by @teauxfu
updated the example config to make the needed backticks around the
password more obvious
Co-authored-by: alex <[email protected]>
Backport #23733 by @wolfogre
I neglected that the `NameKey` of `Unit` is not only for translation,
but also configuration. So it should be `repo.actions` to maintain
consistency.
## ⚠️ BREAKING ⚠️
If users already use `actions.actions` in `DISABLED_REPO_UNITS` or
`DEFAULT_REPO_UNITS`, it will be treated as an invalid unit key.
Co-authored-by: Jason Song <[email protected]>
Follow #21962
After I eat my own dogfood, I would say that
ONLY_SHOW_RELEVANT_REPOS=false is necessary for many private/enterprise
instances, because many private repositories do not have
"description/topic", users just want to search by their names.
This PR also adds `PageIsExploreRepositories` check, to make code more
strict, because the `search` template is shared for different purpose.
And during the test, I found a bug that the "Search" button didn't
respect the "relevant" parameter, so this PR fixes the bug by the way
together.
Backport #23580 by @silverwind
Updated this default to GitHub's latest, adding the `.patch` file
extension to allowed types.
Co-authored-by: silverwind <[email protected]>
Backport #23430 by @brechtvl
* Fix scoped label left and right part breaking across lines.
* Remove slanted divider in scoped label display, make it straight.
After using this for a while, this feels more visually noisy than
helpful.
* Reduce contrast between scope and item to reduce probability of
unreadable text on background.
* Change documentation to remove mention of non-exclusive scoped labels.
Co-authored-by: Brecht Van Lommel <[email protected]>
Co-authored-by: Lunny Xiao <[email protected]>
Co-authored-by: John Olheiser <[email protected]>
Backport #22451 by @philip-peterson
This PR adds support for reflogs on all repositories. It does this by
adding a global configuration entry.
Implements #14865
Signed-off-by: Philip Peterson <[email protected]>
Co-authored-by: Philip Peterson <[email protected]>
Backport #23087
Whitespace was missing from refactoring docs metadata.
backport label applied so it is included in versioned docs.
Co-authored-by: techknowlogick <[email protected]>
"body": "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."
"body": "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."
"_comment":"Algolia DocSearch crawler configuration. The three facets come from the docsearch:* meta tags every page carries; only the versions people read are indexed, the older ones stay reachable through the version picker.",
* Keine "Amtsdeusch"-Umschreibungen, einfach so als ob man den Nutzer direkt persönlich ansprechen würde
Genauer definiert:
* "falsch" anstatt "nicht korrekt/inkorrekt"
* Benutzerkonto oder Konto? Oder Account?
* "Wende dich an ..." anstatt "kontaktiere ..."
* In der selben Zeit übersetzen (sonst wird aus "is" "war")
* Richtige Anführungszeichen verwenden. Also `"` statt `''` oder `'` oder \` oder `´`
*`„` für beginnende Anführungszeichen, `“` für schließende Anführungszeichen
Es gelten Artikel und Worttrennungen aus dem Duden.
## Formulierungen in Modals und Buttons
Es sollten die gleichen Formulierungen auf Buttons und Modals verwendet werden.
Beispiel: Wenn der Button mit `löschen` beschriftet ist, sollte im Modal die Frage lauten `Willst du das wirklich löschen?` und nicht `Willst du das wirklich entfernen?`. Gleiches gilt für Success/Errormeldungen nach der Aktion.
Some jurisdictions (such as EU), requires certain legal pages (e.g. Privacy Policy) to be added to website. Follow these steps to add them to your Gitea instance.
## Getting Pages
Gitea source code ships with sample pages, available in `contrib/service/sample-page` directory. Copy them to `custom/public/assets/`. For example, to add Privacy Policy:
Now you need to edit the page to meet your requirements. In particular you must change the email addresses, web addresses and references to "Your Gitea Instance" to match your situation.
You absolutely must not place a general ToS or privacy statement that implies that the Gitea project is responsible for your server.
## Make it Visible
Create or append to `/path/to/custom/templates/custom/extra_links_footer.tmpl`:
- Example for Microsoft Active Directory (AD): `(&(objectCategory=Person)(memberOf=CN=user-group,OU=example,DC=example,DC=org)(sAMAccountName=%s)(!(UserAccountControl:1.2.840.113556.1.4.803:=2)))`
- To substitute more than once, `%[1]s` should be used instead, e.g. when
matching supplied login name against multiple attributes such as user
- Import the LDIF (change localhost to an IPA server if needed). A prompt for
Directory Manager password will be presented:
```sh
ldapmodify -h localhost -p 389 -x -D \
"cn=Directory Manager" -W -f gitea.ldif
```
- Add an IPA group for gitea_users :
```sh
ipa group-add --desc="Gitea Users" gitea_users
```
- Note: For errors about IPA credentials, run `kinit admin` and provide the
domain admin account password.
- Log in to Gitea as an Administrator and click on "Authentication" under Admin Panel.
Then click `Add New Source` and fill in the details, changing all where appropriate.
## SPNEGO with SSPI (Kerberos/NTLM, for Windows only)
Gitea supports SPNEGO single sign-on authentication (the scheme defined by RFC4559) for the web part of the server via the Security Support Provider Interface (SSPI) built in Windows. SSPI works only in Windows environments - when both the server and the clients are running Windows.
Before activating SSPI single sign-on authentication (SSO) you have to prepare your environment:
- Create a separate user account in active directory, under which the `gitea.exe` process will be running (eg. `user` under domain `domain.local`):
- Create a service principal name for the host where `gitea.exe` is running with class `HTTP`:
- Start `Command Prompt` or `PowerShell` as a privileged domain user (eg. Domain Administrator)
- Run the command below, replacing `host.domain.local` with the fully qualified domain name (FQDN) of the server where the web application will be running, and `domain\user` with the name of the account created in the previous step:
```sh
setspn -A HTTP/host.domain.local domain\user
```
- Sign in (_sign out if you were already signed in_) with the user created
- Make sure that `ROOT_URL` in the `[server]` section of `custom/conf/app.ini` is the fully qualified domain name of the server where the web application will be running - the same you used when creating the service principal name (eg. `host.domain.local`)
- Start the web server (`gitea.exe web`)
- Enable SSPI authentication by adding an `SPNEGO with SSPI` authentication source in `Site Administration -> Authentication Sources`
- Sign in to a client computer in the same domain with any domain user (client computer, different from the server running `gitea.exe`)
- If you are using Chrome or Edge, add the URL of the web app to the Local intranet sites (`Internet Options -> Security -> Local intranet -> Sites`)
- Start Chrome or Edge and navigate to the FQDN URL of Gitea (eg. `http://host.domain.local:3000`)
- Click the `Sign In` button on the dashboard and choose SSPI to be automatically logged in with the same user that is currently logged on to the computer
- If it does not work, make sure that:
- You are not running the web browser on the same server where Gitea is running. You should be running the web browser on a domain joined computer (client) that is different from the server. If both the client and server are running on the same computer NTLM will be preferred over Kerberos.
- There is only one `HTTP/...` SPN for the host
- The SPN contains only the hostname, without the port
- You have added the URL of the web app to the `Local intranet zone`
- The clocks of the server and client should not differ with more than 5 minutes (depends on group policy)
- `Integrated Windows Authentication` should be enabled in Internet Explorer (under `Advanced settings`)
## Reverse Proxy
Gitea supports Reverse Proxy Header authentication, it will read headers as a trusted login user name or user email address. This hasn't been enabled by default, you can enable it with
```ini
[service]
ENABLE_REVERSE_PROXY_AUTHENTICATION = true
```
The default login user name is in the `X-WEBAUTH-USER` header, you can change it via changing `REVERSE_PROXY_AUTHENTICATION_USER` in app.ini. If the user doesn't exist, you can enable automatic registration with `ENABLE_REVERSE_PROXY_AUTO_REGISTRATION=true`.
The default login user email is `X-WEBAUTH-EMAIL`, you can change it via changing `REVERSE_PROXY_AUTHENTICATION_EMAIL` in app.ini, this could also be disabled with `ENABLE_REVERSE_PROXY_EMAIL`
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`.
It applies to the web interface, Git over HTTP, Git LFS and the package registry.
:::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]
ENABLE_REVERSE_PROXY_AUTHENTICATION_API = true
```
:::note
When this method is enabled for the API, the reverse proxy is responsible for handling CSRF protection.
:::
## OAuth2 and OpenID Connect
Gitea can delegate the login to an external OAuth2 or OpenID Connect provider,
which is what "Sign in with ..." on the login page uses. This is the client side
of the protocol; for the other direction, using a Gitea instance as the provider
for another application, see
[Gitea as an OAuth2 provider](../development/oauth2-provider.md).
A source is added under `Site Administration -> Identity & Access ->
Authentication Sources -> Add Authentication Source`, with `OAuth2` as the
authentication type, or on the command line with
`gitea admin auth add-oauth`. The fields are:
- **Authentication Name (required)**
- The name of the source. It is part of the callback URL, so it cannot be
changed later without registering a new callback URL at the provider.
- **OAuth2 Provider (required)**
- `OpenID Connect` for any provider that implements OpenID Connect discovery
(Keycloak, Authentik, Authelia, Entra ID, Okta, Zitadel and others), or one
of the providers Gitea knows about directly: Gitea, GitHub, GitLab, Google,
Gitea currently has a `dump` command that will save the installation to a ZIP file. This
file can be unpacked and used to restore an instance.
## Backup Consistency
To ensure the consistency of the Gitea instance, it must be shutdown during backup.
Gitea consists of a database, files and git repositories, all of which change when it is used. For instance, when a migration is in progress, a transaction is created in the database while the git repository is being copied over. If the backup happens in the middle of the migration, the git repository may be incomplete although the database claims otherwise because it was dumped afterwards. The only way to avoid such race conditions is by stopping the Gitea instance during the backups.
## Backup Command (`dump`)
Switch to the user running Gitea: `su git`. Run `./gitea dump -c /path/to/app.ini` in the Gitea installation
directory. There should be some output similar to the following:
```log
2016/12/27 22:32:09 Creating tmp work dir: /tmp/gitea-dump-417443001
2016/12/27 22:32:09 Dumping local repositories.../home/git/repositories
2016/12/27 22:32:22 Dumping database...
2016/12/27 22:32:22 Packing dump files...
2016/12/27 22:32:34 Removing tmp work dir: /tmp/gitea-dump-417443001
2016/12/27 22:32:34 Finish dumping in file gitea-dump-1482906742.zip
```
Inside the `gitea-dump-1482906742.zip` file, will be the following:
-`app.ini` - Optional copy of configuration file if originally stored outside the default `custom/` directory
-`custom/` - All config or customization files in `custom/`.
-`data/` - Data directory (APP_DATA_PATH), except sessions if you are using file session. This directory includes `attachments`, `avatars`, `lfs`, `indexers`, SQLite file if you are using SQLite.
-`repos/` - Complete copy of the repository directory.
-`gitea-db.sql` - SQL dump of database
-`log/` - Various logs. They are not needed for a recovery or migration.
Intermediate backup files are created in a temporary directory specified either with the
`--tempdir` command-line parameter or the `TMPDIR` environment variable.
## Backup the database
The SQL dump created by `gitea dump` uses XORM and Gitea admins may prefer to use the native the MySQL and PostgreSQL dump tools instead. There are still open issues when using XORM for dumping the database that may cause problems when attempting to restore it.
There are a few caveats for using the `dump` command with Docker.
The command has to be executed with the `RUN_USER = <OS_USERNAME>` specified in `gitea/conf/app.ini`; and, for the zipping of the backup folder to occur without permission error the command `docker exec` must be executed inside of the `--tempdir`.
\*Note: `--tempdir` refers to the temporary directory of the docker environment used by Gitea; if you have not specified a custom `--tempdir`, then Gitea uses `/tmp` or the `TMPDIR` environment variable of the docker container. For `--tempdir` adjust your `docker exec` command options accordingly.
The result should be a file, stored in the `--tempdir` specified, along the lines of: `gitea-dump-1482906742.zip`
## Restore Command (`restore`)
There is currently no support for a recovery command. It is a manual process that mostly
involves moving files to their correct locations and restoring a database dump.
mysql --default-character-set=utf8mb4 -u$USER -p$PASS$DATABASE <gitea-db.sql
# sqlite3
sqlite3 $DATABASE_PATH <gitea-db.sql
# postgres
psql -U $USER -d $DATABASE < gitea-db.sql
service gitea restart
```
Repository Git Hooks should be regenerated if installation method is changed (eg. binary -> Docker), or if Gitea is installed to a different directory than the previous installation.
With Gitea running, and from the directory Gitea's binary is located, execute: `./gitea admin regenerate hooks`
This ensures that application and configuration file paths in repository Git Hooks are consistent and applicable to the current installation. If these paths are not updated, repository `push` actions will fail.
If you still have issues, consider running `./gitea doctor check` to inspect possible errors (or run with `--fix`).
### Using Docker (`restore`)
There is also no support for a recovery command in a Docker-based gitea instance. The restore process contains the same steps as described in the previous section but with different paths.
The `gitea dump` command can produce a SQL file that can be read by another database type, which is useful to convert the database to another in case you did not choose the correct one during the first installation.
Note that this conversion process is not well-tested, which is why it is recommended to choose the final database type during the first installation without attempting to change it afterwards.
Stop the Gitea server, then make sure you have a full backup of your original database.
Before attempting the conversion, ensure that the original database is clean. Run `gitea doctor check --all --fix` and `gitea doctor recreate-table` to address common issues.
Use the `--database` flag to get a Gitea dump with the SQL file in the target format, in this example PostgreSQL: `gitea dump --database postgres`, then extract the file `gitea-db.sql` from the generated ZIP file.
Create the PostgreSQL Gitea user and Gitea database. Then, import the SQL file as the Gitea user into the Gitea database, using commands such as:
```sh
sudo -u postgres psql -d gitea
gitea=# SET synchronous_commit TO off
gitea=# SET on_error_stop TO on
gitea=# \i gitea-db.sql
```
Disabling `synchronous_commit` makes PostgreSQL less resilient to crashes, but makes the import a lot faster. Since we already have a backup of the original database and we can check that the import completes successfully, it should be a good trade-off.
After the import is completed, set up Gitea to use PostgreSQL and start the Gitea server again. Good luck!
-`--install-port number`: Port number to run the install page on. Optional. (default: 3000). Overrides configuration file.
-`--pid path`, `-P path`: Pidfile path. Optional.
-`--quiet`, `-q`: Only emit Fatal logs on the console for logs emitted before logging set up.
-`--verbose`: Emit tracing logs on the console for logs emitted before logging is set-up.
- Examples:
-`gitea web`
-`gitea web --port 80`
-`gitea web --config /etc/gitea.ini --pid /some/custom/gitea.pid`
- Notes:
- Gitea should not be run as root. To bind to a port below 1024, you can use setcap on
Linux: `sudo setcap 'cap_net_bind_service=+ep' /path/to/gitea`. This will need to be
redone every time you update Gitea.
A systemd unit can grant the capability to the process instead, which survives an
upgrade and works with SELinux in enforcing mode, see
[running Gitea with SELinux](../installation/selinux.md).
### admin
Admin operations:
- Commands:
-`user`:
-`list`:
- Options:
-`--admin`: List only admin users. Optional.
- Description: lists all users that exist
- Examples:
-`gitea admin user list`
-`delete`:
- Options:
-`--email`: Email of the user to be deleted.
-`--username`: Username of user to be deleted.
-`--id`: ID of user to be deleted.
- One of `--id`, `--username` or `--email` is required. If more than one is provided then all have to match.
- Examples:
-`gitea admin user delete --id 1`
-`create`:
- Options:
-`--name value`: Username. Required. As of Gitea 1.9.0, use the `--username` flag instead.
-`--username value`: Username. Required. New in Gitea 1.9.0.
-`--password value`: Password. Required.
-`--email value`: Email. Required.
-`--admin`: If provided, this makes the user an admin. Optional.
-`--access-token`: If provided, an access token will be created for the user. Optional. (default: false).
-`--must-change-password`: The created user will be required to set a new password after the initial login, default: true. It could be disabled by `--must-change-password=false`.
-`--random-password`: If provided, a randomly generated password will be used as the password of the created
user. The value of `--password` will be discarded. Optional.
-`--random-password-length`: If provided, it will be used to configure the length of the randomly generated
-`--password value`, `-p value`: New password. Required.
-`--must-change-password`: The user is required to set a new password after the login, default: true. It could be disabled by `--must-change-password=false`.
- Examples:
-`gitea admin user change-password --username myname --password asecurepassword`
-`must-change-password`:
- Args:
-`[username...]`: Users that must change their passwords
- Options:
-`--all`, `-A`: Force a password change for all users
-`--exclude username`, `-e username`: Exclude the given user. Can be set multiple times.
-`--unset`: Revoke forced password change for the given users
-`--scopes value`: Comma-separated list of scopes. Scopes follow the format `[read|write]:<block>` or `all` where `<block>` is one of the available visual groups you can see when opening the API page showing the available routes (for example `repo`).
- Examples:
-`gitea admin user generate-access-token --username myname --token-name mytoken`
-`gitea admin user generate-access-token --help`
-`regenerate`
- Options:
-`hooks`: Regenerate Git Hooks for all repositories
-`keys`: Regenerate authorized_keys file
- Examples:
-`gitea admin regenerate hooks`
-`gitea admin regenerate keys`
-`auth`:
-`list`:
- Description: lists all external authentication sources that exist
- Examples:
-`gitea admin auth list`
-`delete`:
- Options:
-`--id`: ID of source to be deleted. Required.
- Examples:
-`gitea admin auth delete --id 1`
-`add-oauth`:
- Options:
-`--name`: Application Name.
-`--provider`: OAuth2 Provider.
-`--key`: Client ID (Key).
-`--secret`: Client Secret.
-`--auto-discover-url`: OpenID Connect Auto Discovery URL (only required when using OpenID Connect as provider).
-`--use-custom-urls`: Use custom URLs for GitLab/GitHub OAuth endpoints.
-`--custom-tenant-id`: Use custom Tenant ID for OAuth endpoints.
-`--custom-auth-url`: Use a custom Authorization URL (option for GitLab/GitHub).
-`--custom-token-url`: Use a custom Token URL (option for GitLab/GitHub).
-`--custom-profile-url`: Use a custom Profile URL (option for GitLab/GitHub).
-`--custom-email-url`: Use a custom Email URL (option for GitHub).
-`--icon-url`: Custom icon URL for OAuth2 login source.
-`--skip-local-2fa`: Allow source to override local 2FA. (Optional)
-`--scopes`: Additional scopes to request for this OAuth2 source. (Optional)
-`--required-claim-name`: Claim name that has to be set to allow users to login with this source. (Optional)
-`--required-claim-value`: Claim value that has to be set to allow users to login with this source. (Optional)
-`--group-claim-name`: Claim name providing group names for this source. (Optional)
-`--admin-group`: Group Claim value for administrator users. (Optional)
-`--restricted-group`: Group Claim value for restricted users. (Optional)
-`--group-team-map`: JSON mapping between groups and org teams. (Optional)
-`--group-team-map-removal`: Activate automatic team membership removal depending on groups. (Optional)
-`--flags value`, `-F value`: Flags for the logger
-`--expression value`, `-e value`: Matching expression for the logger
-`--prefix value`, `-p value`: Prefix for the logger
-`--color`: Use color in the logs
-`--username value`, `-u value`: Mail server username
-`--password value`, `-P value`: Mail server password
-`--host value`, `-H value`: Mail server host (defaults to: 127.0.0.1:25)
-`--send-to value`, `-s value`: Email address(es) to send to
-`--subject value`, `-S value`: Subject header of sent emails
-`processes`: Display Gitea processes and goroutine information
- Options:
-`--flat`: Show processes as flat table rather than as tree
-`--no-system`: Do not show system processes
-`--stacktraces`: Show stacktraces for goroutines associated with processes
-`--json`: Output as json
-`--cancel PID`: Send cancel to process with PID. (Only for non-system processes.)
### dump-repo
Dump-repo dumps repository data from Git/GitHub/Gitea/GitLab:
- Options:
-`--git_service service` : Git service, it could be `git`, `github`, `gitea`, `gitlab`, If clone_addr could be recognized, this could be ignored.
-`--repo_dir dir`, `-r dir`: Repository dir path to store the data
-`--clone_addr addr`: The URL will be clone, currently could be a git/github/gitea/gitlab http/https URL. i.e. https://github.com/lunny/tango.git
-`--auth_username lunny`: The username to visit the clone_addr
-`--auth_password <password>`: The password to visit the clone_addr
-`--auth_token <token>`: The personal token to visit the clone_addr
-`--owner_name lunny`: The data will be stored on a directory with owner name if not empty
-`--repo_name tango`: The data will be stored on a directory with repository name if not empty
-`--units <units>`: Which items will be migrated, one or more units should be separated as comma. wiki, issues, labels, releases, release_assets, milestones, pull_requests, comments are allowed. Empty means all units.
### restore-repo
Restore-repo restore repository data from disk dir:
- Options:
-`--repo_dir dir`, `-r dir`: Repository dir path to restore from
-`--owner_name lunny`: Restore destination owner name
-`--repo_name tango`: Restore destination repository name
-`--units <units>`: Which items will be restored, one or more units should be separated as comma. wiki, issues, labels, releases, release_assets, milestones, pull_requests, comments are allowed. Empty means all units.
### actions generate-runner-token
Generate a new token for a runner to use to register with the server
- Options:
-`--scope {owner}[/{repo}]`, `-s {owner}[/{repo}]`: To limit the scope of the runner, no scope means the runner can be used for all repos, but you can also limit it to a specific repo or owner
To register a global runner:
```bash
gitea actions generate-runner-token
```
To register a runner for a specific organization, in this case `org`:
```bash
gitea actions generate-runner-token -s org
```
To register a runner for a specific repo, in this case `username/test-repo`:
If the `CustomPath` folder can't be found despite checking `gitea help`, check the `GITEA_CUSTOM`
environment variable; this can be used to override the default path to something else.
`GITEA_CUSTOM` might, for example, be set by an init script. You can check whether the value
is set under the "Configuration" tab on the site administration page.
- [List of Environment Variables](../administration/environment-variables.md)
:::note
Gitea must perform a full restart to see configuration changes.
:::
## Serving custom public files
To make Gitea serve custom public files (like pages and images), use the folder
`$GITEA_CUSTOM/public/` as the webroot. Symbolic links will be followed.
At the moment, only the following files are served:
-`public/robots.txt`
- files in the `public/.well-known/` folder
- files in the `public/assets/` folder
For example, a file `image.png` stored in `$GITEA_CUSTOM/public/assets/`, can be accessed with
the url `http://gitea.domain.tld/assets/image.png`.
## Changing the logo
To build a custom logo and/or favicon clone the Gitea source repository, replace `assets/logo.svg` and/or `assets/favicon.svg` and run
`make generate-images`. `assets/favicon.svg` is used for the favicon only. This will update below output files which you can then place in `$GITEA_CUSTOM/public/assets/img` on your server:
-`public/assets/img/logo.svg` - Used for site icon, app icon
-`public/assets/img/logo.png` - Used for Open Graph
-`public/assets/img/avatar_default.png` - Used as the default avatar image
-`public/assets/img/apple-touch-icon.png` - Used on iOS devices for bookmarks
-`public/assets/img/favicon.svg` - Used for favicon
-`public/assets/img/favicon.png` - Used as fallback for browsers that don't support SVG favicons
In case the source image is not in vector format, you can attempt to convert a raster image using tools like [this](https://www.aconvert.com/image/png-to-svg/).
## Customizing Gitea pages and resources
Gitea's executable contains all the resources required to run: templates, images, style-sheets
and translations. Any of them can be overridden by placing a replacement in a matching path
inside the `custom` directory. For example, to replace the default `.gitignore` provided
for C++ repositories, we want to replace `options/gitignore/C++`. To do this, a replacement
must be placed in `$GITEA_CUSTOM/options/gitignore/C++` (see about the location of the `CustomPath`
directory at the top of this document).
Every single page of Gitea can be changed. Dynamic content is generated using [go templates](https://pkg.go.dev/html/template),
which can be modified by placing replacements below the `$GITEA_CUSTOM/templates` directory.
To obtain any embedded file (including templates), the [`gitea embedded` tool](../administration/cmd-embedded.md) can be used. Alternatively, they can be found in the [`templates`](https://github.com/go-gitea/gitea/tree/main/templates) directory of Gitea source (Note: the example link is from the `main` branch. Make sure to use templates compatible with the release you are using).
Be aware that any statement contained inside `{{` and `}}` are Gitea's template syntax and
shouldn't be touched without fully understanding these components.
Gitea pages are protected by Content-Security-Policy, use `<script nonce="{{ctx.CspScriptNonce}}">...</script>` for script tags.
### Customizing startpage / homepage
Copy [`home.tmpl`](https://github.com/go-gitea/gitea/blob/main/templates/home.tmpl) for your version of Gitea from `templates` to `$GITEA_CUSTOM/templates`.
Edit as you wish.
Dont forget to restart your Gitea to apply the changes.
### Adding links and tabs
If all you want is to add extra links to the top navigation bar or footer, or extra tabs to the repository view, you can put them in `extra_links.tmpl` (links added to the navbar), `extra_links_footer.tmpl` (links added to the left side of footer), and `extra_tabs.tmpl` inside your `$GITEA_CUSTOM/templates/custom/` directory.
For instance, let's say you are in Germany and must add the famously legally-required "Impressum"/about page, listing who is responsible for the site's content:
just place it under your "$GITEA_CUSTOM/public/assets/" directory (for instance `$GITEA_CUSTOM/public/assets/impressum.html`) and put a link to it in either `$GITEA_CUSTOM/templates/custom/extra_links.tmpl` or `$GITEA_CUSTOM/templates/custom/extra_links_footer.tmpl`.
To match the current style, the link should have the class name "item", and you can use `{{AppSubUrl}}` to get the base URL:
For more information, see [Adding Legal Pages](../administration/adding-legal-pages.md).
You can add new tabs in the same way, putting them in `extra_tabs.tmpl`.
The exact HTML needed to match the style of other tabs is in the file
`templates/repo/header.tmpl`
([source in GitHub](https://github.com/go-gitea/gitea/blob/main/templates/repo/header.tmpl))
### Other additions to the page
Apart from `extra_links.tmpl` and `extra_tabs.tmpl`, there are other useful templates you can put in your `$GITEA_CUSTOM/templates/custom/` directory:
-`header.tmpl`, just before the end of the `<head>` tag where you can add custom CSS files for instance.
-`body_outer_pre.tmpl`, right after the start of `<body>`.
-`body_inner_pre.tmpl`, before the top navigation bar, but already inside the main container `<div class="full height">`.
-`body_inner_post.tmpl`, before the end of the main container.
-`body_outer_post.tmpl`, before the bottom `<footer>` element.
-`footer.tmpl`, right before the end of the `<body>` tag, a good place for additional JavaScript.
### Using Gitea variables
It's possible to use various Gitea variables in your custom templates.
First, _temporarily_ enable development mode: in your `app.ini` change from `RUN_MODE = prod` to `RUN_MODE = dev`. Then add `{{ $ | DumpVar }}` to any of your templates, restart Gitea and refresh that page; that will dump all available variables.
Find the data that you need, and use the corresponding variable; for example, if you need the name of the repository then you'd use `{{.Repository.Name}}`.
If you need to transform that data somehow, and aren't familiar with Go, an easy workaround is to add the data to the DOM and add a small JavaScript script block to manipulate the data.
### Example: PlantUML
You can add [PlantUML](https://plantuml.com/) support to Gitea's markdown by using a PlantUML server.
The data is encoded and sent to the PlantUML server which generates the picture. There is an online
demo server at http://www.plantuml.com/plantuml, but if you (or your users) have sensitive data you
can set up your own [PlantUML server](https://plantuml.com/server) instead. To set up PlantUML rendering,
copy JavaScript files from https://gitea.com/davidsvantesson/plantuml-code-highlight and put them in your
`$GITEA_CUSTOM/public/assets/` folder. Then add the following to `$GITEA_CUSTOM/templates/custom/footer.tmpl`:
You can then add blocks like the following to your markdown:
```plantuml
Alice -> Bob: Authentication Request
Bob --> Alice: Authentication Response
Alice -> Bob: Another authentication Request
Alice <-- Bob: Another authentication Response
```
The script will detect tags with `class="language-plantuml"`, but you can change this by providing a second argument to `parsePlantumlCodeBlocks`.
### Example: CAD Files Preview using Online 3D Viewer
You can implement CAD file preview inside your Gitea instance. This implemenation uses [`Online 3D Viewer`](https://github.com/kovacsv/Online3DViewer).
Override by making a copy of the file under `$GITEA_CUSTOM/templates/mail` using a
full path structure matching source.
Any statement contained inside `{{` and `}}` are Gitea's template
syntax and shouldn't be touched without fully understanding these components.
## Adding Analytics to Gitea
Google Analytics, Matomo (previously Piwik), and other analytics services can be added to Gitea. To add the tracking code, refer to the `Other additions to the page` section of this document, and add the JavaScript to the `$GITEA_CUSTOM/templates/custom/header.tmpl` file.
## Customizing gitignores, labels, licenses, locales, and readmes
Place custom files in corresponding sub-folder under `custom/options`.
:::note
The files should not have a file extension, e.g. `Labels` rather than `Labels.txt`
:::
### gitignores
To add custom .gitignore, add a file with existing [.gitignore rules](https://git-scm.com/docs/gitignore) in it to `$GITEA_CUSTOM/options/gitignore`
## Customizing the git configuration
Starting with Gitea 1.20, you can customize the git configuration via the `git.config` section.
### Enabling signed git pushes
[Signed pushes](https://git-scm.com/docs/git-push#Documentation/git-push.txt---signedtruefalseif-asked) allow clients to cryptographically sign the push operation itself (not just individual commits). To enable signed pushes, add the following to `app.ini`:
```ini
[git.config]
receive.certNonceSeed=<randomstring>
```
`certNonceSeed` should be set to a random string and be kept secret. It is used to generate anti-replay nonces. Gitea already sets `receive.advertisePushOptions = true` by default, so no additional configuration is needed. Note that Gitea does not read `/etc/gitconfig`, so this option must be set via `app.ini` as shown above.
On the client side, pushes can be signed via `git push --signed` or enabled permanently using `git config --global push.gpgSign if-asked`.
### Labels
Starting with Gitea 1.19, you can add a file that follows the [YAML label format](https://github.com/go-gitea/gitea/blob/main/options/label/Advanced.yaml) to `$GITEA_CUSTOM/options/label`:
```yaml
labels:
- name:"foo/bar"# name of the label that will appear in the dropdown
exclusive:true# whether to use the exclusive namespace for scoped labels. scoped delimiter is /
color:aabbcc# hex colour coding
description:Some label# long description of label intent
```
The [legacy file format](https://github.com/go-gitea/gitea/blob/main/options/label/Default) can still be used following the format below, however we strongly recommend using the newer YAML format instead.
`#hex-color label name ; label description`
For more information, see the [labels documentation](../usage/issues-prs/labels.md).
### Licenses
To add a custom license, add a file with the license text to `$GITEA_CUSTOM/options/license`
### Locales
Locales are managed via our [Crowdin](https://crowdin.com/project/gitea).
You can override a locale by placing an altered locale file in `$GITEA_CUSTOM/options/locale`.
Gitea's default locale files can be found in the [`options/locale`](https://github.com/go-gitea/gitea/tree/main/options/locale) source folder and these should be used as examples for your changes.
To add a completely new locale, as well as placing the file in the above location, you will need to add the new lang and name to the `[i18n]` section in your `app.ini`. Keep in mind that Gitea will use those settings as **overrides**, so if you want to keep the other languages as well you will need to copy/paste the default values and add your own to them.
```ini title="app.ini"
[i18n]
LANGS = en-US,foo-BAR
NAMES = English,FooBar
```
The first locale will be used as the default if user browser's language doesn't match any locale in the list.
Locales may change between versions, so keeping track of your customized locales is highly encouraged.
### Readmes
To add a custom Readme, add a markdown formatted file (without an `.md` extension) to `$GITEA_CUSTOM/options/readme`
:::note
Readme templates support **variable expansion**.
currently there are `{Name}` (name of repository), `{Description}`, `{CloneURL.SSH}`, `{CloneURL.HTTPS}` and `{OwnerName}`
:::
### Reactions
To change reaction emoji's you can set allowed reactions at app.ini
A full list of supported emoji's is at [emoji list](https://gitea.com/gitea/gitea.com/issues/8)
## Customizing the look of Gitea
The built-in themes are `gitea-light`, `gitea-dark`, and `gitea-auto` (which automatically adapts to OS settings).
The default theme can be changed via `DEFAULT_THEME` in the [ui](../administration/config-cheat-sheet.md#ui-ui) section of `app.ini`.
Gitea also has support for user themes, which means every user can select which theme should be used.
The list of themes a user can choose from can be configured with the `THEMES` value in the [ui](../administration/config-cheat-sheet.md#ui-ui) section of `app.ini`.
To make a custom theme available to all users:
1. Add a CSS file to `$GITEA_CUSTOM/public/assets/css/theme-<theme-name>.css`.
The value of `$GITEA_CUSTOM` of your instance can be queried by calling `gitea help` and looking up the value of "CustomPath".
2. Add `<theme-name>` to the comma-separated list of setting `THEMES` in `app.ini`, or leave `THEMES` empty to allow all themes.
A custom theme file named `theme-my-theme.css` will be displayed as `my-theme` on the user's theme selection page.
It could add theme meta information into the custom theme CSS file to provide more information about the theme.
If a custom theme is a dark theme, please set the global css variable `--is-dark-theme: true` in the `:root` block.
This allows Gitea to adjust the Monaco code editor's theme accordingly.
An "auto" theme could be implemented by using "theme-gitea-auto.css" as a reference.
```css
gitea-theme-meta-info {
--theme-display-name: "My Awesome Theme"; /* this theme will be display as "My Awesome Theme" on the UI */
}
:root {
--is-dark-theme: true; /* if it is a dark theme */
--color-primary: #112233;
/* more custom theme variables ... */
}
```
Community themes are listed in [gitea/awesome-gitea#themes](https://gitea.com/gitea/awesome-gitea#themes).
The default theme sources can be found [here](https://github.com/go-gitea/gitea/blob/main/web_src/css/themes).
Gitea has mailer functionality for sending transactional emails (such as registration confirmation). It can be configured to either use Sendmail (or compatible MTAs like Postfix and msmtp) or directly use SMTP server.
:::note
Be sure to set ENABLE_NOTIFY_MAIL=true to allow Gitea to send email notifications. Check the [Config Cheat Sheet](../administration/config-cheat-sheet.md#service-service) for details.
:::
## Using Sendmail
Use `sendmail` command as mailer.
:::note
For use in the official Gitea Docker image, please configure with the SMTP version (see the following section).
:::
:::note
For Internet-facing sites consult documentation of your MTA for instructions to send emails over TLS. Also set up SPF, DMARC, and DKIM DNS records to make emails sent be accepted as legitimate by various email providers.
Restart Gitea for the configuration changes to take effect.
To send a test email to validate the settings, go to Gitea > Site Administration > Configuration > Summary -> Mailer Configuration.
For the full list of options check the [Config Cheat Sheet](../administration/config-cheat-sheet.md)
:::note
Authentication is only supported when the SMTP server communication is encrypted with TLS or `HOST=localhost`. TLS encryption can be through:
:::
- STARTTLS (also known as Opportunistic TLS) via port 587 with `PROTOCOL=smtp+starttls`. Initial connection is done over cleartext, but then be upgraded over TLS if the server supports it.
- SMTPS connection (SMTP over TLS) via the default port 465. Connection to the server use TLS from the beginning.
- Forced SMTPS connection with `PROTOCOL=smtps`. (These are both known as Implicit TLS.)
This is due to protections imposed by the Go internal libraries against STRIPTLS attacks.
Note that Implicit TLS is recommended by [RFC8314](https://tools.ietf.org/html/rfc8314#section-3) since 2018.
### Gmail
The following configuration should work with GMail's SMTP server:
```ini title="app.ini"
[mailer]
ENABLED = true
HOST = smtp.gmail.com:465 ; Remove this line for Gitea >= 1.18.0
Note that you'll need to create and use an [App password](https://support.google.com/accounts/answer/185833?hl=en) by enabling 2FA on your Google
account. You won't be able to use your Google account password directly.
An app password is the only option: Google
[turned off access for apps that sign in with a password alone](https://workspaceupdates.googleblog.com/2023/09/winding-down-google-sync-and-less-secure-apps-support.html)
in 2024 and asks applications to use OAuth instead, which Gitea's mailer does
not implement — it authenticates with `CRAM-MD5`, `PLAIN` or `LOGIN`.
Two things to keep in mind:
- app passwords require 2-step verification on the account, and a Google
Workspace administrator can switch them off for the whole domain. If they are
- port `587` works as well, with `SMTP_PORT = 587` and
`PROTOCOL = smtp+starttls`.
### ProtonMail
This feature is currently only available for select Proton for Business customers and those with Visionary and Family plans with custom domain addresses. See [ProtonMail's SMTP documentation](https://proton.me/support/smtp-submission) for more information. This limitation can be circumvented by using the ProtonMail Bridge application.
Note that emails sent using SMTP are not [end-to-end encrypted](https://proton.me/support/proton-mail-encryption-explained). However, they’re still stored with zero-access encryption like any other emails in your Proton Mail inbox.
The following configuration should work with ProtonMail's SMTP server:
1. In your browser (or desktop application), sign in to your Proton Mail account and select **Settings → All settings → Proton Mail → IMAP/SMTP → SMTP tokens**.
2. Click **Generate token**.
3. Enter the following details to create a new SMTP token:
- **Token name**: Select a name for your token. This is for your reference only and does not affect the token's functionality.
- **Email address**: Select one of your active custom domain addresses to pair with your token. Copy this email address and use it for the `FROM` and `USER` configuration in `app.ini`.
4. Click **Generate**.
5. Enter your Proton Mail Account password.
Your SMTP username and SMTP token (password) will be generated. You can now enter them as the `USER` and `PASSWD` in your `app.ini` configuration.
After closing the popup, you will not be able to see this SMTP token (password) again for security reasons. You can always generate more tokens if you need to rotate passwords.
Note: Your Proton Mail login or mailbox passwords will not work with SMTP
This supports rendering of whole files. If you want to render code blocks in markdown you would need to do something with javascript. See some examples on the [Customizing Gitea](administration/customizing-gitea.md) page.
This supports rendering of whole files. If you want to render code blocks in markdown you would need to do something with javascript. See some examples on the [Customizing Gitea](../administration/customizing-gitea.md) page.
## Installing external binaries
@@ -32,18 +23,16 @@ In order to get file rendering through external binaries, their associated packa
If you're using a Docker image, your `Dockerfile` should contain something along this lines:
**Remember that fail2ban is powerful and can cause lots of issues if you do it incorrectly, so make
sure to test this before relying on it so you don't lock yourself out.**
Gitea returns an HTTP 200 for bad logins in the web logs, but if you have logging options on in
`app.ini`, then you should be able to go off of `log/gitea.log`, which gives you something like this
on a bad authentication from the web or CLI using SSH or HTTP respectively:
```log
2018/04/26 18:15:54 [I] Failed authentication attempt for user from xxx.xxx.xxx.xxx
```
```log
2020/10/15 16:05:09 modules/ssh/ssh.go:143:publicKeyHandler() [W] Failed authentication attempt from xxx.xxx.xxx.xxx
```
(DEPRECATED: This may be a false positive as the user may still go on to correctly authenticate.)
```log
2020/10/15 16:05:09 modules/ssh/ssh.go:155:publicKeyHandler() [W] Failed authentication attempt from xxx.xxx.xxx.xxx
```
(DEPRECATED: This may be a false positive as the user may still go on to correctly authenticate.)
```log
2020/10/15 16:05:09 modules/ssh/ssh.go:198:publicKeyHandler() [W] Failed authentication attempt from xxx.xxx.xxx.xxx
```
(DEPRECATED: This may be a false positive as the user may still go on to correctly authenticate.)
```log
2020/10/15 16:05:09 modules/ssh/ssh.go:213:publicKeyHandler() [W] Failed authentication attempt from xxx.xxx.xxx.xxx
```
(DEPRECATED: This may be a false positive as the user may still go on to correctly authenticate.)
```log
2020/10/15 16:05:09 modules/ssh/ssh.go:227:publicKeyHandler() [W] Failed authentication attempt from xxx.xxx.xxx.xxx
```
(DEPRECATED: This may be a false positive as the user may still go on to correctly authenticate.)
```log
2020/10/15 16:05:09 modules/ssh/ssh.go:249:sshConnectionFailed() [W] Failed authentication attempt from xxx.xxx.xxx.xxx
```
(From 1.15 this new message will available and doesn't have any of the false positive results that above messages from publicKeyHandler do. This will only be logged if the user has completely failed authentication.)
```log
2020/10/15 16:08:44 ...s/context/context.go:204:HandleText() [E] invalid credentials from xxx.xxx.xxx.xxx
```
Add our filter in `/etc/fail2ban/filter.d/gitea.local`:
```ini
# gitea.local
[Definition]
failregex=.*(Failed authentication attempt|invalid credentials|Attempted access of unknown user).* from <HOST>
ignoreregex=
```
Add our jail in `/etc/fail2ban/jail.d/gitea.local`:
```ini
[gitea]
enabled=true
filter=gitea
logpath=/var/lib/gitea/log/gitea.log
maxretry=10
findtime=3600
bantime=900
action=iptables-allports
```
If you're using Docker, you'll also need to add an additional jail to handle the **FORWARD**
chain in **iptables**. Configure it in `/etc/fail2ban/jail.d/gitea-docker.local`:
```ini
[gitea-docker]
enabled=true
filter=gitea
logpath=/var/lib/gitea/log/gitea.log
maxretry=10
findtime=3600
bantime=900
action=iptables-allports[chain="FORWARD"]
```
Then simply run `service fail2ban restart` to apply your changes. You can check to see if
fail2ban has accepted your configuration using `service fail2ban status`.
Make sure and read up on fail2ban and configure it to your needs, this bans someone
for **15 minutes** (from all ports) when they fail authentication 10 times in an hour.
If you run Gitea behind a reverse proxy with Nginx (for example with Docker), you need to add
this to your Nginx configuration so that IPs don't show up as 127.0.0.1:
```
proxy_set_header X-Real-IP $remote_addr;
```
The security options in `app.ini` need to be adjusted to allow the interpretation of the headers
as well as the list of IP addresses and networks that describe trusted proxy servers
(See the [configuration cheat sheet](../administration/config-cheat-sheet.md#security-security) for more information).
```
REVERSE_PROXY_LIMIT = 1
REVERSE_PROXY_TRUSTED_PROXIES = 127.0.0.1/8 ; 172.17.0.0/16 for the docker default network
Before you enable HTTPS, make sure that you have valid SSL/TLS certificates.
You could use self-generated certificates for evaluation and testing. Please run `gitea cert --host [HOST]` to generate a self signed certificate.
If you are using Apache or nginx on the server, it's recommended to check the [reverse proxy guide](./reverse-proxies.md).
To use Gitea's built-in HTTPS support, you must change your `app.ini` file:
```ini
[server]
PROTOCOL=https
ROOT_URL=https://git.example.com:3000/
HTTP_PORT=3000
CERT_FILE=cert.pem
KEY_FILE=key.pem
```
Note that if your certificate is signed by a third party certificate authority (i.e. not self-signed), then cert.pem should contain the certificate chain. The server certificate must be the first entry in cert.pem, followed by the intermediaries in order (if any). The root certificate does not have to be included because the connecting client must already have it in order to establish the trust relationship.
To learn more about the config values, please checkout the [Config Cheat Sheet](./config-cheat-sheet.md#server-server).
For the `CERT_FILE` or `KEY_FILE` field, the file path is relative to the `GITEA_CUSTOM` environment variable when it is a relative path. It can be an absolute path as well.
### Setting up HTTP redirection
The Gitea server is only able to listen to one port; to redirect HTTP requests to the HTTPS port, you will need to enable the HTTP redirection service:
```ini
[server]
REDIRECT_OTHER_PORT=true
; Port the redirection service should listen on
PORT_TO_REDIRECT=3080
```
If you are using Docker, make sure that this port is configured in your `docker-compose.yml` file.
## Using ACME (Default: Let's Encrypt)
[ACME](https://tools.ietf.org/html/rfc8555) is a Certificate Authority standard protocol that allows you to automatically request and renew SSL/TLS certificates. [Let's Encrypt](https://letsencrypt.org/) is a free publicly trusted Certificate Authority server using this standard. Only `HTTP-01` and `TLS-ALPN-01` challenges are implemented. In order for ACME challenges to pass and verify your domain ownership, external traffic to the gitea domain on port `80` (`HTTP-01`) or port `443` (`TLS-ALPN-01`) has to be served by the gitea instance. Setting up [HTTP redirection](#setting-up-http-redirection) and port-forwards might be needed for external traffic to route correctly. Normal traffic to port `80` will otherwise be automatically redirected to HTTPS. **You must consent** to the ACME provider's terms of service (default Let's Encrypt's [terms of service](https://letsencrypt.org/documents/LE-SA-v1.2-November-15-2017.pdf)).
Minimum setup using the default Let's Encrypt:
```ini
[server]
PROTOCOL=https
DOMAIN=git.example.com
ENABLE_ACME=true
ACME_ACCEPTTOS=true
ACME_DIRECTORY=https
;; Email can be omitted here and provided manually at first run, after which it is cached
Minimum setup using a [smallstep CA](https://github.com/smallstep/certificates), refer to [their tutorial](https://smallstep.com/docs/tutorials/acme-challenge) for more information.
Enabling HTTPS only at the proxy level is referred as [TLS Termination Proxy](https://en.wikipedia.org/wiki/TLS_termination_proxy). The proxy server accepts incoming TLS connections, decrypts the contents, and passes the now unencrypted contents to Gitea. This is normally fine as long as both the proxy and Gitea instances are either on the same machine, or on different machines within private network (with the proxy is exposed to outside network). If your Gitea instance is separated from your proxy over a public network, or if you want full end-to-end encryption, you can also [enable HTTPS support directly in Gitea using built-in server](#using-the-built-in-server) and forward the connections over HTTPS instead.
The logging configuration of Gitea mainly consists of 3 types of components:
- The `[log]` section for general configuration
-`[log.<mode-name>]` sections for the configuration of different log writers to output logs, aka: "writer mode", the mode name is also used as "writer name".
- The `[log]` section can also contain sub-logger configurations following the key schema `logger.<logger-name>.<CONFIG-KEY>`
There is a fully functional log output by default, so it is not necessary to define one.
Security-relevant events (repository transfers, token changes, impersonation, and similar) are recorded separately. See [Audit Logging](audit-logging.md).
## Collecting Logs for Help
To collect logs for help and issue report, see [Support Options](help/support.md).
## The `[log]` section
Configuration of logging facilities in Gitea happen in the `[log]` section and its subsections.
In the top level `[log]` section the following configurations can be placed:
-`ROOT_PATH`: (Default: **%(GITEA_WORK_DIR)/log**): Base path for log files
-`MODE`: (Default: **console**) List of log outputs to use for the Default logger.
-`LEVEL`: (Default: **Info**) Least severe log events to persist, case-insensitive. Possible values are: `Trace`, `Debug`, `Info`, `Warn`, `Error`, `Fatal`.
-`STACKTRACE_LEVEL`: (Default: **None**) For this and more severe events the stacktrace will be printed upon getting logged.
And it can contain the following sub-loggers:
-`logger.router.MODE`: (Default: **,**): List of log outputs to use for the Router logger.
-`logger.access.MODE`: (Default: **_empty_**) List of log outputs to use for the Access logger. By default, the access logger is disabled.
-`logger.xorm.MODE`: (Default: **,**) List of log outputs to use for the XORM logger.
Setting a comma (`,`) to sub-logger's mode means making it use the default global `MODE`.
## Quick samples
### Default (empty) Configuration
The empty configuration is equivalent to default:
```ini
[log]
ROOT_PATH=%(GITEA_WORK_DIR)/log
MODE=console
LEVEL=Info
STACKTRACE_LEVEL=None
logger.router.MODE=,
logger.xorm.MODE=,
logger.access.MODE=
; this is the config options of "console" mode (used by MODE=console above)
[log.console]
MODE=console
FLAGS=stdflags
PREFIX=
COLORIZE=true
```
This is equivalent to sending all logs to the console, with default Golang log being sent to the console log too.
This is only a sample, and it is the default, do not need to write it into your configuration file.
### Disable Router logs and record some access logs to file
The Router logger is disabled, the access logs (>=Warn) goes into `access.log`:
```ini
[log]
logger.router.MODE=
logger.access.MODE=access-file
[log.access-file]
MODE=file
LEVEL=Warn
FILE_NAME=access.log
```
### Set different log levels for different modes
Default logs (>=Warn) goes into `gitea.log`, while Error logs goes into `file-error.log`:
```ini
[log]
LEVEL=Warn
MODE=file, file-error
; by default, the "file" mode will record logs to %(log.ROOT_PATH)/gitea.log, so we don't need to set it
; [log.file]
; by default, the MODE (actually it's the output writer of this logger) is taken from the section name, so we don't need to set it either
; MODE = file
[log.file-error]
MODE=file
LEVEL=Error
FILE_NAME=file-error.log
```
## Log outputs (mode and writer)
Gitea provides the following log output writers:
-`console` - Log to `stdout` (or `stderr` if it is set in the config)
-`file` - Log to a file
-`conn` - Log to a socket (network or unix)
### Common configuration
Certain configuration is common to all modes of log output:
-`MODE` is the mode of the log output writer. It will default to the mode name in the ini section. Thus `[log.console]` will default to `MODE = console`.
-`LEVEL` is the lowest level that this output will log.
-`STACKTRACE_LEVEL` is the lowest level that this output will print a stacktrace.
-`COLORIZE` will default to `true` for `console` as described, otherwise it will default to `false`.
#### `EXPRESSION`
`EXPRESSION` represents a regular expression that log events must match to be logged by the output writer.
Either the log message, (with colors removed), must match or the `longfilename:linenumber:functionname` must match.
NB: the whole message or string doesn't need to completely match.
Please note this expression will be run in the writer's goroutine but not the logging event goroutine.
#### `FLAGS`
`FLAGS` represents the preceding logging context information that is
printed before each message. It is a comma-separated string set. The order of values does not matter.
It defaults to `stdflags` (= `date,time,medfile,shortfuncname,levelinitial`)
Possible values are:
-`none` or `,` - No flags.
-`date` - the date in the local time zone: `2009/01/23`.
-`time` - the time in the local time zone: `01:23:23`.
-`longfile` - full file name and line number: `/a/b/c/d.go:23`.
-`shortfile` - final file name element and line number: `d.go:23`.
-`funcname` - function name of the caller: `runtime.Caller()`.
-`shortfuncname` - last part of the function name. Overrides `funcname`.
-`utc` - if date or time is set, use UTC rather than the local time zone.
-`levelinitial` - initial character of the provided level in brackets eg. `[I]` for info.
-`level` - level in brackets `[INFO]`.
-`gopid` - the Goroutine-PID of the context.
-`medfile` - last 20 characters of the filename - equivalent to `shortfile,longfile`.
-`stdflags` - equivalent to `date,time,medfile,shortfuncname,levelinitial`.
### Console mode
In this mode the logger will forward log messages to the stdout and
stderr streams attached to the Gitea process.
For loggers in console mode, `COLORIZE` will default to `true` if not
on windows, or the Windows terminal can be set into ANSI mode or is a
cygwin or Msys pipe.
Settings:
-`STDERR`: **false**: Whether the logger should print to `stderr` instead of `stdout`.
### File mode
In this mode the logger will save log messages to a file.
Settings:
-`FILE_NAME`: The file to write the log events to, relative to `ROOT_PATH`, Default to `%(ROOT_PATH)/gitea.log`. Exception: access log will default to `%(ROOT_PATH)/access.log`.
-`MAX_SIZE_SHIFT`: **28**: Maximum size shift of a single file. 28 represents 256Mb. For details see below.
-`LOG_ROTATE`**true**: Whether to rotate the log files. TODO: if false, will it delete instead on daily rotate, or do nothing?.
-`DAILY_ROTATE`: **true**: Whether to rotate logs daily.
-`MAX_DAYS`: **7**: Delete rotated log files after this number of days.
-`COMPRESS`: **true**: Whether to compress old log files by default with gzip.
-`COMPRESSION_LEVEL`: **-1**: Compression level. For details see below.
`MAX_SIZE_SHIFT` defines the maximum size of a file by left shifting 1 the given number of times (`1 << x`).
The exact behavior at the time of v1.17.3 can be seen [here](https://github.com/go-gitea/gitea/blob/v1.17.3/modules/setting/log.go#L185).
The useful values of `COMPRESSION_LEVEL` are from 1 (best speed) to 9 (best compression). [DefaultCompression](https://pkg.go.dev/compress/gzip#pkg-constants) (-1) and [HuffmanOnly](https://pkg.go.dev/compress/flate#HuffmanOnly) (-2) can also be chosen.
Beware that better compression might come with higher resource usage.
### Conn mode
In this mode the logger will send log messages over a network socket.
Settings:
-`ADDR`: **:7020**: Sets the address to connect to.
-`PROTOCOL`: **tcp**: Set the protocol, either "tcp", "unix" or "udp".
-`RECONNECT`: **false**: Try to reconnect when connection is lost.
-`RECONNECT_ON_MSG`: **false**: Reconnect host for every single message.
### The "Router" logger
The Router logger logs the following message types when Gitea's route handlers work:
-`started` messages will be logged at TRACE level
-`polling`/`completed` routers will be logged at INFO. Exception: "/assets" static resource requests are also logged at TRACE.
-`slow` routers will be logged at WARN
-`failed` routers will be logged at WARN
### The "XORM" logger
To make XORM outputs SQL logs, the `LOG_SQL` in `[database]` section should also be set to `true`.
### The "Access" logger
The Access logger is a new logger since Gitea 1.9. It provides a NCSA
Common Log compliant log format. It's highly configurable but caution
should be taken when changing its template. The main benefit of this
logger is that Gitea can now log accesses in a standard log format so
standard tools may be used.
You can enable this logger using `logger.access.MODE = ...`.
If desired the format of the Access logger can be changed by changing
the value of the `ACCESS_LOG_TEMPLATE`.
Please note, the access logger will log at `INFO` level, setting the
`LEVEL` of this logger to `WARN` or above will result in no access logs.
#### The ACCESS_LOG_TEMPLATE
This value represents a go template. Its default value is
| `.FallbackSubject` | string | Always | A default subject line. See Below. |
| `.Subject` | string | Only in body | The _subject_, once resolved. |
| `.Body` | string | Always | The message of the issue, pull request or comment, parsed from Markdown into HTML and sanitized. Do not confuse with the _mail body_. |
| `.Link` | string | Always | The address of the originating issue, pull request or comment. |
| `.Issue` | models.Issue | Always | The issue (or pull request) originating the notification. To get data specific to a pull request (e.g. `HasMerged`), `.Issue.PullRequest` can be used, but care should be taken as this field will be `nil` if the issue is _not_ a pull request. |
| `.Comment` | models.Comment | If applicable | If the notification is from a comment added to an issue or pull request, this will contain the information about the comment. |
| `.IsPull` | bool | Always | `true` if the mail notification is associated with a pull request (i.e. `.Issue.PullRequest` is not `nil`). |
| `.Repo` | string | Always | Name of the repository, including owner name (e.g. `mike/stuff`) |
| `.User` | models.User | Always | Owner of the repository from which the event originated. To get the user name (e.g. `mike`),`.User.Name` can be used. |
| `.Doer` | models.User | Always | User that executed the action triggering the notification event. To get the user name (e.g. `rhonda`), `.Doer.Name` can be used. |
| `.IsMention` | bool | Always | `true` if this notification was only generated because the user was mentioned in the comment, while not being subscribed to the source. It will be `false` if the recipient was subscribed to the issue or repository. |
| `.SubjectPrefix` | string | Always | `Re: ` if the notification is about other than issue or pull request creation; otherwise an empty string. |
| `.ActionType` | string | Always | `"issue"` or `"pull"`. Will correspond to the actual _action type_ independently of which template was selected. |
| `.ActionName` | string | Always | It will be one of the action types described above (`new`, `comment`, etc.), and will correspond to the actual _action name_ independently of which template was selected. |
| `.ReviewComments` | []models.Comment | Always | List of code comments in a review. The comment text will be in `.RenderedContent` and the referenced code will be in `.Patch`. |
All names are case sensitive.
### The _subject_ part of the template
The template engine used for the mail _subject_ is golang's [`text/template`](https://go.dev/pkg/text/template/).
Please refer to the linked documentation for details about its syntax.
The _subject_ is built using the following steps:
- A template is selected according to the type of notification and to what templates are present.
- The template is parsed and resolved (e.g. `{{.Issue.Index}}` is converted to the number of the issue
or pull request).
- All space-like characters (e.g. `TAB`, `LF`, etc.) are converted to normal spaces.
- All leading, trailing and redundant spaces are removed.
- The string is truncated to its first 256 runes (characters).
If the end result is an empty string, **or** no subject template was available (i.e. the selected template
did not include a subject part), Gitea's **internal default** will be used.
The internal default (fallback) subject is the equivalent of:
1. Set `[server] ROOT_URL = https://git.example.com/` in your `app.ini` file.
2. Make the reverse-proxy pass `https://git.example.com/foo` to `http://gitea:3000/foo`.
3. Make sure the reverse-proxy does not decode the URI. The request `https://git.example.com/a%2Fb` should be passed as `http://gitea:3000/a%2Fb`.
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.
### Use a sub-path
Usually it's **not recommended** to put Gitea in a sub-path, it's not widely used and may have some issues in rare cases.
To make Gitea work with a sub-path (eg: `https://common.example.com/gitea/`),
there are some extra requirements besides the general configuration above:
1. Use `[server] ROOT_URL = https://common.example.com/gitea/` in your `app.ini` file.
2. Make the reverse-proxy pass `https://common.example.com/gitea/foo` to `http://gitea:3000/foo`.
3. The container registry requires a fixed sub-path `/v2` at the root level which must be configured:
- Make the reverse-proxy pass `https://common.example.com/v2` to `http://gitea:3000/v2`.
- Make sure the URI and headers are also correctly passed (see the general configuration above).
## Nginx
If you want Nginx to serve your Gitea instance, add the following `server` section to the `http` section of `nginx.conf`.
Make sure `client_max_body_size` is large enough, otherwise there would be "413 Request Entity Too Large" error when uploading large files.
Then you **MUST** set something like `[server] ROOT_URL = http://git.example.com/gitea/` correctly in your configuration.
## Nginx and serve static resources directly
We can tune the performance in splitting requests into categories static and dynamic.
CSS files, JavaScript files, images and web fonts are static content.
The front page, a repository view or issue list is dynamic content.
Nginx can serve static resources directly and proxy only the dynamic requests to Gitea.
Nginx is optimized for serving static content, while the proxying of large responses might be the opposite of that
(see [https://serverfault.com/q/587386](https://serverfault.com/q/587386)).
Download a snapshot of the Gitea source repository to `/path/to/gitea/`.
After this, run `make frontend` in the repository directory to generate the static resources. We are only interested in the `public/` directory for this task, so you can delete the rest.
(You will need to have [Node with npm](https://nodejs.org/en/download/) and `make` installed to generate the static resources)
Depending on the scale of your user base, you might want to split the traffic to two distinct servers,
or use a cdn for the static files.
### Single node and single domain
Set `[server] STATIC_URL_PREFIX = /_/static` in your configuration.
```nginx
server {
listen 80;
server_name git.example.com;
location /_/static/assets/ {
alias /path/to/gitea/public/;
}
location / {
# other directives, see the "Nginx" config section above
proxy_pass http://localhost:3000;
}
}
```
### Two nodes and two domains
Set `[server] STATIC_URL_PREFIX = http://cdn.example.com/gitea` in your configuration.
```nginx
# application server running Gitea
server {
listen 80;
server_name git.example.com;
location / {
# other directives, see the "Nginx" config section above
proxy_pass http://localhost:3000;
}
}
```
```nginx
# static content delivery server
server {
listen 80;
server_name cdn.example.com;
location /gitea/ {
alias /path/to/gitea/public/;
}
location / {
return 404;
}
}
```
## Nginx Proxy Manager
If you are using Nginx Proxy Manager to serve your Gitea instance it differs slighly from the raw Nginx.
It is [adding some directives](https://github.com/NginxProxyManager/nginx-proxy-manager/blob/master/docker/rootfs/etc/nginx/conf.d/include/proxy.conf) for a custom location by default, so you have to skip these from the above mentioned Nginx config. Otherwise Nginx will produce `400 bad request` error due to duplicated directives (`proxy_set_header Host $host` is the particularly problematic one).
So while creating the `/` Custom location, just add the following line to it's configuration:
```nginx
client_max_body_size 512M;
```
Enable `Websockets Support` on the proxy host, Nginx Proxy Manager then adds the WebSocket directives itself. Adding `proxy_http_version` on top of that makes Nginx refuse to start.
## Apache HTTPD
If you want Apache HTTPD to serve your Gitea instance, you can add the following to your Apache HTTPD configuration (usually located at `/etc/apache2/httpd.conf` in Ubuntu):
RequestHeader set "X-Forwarded-Proto" expr=%{REQUEST_SCHEME}
</VirtualHost>
```
:::note
The following Apache HTTPD mods must be enabled: `proxy`, `proxy_http`.
`upgrade=websocket` requires Apache HTTPD 2.4.48 or later. On older versions you **MUST** remove it, otherwise Apache HTTPD will not start.
:::
If you wish to use Let's Encrypt with webroot validation, add the line `ProxyPass /.well-known !` before `ProxyPass` to disable proxying these requests to Gitea.
## Apache HTTPD with a sub-path
In case you already have a site, and you want Gitea to share the domain name, you can setup Apache HTTPD to serve Gitea under a sub-path by adding the following to you Apache HTTPD configuration (usually located at `/etc/apache2/httpd.conf` in Ubuntu):
```apacheconf
<VirtualHost *:80>
...
<Proxy *>
Order allow,deny
Allow from all
</Proxy>
AllowEncodedSlashes NoDecode
# Note: no trailing slash after either /git or port
RequestHeader set "X-Forwarded-Proto" expr=%{REQUEST_SCHEME}
</VirtualHost>
```
Then you **MUST** set something like `[server] ROOT_URL = http://git.example.com/git/` correctly in your configuration.
:::note
The following Apache HTTPD mods must be enabled: `proxy`, `proxy_http`. `upgrade=websocket` requires Apache HTTPD 2.4.48 or later, remove it on older versions.
:::
## Apache HTTPD and serve static resources directly
We can tune the performance in splitting requests into categories static and dynamic.
CSS files, JavaScript files, images and web fonts are static content.
The front page, a repository view or issue list is dynamic content.
Apache HTTPD can serve static resources directly and proxy only the dynamic requests to Gitea.
Download a snapshot of the Gitea source repository to `/path/to/gitea/`.
After this, run `make frontend` in the repository directory to generate the static resources. We are only interested in the `public/` directory for this task, so you can delete the rest.
(You will need to have [Node with npm](https://nodejs.org/en/download/) and `make` installed to generate the static resources)
Depending on the scale of your user base, you might want to split the traffic to two distinct servers,
or use a cdn for the static files.
### Single node and single domain
Set `[server] STATIC_URL_PREFIX = /_/static` in your configuration.
```apacheconf
<VirtualHost *:80>
...
<Proxy *>
Order allow,deny
Allow from all
</Proxy>
ProxyPass /_/static/ !
Alias /_/static/ /path/to/gitea/public/
<Directory /path/to/gitea/public/>
Options FollowSymlinks
AllowOverride None
Require all granted
</Directory>
AllowEncodedSlashes NoDecode
# Note: no trailing slash after either /git or port
RequestHeader set "X-Forwarded-Proto" expr=%{REQUEST_SCHEME}
</VirtualHost>
```
:::note
The following Apache HTTPD mods must be enabled: `proxy`, `proxy_http`. `upgrade=websocket` requires Apache HTTPD 2.4.48 or later, remove it on older versions.
:::
## Caddy
If you want Caddy to serve your Gitea instance, you can add the following server block to your Caddyfile:
```
git.example.com {
reverse_proxy localhost:3000
}
```
## Caddy with a sub-path
In case you already have a site, and you want Gitea to share the domain name, you can setup Caddy to serve Gitea under a sub-path by adding the following to your server block in your Caddyfile:
```
git.example.com {
route /git/* {
uri strip_prefix /git
reverse_proxy localhost:3000
}
}
```
Then set `[server] ROOT_URL = http://git.example.com/git/` in your configuration.
## IIS
If you wish to run Gitea with IIS. You will need to setup IIS with URL Rewrite as reverse proxy.
1. Setup an empty website in IIS, named let's say, `Gitea Proxy`.
2. Follow the first two steps in [Microsoft's Technical Community Guide to Setup IIS with URL Rewrite](https://techcommunity.microsoft.com/t5/iis-support-blog/setup-iis-with-url-rewrite-as-a-reverse-proxy-for-real-world/ba-p/846222#M343). That is:
- Install Application Request Routing (ARR for short) either by using the Microsoft Web Platform Installer 5.1 (WebPI) or downloading the extension from [IIS.net](https://www.iis.net/downloads/microsoft/application-request-routing)
- Once the module is installed in IIS, you will see a new Icon in the IIS Administration Console called URL Rewrite.
- Install the `WebSocket Protocol` Windows feature, otherwise ARR cannot proxy Gitea's WebSocket endpoint.
- Open the IIS Manager Console and click on the `Gitea Proxy` Website from the tree view on the left. Select and double click the URL Rewrite Icon from the middle pane to load the URL Rewrite interface.
- Choose the `Add Rule` action from the right pane of the management console and select the `Reverse Proxy Rule` from the `Inbound and Outbound Rules` category.
- In the Inbound Rules section, set the server name to be the host that Gitea is running on with its port. e.g. if you are running Gitea on the localhost with port 3000, the following should work: `127.0.0.1:3000`
- Enable SSL Offloading
- In the Outbound Rules, ensure `Rewrite the domain names of the links in HTTP response` is set and set the `From:` field as above and the `To:` to your external hostname, say: `git.example.com`
- Turn `Reverse rewrite host in response headers` off, in the `Server Proxy Settings` of Application Request Routing (select the server node in the IIS Manager, open `Application Request Routing Cache`, then `Server Proxy Settings` in the right pane). While it is on, ARR rewrites the host of the `Location` header back to the internal one, so the redirects Gitea sends after a sign-in or a form submission send the browser to `127.0.0.1:3000` instead of `git.example.com`.
- Now edit the `web.config` for your website to match the following: (changing `127.0.0.1:3000` and `git.example.com` as appropriate)
If you want HAProxy to serve your Gitea instance, you can add the following to your HAProxy configuration
add an acl in the frontend section to redirect calls to gitea.example.com to the correct backend
```
frontend http-in
...
acl acl_gitea hdr(host) -i gitea.example.com
use_backend gitea if acl_gitea
...
```
add the previously defined backend section
```
backend gitea
server localhost:3000 check
```
If you redirect the http content to https, the configuration work the same way, just remember that the connection between HAProxy and Gitea will be done via http so you do not have to enable https in Gitea's configuration.
## HAProxy with a sub-path
In case you already have a site, and you want Gitea to share the domain name, you can setup HAProxy to serve Gitea under a sub-path by adding the following to you HAProxy configuration:
```
frontend http-in
...
acl acl_gitea path_beg /gitea
use_backend gitea if acl_gitea
...
```
With that configuration http://example.com/gitea/ will redirect to your Gitea instance.
then for the backend section
```
backend gitea
http-request replace-path /gitea\/?(.*) \/\1
server localhost:3000 check
```
The added http-request will automatically add a trailing slash if needed and internally remove /gitea from the path to allow it to work correctly with Gitea by setting properly http://example.com/gitea as the root.
Then you **MUST** set something like `[server] ROOT_URL = http://example.com/gitea/` correctly in your configuration.
## Traefik
If you want traefik to serve your Gitea instance, you can add the following label section to your `docker-compose.yaml` (Assuming the provider is docker).
This config assumes that you are handling HTTPS on the traefik side and using HTTP between Gitea and traefik.
## Traefik with a sub-path
In case you already have a site, and you want Gitea to share the domain name, you can setup Traefik to serve Gitea under a sub-path by adding the following to your `docker-compose.yaml` (Assuming the provider is docker) :
Gitea will verify gpg/ssh commit signatures in the provided tree by
checking if the commits are signed by a key within the Gitea database,
or if the commit matches the default key for Git.
Additionally Gitea will verify commits signed by ssh keys, which public keys are part of [`TRUSTED_SSH_KEYS`](#general-configuration).
Keys are not checked to determine if they have expired or revoked.
Keys are also not checked with keyservers.
A commit will be marked with a grey unlocked icon if no key can be
found to verify it. If a commit is marked with a red unlocked icon,
it is reported to be signed with a key with an id.
:::note
The signer of a commit does not have to be an author or
committer of a commit.
:::
## Automatic Signing
There are a number of places where Gitea will generate commits itself:
- Repository Initialisation
- Wiki Changes
- CRUD actions using the editor or the API
- Merges from Pull Requests
Depending on configuration and server trust you may want Gitea to
sign these commits.
## Installing and generating a GPG key for Gitea
It is up to a server administrator to determine how best to install
a signing key. Gitea generates all its commits using the server `git`
command at present - and therefore the server `gpg` will be used for
signing (if configured.) Administrators should review best-practices
for GPG - in particular it is probably advisable to only install a
signing secret subkey without the master signing and certifying secret
key.
## Installing and generating a SSH key for Gitea
You can run `ssh-keygen -t ed25519 -f gitea-signing-key` to generate the private/public keypair for commit signing without any password. Usually you would store the key next to the gitea configuration, then point `SIGNING_KEY` to the generated public key `/path/to/gitea-signing-key.pub`. Gitea generates all its commits using the server `git` command at present - and therefore the server `ssh-keygen` will be used for
signing (if configured.)
## General Configuration
Gitea's configuration for signing can be found with the
`[repository.signing]` section of `app.ini`:
```ini
...
[repository.signing]
SIGNING_KEY = default
SIGNING_NAME =
SIGNING_EMAIL =
INITIAL_COMMIT = always
CRUD_ACTIONS = pubkey, twofa, parentsigned
WIKI = never
MERGES = pubkey, twofa, basesigned, commitssigned
...
```
---
For SSH commit signing, you need to specify the `SIGNING_FORMAT` to `ssh` instead of the default `openpgp`. `SIGNING_NAME` and `SIGNING_EMAIL` are required for verifing the signatures.
This looks like this:
```ini
...
[repository.signing]
SIGNING_KEY = /path/to/gitea-signing-key.pub
SIGNING_NAME =
SIGNING_EMAIL =
SIGNING_FORMAT = ssh
INITIAL_COMMIT = always
CRUD_ACTIONS = pubkey, twofa, parentsigned
WIKI = never
MERGES = pubkey, twofa, basesigned, commitssigned
...
```
- `/path/to/gitea-signing-key` is expected to be the private key for `/path/to/gitea-signing-key.pub` [see here how to generate a new ssh keypair](#installing-and-generating-a-ssh-key-for-gitea).
- `TRUSTED_SSH_KEYS = ssh-<algorithm> <key>` or `TRUSTED_SSH_KEYS = ssh-<algorithm> <key1>, ssh-<algorithm> <key2>` can be used for rotating the global ssh signing key to continue verifying commits signed by the previous keys.
### `SIGNING_KEY`
The first option to discuss is the `SIGNING_KEY`. There are three main
options:
- `none` - this prevents Gitea from signing any commits
- `default` - Gitea will default to the gpg key configured within `git config`
- `KEYID` - Gitea will sign commits with the gpg key with the ID
`KEYID`. In this case you should provide a `SIGNING_NAME` and
`SIGNING_EMAIL` to be displayed for this key.
- `/path/to/gitea-signing-key.pub` - Gitea will sign commits with the ssh key without the `.pub` suffix `/path/to/gitea-signing-key`. In this case you should provide a `SIGNING_NAME` and
`SIGNING_EMAIL` to be displayed for this key and set `SIGNING_FORMAT` to `ssh`.
The `default` option will interrogate `git config` for
`commit.gpgsign` option - if this is set, then it will use the results
of the `user.signingkey`, `user.name` and `user.email` as appropriate.
By adjusting Git's `config` file within Gitea's
repositories, `SIGNING_KEY=default` could be used to provide different
signing keys on a per-repository basis. However, this is clearly not an
ideal UI and therefore subject to change.
:::warning
**Since 1.17**, Gitea runs git in its own home directory `[git].HOME_PATH` (default to `%(APP_DATA_PATH)/home`)
and uses its own config `{[git].HOME_PATH}/.gitconfig`.
If you have your own customized git config for Gitea, you should set these configs in system git config (aka `/etc/gitconfig`)
or the Gitea internal git config `{[git].HOME_PATH}/.gitconfig`.
Related home files for git command (like `.gnupg`) should also be put in Gitea's git home directory `[git].HOME_PATH`.
If you like to keep the `.gnupg` directory outside of `{[git].HOME_PATH}/`, consider setting the `$GNUPGHOME` environment variable to your preferred location, otherwise Gitea will use the gpg keys only under `{[git].HOME_PATH}/.gnupg`.
:::
:::warning
The default option and repository specific signing keys are not supported for ssh keys
:::
### `INITIAL_COMMIT`
This option determines whether Gitea should sign the initial commit
when creating a repository. The possible values are:
- `never`: Never sign
- `pubkey`: Only sign if the user has a public key
- `twofa`: Only sign if the user logs in with two factor authentication
- `always`: Always sign
Options other than `never` and `always` can be combined as a comma
separated list. The commit will be signed if all selected options are true.
### `WIKI`
This options determines if Gitea should sign commits to the Wiki.
The possible values are:
- `never`: Never sign
- `pubkey`: Only sign if the user has a public key
- `twofa`: Only sign if the user logs in with two-factor authentication
- `parentsigned`: Only sign if the parent commit is signed.
- `always`: Always sign
Options other than `never` and `always` can be combined as a comma
separated list. The commit will be signed if all selected options are true.
### `CRUD_ACTIONS`
This option determines if Gitea should sign commits from the web
editor or API CRUD actions. The possible values are:
- `never`: Never sign
- `pubkey`: Only sign if the user has a public key
- `twofa`: Only sign if the user logs in with two-factor authentication
- `parentsigned`: Only sign if the parent commit is signed.
- `always`: Always sign
Options other than `never` and `always` can be combined as a comma
separated list. The change will be signed if all selected options are true.
### `MERGES`
This option determines if Gitea should sign merge commits from PRs.
The possible options are:
- `never`: Never sign
- `pubkey`: Only sign if the user has a public key
- `twofa`: Only sign if the user logs in with two-factor authentication
- `basesigned`: Only sign if the parent commit in the base repo is signed.
- `headsigned`: Only sign if the head commit in the head branch is signed.
- `commitssigned`: Only sign if all the commits in the head branch to the merge point are signed.
- `approved`: Only sign approved merges to a protected branch.
- `always`: Always sign
Options other than `never` and `always` can be combined as a comma
separated list. The merge will be signed if all selected options are true.
## Obtaining the Public Key of the Signing Key
The public key used to sign Gitea's commits can be obtained from the API at:
```sh
/api/v1/signing-key.gpg
```
In cases where there is a repository specific key this can be obtained from:
```sh
/api/v1/repos/:username/:reponame/signing-key.gpg
```
For ssh commit signing the default ssh public key can be obtained via the API at:
By default, `ENABLE_SWAGGER` is true, and `MAX_RESPONSE_ITEMS` is set to 50. See [Config Cheat Sheet](../administration/config-cheat-sheet.md) for more information.
## Authentication
Gitea supports these methods of API authentication:
- HTTP basic authentication
- `token=...` parameter in URL query string
- `access_token=...` parameter in URL query string
- `Authorization: token ...` header in HTTP headers
- HTTP signatures using an SSH public key or SSH certificate
All of these methods accept the same API key token type. You can
better understand this by looking at the code -- as of this writing,
Gitea parses queries and headers to find the token in
specification (not RFC 9421). The signature is sent in the `Signature` header,
and SSH certificates additionally include an `x-ssh-certificate` header. The
official [go-sdk](https://gitea.com/gitea/go-sdk) implements this flow if you
need a reference implementation.
## Generating and listing API tokens
API tokens can be created either in the user interface or via the API. Tokens have by default limited permissions and it is important to create tokens with the correct permissions for your task.
### User Interface
Tokens can be created via the `Manage Access Tokens` dialog, accessed via `User Settings` / `Applications` or via the link `gitea-domain.example/user/settings/applications`. The interface allows you to create tokens and manage their permissions via the `Select permissions` sub-menu.
Once created, the token is displayed in a toast message above the `Manage Access Tokens` dialog. Please note, that you can view this toast only once and it is not possible to redisplay the token for security reasons.
### Token API
A new token can be generated with a `POST` request to
`/users/:name/tokens`.
Note that `/users/:name/tokens` is a special endpoint and requires you
to authenticate using `BasicAuth` and a password, as follows:
By default, this creates a token with very limited permissions. To complete your tasks, your token may require extra permissions. These permissions are created via the json variable `scopes`, which takes an array of permissions as strings. Eg.: `"scopes":["all"]`. Possible permissions, as reflected in the user interface are:
- `activitypub`
- `admin`
- `issue`
- `misc`
- `notification`
- `organization`
- `package`
- `repository`
- `user`
Each permission may be set to `read` or `write`. `write` implies both Read & Write. To set permissions you write the permissions string as `<read or write>:<permission name>`, eg.: `write:package` or `read:notification`. A properly formatted API call may look like:
As mentioned above, the token used is the same one you would use in
the `token=` string in a GET request.
## Pagination
The API supports pagination. The `page` and `limit` parameters are used to specify the page number and the number of items per page. As well, the `Link` header is returned with the next, previous, and last page links if there are more than one pages. The `x-total-count` is also returned to indicate the total number of items.
API Reference guide is auto-generated by swagger and available on:
`https://gitea.your.host/api/swagger`
or on the
[Gitea instance](https://gitea.com/api/swagger)
The OpenAPI document is at:
`https://gitea.your.host/swagger.v1.json`
## Sudo
The API allows admin users to sudo API requests as another user. Simply add either a `sudo=` parameter or `Sudo:` request header with the username of the user to sudo.
Gitea supports acting as an OAuth2 Provider, allowing third-party applications to access its resources with user consent.
When acting as the OAuth2 Provider, Gitea verifies every authorization request against the related OAuth2 Application. This application can be set up by an individual user, an organization admin, or a Gitea instance admin.
Regardless of who configured the application, the first authorization attempt opens a new page in the user's web browser, prompting them to authorize the application.
## Configuration
An OAuth2 Application in Gitea requires the following configuration made in two steps:
- Confidential client status (`/admin/applications/oauth2/_id_`)

### Third Party Step 3
The third-party (Relaying Parties) application's request must include:
- Credentials (Client ID and Client Secret)
- Desired scope and claims (expected to be provided by Gitea)
An example of MinIO:

### Gitea's User Approval Step 3
For example, logging in with a Gitea account on MinIO...

...will display the approval popup after a successful login:

By default, if the third party sets the scopes to `openid`, `email`, `profile`, and `groups`, and the user approves, the application gains full access to all of the user's public and private resources (repositories, issues, user information, etc.).
> **NOTE:** At present, an admin who sets up the OAuth2 Application in Gitea must rely on the scopes sent by the third party and an approval decision by the informed user if restrictive access is expected. There is no way for the admin to set restrictive access via scopes during the application setup process.
## Granular Scopes
As of version v1.23, Gitea supports granular scopes, allowing third parties to request more limited access. These scopes, previously available only for [Personal Access Tokens](#scopes), enable users to restrict access to specific URL routes.
Scopes are grouped by high-level API routes and further refined as follows:
- `read`: `GET` routes
- `write`: `POST`, `PUT`, `PATCH`, and `DELETE` routes (in addition to `GET`)
For example, a third party can request minimal access, allowing Gitea to act as a simple OpenID Connect (OIDC) Provider. If the third party adds only `public-only` to 'openid', no other or any combination of the scopes `email`, `userinfo`, or `groups`, Gitea will act as a basic Single Sign-On provider. This configuration provides only verification that the user can log in with the correct credentials, supplying only basic information such as username, email, and a list of public memberships in organizations and teams.
When any of the granular scopes known from Personal Access Tokens is introduced, Gitea will not allow full access (as it does by default). Instead, it will build granular access following read or write permissions to resources such as Repositories, Issues, ActivityPub, Admin functions, Organizations, Users, Packages, or miscellaneous features.
> **NOTE:** If third party adds any scope other than the OIDC ones: `openid`, `email`, `profile`, and `groups` or the ones found already in Personal Access Token the scope will fallback to full access as it was the case before v1.23
The approval page displayed to the user shows the list of scopes requested by the third party. Once approved, this decision is remembered. If the third party changes its requested scopes in future requests, the entire flow will fail, requiring re-authorization.
At the moment Gitea only supports the [**Authorization Code Grant**](https://tools.ietf.org/html/rfc6749#section-1.3.1) standard with additional support of the following extensions:
- [Proof Key for Code Exchange (PKCE)](https://tools.ietf.org/html/rfc7636)
To use the Authorization Code Grant as a third party application it is required to register a new application via the "Settings" (`/user/settings/applications`) section of the settings. To test or debug you can use the web-tool https://oauthdebugger.com/.
## Scopes
Gitea supports scoped access tokens, which allow users the ability to restrict tokens to operate only on selected url routes. Scopes are grouped by high-level API routes, and further refined to the following:
- `read`: `GET` routes
- `write`: `POST`, `PUT`, `PATCH`, and `DELETE` routes (in addition to `GET`)
| **read:issue** | Grants read access for issues operations, such as getting issue comments, issue attachments, and milestones. |
| **write:issue** | Grants read/write/delete access for issues operations, such as posting or editing an issue comment or attachment, and updating milestones. |
| **misc** | Reserved for future usage. |
| **read:misc** | Reserved for future usage. |
| **write:misc** | Reserved for future usage. |
| **notification** | `notification/*` API routes: user notification operations. |
| **read:notification** | Grants read access to user notifications, such as which notifications users are subscribed to and read new notifications. |
| **write:notification** | Grants read/write/delete access to user notifications, such as marking notifications as read. |
| **organization** | `orgs/*` and `teams/*` API routes: Organization and team management operations. |
| **read:organization** | Grants read access to org and team status, such as listing all orgs a user has visibility to, teams, and team members. |
| **write:organization** | Grants read/write/delete access to org and team status, such as creating and updating teams and updating org settings. |
| **package** | `/packages/*` API routes: Packages operations |
| **read:package** | Grants read access to package operations, such as reading and downloading available packages. |
| **write:package** | Grants read/write/delete access to package operations. Currently the same as `read:package`. |
| **repository** | `/repos/*` API routes except `/repos/issues/*`: Repository file, pull-request, and release operations. |
| **read:repository** | Grants read access to repository operations, such as getting repository files, releases, collaborators. |
| **write:repository** | Grants read/write/delete access to repository operations, such as getting updating repository files, creating pull requests, updating collaborators. |
| **user** | `/user/*` and `/users/*` API routes: User-related operations. |
| **read:user** | Grants read access to user operations, such as getting user repo subscriptions and user settings. |
| **write:user** | Grants read/write/delete access to user operations, such as updating user repo subscriptions, followed users, and user settings. |
## Pre-configured Applications
Gitea creates OAuth applications for the following services by default on startup, as we assume that these are universally useful.
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`.
## Client types
Gitea supports both confidential and public client types, [as defined by RFC 6749](https://datatracker.ietf.org/doc/html/rfc6749#section-2.1).
For public clients, a redirect URI of a loopback IP address such as `http://127.0.0.1/` allows any port. Avoid using `localhost`, [as recommended by RFC 8252](https://datatracker.ietf.org/doc/html/rfc8252#section-8.3).
## Examples
### Confidential client
:::note
This example does not use PKCE.
:::
1. Redirect the user to the authorization endpoint in order to get their consent for accessing the resources:
The `CLIENT_ID` can be obtained by registering an application in the settings. The `STATE` is a random string that will be sent back to your application after the user authorizes. The `state` parameter is optional, but should be used to prevent CSRF attacks.

The user will now be asked to authorize your application. If they authorize it, the user will be redirected to the `REDIRECT_URL`, for example:
2. Using the provided `code` from the redirect, you can request a new application and refresh token. The access token endpoint accepts POST requests with `application/json` and `application/x-www-form-urlencoded` body, for example:
```curl
POST https://[YOUR-GITEA-URL]/login/oauth/access_token
The `CLIENT_SECRET` is the unique secret code generated for this application. Please note that the secret will only be visible after you created/registered the application with Gitea and cannot be recovered. If you lose the secret, you must regenerate the secret via the application's settings.
The `REDIRECT_URI` in the `access_token` request must match the `REDIRECT_URI` in the `authorize` request.
3. Use the `access_token` to make [API requests](development/api-usage.md#oauth2-provider) to access the user's resources.
### Public client (PKCE)
PKCE (Proof Key for Code Exchange) is an extension to the OAuth flow which allows for a secure credential exchange without the requirement to provide a client secret.
**Note**: Please ensure you have registered your OAuth application as a public client.
To achieve this, you have to provide a `code_verifier` for every authorization request. A `code_verifier` has to be a random string with a minimum length of 43 characters and a maximum length of 128 characters. It can contain alphanumeric characters as well as the characters `-`, `.`, `_` and `~`.
Using this `code_verifier` string, a new one called `code_challenge` is created by using one of two methods:
- If you have the required functionality on your client, set `code_challenge` to be a URL-safe base64-encoded string of the SHA256 hash of `code_verifier`. In that case, your `code_challenge_method` becomes `S256`.
- If you are unable to do so, you can provide your `code_verifier` as a plain string to `code_challenge`. Then you have to set your `code_challenge_method` as `plain`.
After you have generated this values, you can continue with your request.
1. Redirect the user to the authorization endpoint in order to get their consent for accessing the resources:
The `CLIENT_ID` can be obtained by registering an application in the settings. The `STATE` is a random string that will be sent back to your application after the user authorizes. The `state` parameter is optional, but should be used to prevent CSRF attacks.

The user will now be asked to authorize your application. If they authorize it, the user will be redirected to the `REDIRECT_URL`, for example:
2. Using the provided `code` from the redirect, you can request a new application and refresh token. The access token endpoint accepts POST requests with `application/json` and `application/x-www-form-urlencoded` body, for example:
```curl
POST https://[YOUR-GITEA-URL]/login/oauth/access_token
This page contains some common questions and answers.
For more help resources, check all [Support Options](help/support.md).
## Difference between 1.x and 1.x.x downloads, how can I get latest stable release with bug fixes?
Version 1.20.x will be used for this example.
On our [downloads page](https://dl.gitea.com/gitea/) you will see a 1.20 directory, as well as directories for 1.20.0, 1.20.1.
The 1.20 directory is the nightly build, which is built on each merged commit to the [`release/v1.20`](https://github.com/go-gitea/gitea/tree/release/v1.20) branch.
The 1.20.0 directory is a release build that was created when the [`v1.20.0`](https://github.com/go-gitea/gitea/releases/tag/v1.20.0) tag was created.
The nightly builds (1.x) downloads will change as commits are merged to their respective branch, they contain the latest changes/fixes before a tag release is built.
If a bug fix is targeted on 1.20.1 but 1.20.1 is not released yet, you can get the "1.20-nightly" build to get the bug fix.
## How to find the config file "app.ini"
It depends on how you installed Gitea. If you didn't set a path for custom path or config file manually,
then the config file (app.ini) should exists in the "custom/conf" directory of your Gitea's working path.
Some package vendors might use "/etc/gitea" to store the config file, while some others don't.
You could manually find the config file (app.ini) by checking Gitea's startup logs
or reading the Gitea Web's Site Administrator -> Configuration Summary.
If you are using some isolated enviroments like container (docker),
the path you see usually is not what it is in the host's filesystem.
In this case you need to check the container's filesystem volume mapping
and figure out the real path of the config file on the host.
## Where does Gitea store what file
- _`AppWorkPath`_
- The `WORK_PATH` option in `app.ini`
- Else the `--work-path` flag
- Else Environment variable `GITEA_WORK_DIR`
- Else a built-in value set at build time
- Else the directory that contains the Gitea binary
- `AppDataPath` (default for database, indexers, etc.)
- `ROOT` in the \[repository] section of `app.ini` if absolute
- Else _`AppWorkPath`_`/ROOT` if `ROOT` in the \[repository] section of `app.ini` if relative
- Default `%(APP_DATA_PATH)/gitea-repositories`
- INI (config file)
- `--config` flag
- A possible built-in value set a build time
- Else _`CustomPath`_`/conf/app.ini`
- SQLite Database
- `PATH` in `database` section of `app.ini`
- Else `%(APP_DATA_PATH)/gitea.db`
## Not seeing a clone URL or the clone URL being incorrect
There are a few places that could make this show incorrectly.
1. If using a reverse proxy, make sure you have followed the correction directions in the [reverse proxy guide](../administration/reverse-proxies.md)
2. Make sure you have correctly set `ROOT_URL` in the `server` section of your `app.ini`
If certain clone options aren't showing up (HTTP/S or SSH), the following options can be checked in your `app.ini`
- `DISABLE_HTTP_GIT`: if set to true, there will be no HTTP/HTTPS link
- `DISABLE_SSH`: if set to true, there will be no SSH link
- `SSH_EXPOSE_ANONYMOUS`: if set to false, SSH links will be hidden for anonymous users
## File upload fails with: 413 Request Entity Too Large
This error occurs when the reverse proxy limits the file upload size.
See the [reverse proxy guide](../administration/reverse-proxies.md) for a solution with nginx.
## Custom Templates not loading or working incorrectly
Gitea's custom templates must be added to the correct location or Gitea will not find and use them.
The correct path for the template(s) will be relative to the `CustomPath`
1. To find `CustomPath`, look for Custom File Root Path in Site Administration -> Configuration
2. If you are still unable to find a path, the default can be [calculated above](#where-does-gitea-store-what-file)
3. Once you have figured out the correct custom path, you can refer to the [customizing Gitea](../administration/customizing-gitea.md) page to add your template to the correct location.
## Does Gitea have a "GitHub/GitLab pages" feature?
Gitea doesn't provide a built-in Pages server. You need a dedicated domain to serve static pages to avoid CSRF security risks.
For simple usage, you can use a reverse proxy to rewrite & serve static contents from Gitea's raw file URLs.
And there are already available third-party services, like a standalone [pages server](https://codeberg.org/Codeberg/pages-server) or a [caddy plugin](https://github.com/42wim/caddy-gitea), that can provide the required functionality.
## Active user vs login prohibited user
In Gitea, an "active" user refers to a user that has activated their account via email.
A "login prohibited" user is a user that is not allowed to log in to Gitea anymore
## What is Swagger?
[Swagger](https://swagger.io/) is what Gitea uses for its API documentation.
All Gitea instances have the built-in API and there is no way to disable it completely.
You can, however, disable showing its documentation by setting `ENABLE_SWAGGER` to `false` in the `api` section of your `app.ini`.
For more information, refer to Gitea's [API docs](development/api-usage.md).
You can see the latest API (for example) on https://gitea.com/api/swagger
You can also see an example of the `swagger.json` file at https://gitea.com/swagger.v1.json
## Adjusting your server for public/private use
### Preventing spammers
There are multiple things you can combine to prevent spammers.
1. By whitelisting or blocklisting certain email domains
2. By only whitelisting certain domains with OpenID (see below)
3. Setting `ENABLE_CAPTCHA` to `true` in your `app.ini` and properly configuring `RECAPTCHA_SECRET` and `RECAPTCHA_SITEKEY`
4. Settings `DISABLE_REGISTRATION` to `true` and creating new users via the [CLI](../administration/command-line.md), [API](development/api-usage.md), or Gitea's Admin UI
### Only allow/block certain email domains
You can configure `EMAIL_DOMAIN_WHITELIST` or `EMAIL_DOMAIN_BLOCKLIST` in your app.ini under `[service]`
### Only allow/block certain OpenID providers
You can configure `WHITELISTED_URIS` or `BLACKLISTED_URIS` under `[openid]` in your `app.ini`
:::note
Whitelisted takes precedence, so if it is non-blank then blacklisted is ignored.
:::
### Issue only users
The current way to achieve this is to create/modify a user with a max repo creation limit of 0.
### Restricted users
Restricted users are limited to a subset of the content based on their organization/team memberships and collaborations, ignoring the public flag on organizations/repos etc.\_\_
Example use case: A company runs a Gitea instance that requires login. Most repos are public (accessible/browsable by all co-workers).
At some point, a customer or third party needs access to a specific repo and only that repo. Making such a customer account restricted and granting any needed access using team membership(s) and/or collaboration(s) is a simple way to achieve that without the need to make everything private.
### Enable Fail2ban
Use [Fail2Ban](../administration/fail2ban-setup.md) to monitor and stop automated login attempts or other malicious behavior based on log patterns
## SSHD vs built-in SSH
SSHD is the built-in SSH server on most Unix systems.
Gitea also provides its own SSH server, for usage when SSHD is not available.
## Translation is incorrect/how to add more translations
Our translations are currently crowd-sourced on our [Crowdin project](https://crowdin.com/project/gitea)
Whether you want to change a translation or add a new one, it will need to be there as all translations are overwritten in our CI via the Crowdin integration.
## Push Hook / Webhook / Actions aren't running
If you can push but can't see push activities on the home dashboard, or the push doesn't trigger webhook and Actions workflows, it's likely that the git hooks are not working.
There are a few possibilities:
1. The git hooks are out of sync. Run the following actions on the site admin panel:
- "Sync missed branches from git data to databases"
- "Sync tags from git data to database"
- "Resynchronize pre-receive, update and post-receive hooks of all repositories"
2. The git repositories (and hooks) are stored on some filesystems (ex: mounted by NAS) which don't support script execution, make sure the filesystem supports `chmod a+x any-script`. Also make sure that the filesystem of the repositories is not mounted with the `noexec` option.
3. If you are using docker, make sure Docker Server (not the client) >= 20.10.6
## SSH issues
If you cannot reach repositories over `ssh`, but `https` works fine, consider looking into the following.
If the connection is successful, you should receive an error message like the following:
```
Hi there, You've successfully authenticated, but Gitea does not provide shell access.
If this is unexpected, please log in with password and setup Gitea under another user.
```
If you do not get the above message but still connect, it means your SSH key is **not** being managed by Gitea. This means hooks won't run, among other potential problems.
If you cannot connect at all, your SSH key may not be configured correctly locally.
This is specific to SSH and not Gitea, so will not be covered here.
### SSH Common Errors
```
Permission denied (publickey).
fatal: Could not read from remote repository.
```
This error signifies that the server rejected a log in attempt, check the
following things:
- On the client:
- Ensure the public and private ssh keys are added to the correct Gitea user.
- Make sure there are no issues in the remote url. In particular, ensure the name of the
Git user (before the `@`) is spelled correctly.
- Ensure public and private ssh keys are correct on client machine.
- On the server:
- Make sure the repository exists and is correctly named.
- Check the permissions of the `.ssh` directory in the system user's home directory.
- Verify that the correct public keys are added to `.ssh/authorized_keys`.
Try to run `Rewrite '.ssh/authorized_keys' file (for Gitea SSH keys)` on the
Gitea admin panel.
- Read Gitea logs.
- Read /var/log/auth (or similar).
- Check permissions of repositories.
The following is an example of a missing public SSH key where authentication
succeeded, but some other setting is preventing SSH from reaching the correct
repository.
```
fatal: Could not read from remote repository.
Please make sure you have the correct access rights
and the repository exists.
```
In this case, look into the following settings:
- On the server:
- Make sure that the `git` system user has a usable shell set
- Verify this with `getent passwd git | cut -d: -f7`
- `usermod` or `chsh` can be used to modify this.
- Ensure that the `gitea serv` command in `.ssh/authorized_keys` uses the
correct configuration file.
## Missing releases after migrating repository with tags
To migrate an repository _with_ all tags, you need to do two things:
- Push tags to the repository:
```
git push --tags
```
- (Re-)sync tags of all repositories within Gitea:
```
gitea admin repo-sync-releases
```
## How can I create users before starting Gitea
Gitea provides a sub-command `gitea migrate` to initialize the database, after which you can use the [admin CLI commands](../administration/command-line.md#admin) to add users like normal.
## How can I enable password reset
There is no setting for password resets. It is enabled when a [mail service](../administration/email-setup.md) is configured, and disabled otherwise.
## How can a user's password be changed
- As an **admin**, you can change any user's password (and optionally force them to change it on next login)...
- By navigating to your `Site Administration -> User Accounts` page and editing a user.
- By using the [admin CLI commands](../administration/command-line.md#admin).
Keep in mind most commands will also need a [global flag](../administration/command-line.md#global-options) to point the CLI at the correct configuration.
- As a **user** you can change it...
- In your account `Settings -> Account` page (this method **requires** you to know your current password).
- By using the `Forgot Password` link.
If the `Forgot Password/Account Recovery` page is disabled, please contact your administrator to configure a [mail service](../administration/email-setup.md).
## Warnings about struct defaults during database startup
Sometimes when there are migrations the old columns and default values may be left
unchanged in the database schema. This may lead to warning such as:
```
2020/08/02 11:32:29 ...rm/session_schema.go:360:Sync() [W] Table user Column keep_activity_private db default is , struct default is 0
```
These can safely be ignored, but you are able to stop these warnings by getting Gitea to recreate these tables using:
```
gitea doctor recreate-table user
```
This will cause Gitea to recreate the user table and copy the old data into the new table
with the defaults set appropriately.
You can ask Gitea to recreate multiple tables using:
```
gitea doctor recreate-table table1 table2 ...
```
And if you would like Gitea to recreate all tables simply call:
```
gitea doctor recreate-table
```
It is highly recommended to back-up your database before running these commands.
## How to adopt repositories from disk
- Add your (bare) repositories to the correct spot for your configuration (`repository.ROOT`), ensuring they are in the correct layout `<REPO_ROOT>/[user]/[repo].git`.
- **Note:** the directory names must be lowercase.
- You can also check `<ROOT_URL>/-/admin/config` for the repository root path.
- Ensure that the user/org exists that you want to adopt repositories for.
- As an admin, go to `<ROOT_URL>/-/admin/repos/unadopted` and search.
- Users can also be given similar permissions via config [`ALLOW_ADOPTION_OF_UNADOPTED_REPOSITORIES`](../administration/config-cheat-sheet.md#repository-repository).
- If the above steps are done correctly, you should be able to select repositories to adopt.
- If no repositories are found, enable [debug logging](../administration/config-cheat-sheet.md#repository-repository) to check for any specific errors.
## Gitea can't start on NFS
In most cases, it's caused by broken NFS lock system. You can try to stop Gitea process and
run `flock -n /data-nfs/gitea/queues/LOCK echo 'lock acquired'` to see whether the lock can be acquired immediately.
If the lock can't be acquired, NFS might report some errors like `lockd: cannot monitor node-3, statd: server rpc.statd not responding, timed out` in its server logs.
Gitea is a painless, self-hosted, all-in-one software development service. It includes Git hosting, code review, team collaboration, package registry, and CI/CD. It is similar to GitHub, Bitbucket and GitLab.
Gitea was originally forked from [Gogs](https://gogs.io) and almost all the code has been changed. See the [Gitea Announcement](https://blog.gitea.com/welcome-to-gitea/) blog post to read about the justification for a fork.
:::warning
Gitea does not send commits to upstream or cherry-pick commits from it, so there is no guarantee it will work if you upgrade from Gogs to Gitea. The recommended method is to migrate repositories from Gogs to Gitea.
:::
## Purpose
The goal of this project is to provide the easiest, fastest, and most painless way of setting
up a self-hosted Git service.
With Go, this can be done platform-independently across
**all platforms** which Go supports, including Linux, macOS, and Windows,
on x86, amd64, ARM and PowerPC architectures.
You can try it out using [the online demo](https://demo.gitea.com).
## Features
- **Code Hosting**
Gitea supports creating and managing repositories, browsing commit history and code files, reviewing and merging code submissions, managing collaborators, handling branches, and more. It also supports many common Git features such as tags, cherry-picking, hooks, integrated collaboration tools, and more.
- **Lightweight and Fast**
One of Gitea's design goals is to be lightweight and fast in response. Unlike some large code hosting platforms, it remains lean, performs well in terms of speed, and is suitable for resource-limited server environments.
- **Easy Deployment and Maintenance**
It can be easily deployed on various servers without complex configurations or dependencies. This makes it convenient for individual developers or small teams to set up and manage their own Git services.
- **Security**
Gitea places a strong emphasis on security, offering features such as user permission management, access control lists, [audit logging](administration/audit-logging.md), and more to ensure the security of code and data.
- **Code Review**
Code review supports both the Pull Request workflow and AGit workflow. Reviewers can browse code online and provide review comments or feedback. Submitters can receive review comments and respond or modify code online. Code reviews can help individuals and organizations enhance code quality.
- **CI/CD**
Gitea Actions supports CI/CD functionality, compatible with GitHub Actions. Users can write workflows in familiar YAML format and reuse a variety of existing Actions plugins. Actions plugins support downloading from any Git website.
- **Project Management**
Gitea tracks project requirements, features, and bugs through columns and issues. Issues support features like branches, tags, milestones, assignments, time tracking, due dates, dependencies, and more.
- **Artifact Repository**
Gitea supports over 20 different types of public or private software package management, including Cargo, Chef, Composer, Conan, Conda, Container, Helm, Maven, npm, NuGet, Pub, PyPI, RubyGems, Vagrant, and more.
- **Open Source Community Support**
Gitea is an open-source project based on the MIT license. It has an active open-source community that continuously develops and improves the platform. The project also actively welcomes community contributions, ensuring updates and innovation.
- **Multilingual Support**
Gitea provides interfaces in multiple languages, catering to users globally and promoting internationalization and localization.
For more detailed information, please refer to: https://docs.gitea.com/installation/comparison#general-features
## System Requirements
- A Raspberry Pi 3 is powerful enough to run Gitea for small workloads.
- 2 CPU cores and 1GB RAM is typically sufficient for small teams/projects.
- Gitea should be run with a dedicated non-root system account on UNIX-type systems.
- Note: Gitea manages the `~/.ssh/authorized_keys` file. Running Gitea as a regular user could break that user's ability to log in.
- [Git](https://git-scm.com/) version 2.0.0 or later is required.
- [Git Large File Storage](https://git-lfs.github.com/) will be available if enabled and if your Git version is >= 2.1.2
- Git commit-graph rendering will be enabled automatically if your Git version is >= 2.18
## Browser Support
- Last 2 versions of Chrome, Firefox, Safari and Edge
- Firefox ESR
## Components
- Web server framework: [Chi](http://github.com/go-chi/chi)
To help decide if Gitea is suited for your needs, here is how it compares to other Git self hosted options.
Be warned that we don't regularly check for feature changes in other products, so this list may be outdated. If you find anything that needs to be updated in the table below, please [open an issue](https://github.com/go-gitea/gitea/issues/new/choose).
_Symbols used in table:_
- _✓ - supported_
- _⁄ - supported with limited functionality_
- _✘ - unsupported_
- _⚙️ - supported through third-party software_
## General Features
| Feature | Gitea | GitHub EE | GitLab CE | GitLab EE | BitBucket | RhodeCode CE | RhodeCode EE |
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.
:::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.
:::
If you use an unsupported database version, please [get in touch](/help/support) with us for information on our Extended Support Contracts. We can provide testing and support for older databases and integrate those fixes into the Gitea codebase.
Database instance can be on same machine as Gitea (local database setup), or on different machine (remote database).
:::note
All steps below requires that the database engine of your choice is installed on your system. For remote database setup, install the server application on database instance and client program on your Gitea server. The client program is used to test connection to the database from Gitea server, while Gitea itself use database driver provided by Go to accomplish the same thing. In addition, make sure you use same engine version for both server and client for some engine features to work. For security reason, protect `root` (MySQL) or `postgres` (PostgreSQL) database superuser with secure password. The steps assumes that you run Linux for both database and Gitea servers.
:::
## MySQL/MariaDB
1. For remote database setup, you will need to make MySQL listen to your IP address. Configure the `bind-address` option in `/etc/mysql/my.cnf` on database instance by adding the following lines:
```ini
[mysqld]
bind-address = 203.0.113.3
```
2. On database instance, login to database console as root:
```sh
mysql -u root -p
```
Enter the password as prompted.
3. Create database user which will be used by Gitea, authenticated by password. This example uses `'gitea'` as password. Please use a secure password for your instance.
For local database:
```sql
SET old_passwords=0;
CREATE USER 'gitea'@'%' IDENTIFIED BY 'gitea';
```
For remote database:
```sql
SET old_passwords=0;
CREATE USER 'gitea'@'192.0.2.10' IDENTIFIED BY 'gitea';
```
where `192.0.2.10` is the IP address of your Gitea instance.
Replace username and password above as appropriate.
4. Create database with UTF-8 charset and case-sensitive collation.
`utf8mb4_bin` is a common collation for both MySQL/MariaDB.
When Gitea starts, it will try to find a better collation (`utf8mb4_0900_as_cs` or `uca1400_as_cs`) and alter the database if it is possible.
If you would like to use other collation, you can set `[database].CHARSET_COLLATION` in the `app.ini` file.
```sql
CREATE DATABASE giteadb CHARACTER SET 'utf8mb4' COLLATE 'utf8mb4_bin';
```
Replace database name as appropriate.
5. Grant all privileges on the database to database user created above.
For local database:
```sql
GRANT ALL PRIVILEGES ON giteadb.* TO 'gitea';
FLUSH PRIVILEGES;
```
For remote database:
```sql
GRANT ALL PRIVILEGES ON giteadb.* TO 'gitea'@'192.0.2.10';
FLUSH PRIVILEGES;
```
6. Quit from database console by `exit`.
7. On your Gitea server, test connection to the database:
```
mysql -u gitea -h 203.0.113.3 -p giteadb
```
where `gitea` is database username, `giteadb` is database name, and `203.0.113.3` is IP address of database instance. Omit `-h` option for local database.
You should be connected to the database.
## PostgreSQL
1. For remote database setup, configure PostgreSQL on database instance to listen to your IP address by editing `listen_addresses` on `postgresql.conf` to:
```ini
listen_addresses = 'localhost, 203.0.113.3'
```
2. PostgreSQL uses `md5` challenge-response encryption scheme for password authentication by default. Nowadays this scheme is not considered secure anymore. Use SCRAM-SHA-256 scheme instead by editing the `postgresql.conf` configuration file on the database server to:
```ini
password_encryption = scram-sha-256
```
Restart PostgreSQL to apply the setting.
3. On the database server, login to the database console as superuser:
```
su -c "psql" - postgres
```
4. Create database user (role in PostgreSQL terms) with login privilege and password. Please use a secure, strong password instead of `'gitea'` below:
```sql
CREATE ROLE gitea WITH LOGIN PASSWORD 'gitea';
```
Replace username and password as appropriate.
5. Create database with UTF-8 charset and owned by the database user created earlier. Any `libc` collations can be specified with `LC_COLLATE` and `LC_CTYPE` parameter, depending on expected content:
6. Allow the database user to access the database created above by adding the following authentication rule to `pg_hba.conf`.
For local database:
```ini
local giteadb gitea scram-sha-256
```
For remote database:
```ini
host giteadb gitea 192.0.2.10/32 scram-sha-256
```
Replace database name, user, and IP address of Gitea instance with your own.
:::note
Rules on `pg_hba.conf` are evaluated sequentially, that is the first matching rule will be used for authentication. Your PostgreSQL installation may come with generic authentication rules that match all users and databases. You may need to place the rules presented here above such generic rules if it is the case.
:::
Restart PostgreSQL to apply new authentication rules.
7. On your Gitea server, test connection to the database.
For local database:
```bash
psql -U gitea -d giteadb
```
For remote database:
```bash
psql "postgres://gitea@203.0.113.3/giteadb"
```
where `gitea` is database user, `giteadb` is database name, and `203.0.113.3` is IP address of your database instance.
You should be prompted to enter password for the database user, and connected to the database.
## 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.
### Prerequisites
- You need two valid TLS certificates, one for the database instance (database server) and one for the Gitea instance (database client). Both certificates must be signed by a trusted CA.
- The database certificate must contain `TLS Web Server Authentication` in the `X509v3 Extended Key Usage` extension attribute, while the client certificate needs `TLS Web Client Authentication` in the corresponding attribute.
- On the database server certificate, one of `Subject Alternative Name` or `Common Name` entries must be the fully-qualified domain name (FQDN) of the database instance (e.g. `db.example.com`). On the database client certificate, one of the entries mentioned above must contain the database username that Gitea will be using to connect.
- You need domain name mappings of both Gitea and database servers to their respective IP addresses. Either set up DNS records for them or add local mappings to `/etc/hosts` (`%WINDIR%\System32\drivers\etc\hosts` in Windows) on each system. This allows the database connections to be performed by domain name instead of IP address. See documentation of your system for details.
### PostgreSQL TLS
The PostgreSQL driver used by Gitea supports two-way TLS. In two-way TLS, both database client and server authenticate each other by sending their respective certificates to their respective opposite for validation. In other words, the server verifies client certificate, and the client verifies server certificate.
1. On the server with the database instance, place the following credentials:
You should be prompted to enter password for the database user, and then be connected to the database.
### MySQL/MariaDB TLS
While the MySQL driver used by Gitea also supports two-way TLS, Gitea currently supports only one-way TLS. See issue #10828 for details.
In one-way TLS, the database client verifies the certificate sent from server during the connection handshake, and the server assumes that the connected client is legitimate, since client certificate verification doesn't take place.
1. On the database instance, place the following credentials:
5. The database user for Gitea may have been created earlier, but it would authenticate only against the IP addresses of the server running Gitea. To authenticate against its domain name, recreate the user, and this time also set it to require TLS for connecting to the database:
```sql
DROP USER 'gitea'@'192.0.2.10';
CREATE USER 'gitea'@'example.gitea' IDENTIFIED BY 'gitea' REQUIRE SSL;
GRANT ALL PRIVILEGES ON giteadb.* TO 'gitea'@'example.gitea';
FLUSH PRIVILEGES;
```
Replace database user name, password, and Gitea instance domain as appropriate.
6. Make sure that the CA certificate chain required to validate the database server certificate is on the system certificate store of both the database and Gitea servers. Consult your system documentation for instructions on adding a CA certificate to the certificate store.
7. On the server running Gitea, test connection to the database:
All downloads come with SQLite, MySQL and PostgreSQL support, and are built with
embedded assets. This can be different from Gogs.
## Download
You can find the file matching your platform from the [downloads page](https://dl.gitea.com/gitea/) after navigating to the version you want to download.
### Choosing the right file
**For Linux**, you will likely want `linux-amd64`. It's for 64-bit Intel/AMD platforms, but there are other platforms available, including `arm64` (e.g. Raspberry PI 4), `386` (i.e. 32-bit), `arm-5`, and `arm-6`.
**For Windows**, you will likely want `windows-4.0-amd64`. It's for all modern versions of Windows, but there is also a `386` platform available designed for older, 32-bit versions of Windows.
:::info
There is also a `gogit-windows` file available that was created to help with some [performance problems](https://github.com/go-gitea/gitea/pull/15482) reported by some Windows users on older systems/versions. You should consider using this file if you're experiencing performance issues, and let us know if it improves performance.
:::
**For macOS**, you should choose `darwin-arm64` if your hardware uses Apple Silicon, or `darwin-amd64` for Intel.
**For FreeBSD**, you should choose `freebsd12-amd64` for 64-bit Intel/AMD platforms.
### Downloading with wget
Copy the commands below and replace the URL within the one you wish to download.
Note that the above command will download Gitea @version@ for 64-bit Linux.
## Verify signature
### Sigstore
Starting with v1.27 Gitea signs all binaries using [sigstore](https://sigstore.dev/) to prevent against unwanted modification of binaries.
To validate the binary, download the bundle file which ends in `sigstore.json` for the binary you downloaded and use the [cosign](https://docs.sigstore.dev/cosign/system_config/installation/) command line tool.
If the command outputs `Verified OK`, binary was not modified.
:::note
The above command will match any release workflow. You may choose to restrict the identity further by requiring a specific branch or workflow to be matched. For example this url will match only nightly builds made on main branch: `https://github.com/go-gitea/gitea/.github/workflows/release-nightly.yml@refs/heads/main`
:::
### GPG
Gitea signs all binaries with a [GPG key](https://keys.openpgp.org/search?q=teabot%40gitea.io) to prevent against unwanted modification of binaries.
To validate the binary, download the signature file which ends in `.asc` for the binary you downloaded and use the GPG command line tool.
Look for the text `Good signature from "Teabot <[email protected]>"` to assert a good binary,
despite warnings like `This key is not certified with a trusted signature!`.
## Recommended server configuration
:::note
Many of the following directories can be configured using [Environment Variables](../administration/environment-variables.md) as well!
Of note, configuring `GITEA_WORK_DIR` will tell Gitea where to base its working directory, as well as ease installation.
:::
### Prepare environment
Check that Git is installed on the server. If it is not, install it first. Gitea requires Git version >= 2.0.
```sh
git --version
```
Create a user to run Gitea (e.g. `git`)
```sh
# On Ubuntu/Debian:
adduser \
--system \
--shell /bin/bash \
--gecos 'Git Version Control' \
--group \
--disabled-password \
--home /home/git \
git
# On Fedora/RHEL/CentOS:
groupadd --system git
adduser \
--system \
--shell /bin/bash \
--comment 'Git Version Control' \
--gid git \
--home-dir /home/git \
--create-home \
git
```
### Create required directory structure
```sh
mkdir -p /var/lib/gitea/{custom,data,log}
chown -R git:git /var/lib/gitea/
chmod -R 750 /var/lib/gitea/
mkdir /etc/gitea
chown root:git /etc/gitea
chmod 770 /etc/gitea
```
:::note
> `/etc/gitea` is temporarily set with write permissions for user `git` so that the web installer can write the configuration file. After the installation is finished, it is recommended to set permissions to read-only using:
:::
>
> ```sh
> chmod 750 /etc/gitea
> chmod 640 /etc/gitea/app.ini
> ```
If you don't want the web installer to be able to write to the config file, it is possible to make the config file read-only for the Gitea user (owner/group `root:git`, mode `0640`) however you will need to edit your config file manually to:
* Set `INSTALL_LOCK= true`,
* Ensure all database configuration details are set correctly
* Ensure that the `SECRET_KEY` and `INTERNAL_TOKEN` values are set. (You may want to use the `gitea generate secret` to generate these secret keys.)
* Ensure that any other secret keys you need are set.
See the [command line documentation](../administration/command-line.md) for information on using `gitea generate secret`.
### Configure Gitea's working directory
:::note
If you plan on running Gitea as a Linux service, you can skip this step, as the service file allows you to set `WorkingDirectory`. Otherwise, consider setting this environment variable (semi-)permanently so that Gitea consistently uses the correct working directory.
:::
```sh
export GITEA_WORK_DIR=/var/lib/gitea/
```
### Copy the Gitea binary to a global location
```sh
cp gitea /usr/local/bin/gitea
```
### Adding shell autocompletion (from 1.25)
Shell completion can be generated directly from binary with:
```sh
gitea completion <shell>
```
Supported values for `<shell>` are `bash`, `fish`, `pwsh` and `zsh`. Details on how to load the completion for your shell can be found in the completion command help.
## Running Gitea
After you complete the above steps, you can run Gitea two ways:
:::note
On a distribution with SELinux in enforcing mode (Fedora, RHEL, CentOS Stream,
Rocky Linux, AlmaLinux and others), the binary and the data directory need
labels the default policy does not give them, and binding to a port below 1024
takes an extra step. See [running Gitea with SELinux](selinux.md).
:::
### 1. Creating a service file to start Gitea automatically (recommended)
See how to create [Linux service](installation/run-as-service-in-ubuntu.md)
### 2. Running from command-line/terminal
```sh
GITEA_WORK_DIR=/var/lib/gitea/ /usr/local/bin/gitea web -c /etc/gitea/app.ini
```
## Updating to a new version
You can update to a new version of Gitea by stopping Gitea, replacing the binary at `/usr/local/bin/gitea` and restarting the instance.
The binary file name should not be changed during the update to avoid problems in existing repositories.
It is recommended that you make a [backup](../administration/backup-and-restore.md) before updating your installation.
If you have carried out the installation steps as described above, the binary should
have the generic name `gitea`. Do not change this, i.e. to include the version number.
### 1. Restarting Gitea with systemd (recommended)
As we explained before, we recommend to use systemd as the service manager. In this case, `systemctl restart gitea` should be fine.
### 2. Restarting Gitea without systemd
To restart your Gitea instance, we recommend to use SIGHUP signal. If you know your Gitea PID, use `kill -1 $GITEA_PID`, otherwise you can use `killall -1 gitea`.
To gracefully stop the Gitea instance, a simple `kill $GITEA_PID` or `killall gitea` is enough.
:::note
We don't recommend to use the SIGKILL signal (`-9`); you may be forcefully stopping some of Gitea's internal tasks, and it will not gracefully stop (tasks in queues, indexers, etc.)
:::
See below for troubleshooting instructions to repair broken repositories after
an update of your Gitea version.
## Troubleshooting
### Old glibc versions
Older Linux distributions (such as Debian 7 and CentOS 6) may not be able to load the
Gitea binary, usually producing an error such as `./gitea: /lib/x86_64-linux-gnu/libc.so.6:
version 'GLIBC\_2.14' not found (required by ./gitea)`. This is due to the integrated
SQLite support in the binaries provided by dl.gitea.com. In this situation, it is usually
possible to [install from source](installation/from-source.md), without including
SQLite support.
### Running Gitea on another port
For errors like `702 runWeb()] [E] Failed to start server: listen tcp 0.0.0.0:3000:
bind: address already in use`, Gitea needs to be started on another free port. This
is possible using `./gitea web -p $PORT`. It's possible another instance of Gitea
is already running.
### Running Gitea on Raspbian
As of v1.8, there is a problem with the arm7 version of Gitea, and it doesn't run on Raspberry Pis and similar devices.
It is recommended to switch to the arm6 version, which has been tested and shown to work on Raspberry Pis and similar devices.
{/* please remove after fixing the arm7 bug */}
### Git error after updating to a new version of Gitea
If during the update, the binary file name has been changed to a new version of Gitea,
Git Hooks in existing repositories will not work any more. In that case, a Git
error will be displayed when pushing to the repository.
```
remote: ./hooks/pre-receive.d/gitea: line 2: [...]: No such file or directory
```
The `[...]` part of the error message will contain the path to your previous Gitea
binary.
To solve this, go to the admin options and run the task `Resynchronize pre-receive,
update and post-receive hooks of all repositories` to update all hooks to contain
the new binary path. Please note that this overwrites all Git Hooks, including ones
with customizations made.
If you aren't using the Gitea built-in SSH server, you will also need to re-write
the authorized key file by running the `Update the '.ssh/authorized_keys' file with
Next, [install Node.js with npm](https://nodejs.org/en/download/) which is
required to build the JavaScript and CSS files. The minimum supported Node.js
version is @minNodeVersion@ and the latest LTS version is recommended.
:::note
Go version @minGoVersion@ or higher is required. However, it is recommended to
obtain the same version as our continuous integration, see the advice given in
[Hacking on Gitea](https://github.com/go-gitea/gitea/blob/main/docs/build-setup.md)
:::
## Download
First, we must retrieve the source code. Since, the advent of go modules, the
simplest way of doing this is to use Git directly as we no longer have to have
Gitea built from within the GOPATH.
```bash
git clone https://github.com/go-gitea/gitea
```
(Previous versions of this document recommended using `go get`. This is
no longer necessary.)
Decide which version of Gitea to build and install. Currently, there are
multiple options to choose from. The `main` branch represents the current
development version. To build with main, skip to the [build section](#build).
To work with tagged releases, the following commands can be used:
```bash
git branch -a
git checkout @sourceBranch@
```
To validate a Pull Request, first enable the new branch (`xyz` is the PR id;
for example `2663` for [#2663](https://github.com/go-gitea/gitea/pull/2663)):
```bash
git fetch origin pull/xyz/head:pr-xyz
```
To build Gitea from source at a specific tagged release (like @sourceVersion@), list the
available tags and check out the specific tag.
List available tags with the following.
```bash
git tag -l
git checkout @sourceVersion@ # or git checkout pr-xyz
```
## Build
To build from source, the following programs must be present on the system:
- `go`@minGoVersion@ or higher, see [here](https://go.dev/dl/)
- `node`@minNodeVersion@ or higher with `npm`, see [here](https://nodejs.org/en/download/)
- `make`, see [here](https://github.com/go-gitea/gitea/blob/main/docs/build-setup.md#make)
Various [make tasks](https://github.com/go-gitea/gitea/blob/main/Makefile)
are provided to keep the build process as simple as possible.
Depending on requirements, the following build tags can be included.
- `bindata`: Build a single monolithic binary, with all assets included. Required for production build.
- `sqlite sqlite_unlock_notify`: Enable support for a
[SQLite3](https://sqlite.org/) database. Suggested only for tiny
installations.
- `pam`: Enable support for PAM (Linux Pluggable Authentication Modules). Can
be used to authenticate local users or extend authentication to methods
available to PAM.
- `gogit`: (EXPERIMENTAL) Use go-git variants of Git commands.
Bundling all assets (JS/CSS/templates, etc) into the binary. Using the `bindata` build tag is required for
production deployments. You could exclude `bindata` when you are developing/testing Gitea or able to separate the assets correctly.
To include all assets, use the `bindata` tag:
```bash
TAGS="bindata" make build
```
In the default release build of our continuous integration system, the build
tags are: `TAGS="bindata sqlite sqlite_unlock_notify"`. The simplest
recommended way to build from source is therefore:
```bash
TAGS="bindata sqlite sqlite_unlock_notify" make build
```
The `build` target is split into two sub-targets:
- `make backend` which requires [Go @minGoVersion@](https://go.dev/dl/) or greater.
- `make frontend` which requires [Node.js @minNodeVersion@](https://nodejs.org/en/download/) or greater.
If pre-built frontend files are present it is possible to only build the backend:
```bash
TAGS="bindata" make backend
```
## Test
After following the steps above, a `gitea` binary will be available in the working directory.
It can be tested from this directory or moved to a directory with test data. When Gitea is
launched manually from command line, it can be killed by pressing `Ctrl + C`.
```bash
./gitea web
```
:::note
A binary built this way carries no SELinux label of its own. On a distribution
with SELinux in enforcing mode, see
[running Gitea with SELinux](selinux.md) before installing it as a service.
:::
## Changing default paths
Gitea will search for a number of things from the _`CustomPath`_. By default this is
the `custom/` directory in the current working directory when running Gitea. It will also
look for its configuration file _`CustomConf`_ in `$(CustomPath)/conf/app.ini`, and will use the
current working directory as the relative base path _`AppWorkPath`_ for a number configurable
values. Finally the static files will be served from _`StaticRootPath`_ which defaults to the _`AppWorkPath`_.
These values, although useful when developing, may conflict with downstream users preferences.
One option is to use a script file to shadow the `gitea` binary and create an appropriate
environment before running Gitea. However, when building you can change these defaults
using the `LDFLAGS` environment variable for `make`. The appropriate settings are as follows
- To set the _`CustomPath`_ use `LDFLAGS="-X \"code.gitea.io/gitea/modules/setting.CustomPath=custom-path\""`
- For _`CustomConf`_ you should use `-X \"code.gitea.io/gitea/modules/setting.CustomConf=conf.ini\"`
- For _`AppWorkPath`_ you should use `-X \"code.gitea.io/gitea/modules/setting.AppWorkPath=working-path\"`
- For _`StaticRootPath`_ you should use `-X \"code.gitea.io/gitea/modules/setting.StaticRootPath=static-root-path\"`
- To change the default PID file location use `-X \"code.gitea.io/gitea/cmd.PIDFile=/run/gitea.pid\"`
Add as many of the strings with their preceding `-X` to the `LDFLAGS` variable and run `make build`
with the appropriate `TAGS` as above.
Running `gitea help` will allow you to review what the computed settings will be for your `gitea`.
## Cross Build
The `go` compiler toolchain supports cross-compiling to different architecture targets that are supported by the toolchain. See [`GOOS` and `GOARCH` environment variable](https://go.dev/doc/install/source#environment) for the list of supported targets. Cross compilation is helpful if you want to build Gitea for less-powerful systems (such as Raspberry Pi).
To cross build Gitea with build tags (`TAGS`), you also need a C cross compiler which targets the same architecture as selected by the `GOOS` and `GOARCH` variables. For example, to cross build for Linux ARM64 (`GOOS=linux` and `GOARCH=arm64`), you need the `aarch64-unknown-linux-gnu-gcc` cross compiler. This is required because Gitea build tags uses `cgo`'s foreign-function interface (FFI).
Cross-build Gitea for Linux ARM64, without any tags:
```
GOOS=linux GOARCH=arm64 make build
```
Cross-build Gitea for Linux ARM64, with recommended build tags:
```
CC=aarch64-unknown-linux-gnu-gcc GOOS=linux GOARCH=arm64 TAGS="bindata sqlite sqlite_unlock_notify" make build
```
Replace `CC`, `GOOS`, and `GOARCH` as appropriate for your architecture target.
You will sometimes need to build a static compiled image. To do this you will need to add:
```
LDFLAGS="-linkmode external -extldflags '-static' $LDFLAGS" TAGS="netgo osusergo $TAGS" make build
```
This can be combined with `CC`, `GOOS`, and `GOARCH` as above.
### Adding shell autocompletion (from 1.25)
Shell completion can be generated directly from binary with:
```sh
gitea completion <shell>
```
Supported values for `<shell>` are `bash`, `fish`, `pwsh` and `zsh`. Details on how to load the completion for your shell can be found in the completion command help.
## Compile or cross-compile using Linux with Zig
Follow [Getting Started of Zig](https://ziglang.org/learn/getting-started/#installing-zig) to install zig.
- Compile (Linux ➝ Linux)
```sh
CC="zig cc -target x86_64-linux-gnu" \
CGO_ENABLED=1 \
CGO_CFLAGS="-O2 -g -pthread" \
CGO_LDFLAGS="-linkmode=external -v"
GOOS=linux \
GOARCH=amd64 \
TAGS="bindata sqlite sqlite_unlock_notify" \
make build
```
- Cross-compile (Linux ➝ Windows)
```sh
CC="zig cc -target x86_64-windows-gnu" \
CGO_ENABLED=1 \
CGO_CFLAGS="-O2 -g -pthread" \
GOOS=windows \
GOARCH=amd64 \
TAGS="bindata sqlite sqlite_unlock_notify" \
make build
```
## Compile or cross-compile with Zig using Windows
Compile with `GIT BASH`.
- Compile (Windows ➝ Windows)
```sh
CC="zig cc -target x86_64-windows-gnu" \
CGO_ENABLED=1 \
CGO_CFLAGS="-O2 -g -pthread" \
GOOS=windows \
GOARCH=amd64 \
TAGS="bindata sqlite sqlite_unlock_notify" \
make build
```
- Cross-compile (Windows ➝ Linux)
```sh
CC="zig cc -target x86_64-linux-gnu" \
CGO_ENABLED=1 \
CGO_CFLAGS="-O2 -g -pthread" \
CGO_LDFLAGS="-linkmode=external -v"
GOOS=linux \
GOARCH=amd64 \
TAGS="bindata sqlite sqlite_unlock_notify" \
make build
```
## Source Maps
By default, gitea generates reduced source maps for frontend files to conserve space. This can be controlled with the `ENABLE_SOURCEMAP` environment variable:
- `ENABLE_SOURCEMAP=true` generates all source maps, the default for development builds
- `ENABLE_SOURCEMAP=reduced` generates limited source maps, the default for production builds
- `ENABLE_SOURCEMAP=false` generates no source maps
You can run Gitea as a Linux service, using either systemd or supervisor. The steps below tested on Ubuntu 16.04, but those should work on any Linux distributions (with little modification).
## Using systemd
Copy the sample [gitea.service](https://github.com/go-gitea/gitea/blob/main/contrib/service/systemd/gitea.service) to `/etc/systemd/system/gitea.service`, then edit the file with your favorite editor.
Uncomment any service that needs to be enabled on this host, such as MySQL.
Change the user, home directory, and other required startup values. Change the
PORT or remove the -p flag if default port is used.
Enable and start Gitea at boot:
```
sudo systemctl enable gitea
sudo systemctl start gitea
```
If you have systemd version 220 or later, you can enable and immediately start Gitea at once by:
```
sudo systemctl enable gitea --now
```
## Using supervisor
Install supervisor by running below command in terminal:
```
sudo apt install supervisor
```
Create a log dir for the supervisor logs:
```
# assuming Gitea is installed in /home/git/gitea/
mkdir /home/git/gitea/log/supervisor
```
Append the configuration from the sample
[supervisord config](https://github.com/go-gitea/gitea/blob/main/contrib/service/supervisor/gitea) to `/etc/supervisor/supervisord.conf`.
Using your favorite editor, change the user (`git`) and home
(`/home/git`) settings to match the deployment environment. Change the PORT
or remove the -p flag if default port is used.
Lastly enable and start supervisor at boot:
```
sudo systemctl enable supervisor
sudo systemctl start supervisor
```
If you have systemd version 220 or later, you can enable and immediately start supervisor by:
Note that the volume should be owned by the user/group with the UID/GID specified in the config file. By default Gitea in docker will use uid:1000 gid:1000. If needed you can set ownership on those folders with the command:
```sh
sudo chown 1000:1000 config/ data/
```
> If you don't give the volume correct permissions, the container may present the following errors in the logs:
For a stable release you could use `:latest-rootless`, `:1-rootless` or specify a certain release like `:@dockerVersion@-rootless`, but if you'd like to use the latest development version then `:main-nightly-rootless` would be an appropriate tag. If you'd like to run the latest commit from a release branch you can use the `:1.x-nightly-rootless` tag, where x is the minor version of Gitea. (e.g. `:1.27-nightly-rootless`)
## Named volumes
To use named volumes instead of host volumes, define and use the named volume
within the `docker-compose.yml` configuration. This change will automatically
create the required volume. You don't need to worry about permissions with
named volumes; Docker will deal with that automatically.
Gitea provides automatically updated Docker images within its Docker Hub organization. It is
possible to always use the latest stable tag or to use another service that handles updating
Docker images.
This reference setup guides users through the setup based on `docker compose`.
Docker compose has been included in the Docker Engine since the shift to Compose v2.
If, for some reason, compose is not available on your system, follow the official [install instructions](https://docs.docker.com/compose/install/).
This document targets for the default rootful image. For the rootless image,
see "[Installation with Docker (rootless)](./with-docker-rootless.md)"
ATTENTION: the rootful/rootless images are not compatible with the other.
If you have chosen one, you should always use the same one,
don't switch to the other one by changing the compose file's `image` value.
## Basics
The most simple setup just creates a volume and a network and starts the `docker.gitea.com/gitea:latest`
image as a service. Since there is no database available, one can be initialized using SQLite3.
Create a directory like `gitea` and paste the following content into a file named `docker-compose.yml`.
Note that the volume should be owned by the user/group with the UID/GID specified in the config file.
If you don't give the volume correct permissions, the container may not start.
For a stable release you can use `:latest`, `:1` or specify a certain release like `:@dockerVersion@`, but if you'd like to use the latest development version of Gitea then you could use the `:main-nightly` tag. If you'd like to run the latest commit from a release branch you can use the `:1.x-nightly` tag, where x is the minor version of Gitea. (e.g. `:1.27-nightly`)
```yaml
networks:
gitea:
external: false
services:
server:
image: docker.gitea.com/gitea:@dockerVersion@
container_name: gitea
environment:
- USER_UID=1000
- USER_GID=1000
restart: always
networks:
- gitea
volumes:
- ./gitea:/data
- /etc/timezone:/etc/timezone:ro
- /etc/localtime:/etc/localtime:ro
ports:
- "3000:3000"
- "222:22"
```
## Ports
To bind the integrated OpenSSH daemon and the webserver on a different port, adjust
the port section. It's common to just change the host port and keep the ports within
the container like they are.
```diff
networks:
gitea:
external: false
services:
server:
image: docker.gitea.com/gitea:@dockerVersion@
container_name: gitea
environment:
- USER_UID=1000
- USER_GID=1000
restart: always
networks:
- gitea
volumes:
- ./gitea:/data
- /etc/timezone:/etc/timezone:ro
- /etc/localtime:/etc/localtime:ro
ports:
- - "3000:3000"
- - "222:22"
+ - "8080:3000"
+ - "2221:22"
```
## Databases
### MySQL database
To start Gitea in combination with a MySQL database, apply these changes to the
`docker-compose.yml` file created above.
```diff
networks:
gitea:
external: false
services:
server:
image: docker.gitea.com/gitea:@dockerVersion@
container_name: gitea
environment:
- USER_UID=1000
- USER_GID=1000
+ - GITEA__database__DB_TYPE=mysql
+ - GITEA__database__HOST=db:3306
+ - GITEA__database__NAME=gitea
+ - GITEA__database__USER=gitea
+ - GITEA__database__PASSWD=gitea
restart: always
networks:
- gitea
volumes:
- ./gitea:/data
- /etc/timezone:/etc/timezone:ro
- /etc/localtime:/etc/localtime:ro
ports:
- "3000:3000"
- "222:22"
+ depends_on:
+ - db
+
+ db:
+ image: docker.io/library/mysql:8
+ restart: always
+ environment:
+ - MYSQL_ROOT_PASSWORD=gitea
+ - MYSQL_USER=gitea
+ - MYSQL_PASSWORD=gitea
+ - MYSQL_DATABASE=gitea
+ networks:
+ - gitea
+ volumes:
+ - ./mysql:/var/lib/mysql
```
### PostgreSQL database
To start Gitea in combination with a PostgreSQL database, apply these changes to
the `docker-compose.yml` file created above.
:::note
The example pins a major version on purpose: PostgreSQL does not upgrade its data
directory on its own, moving to a newer major version later requires `pg_upgrade`
or a dump and restore. Start a new instance on the newest version Gitea supports,
see [database preparation](database-preparation.md) for the supported versions.
:::
```diff
networks:
gitea:
external: false
services:
server:
image: docker.gitea.com/gitea:@dockerVersion@
container_name: gitea
environment:
- USER_UID=1000
- USER_GID=1000
+ - GITEA__database__DB_TYPE=postgres
+ - GITEA__database__HOST=db:5432
+ - GITEA__database__NAME=gitea
+ - GITEA__database__USER=gitea
+ - GITEA__database__PASSWD=gitea
restart: always
networks:
- gitea
volumes:
- ./gitea:/data
- /etc/timezone:/etc/timezone:ro
- /etc/localtime:/etc/localtime:ro
ports:
- "3000:3000"
- "222:22"
+ depends_on:
+ - db
+
+ db:
+ image: docker.io/library/postgres:18
+ restart: always
+ environment:
+ - POSTGRES_USER=gitea
+ - POSTGRES_PASSWORD=gitea
+ - POSTGRES_DB=gitea
+ networks:
+ - gitea
+ volumes:
+ - ./postgres:/var/lib/postgresql
```
## Named volumes
To use named volumes instead of host volumes, define and use the named volume
within the `docker-compose.yml` configuration. This change will automatically
create the required volume. You don't need to worry about permissions with
named volumes; Docker will deal with that automatically.
```diff
networks:
gitea:
external: false
+volumes:
+ gitea:
+ driver: local
+
services:
server:
image: docker.gitea.com/gitea:@dockerVersion@
container_name: gitea
restart: always
networks:
- gitea
volumes:
- - ./gitea:/data
+ - gitea:/data
- /etc/timezone:/etc/timezone:ro
- /etc/localtime:/etc/localtime:ro
ports:
- "3000:3000"
- "222:22"
```
MySQL or PostgreSQL containers will need to be created separately.
## Startup
To start this setup based on `docker compose`, execute `docker compose up -d`,
to launch Gitea in the background. Using `docker compose ps` will show if Gitea
started properly. Logs can be viewed with `docker compose logs`.
To shut down the setup, execute `docker compose down`. This will stop
and kill the containers. The volumes will still exist.
:::note
If using a non-3000 port on http, change app.ini to match
`LOCAL_ROOT_URL = http://localhost:3000/`.
:::
## Installation
After starting the Docker setup via `docker compose`, Gitea should be available using a
favorite browser to finalize the installation. Visit http://server-ip:3000 and follow the
installation wizard. If the database was started with the `docker compose` setup as
documented above, please note that `db` must be used as the database hostname.
## Configure the user inside Gitea using environment variables
- `USER`: **git**: The username of the user that runs Gitea within the container.
- `USER_UID`: **1000**: The UID (Unix user ID) of the user that runs Gitea within the container. Match this to the UID of the owner of the `/data` volume if using host volumes (this is not necessary with named volumes).
- `USER_GID`: **1000**: The GID (Unix group ID) of the user that runs Gitea within the container. Match this to the GID of the owner of the `/data` volume if using host volumes (this is not necessary with named volumes).
## Customization
Customization files described [here](../administration/customizing-gitea.md) should
be placed in `/data/gitea` directory. If using host volumes, it's quite easy to access these
files; for named volumes, this is done through another container or by direct access at
`/var/lib/docker/volumes/gitea_gitea/_data`. The configuration file will be saved at
`/data/gitea/conf/app.ini` after the installation.
Example: Analogous to the non-docker-installation customization linked above, you can create a `/public` folder within `/data/gitea` and place your custom `robots.txt` there which will then be served normally.
## Upgrading
:::warning
Make sure you have volumed data to somewhere outside Docker container
:::
To upgrade your installation to the latest release:
```bash
# Edit `docker-compose.yml` to update the version, if you have one specified
# Pull new images
docker compose pull
# Start a new container, automatically removes old one
docker compose up -d
```
## Managing Deployments With Environment Variables
In addition to the environment variables above, any settings in `app.ini` can be set
or overridden with an environment variable of the form: `GITEA__section_name__KEY_NAME=value`.
These settings are applied each time the docker container starts by `environment-to-ini` command
(a warpper of `gitea config edit-ini`), and won't be passed into Gitea's sub-processes.
See `./gitea config edit-ini --help` for more details.
These environment variables can be passed to the docker container in `docker-compose.yml`.
The following example will enable an smtp mail server if the required env variables
`GITEA__mailer__FROM`, `GITEA__mailer__HOST`, `GITEA__mailer__PASSWD` are set on the host
or in a `.env` file in the same directory as `docker-compose.yml`.
The settings can be also set or overridden with the content of a file by defining an environment variable of the form:
`GITEA__section_name__KEY_NAME__FILE` that points to a file.
```yaml
...
services:
server:
environment:
- GITEA__mailer__ENABLED=true
- GITEA__mailer__FROM=${GITEA__mailer__FROM:?GITEA__mailer__FROM not set}
- GITEA__mailer__PROTOCOL=smtps
- GITEA__mailer__SMTP_ADDR=${GITEA__mailer__SMTP_ADDR:?GITEA__mailer__SMTP_ADDR not set}
- GITEA__mailer__SMTP_PORT=${GITEA__mailer__SMTP_PORT:?GITEA__mailer__SMTP_PORT not set}
- GITEA__mailer__PASSWD="""${GITEA__mailer__PASSWD:?GITEA__mailer__PASSWD not set}"""
```
Gitea will generate new secrets/tokens for every new installation automatically and write them into the app.ini. If you want to set the secrets/tokens manually, you can use the following docker commands to use of Gitea's built-in [generate utility functions](../administration/command-line.md#generate). Do not lose/change your SECRET_KEY after the installation, otherwise the encrypted data can not be decrypted anymore.
The following commands will output a new `SECRET_KEY` and `INTERNAL_TOKEN` to `stdout`, which you can then place in your environment variables.
```bash
docker run -it --rm docker.gitea.com/gitea:1 gitea generate secret SECRET_KEY
docker run -it --rm docker.gitea.com/gitea:1 gitea generate secret INTERNAL_TOKEN
```
```yaml
...
services:
server:
environment:
- GITEA__security__SECRET_KEY=[value returned by generate secret SECRET_KEY]
- GITEA__security__INTERNAL_TOKEN=[value returned by generate secret INTERNAL_TOKEN]
```
### SSH with multiple IP addresses
This assumes that the host machine has more than one reachable IP address: `192.168.1.1` (host) `192.168.1.2` (gitea)
On the host machine, configure SSHD in `/etc/ssh/sshd_config` to listen on one IP address `ListenAddress 192.168.1.1`. In the compose file the SSH port forwarding then needs to be changed to `"192.168.1.2:22:22"`. The port forwarding needs to be adjusted similarily for all other forwarded ports to avoid problems with DNS.
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, 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.
## Rule matching and priorities
- The **Protected branch name pattern** accepts [glob](https://github.com/gobwas/glob) expressions and matches the entire branch name. Patterns are case-sensitive. Using a simple name such as `main` without glob special characters always matches that specific branch (case-insensitive).
- If multiple rules match the same branch, only the first rule is used. Reorder the list on the **Branches** page by dragging the grab handle. The first entry (priority 1) has the highest priority, so place patterns such as `main` or `release/*` before generic fallbacks such as `*`.
| `hotfix/**` | Nested branches such as `hotfix/security/CVE` |
| `*` | Every branch (use as a fallback) |
### File-level pattern controls
- **Protected file patterns** block changes to sensitive files (for example `.drone.yml` or `/docs/**/*.txt`). Patterns are case-insensitive and separated by semicolons. Commits and merge attempts that touch one of the files are rejected.
- **Unprotected file patterns** do the opposite: if pushes are blocked, users with write access can still push commits that modify only the listed files. This is useful for letting contributors update documentation while still requiring pull requests for code.
Both fields use the same `glob` syntax and match paths relative to the repository root.
## Controlling direct pushes
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.
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.
### Force pushes
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 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
- **Merge allowlist**: keep the default to let anyone with write access merge pull requests, or enable the allowlist to restrict merges to selected users/teams.
- **Required approvals**: specify how many approvals are needed before a merge is allowed. Reviews from users with write access count, unless the **Restrict approvals to allowlisted users or teams** option is enabled.
- **Dismiss stale approvals** removes existing approvals whenever new commits that change the pull request content are pushed.
- **Ignore stale approvals** keeps approvals but does not count reviews that were made on older commits. This option is disabled while dismissal of stale approvals is enabled.
- **Block merge on rejected reviews** prevents merging while any official reviewer has requested changes.
- **Block merge on official review requests** blocks merges while there are outstanding review requests (for example when CODEOWNERS requires a review).
- **Block merge if the pull request is outdated** makes sure the head branch is up to date with the base branch before it can be merged.
- **Administrators must follow branch protection rules** removes the ability for repository administrators to bypass the rules with the "Force merge" button.
Protected file patterns apply to pull requests as well. When a pull request changes one of the protected files, the pull request banner shows the affected paths and merging stays disabled.
## Status checks
Enable status checks to require one or more CI jobs to succeed before merging:
1. Check **Enable status check**.
2. Enter one pattern per line in **Status check patterns**. Each pattern is a `glob` expression that matches the context name reported by Actions, Drone, Woodpecker, or another Check API client (for example `actions/test-*`).
3. Pick contexts from the table of jobs that have reported results in the last week to verify their names.
When the option is active, Gitea requires at least one context that matches each pattern to report success on the pull request head commit. An empty list is not allowed; use `*` to require the latest commit to be successful regardless of the context name.
## Signed commits and other safeguards
- **Require signed commits** rejects pushes that contain unsigned or unverifiable commits. The check runs inside the server hook before the push is accepted.
- **Protected file patterns** (see above) prevent both pushes and merges that modify sensitive files.
- **Unprotected file patterns** allow limited pushes while keeping the branch protected.
Workflow runs can store files such as test results, coverage reports, and build output as artifacts. Artifacts are uploaded by workflows, for example with [`actions/upload-artifact`](https://github.com/actions/upload-artifact), and appear on the run page after the upload completes.
## Previewing artifacts
Select an artifact on the run page to open its file browser and preview in the same tab. The preview identifies the originating run and attempt, and provides links back to that run and to download the complete artifact. Selecting files in the browser updates the preview in the same tab.
Artifact previews:
- require sign-in and access to the Actions run to open the preview page
- render text-based files, images (including SVG), and PDFs
- run HTML and JavaScript content in a sandboxed frame
Preview content is loaded through signed URLs that expire after one hour and do not require a session cookie. Reopen the preview page to obtain a fresh URL after it expires.
The file browser lists up to 2,000 files per artifact. Larger artifacts show a notice when the list is truncated.
Unsupported or oversized files can still be downloaded with the complete artifact.
## Instance configuration
Site administrators can limit the total artifact size that can be browsed or previewed with `ARTIFACT_PREVIEW_MAX_SIZE` in the `[actions]` section of `app.ini`. The default is 10 MiB. Set it to `0` to disable previews or `-1` to allow artifacts of any size.
The size of an individual previewed file is also limited by [`MAX_DISPLAY_FILE_SIZE`](../../administration/config-cheat-sheet.md#ui-ui) in the `[ui]` section.
Gitea Actions is designed to be compatible with GitHub Actions, with the differences below.
## Additional features
### Absolute action URLs
`uses:` accepts an absolute URL to any git host, e.g. `uses: https://gitea.com/actions/checkout@v4`.
### Action prefixes
`uses:` accepts these prefixes:
- `self:` references your own Gitea instance, e.g. `uses: self:owner/repo@v1` or `uses: self:owner/repo/.gitea/workflows/build.yml@v1`
- `builtin:` runs an action built into Gitea Runner, which needs no download and no Node in the job image, e.g. `uses: builtin:checkout`
`builtin:checkout` accepts the `repository`, `ref`, `token`, `path` and `fetch-depth` inputs of `actions/checkout` with the same defaults, and fails on any other input.
### Workflow-relative actions
`$/` references the repository and commit of the workflow, or of the enclosing composite action, e.g. `uses: $/.gitea/actions/build`.
Unlike `./`, it needs no checkout first.
### Actions written in Go
See [Creating Go Actions](https://blog.gitea.com/creating-go-actions/).
### Support the non-standard syntax @yearly, @monthly, @weekly, @daily, @hourly on schedule
`schedule` also accepts `@yearly`, `@monthly`, `@weekly`, `@daily` and `@hourly`, which [GitHub does not](https://docs.github.com/en/actions/using-workflows/events-that-trigger-workflows#schedule).
## Unsupported workflows syntax
### `jobs.<job_id>.environment`
[`jobs.<job_id>.environment`](https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idenvironment) is ignored.
`GITEA_TOKEN` cannot publish to the package registry of its repository, e.g. to push OCI images.
Use a personal access token instead, see [this issue](https://github.com/go-gitea/gitea/issues/23642#issuecomment-2119876692).
### Problem Matchers
[Problem matchers](https://github.com/actions/toolkit/blob/main/docs/problem-matchers.md) are ignored.
### Create an error annotation
[Error annotations](https://docs.github.com/en/actions/using-workflows/workflow-commands-for-github-actions#example-creating-an-annotation-for-an-error) are ignored.
### Expressions
[Expressions](https://docs.github.com/en/actions/learn-github-actions/expressions) support the standard GitHub functions and contexts.
## Missing UI features
### Pre and Post steps
Pre and post steps have no section of their own in the job log.
### Services steps
Service steps have no section of their own in the job log.
## Different behavior
### Job token permissions (`permissions`)
`permissions` and `jobs.<job_id>.permissions` control the `GITEA_TOKEN` permissions, clamped by the repository and owner settings and further restricted for fork pull requests and cross-repository access.
GitHub-only scopes like `statuses`, `checks`, `deployments`, `security-events` and `pages` are not supported, while Gitea adds `code`, `releases`, `wiki` and `projects`. `id-token` works like on GitHub, see [OpenID Connect](openid-connect.md).
See [Actions job token permissions](token-permissions.md).
### Downloading actions
Actions without a host, like `uses: actions/checkout@v4`, are downloaded from `https://github.com`, or from your own instance when `[actions].DEFAULT_ACTIONS_URL` is `self`.
See the [Configuration Cheat Sheet](../../administration/config-cheat-sheet.md#actions-actions).
For other sources, see [Absolute action URLs](#absolute-action-urls), [Action prefixes](#action-prefixes) and [Workflow-relative actions](#workflow-relative-actions).
### Context availability
[Context availability](https://docs.github.com/en/actions/learn-github-actions/contexts#context-availability) is not checked, so contexts like `env` work in more places.
Gitea Actions has multiple components. This document describes them individually.
## Gitea Runner
Gitea Runner is based on a hard fork of [nektos/act](https://github.com/nektos/act).
Like other CI runners, we designed it as an external part of Gitea, which means it should run on a different server than Gitea.
To ensure that the runner connects to the correct Gitea instance, we need to register it with a token.
Additionally, the runner will introduce itself to Gitea and declare what kind of jobs it can run by reporting its labels.
Earlier, we mentioned that `runs-on: ubuntu-latest` in a workflow file means that the job will be run on a runner with the `ubuntu-latest` label.
But how does the runner know to run `ubuntu-latest`? The answer lies in mapping the label to an environment.
That's why each label of a runner names an environment, like `my_custom_label:docker://centos:7`.
This means that the runner can take the job which needs to run on `my_custom_label`, and it will run it via a docker container with the image `centos:7`.
Docker isn't the only option, though.
The runner also supports running jobs directly on the host.
This is achieved through labels like `linux_arm:host`.
This label indicates that the runner can take a job that needs to run on `linux_arm` and run it directly on the host.
The label's design follows the format `label[:schema[:args]]`.
If the schema is omitted, it defaults to `host`.
So,
- `my_custom_label:docker://node:18`: Run jobs labeled with `my_custom_label` using the `node:18` Docker image.
- `my_custom_label:host`: Run jobs labeled with `my_custom_label` directly on the host.
- `my_custom_label`: Same as `my_custom_label:host`.
## Communication protocol
As the runner is an independent part of Gitea, we needed a protocol for runners to communicate with the Gitea instance.
However, we did not think it was a good idea to have Gitea listen on a new port.
Instead, we wanted to reuse the HTTP port, which means we needed a protocol that is compatible with HTTP.
We chose to use gRPC over HTTP.
The protocol is defined in [actions-proto-def](https://gitea.com/gitea/actions-proto-def), its Go code lives in [actionslib](https://gitea.com/gitea/actionslib).
More information about gRPC can be found on [its website](https://grpc.io/).
## Network architecture
Let's examine the overall network architecture.
This will help you troubleshoot some problems and explain why it's a bad idea to register a runner with a loopback address of the Gitea instance.

There are four network connections marked in the picture, and the direction of the arrows indicates the direction of establishing the connections.
### Connection 1, runner to Gitea instance
The runner must be able to connect to Gitea to receive tasks and send back the execution results.
### Connection 2, job containers to Gitea instance
The job containers have different network namespaces than the runner, even if they are on the same machine.
They need to connect to Gitea to fetch codes if there is `actions/checkout@v4` in the workflow, for example.
Fetching code is not always necessary to run some jobs, but it is required in most cases.
If you use a loopback address to register a runner, the runner can connect to Gitea when it is on the same machine.
However, if a job container tries to fetch code from localhost, it will fail because Gitea is not in the same container.
### Connection 3, runner to internet
When you use some actions like `actions/checkout@v4`, the runner downloads the scripts, not the job containers.
By default, it downloads them from [github.com](https://github.com/), which requires internet access.
With `DEFAULT_ACTIONS_URL` set to `self`, or with `uses: self:owner/repo@ref`, it downloads them from your Gitea instance instead.
It also downloads some docker images from Docker Hub by default, which also requires internet access.
However, internet access is not strictly necessary.
You can configure your Gitea instance to fetch actions or images from your intranet facilities.
In fact, your Gitea instance can serve as both the actions marketplace and the image registry.
You can mirror actions repositories from GitHub to your Gitea instance, and use them as normal.
And [Gitea Container Registry](usage/packages/container.md) can be used as a Docker image registry.
### Connection 4, job containers to internet
When using actions such as `actions/setup-go@v5`, it may be necessary to download resources from the internet to set up the Go language environment in job containers.
Therefore, access to the internet is required for the successful completion of these actions.
However, it is optional as well.
You can use your own custom actions to avoid relying on internet access, or you can use your packaged Docker image to run jobs with all dependencies installed.
## Summary
Using Gitea Actions only requires ensuring that the runner can connect to the Gitea instance.
Internet access is optional, but not having it will require some additional work.
In other words: The runner works best when it can query the internet itself, but you don't need to expose it to the internet (in either direction).
If you encounter any network issues while using Gitea Actions, hopefully the image above can help you troubleshoot them.
This page contains some common questions and answers about Gitea Actions.
## Is it possible to disable Actions for new repositories by default for my own instance?
Yes, when you enable Actions for the instance, you can choose to enable the `actions` unit for all new repositories by default.
```ini
[repository]
; remove repo.actions will not enable actions for newly created repositories.
DEFAULT_REPO_UNITS = ...,repo.actions
```
## Should we use `${{ github.xyz }}` or `${{ gitea.xyz }}` in workflow files?
You can use `github.xyz` and Gitea will work fine.
As mentioned, Gitea Actions is designed to be compatible with GitHub Actions.
However, we recommend using `gitea.xyz` in case Gitea adds something that GitHub does not have to avoid different kinds of secrets in your workflow file (and because you are using this workflow on Gitea, not GitHub).
Still, this is completely optional since both options have the same effect at the moment.
## Where will the runner download scripts when using actions such as `actions/checkout@v4`?
From `https://github.com` by default, or from your own instance when `[actions].DEFAULT_ACTIONS_URL` is `self`, see the [Configuration Cheat Sheet](../../administration/config-cheat-sheet.md#actions-actions).
- `uses: self:owner/repo@ref` for your own Gitea instance
## How to limit the permission of the runners?
Runners only connect to your Gitea instance.
For each job, a runner gets a `GITEA_TOKEN` limited to the job's repository, see [Actions job token permissions](token-permissions.md).
To give a job access to more private repositories or external systems, pass it [secrets](usage/actions/secrets.md).
## Which operating systems are supported by Gitea Runner?
Official binaries are released for Linux, macOS and Windows.
Other systems supported by Go and Docker work in theory.
## How to avoid being hacked?
There are two types of possible attacks: unknown runner stealing the code or secrets from your repository, or malicious scripts controlling your runner.
Avoiding the former means not allowing people you don't know to register runners for your repository, organization, or instance.
The latter is a bit more complicated.
If you're using a private Gitea instance for your company, you may not need to worry about security since you trust your colleagues and can hold them accountable.
For public instances, things are a little different.
Here's how we do it on [gitea.com](http://gitea.com/):
- We only register runners for the "gitea" organization, so our runners will not execute jobs from other repositories.
- Our runners always run jobs with isolated containers. While it is possible to do this directly on the host, we choose not to for more security.
- To run actions for fork pull requests, approval is required. See [#22803](https://github.com/go-gitea/gitea/pull/22803).
- If someone registers their own runner for their repository or organization on [gitea.com](http://gitea.com/), we have no objections and will just not use it in our org. However, they should take care to ensure that the runner is not used by other users they do not know.
## Why choose GitHub Actions? Why not something compatible with GitLab CI/CD?
[@lunny](https://gitea.com/lunny) has explained this in the [issue to implement actions](https://github.com/go-gitea/gitea/issues/13539).
Furthermore, Actions is not only a CI/CD system but also an automation tool.
There have also been numerous [marketplace actions](https://github.com/marketplace?type=actions) implemented in the open-source world.
It is exciting to be able to reuse them.
## What if it runs on multiple labels, such as `runs-on: [label_a, label_b]`?
The job runs on a runner that has all of the labels, as on [GitHub](https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idruns-on).
The runner uses the environment of the first label it has.
## What is the difference between agent labels and custom labels for a runner?
Gitea no longer has custom labels.
A runner declares its labels every time it starts, from its `runner.labels` configuration or the labels given at registration, so change them there and restart the runner.
## Will there be more implementations for Gitea Actions runner?
Although we would like to provide more options, our limited manpower means that Gitea Runner will be the only officially supported runner at the moment.
However, both Gitea and Gitea Runner are completely open source under MIT License, so anyone can modify the code to satisfy their requirements.
In case you fork Gitea Runner to create your own version: Please contribute the changes back if you can and if you think your changes will help others as well.
## What workflow trigger events does Gitea support?
All events listed in this table are supported events and are compatible with GitHub.
For events supported only by GitHub, see GitHub's [documentation](https://docs.github.com/en/actions/using-workflows/events-that-trigger-workflows).
> Without an explicit `types:` filter, `pull_request` and `pull_request_target` only run on `opened`, `reopened` and `synchronize`, matching GitHub. All other events run on every activity type they support.
> For `pull_request` events, in [GitHub Actions](https://docs.github.com/en/actions/using-workflows/events-that-trigger-workflows#pull_request), the `ref` is `refs/pull/:prNumber/merge`, which is a reference to the merge commit preview. However, Gitea has no such reference.
> Therefore, the `ref` in Gitea Actions is `refs/pull/:prNumber/head`, which points to the head of pull request rather than the preview of the merge commit.
## How to share actions and reusable workflows from private repositories?
Go to the repository's **Settings** > **Actions** > **General** page and add collaborative owners.
The private repositories of collaborative owners are allowed to access the actions and workflows in the current repository.
To share within the same user or organization, add the private repositories under its **Settings** > **Actions** > **General** > **Cross-Repository Access** instead.
See [Cross-repository access](token-permissions.md#cross-repository-access).
Jobs can request short-lived OpenID Connect (OIDC) tokens to sign in to cloud providers and other services without storing long-lived secrets. The tokens use the GitHub Actions format, so actions like `aws-actions/configure-aws-credentials`, `google-github-actions/auth` and `azure/login` work unchanged.
## Trusting Gitea
Add Gitea as an OIDC identity provider in the service:
- Issuer: `<ROOT_URL>/api/actions/oidc`
- Audience: `<ROOT_URL>/<owner>`, unless the workflow requests another one
Then limit which workflows can sign in with a condition on the [`sub` claim](#claims).
Gitea signs the tokens with a key it creates at `<APP_DATA_PATH>/jwt/actions_oidc.pem`. Instances behind a load balancer must share this file.
## Requesting a token
Grant the `id-token` permission to the workflow or job:
```yaml
permissions:
id-token: write
```
Default permissions never include it, and jobs from fork pull requests never get it. Owners and repositories can disable it with their [maximum token permissions](token-permissions.md#maximum-token-permissions-clamping).
Actions like the ones above request the token themselves. In a script, use `ACTIONS_ID_TOKEN_REQUEST_URL` and `ACTIONS_ID_TOKEN_REQUEST_TOKEN`:
The `pull_request` event uses the second form, all other events use the first, including `pull_request_target`.
The other claims match GitHub's, like `repository`, `ref`, `sha`, `workflow_ref` and `job_workflow_ref`. The discovery document at `<ROOT_URL>/api/actions/oidc/.well-known/openid-configuration` lists them all.
Differences from GitHub Actions:
- There is no `environment` claim.
- Pull request runs use `refs/pull/<index>/head` and its commit, not the merge ref and merge commit.
- `run_number` counts runs per repository, not per workflow.
Starting with Gitea **1.19**, Gitea Actions are available as a built-in CI/CD solution.
## Name
It is similar and mostly compatible to [GitHub Actions](https://github.com/features/actions), and its name is inspired by it too.
To avoid confusion, we have clarified the spelling here:
- "Gitea Actions" (with an "s", both words capitalized) is the name of the Gitea feature.
- "GitHub Actions" is the name of the GitHub feature.
- "Actions" could refer to either of the above, depending on the context. So it refers to "Gitea Actions" in this document.
- "action" or "actions" refer to some scripts/plugins to be used, like "actions/checkout@v4" or "actions/cache@v3".
## Runners
Just like other CI/CD solutions, Gitea doesn't run the jobs itself, but delegates the jobs to runners.
The runner of Gitea Actions is called [Gitea Runner](https://gitea.com/gitea/runner), it is a standalone program and also written in Go.
An important part of the application comes from a hard fork of [nektos/act](http://github.com/nektos/act).
Because the runner is deployed independently, there could be potential security issues.
To avoid them, please follow two simple rules:
- Don't use a runner you don't trust for your repository, organization or instance.
- Don't provide a runner to a repository, organization or instance you don't trust.
For Gitea instances used internally, such as instances used by enterprises or individuals, neither of these two rules is a problem, they are naturally so.
However, for public Gitea instances, such as [gitea.com](https://gitea.com), these two rules should be kept in mind when adding or using runners.
## Status
Gitea Actions is stable enough for production usage and we use Gitea Actions in all the repositories in https://gitea.com/gitea.
This page will guide you through the process of using Gitea Actions.
## Set up Gitea
First of all, you need a Gitea instance.
You can follow the [documentation](installation/from-package.md) to set up a new instance or upgrade your existing one.
It doesn't matter how you install or run Gitea, as long as its version is 1.19.0 or higher.
Since 1.21.0, Actions are enabled by default. If you are using versions before 1.21.0, you need to add the following to the configuration file to enable it:
```ini
[actions]
ENABLED=true
```
If you want to learn more or encounter any problems while configuring it, please refer to the [Configuration Cheat Sheet](../../administration/config-cheat-sheet.md#actions-actions).
### Set up runner
Gitea Actions requires [Gitea Runner](https://gitea.com/gitea/runner) to run the jobs.
In order to avoid consuming too many resources and affecting the Gitea instance, it is recommended to start runners on separate machines from the Gitea instance.
You can use the [pre-built binaries](http://dl.gitea.com/gitea-runner) or the [docker images](https://hub.docker.com/r/gitea/runner/tags) to set up the runner.
Before proceeding any further, we suggest running it as a command line with pre-built binaries to ensure that it works with your environment, especially if you are running a runner on your localhost.
And it could be easier to debug if something goes wrong.
The runner can run the jobs in isolated Docker containers, so you need to make sure that the Docker has been installed and Docker daemon is running.
While it is not strictly necessary, because the runner can also run the jobs directly on the host, it depends on how you configure it.
However, it is recommended to use Docker to run the jobs, because it is more secure and easier to manage.
Before running a runner, you should first register it to your Gitea instance using the following command:
You will need to study [the workflow syntax](https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions) for Actions and write the workflow files you want.
However, we can just start from a simple demo:
```yaml
name: Gitea Actions Demo
run-name: ${{ gitea.actor }} is testing out Gitea Actions 🚀
on: [push]
jobs:
Explore-Gitea-Actions:
runs-on: ubuntu-latest
steps:
- run: echo "🎉 The job was automatically triggered by a ${{ gitea.event_name }} event."
- run: echo "🐧 This job is now running on a ${{ runner.os }} server hosted by Gitea!"
- run: echo "🔎 The name of your branch is ${{ gitea.ref }} and your repository is ${{ gitea.repository }}."
- name: Check out repository code
uses: actions/checkout@v4
- run: echo "💡 The ${{ gitea.repository }} repository has been cloned to the runner."
- run: echo "🖥️ The workflow is now ready to test your code on the runner."
- name: List files in the repository
run: |
ls ${{ gitea.workspace }}
- run: echo "🍏 This job's status is ${{ job.status }}."
```
:::warning
Certain actions may not function correctly within SHA256 repositories or when Gitea runs on subpath. This includes [actions/checkout](https://github.com/actions/checkout/issues/1843).
:::
You can upload it as a file with the extension `.yaml` in the directory `.gitea/workflows/` of the repository, for example `.gitea/workflows/demo.yaml`.
You might notice that this is fairly similar from the [Quickstart for GitHub Actions](https://docs.github.com/en/actions/quickstart).
That is because Gitea Actions is designed to be compatible with GitHub Actions wherever possible.
Be careful, the demo file contains some emojis.
Please make sure your database supports them, especially when using MySQL.
If the charset is not `utf8mb4`, errors will occur, such as `Error 1366 (HY000): Incorrect string value: '\\xF0\\x9F\\x8E\\x89 T...' for column 'name' at row 1`.
See [Database Preparation](../../installation/database-preparation.md#mysqlmariadb) for more information.
Alternatively, you can remove all emojis from the demo file and try again.
The line `on: [push]` indicates that the workflow will be triggered when you push commits to this repository.
However, when you upload the YAML file, it also pushes a commit, so you should see a new task in the Actions tab.

Great job! You have successfully started working with Actions.
This page will introduce the [Gitea Runner](https://gitea.com/gitea/runner), which is the runner for Gitea Actions.
:::tip
For detailed installation, configuration, and usage instructions, see the [Gitea Runner documentation](/runner/).
:::
## Obtain a registration token
You can register a runner at different levels. It can be:
- Instance level: The runner will run jobs for all repositories in the instance.
- Organization level: The runner will run jobs for all repositories in the organization.
- Repository level: The runner will run jobs for the repository it belongs to.
Note that the repository may still use instance-level or organization-level runners even if it has its own repository-level runners. A future release may provide an option to allow more control over this.
Before registering the runner and running it, you need a registration token. The level of the runner determines where to obtain the registration token.
- Instance level: The admin settings page, like `<your_gitea.com>/-/admin/actions/runners`.
- Organization level: The organization settings page, like `<your_gitea.com>/<org>/settings/actions/runners`.
- Repository level: The repository settings page, like `<your_gitea.com>/<owner>/<repo>/settings/actions/runners`.
If you cannot see the settings page, please make sure that you have the right permissions and that Actions have been enabled.
The format of the registration token is a random string `D0gvfu2iHfUjNqCYVljVyRV14fISpJxxxxxxxxxx`.
A registration token can also be obtained from the Gitea [command-line interface](../../administration/command-line.md#actions-generate-runner-token):
You can also use `GITEA_RUNNER_REGISTRATION_TOKEN`/`GITEA_RUNNER_REGISTRATION_TOKEN_FILE` environment variables to set a global runner registration token when Gitea starts, for example:
Scoped workflows let you maintain Actions workflows in one central **source** repository and have them run automatically on many other repositories, without copying the workflow files into each one.
A workflow file committed under a source repository's scoped-workflow directory runs on every repository that the source applies to (a "consuming" repository). Each run executes **in the consuming repository's own context**: its runners, secrets, `GITEA_TOKEN`, and branch, while the workflow **content is read from the source repository**. This is useful for mandating shared CI across an organization or instance: linting, security scans, compliance or policy checks, and similar.
## Levels
A source repository can be registered at two levels:
- **Owner level**: registered by an organization or user. The source's workflows run on every repository owned by that organization or user.
- **Instance level**: registered by a site administrator. The source's workflows run on **every** repository on the instance.
The same repository may be registered at both levels; it is still evaluated once per matching event.
:::note
Instance-level sources apply to **every** repository on the instance, and a required one cannot be opted out. Detection runs on every repository's events. Register them deliberately.
:::
## Configuration
Scoped workflows live in a directory that is separate from regular workflows. It is controlled by `SCOPED_WORKFLOW_DIRS` in the `[actions]` section of `app.ini`:
```ini
[actions]
SCOPED_WORKFLOW_DIRS = .gitea/scoped_workflows
```
- The default is `.gitea/scoped_workflows`.
- It may list multiple directories (comma-separated).
- It **must not overlap** with `WORKFLOW_DIRS` (the regular workflow directories, by default `.gitea/workflows` and `.github/workflows`).
- Leaving it empty means no directory holds scoped workflows, so none are found or run. The settings page still appears and sources can still be registered, but no scoped workflow runs.
Keeping scoped workflows in their own directory means a source repository's *own* Actions are unaffected: only files under `SCOPED_WORKFLOW_DIRS` are treated as scoped workflows.
## Providing scoped workflows from a repository
1. In the would-be source repository, commit your workflow files under the scoped-workflow directory on its **default branch**, for example `.gitea/scoped_workflows/lint.yaml`:
Search for and add the repository. From then on, its scoped workflows apply to the consuming repositories at that level.
The source repository is within its own scope, so it runs these workflows on itself like any other consumer.
## How scoped workflows run
- The run belongs to the **consuming** repository and uses its runners, secrets, `GITEA_TOKEN`, and ref. It behaves like a normal run there, including rerun, logs, and commit statuses.
- The workflow **content is read from the source** repository, pinned to the source's default-branch commit at the time of the event.
- Detection uses the consuming repository's event, so the workflow's `on:` triggers (such as `push` and `pull_request`) must match that event.
## Opting out
A consuming repository can disable a scoped workflow it does not want, from its **Actions** page (the workflow appears under its source). A workflow that has been marked **required** (see below) can never be disabled or opted out by a consuming repository.
## Required workflows and the merge gate
On the Scoped Workflows settings page you can mark individual workflows as **required** and give each one or more **status-check patterns** (one glob per line). A required scoped workflow:
- Cannot be disabled by consuming repositories.
- Gates pull-request merges: a consuming pull request can only be merged once a commit status matching **every** pattern has **passed** (must-present-and-pass). A required check that never posts a status **blocks** the merge rather than being silently skipped, so disabling Actions on the consumer cannot bypass it.
The status-check context a scoped run posts has the form:
```
<source repo full name>: <workflow display name> / <job> (<event>)
```
The settings page previews these "Expected status checks" for each workflow and marks the ones your patterns match, so you can confirm a pattern is correct. A common pattern wildcards the job and event, for example `my-org/ci-repo: Lint / *`.
Enforcement scope:
- A required check is enforced on any target branch that has a **branch protection rule**, even one whose own status checks are disabled.
- A target branch with **no** protection rule is not gated.
:::warning
Only a workflow that runs on an event that posts a commit status (`push`, `pull_request`, `pull_request_target`, `release`) can be required. A workflow that runs only on events like `workflow_dispatch`, `schedule` or `workflow_call` posts no status, so marking it required would block every consuming pull request forever. The settings page warns you in this case.
:::
## Reusable workflows (`uses:`)
Scoped workflows can call reusable workflows:
- A **local** reference (`uses: ./...` or `uses: $/...`) resolves against the **source** repository: the repository the calling workflow's content came from, not the consuming repository.
- A **cross-repository** reference (`uses: owner/repo/...@ref` or `uses: self:owner/repo/...@ref`) is resolved with the **consuming repository's** read permission. If it points to a private repository, make sure every consuming repository can read it; otherwise the workflow will fail there.
A reusable workflow can live under `SCOPED_WORKFLOW_DIRS` (or `WORKFLOW_DIRS`) of the source repository.
## Security considerations
A source repository's workflow content is executed in every repository it applies to, and its step scripts and their output are written to that repository's Actions logs, readable by anyone who can view the consuming repository's Actions.
- Registering a **private** repository as a source therefore discloses its workflow logic through those logs. Only register repositories whose workflow content may be shared with every consuming repository.
## Limitations
- `on: schedule` and `on: workflow_run` are currently not supported as scoped workflow triggers.
- Scoped workflow content is read from the source's default branch; other branches of the source are not used.
@@ -36,4 +25,11 @@ The following rules apply to secret names:
For example, a secret created at the repository level must have a unique name in that repository, and a secret created at the organization level must have a unique name at that level.
### Using secrets
After creating configuration variables, they will be automatically filled in the `secrets` context.
They can be accessed through expressions like `${{ secrets.SECRET_NAME }}` in the workflow.
### Precedence
If a secret with the same name exists at multiple levels, the secret at the lowest level takes precedence. For example, if an organization-level secret has the same name as a repository-level secret, then the repository-level secret takes precedence.
Every Actions job receives a built-in token (`GITEA_TOKEN`) which can be used to access Gitea (Git over HTTP(S), API requests, etc.).
This page documents how Gitea decides what the token is allowed to do.
In workflows, it is available as `${{ secrets.GITEA_TOKEN }}`. These settings and `permissions:` only affect `GITEA_TOKEN` (not other secrets like personal access tokens). For API calls, see [API authentication](../../development/api-usage.md#authentication).
## Where permissions come from
Gitea determines the job token permissions in this order:
- For repositories in an organization, the repository can follow the owner-level configuration, or opt out with **Override owner-level configuration**.
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.
## Cross-repository access
By default, `GITEA_TOKEN` only has access to:
- The job's repository (according to the computed permissions)
- Public repositories (read-only)
Access to other private repositories is denied by default.
A user or organization can allow read-only access to selected private repositories via **Settings → Actions → General → Cross-Repository Access**.
Private repositories can also allow selected collaborative owners via **Settings → Actions → General**.
This allows the private repositories of those owners to read the current private repository for Actions use, such as private actions and reusable workflows.
Fork pull request workflows never gain cross-repository access to private repositories, even if those repositories are listed.
These environment variables are set automatically in every workflow run and can be accessed directly (e.g. `$CI` in shell scripts).
| Name | Description | Example |
|---|---|---|
| `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 artifacts are kept, empty when kept forever. | `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` |
### Runner environment variables
| Name | Description | Example |
|---|---|---|
| `RUNNER_ARCH` | The architecture of the runner. | `X64` |
| `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` |
### Internal environment variables
These are used internally by the runner and actions. They are typically not needed in workflows directly.
| Name | Description | Example |
|---|---|---|
| `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_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/` |
## Context variables
These variables are available in workflow expressions via `${{ gitea.<name> }}`. For compatibility, `${{ github.<name> }}` works as an alias.
| 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_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.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.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.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 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.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.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.gitea_default_actions_url` | The default URL for downloading actions. Gitea-specific. | `https://github.com` |
### Event payload
`gitea.event` is the webhook payload of the event that triggered the run, so which fields exist depends on `gitea.event_name`.
The same payload is written as JSON to the file at `gitea.event_path`.
Payloads use Gitea's webhook format, which is close to GitHub's but not identical. Differences are noted below.
Reading a field that the payload does not have returns an empty value instead of failing, so `${{ gitea.event.pull_request.number }}` is empty on a `push` run.
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.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` |
The remaining fields are specific to one or a few events:
| Name | Events | Description | Example |
|---|---|---|---|
| `gitea.event.schedule` | `schedule` | The cron expression that triggered the run. | `*/5 * * * *` |
| `gitea.event.inputs.<name>` | `workflow_dispatch`, `workflow_call` | A raw input value. Prefer `${{ inputs.<name> }}`, which also applies the input's `default`. | `true` |
| `gitea.event.workflow` | `workflow_dispatch` | The file name of the dispatched workflow. | `ci.yaml` |
| `gitea.event.ref` | `push`, `create`, `delete`, `workflow_dispatch` | The ref of the event. Unlike GitHub, `create` and `delete` use the full ref instead of the short name. | `refs/heads/main` |
| `gitea.event.ref_type` | `create`, `delete` | The type of ref: `branch` or `tag`. | `tag` |
| `gitea.event.before`<br/>`gitea.event.after` | `push` | The commit SHA before and after the push. | `a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2` |
| `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.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.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` |
| `gitea.event.pull_request.head.ref`<br/>`gitea.event.pull_request.head.sha`<br/>`gitea.event.pull_request.head.repo.full_name` | `pull_request`, `pull_request_target`, `pull_request_review`, `pull_request_review_comment` | The source branch of the pull request. | `feature-branch` |
| `gitea.event.pull_request.base.ref`<br/>`gitea.event.pull_request.base.sha` | `pull_request`, `pull_request_target`, `pull_request_review`, `pull_request_review_comment` | The target branch of the pull request. | `main` |
| `gitea.event.review.type` | `pull_request_review`, `pull_request_review_comment` | The kind of review: `pull_request_review_approved`, `pull_request_review_rejected` or `pull_request_review_comment`. GitHub uses `review.state` with the values `approved`, `changes_requested` and `commented`. | `pull_request_review_approved` |
| `gitea.event.review.content` | `pull_request_review`, `pull_request_review_comment` | The body of the review. GitHub names this field `review.body`. | `Ship it` |
| `gitea.event.changes.title.from`<br/>`gitea.event.changes.body.from` | `issues`, `issue_comment`, `pull_request` | The previous value. Only set when `action` is `edited`. | `Old title` |
| `gitea.event.release.tag_name`<br/>`gitea.event.release.name`<br/>`gitea.event.release.body`<br/>`gitea.event.release.draft`<br/>`gitea.event.release.prerelease` | `release` | The release the event happened on. | `v1.2.3` |
| `gitea.event.package.name`<br/>`gitea.event.package.type`<br/>`gitea.event.package.version` | `registry_package` | The package the event happened on. | `gitea` |
| `gitea.event.workflow_run.id`<br/>`gitea.event.workflow_run.event`<br/>`gitea.event.workflow_run.status`<br/>`gitea.event.workflow_run.conclusion`<br/>`gitea.event.workflow_run.head_branch`<br/>`gitea.event.workflow_run.head_sha` | `workflow_run` | The workflow run that the event reports on. | `success` |
`schedule` runs have no webhook of their own. Their payload is the payload of the push that registered the schedule, with `schedule`, `repository`, `sender` and, for organization-owned repositories, `organization` filled in.
Only those fields are meaningful, and `sender.login` is always `gitea-actions`.
## User-defined variables
You can create configuration variables on the user, organization and repository level.
The level of the variable depends on where you created it. When creating a variable, the
key will be converted to uppercase. You need use uppercase on the yaml file.
### Naming conventions
The following rules apply to variable names:
- Variable names can only contain alphanumeric characters (`[a-z]`, `[A-Z]`, `[0-9]`) or underscores (`_`). Spaces are not allowed.
- Variable names must not start with the `GITHUB_` and `GITEA_` prefix.
- Variable names must not start with a number.
- Variable names are case-insensitive.
- Variable names must be unique at the level they are created at.
- Variable names must not start with `CI`.
### Using variables
After creating configuration variables, they will be automatically filled in the `vars` context.
They can be accessed through expressions like `${{ vars.VARIABLE_NAME }}` in the workflow.
### Precedence
If a variable with the same name exists at multiple levels, the variable at the lowest level takes precedence:
A repository variable will always be chosen over an organization/user variable.
In Gitea `1.13`, support for [AGit](https://git-repo.info/en/2020/03/agit-flow-and-git-repo/) was added. AGit enables users to create pull requests directly, even without write permissions of the repository, eliminating the need to fork it. This helps reduce the number of duplicated repositories and minimizes unnecessary disk usage.
:::note
Git version 2.29 or higher is required on the server side for this to work.
:::
## Creating PRs with AGit
AGit allows to create PRs while pushing code to the remote repo.
This can be done by pushing to a special refspec (a location identifier known to git) instead of to a branch.
- `<remote>`: The remote pointing at the target repository (not a fork!), `origin` in the example above
- `<local-rev>`: The local branch or commit containing the changes you are proposing, `HEAD` in the example above
- `refs/for/<branch>`: The target PR type and configuration
- `for`: Create a normal PR with `<branch>` as the target branch. This is the only prefix supported at the moment, `refs/draft/...` and `refs/for-review/...` are reserved for future use and do not create a PR.
- `<branch>`: The branch you want your changes to be merged into
- `<topic>`: Optional, an alternative to `-o topic=<topic>`. The push is rejected with `topic-branch is not set` if the topic is given in neither place.
- `-o <option>[=<value>]`: Options for the PR
- `topic`: The topic of this change. It will become the name of the branch holding the changes waiting for review (prefixed with your user name). This is REQUIRED to trigger a pull request, unless it is already part of the refspec.
- `title`: The PR title (optional but recommended), only used for topics not already having an associated PR.
- `description`: The PR description (optional but recommended), only used for topics not already having an associated PR.
- `force-push=true`: Specifies whether to force-update the target branch.
- Note: omitting the value and using just `-o force-push` will also work.
Here's another example for creating a new PR targeting `main` with `topic`, `title`, and `description`:
```shell
git push origin HEAD:refs/for/main -o topic="topic_of_my_PR" -o title="Title of the PR" -o description="# The PR Description\nThis can be **any** markdown content.\n- [x] Ok"
Some projects have a standard list of questions that users need to answer
when creating an issue or pull request. Gitea supports adding templates to the
**default branch of the repository** so that they can autopopulate the form when users are
creating issues and pull requests. This will cut down on the initial back and forth
of getting some clarifying details.
It is currently not possible to provide generic issue/pull-request templates globally.
Additionally, the New Issue page URL can be suffixed with `?title=Issue+Title&body=Issue+Text` and the form will be populated with those strings. Those strings will be used instead of the template if there is one. See [Pre-filling Issues and Pull Requests](prefill-issues-pull-requests.md) for all parameters.
## File names
Possible file names for issue templates:
- `ISSUE_TEMPLATE.md`
- `ISSUE_TEMPLATE.yaml`
- `ISSUE_TEMPLATE.yml`
- `issue_template.md`
- `issue_template.yaml`
- `issue_template.yml`
- `.gitea/ISSUE_TEMPLATE.md`
- `.gitea/ISSUE_TEMPLATE.yaml`
- `.gitea/ISSUE_TEMPLATE.yml`
- `.gitea/issue_template.md`
- `.gitea/issue_template.yaml`
- `.gitea/issue_template.yml`
- `.github/ISSUE_TEMPLATE.md`
- `.github/ISSUE_TEMPLATE.yaml`
- `.github/ISSUE_TEMPLATE.yml`
- `.github/issue_template.md`
- `.github/issue_template.yaml`
- `.github/issue_template.yml`
Possible file names for issue config:
- `.gitea/ISSUE_TEMPLATE/config.yaml`
- `.gitea/ISSUE_TEMPLATE/config.yml`
- `.gitea/issue_template/config.yaml`
- `.gitea/issue_template/config.yml`
- `.github/ISSUE_TEMPLATE/config.yaml`
- `.github/ISSUE_TEMPLATE/config.yml`
- `.github/issue_template/config.yaml`
- `.github/issue_template/config.yml`
Possible file names for PR templates:
- `PULL_REQUEST_TEMPLATE.md`
- `PULL_REQUEST_TEMPLATE.yaml`
- `PULL_REQUEST_TEMPLATE.yml`
- `pull_request_template.md`
- `pull_request_template.yaml`
- `pull_request_template.yml`
- `.gitea/PULL_REQUEST_TEMPLATE.md`
- `.gitea/PULL_REQUEST_TEMPLATE.yaml`
- `.gitea/PULL_REQUEST_TEMPLATE.yml`
- `.gitea/pull_request_template.md`
- `.gitea/pull_request_template.yaml`
- `.gitea/pull_request_template.yml`
- `.github/PULL_REQUEST_TEMPLATE.md`
- `.github/PULL_REQUEST_TEMPLATE.yaml`
- `.github/PULL_REQUEST_TEMPLATE.yml`
- `.github/pull_request_template.md`
- `.github/pull_request_template.yaml`
- `.github/pull_request_template.yml`
## Directory names
Alternatively, users can create multiple issue templates inside a special directory and allow users to choose one that more specifically
addresses their problem.
Possible directory names for issue templates:
- `ISSUE_TEMPLATE`
- `issue_template`
- `.gitea/ISSUE_TEMPLATE`
- `.gitea/issue_template`
- `.github/ISSUE_TEMPLATE`
- `.github/issue_template`
- `.gitlab/ISSUE_TEMPLATE`
- `.gitlab/issue_template`
Inside the directory can be multiple markdown (`.md`) or yaml (`.yaml`/`.yml`) issue templates of the form.
## Syntax for markdown template
```md
---
name: "Template Name"
about: "This template is for testing!"
title: "[TEST] "
ref: "main"
assignees: ["user1"]
projects:
- Example Project
labels:
- bug
- "help needed"
---
This is the template!
```
In the above example, when a user is presented with the list of issues they can submit, this would show as `Template Name` with the description
`This template is for testing!`. When submitting an issue with the above example, the issue title would be pre-populated with
`[TEST] ` while the issue body would be pre-populated with `This is the template!`.
The issue would be assigned to `user1`.
The issue would be assigned to the project `Example Project`.
The issue would also be assigned two labels,
`bug` and `help needed`, and the issue will have a reference to `main`.
## Syntax for yaml template
This example YAML configuration file defines an issue form using several inputs to report a bug.
```yaml
name: Bug Report
about: File a bug report
title: "[Bug]: "
body:
- type: markdown
attributes:
value: |
Thanks for taking the time to fill out this bug report!
# some markdown that will only be visible once the issue has been created
- type: markdown
attributes:
value: |
This issue was created by an issue **template** :)
visible: [content]
- type: input
id: contact
attributes:
label: Contact Details
description: How can we get in touch with you if we need more info?
placeholder: ex. email@example.com
validations:
required: false
- type: textarea
id: what-happened
attributes:
label: What happened?
description: Also tell us, what did you expect to happen?
placeholder: Tell us what you see!
value: "A bug happened!"
validations:
required: true
- type: dropdown
id: version
attributes:
label: Version
description: What version of our software are you running?
options:
- 1.0.2 (Default)
- 1.0.3 (Edge)
validations:
required: true
- type: dropdown
id: browsers
attributes:
label: What browsers are you seeing the problem on?
multiple: true
options:
- Firefox
- Chrome
- Safari
- Microsoft Edge
- type: textarea
id: logs
attributes:
label: Relevant log output
description: Please copy and paste any relevant log output. This will be automatically formatted into code, so no need for backticks.
render: shell
- type: checkboxes
id: terms
attributes:
label: Code of Conduct
hide_label: true
description: By submitting this issue, you agree to follow our [Code of Conduct](https://example.com)
options:
- label: I agree to follow this project's Code of Conduct
required: true
- label: I have also read the CONTRIBUTION.MD
required: true
visible: [form]
- label: This is a TODO only visible after issue creation
visible: [content]
```
### Markdown
You can use a `markdown` element to display Markdown in your form that provides extra context to the user, but is not submitted by default.
| label | A brief description of the expected user input, which is also displayed in the form. | Required | String | - | - |
| hide_label | If true, the label normally used as a headline is not visible. | Optional | Boolean | false | - |
| description | A description of the text area to provide context or guidance, which is displayed in the form. | Optional | String | Empty String | - |
| placeholder | A semi-opaque placeholder that renders in the text area when empty. | Optional | String | Empty String | - |
| value | Text that is pre-filled in the text area. | Optional | String | - | - |
| render | If a value is provided, submitted text will be formatted into a codeblock. When this key is provided, the text area will not expand for file attachments or Markdown editing. | Optional | String | - | Languages known to Gitea. |
| required | Prevents form submission until element is completed. | Optional | Boolean | false | - |
| is_number | Prevents form submission until element is filled with a number. | Optional | Boolean | false | - |
| regex | Prevents form submission until element is filled with a value that match the regular expression. | Optional | String | - | a [regular expression](https://en.wikipedia.org/wiki/Regular_expression) |
visible: Default is **[form, content]**
### Dropdown
You can use a `dropdown` element to add a dropdown menu in your form.
| label | A brief description of the expected user input, which is displayed in the form. | Required | String | - | - |
| hide_label | If true, the label normally used as a headline is not visible. | Optional | Boolean | false | - |
| description | A description of the dropdown to provide extra context or guidance, which is displayed in the form. | Optional | String | Empty String | - |
| multiple | Determines if the user can select more than one option. | Optional | Boolean | false | - |
| list | If true, display as a list. If false, print items on one line with commas. | Optional | Boolean | false | - |
| options | An array of options the user can choose from. Cannot be empty and all choices must be distinct. | Required | String array | - | - |
| label | A brief description of the expected user input, which is displayed in the form. | Required | String | - | - |
| hide_label | If true, the label normally used as a headline is not visible. | Optional | Boolean | false | - |
| description | A description of the set of checkboxes, which is displayed in the form. Supports Markdown formatting. | Optional | String | Empty String | - |
| options | An array of checkboxes that the user can select. For syntax, see below. | Required | Array | - | - |
For each value in the options array, you can set the following keys.
| label | The identifier for the option, which is displayed in the form. Markdown is supported for bold or italic text formatting, and hyperlinks. | Required | String | - | - |
| required | Prevents form submission until element is completed. | Optional | Boolean | false | - |
| visible | Whether a specific checkbox appears in the form only, in the created issue only, or both. Valid options are "form" and "content". | Optional | String array | false | - |
You can use labels to classify issues and pull requests and to improve your overview over them.
## Creating Labels
For repositories, labels can be created by going to `Issues` and clicking on `Labels`.
For organizations, you can define organization-wide labels that are shared with all organization repositories, including both already-existing repositories as well as newly created ones. Organization-wide labels can be created in the organization `Settings`.
Labels have a mandatory name, a mandatory color, an optional description, and must either be exclusive or not (see `Scoped Labels` below).
When you create a repository, you can ensure certain labels exist by using the `Issue Labels` option. This option lists a number of available label sets that are [configured globally on your instance](../../administration/customizing-gitea.md#labels). Its contained labels will all be created as well while creating the repository.
## Scoped Labels
Scoped labels are used to ensure at most a single label with the same scope is assigned to an issue or pull request. For example, if labels `kind/bug` and `kind/enhancement` have the Exclusive option set, an issue can only be classified as a bug or an enhancement.
A scoped label must contain `/` in its name (not at either end of the name). The scope of a label is determined based on the **last**`/`, so for example the scope of label `scope/subscope/item` is `scope/subscope`.
## Filtering by Label
Issue and pull request lists can be filtered by label. Selecting multiple labels shows issues and pull requests that have all selected labels assigned.
By holding alt to click the label, issues and pull requests with the chosen label are excluded from the list.
- PullRequestPosterName: Pull request's poster name
- PullRequestIndex: Pull request's index number
- PullRequestReference: Pull request's reference char with index number. i.e. #1, !2
- ClosingIssues: return a string contains all issues which will be closed by this pull request i.e. `close #1, close #2`
- ReviewedOn: Which pull request this commit belongs to. For example `Reviewed-on: https://gitea.com/foo/bar/pulls/1`
- ReviewedBy: Who approved the pull request before the merge. For example `Reviewed-by: Jane Doe <[email protected]>`
## Rebase
When rebasing without a merge commit, `REBASE_TEMPLATE.md` modifies the message of the last commit. The following additional variables are available in this template:
- CommitTitle: Commit's title
- CommitBody: Commits's body text
Loaded 100 of 2679 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.