- JavaScript 100%
| Filename | Latest commit message | Latest commit date |
|---|---|---|
|
All checks were successful
test / test (push) Successful in 15s
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
| .forgejo/workflows | ||
| delete | ||
| delete-deb | ||
| docker-login | ||
| src | ||
| tests | ||
| token | ||
| upload | ||
| upload-deb | ||
| .gitignore | ||
| esbuild.build.js | ||
| package-lock.json | ||
| package.json | ||
| README.md | ||
upload-package
Forgejo Actions for uploading to and deleting from a generic package
registry, uploading to and deleting from a Debian (APT) package registry, and authenticating with a job-scoped JWT obtained via OIDC
(ACTIONS_ID_TOKEN_REQUEST_URL / ACTIONS_ID_TOKEN_REQUEST_TOKEN). See
Forgejo Authorized Integrations.
Six actions, referenced independently:
upload/— uploads a local file.delete/— deletes a remote package file.upload-deb/— uploads.debfiles to the Debian package registry, so they can be installed withapt.delete-deb/— deletes a package from the Debian package registry.token/— retrieves the JWT itself, for cases like pushing container images that need the raw token rather than a package upload/delete.docker-login/— writes the JWT into the Dockerconfig.json, so Docker, kaniko, buildah, crane etc. pick it up without further configuration.
The actions require that the workflow has enable-openid-connect: true set.
Upload
permissions:
id-token: write
steps:
- uses: actions/checkout@v4
- name: Upload theme package
uses: https://git.hanse.de/hanse/upload-package/upload@v3
with:
audience: u:2:50bef203-4429-4694-83b3-a9b4ad683d20
owner: vvm
package-name: vvm-theme
version: latest
file-path: vvm-theme.zip
Inputs
| Input | Required | Default | Description |
|---|---|---|---|
server-url |
no | GITHUB_SERVER_URL |
Base URL of the Forgejo/Gitea instance |
audience |
yes | Audience passed to the OIDC ID token request, e.g. u:2:<uuid> |
|
owner |
yes | Package owner (user or org) | |
package-name |
yes | Package name | |
package-type |
no | generic |
Package type path segment |
version |
no | latest |
Package version |
file-path |
yes | Local file to upload | |
file-name |
no | basename of file-path |
Remote file name |
content-type |
no | application/octet-stream |
Content-Type header sent with the upload |
Outputs
| Output | Description |
|---|---|
package-url |
Full URL of the uploaded package file |
Delete
permissions:
id-token: write
steps:
- name: Delete previous theme package
uses: https://git.hanse.de/hanse/upload-package/delete@v3
with:
audience: u:2:50bef203-4429-4694-83b3-a9b4ad683d20
owner: vvm
package-name: vvm-theme
version: latest
file-name: vvm-theme.zip
Inputs
| Input | Required | Default | Description |
|---|---|---|---|
server-url |
no | GITHUB_SERVER_URL |
Base URL of the Forgejo/Gitea instance |
audience |
yes | Audience passed to the OIDC ID token request | |
owner |
yes | Package owner (user or org) | |
package-name |
yes | Package name | |
package-type |
no | generic |
Package type path segment |
version |
no | latest |
Package version |
file-name |
yes | Remote file name to delete | |
ignore-not-found |
no | true |
Treat a 404 response as success instead of failing the step |
Outputs
| Output | Description |
|---|---|
deleted |
"true" if a file was deleted, "false" if it didn't exist and ignore-not-found applied |
Upload Debian package
Uploads one or more .deb files to the owner's
Debian package registry.
Unlike the generic registry, the package name, version and architecture
come from the .deb itself; you only choose the distribution and component
it's added to. Forgejo regenerates and signs the repository index after each
upload.
The upload-deb action is only available from v4 on.
permissions:
id-token: write
steps:
- uses: actions/checkout@v4
- name: Build packages
run: dpkg-buildpackage -b -us -uc && mv ../*.deb .
- name: Upload Debian packages
uses: https://git.hanse.de/hanse/upload-package/upload-deb@v4
with:
audience: u:2:50bef203-4429-4694-83b3-a9b4ad683d20
owner: vvm
distribution: bookworm
file-path: |
vvm-tools_1.2.0_amd64.deb
vvm-tools_1.2.0_arm64.deb
The registry rejects a package whose name, version and architecture already
exist in the distribution and component with 409 Conflict; bump the
package version, or delete the old one first with
delete-deb, instead of re-uploading the same
version. Every file is checked before the first upload,
but an upload failing part way through leaves the earlier files uploaded.
To install from the repository:
sudo curl https://git.hanse.de/api/packages/vvm/debian/repository.key \
-o /etc/apt/keyrings/forgejo-vvm.asc
echo "deb [signed-by=/etc/apt/keyrings/forgejo-vvm.asc] https://git.hanse.de/api/packages/vvm/debian bookworm main" | \
sudo tee /etc/apt/sources.list.d/forgejo-vvm.list
sudo apt update
Inputs
| Input | Required | Default | Description |
|---|---|---|---|
server-url |
no | GITHUB_SERVER_URL |
Base URL of the Forgejo/Gitea instance |
audience |
yes | Audience passed to the OIDC ID token request, e.g. u:2:<uuid> |
|
owner |
yes | Package owner (user or org) | |
distribution |
yes | Distribution to add the packages to, e.g. bookworm |
|
component |
no | main |
Component to add the packages to |
file-path |
yes | Local .deb file to upload; several paths may be given, one per line |
Outputs
| Output | Description |
|---|---|
repository-url |
URL of the Debian repository, e.g. https://git.hanse.de/api/packages/vvm/debian, for an apt sources entry |
Delete Debian package
Removes one package (name, version and architecture) from a distribution and component of the Debian package registry. Forgejo regenerates the repository index afterwards.
The delete-deb action is only available from v4 on.
permissions:
id-token: write
steps:
- name: Delete the previous build
uses: https://git.hanse.de/hanse/upload-package/delete-deb@v4
with:
audience: u:2:50bef203-4429-4694-83b3-a9b4ad683d20
owner: vvm
distribution: bookworm
package-name: vvm-tools
version: 1.2.0
architecture: amd64
version is the full version from the package's control file, including any
epoch (e.g. 1:1.2.0-1). Architecture-independent packages use all.
Inputs
| Input | Required | Default | Description |
|---|---|---|---|
server-url |
no | GITHUB_SERVER_URL |
Base URL of the Forgejo/Gitea instance |
audience |
yes | Audience passed to the OIDC ID token request | |
owner |
yes | Package owner (user or org) | |
distribution |
yes | Distribution to delete the package from, e.g. bookworm |
|
component |
no | main |
Component to delete the package from |
package-name |
yes | Debian package name | |
version |
yes | Debian package version | |
architecture |
yes | Package architecture, e.g. amd64 or all |
|
ignore-not-found |
no | true |
Treat a 404 response as success instead of failing the step |
Outputs
| Output | Description |
|---|---|
deleted |
"true" if the package was deleted, "false" if it didn't exist and ignore-not-found applied |
Token
Use this when you need the raw JWT for something other than the generic
package API: to push container images (see below), or to call the Forgejo
API directly with Authorization: bearer <token>.
permissions:
id-token: write
steps:
- name: Get registry token
id: registry-token
uses: https://git.hanse.de/hanse/upload-package/token@v3
with:
audience: u:2:50bef203-4429-4694-83b3-a9b4ad683d20
- name: Call the Forgejo API with the token
run: |
curl -sS -H "Authorization: bearer ${{ steps.registry-token.outputs.token }}" \
https://git.hanse.de/api/v1/user
The token action is only available from v2 on; token@v1 does not exist.
Pushing a container image
The JWT works as the password for Forgejo's container registry. The username is not checked; the JWT alone determines identity and permissions. See forgejo/forgejo#3571 and Forgejo's end-to-end test.
This example builds and pushes with kaniko,
so it needs no Docker daemon on the runner. Node.js is installed because
actions/checkout and the token action are JavaScript actions:
name: container-image
on:
push:
branches: [main]
enable-openid-connect: true
jobs:
build-image:
runs-on: alpine-latest
container:
image: ghcr.io/osscontainertools/kaniko:alpine
steps:
- name: Install Node.js for JavaScript actions
run: apk add --no-cache nodejs git
- uses: actions/checkout@v4
- name: Get registry token
id: registry-token
uses: https://git.hanse.de/hanse/upload-package/token@v3
with:
audience: u:2:50bef203-4429-4694-83b3-a9b4ad683d20
- name: Build and push image
env:
# kaniko's "env" credential helper reads KANIKO_<HOST>_USER/_PASSWORD,
# with the registry host upper-cased and dots replaced by underscores.
KANIKO_GIT_HANSE_DE_USER: ${{ forgejo.actor }}
KANIKO_GIT_HANSE_DE_PASSWORD: ${{ steps.registry-token.outputs.token }}
run: >-
/kaniko/executor
--context=dir://${{ forgejo.workspace }}
--dockerfile=${{ forgejo.workspace }}/Dockerfile
--destination=git.hanse.de/${{ forgejo.repository }}:latest
--destination=git.hanse.de/${{ forgejo.repository }}:${{ forgejo.sha }}
--credential-helpers=env
On a runner with a Docker daemon, pass the token to docker login instead
(docker/login-action works the same way, with the token as password):
- name: Log in to the container registry
run: |
echo "${{ steps.registry-token.outputs.token }}" | \
docker login git.hanse.de -u "$GITHUB_ACTOR" --password-stdin
Container image names must be lower case; if the repository path has upper case letters, lower-case it before using it as the destination.
Troubleshooting
A rejected JWT shows up as a generic invalid username/password (docker) or
UNAUTHORIZED (kaniko), the same error you get with a wrong password. Check:
- Audience. It must be the audience of an Authorized Integration that
grants
write:packageon the image's owner. The value starts with the user ID of the integration's owner (u:<user-id>:<uuid>); don't copy the example values above, use the one shown for your own integration. - Ref filter. The integration's ref filter must match the run's ref
exactly, e.g.
refs/heads/main(notrefs/head/main). Runs triggered bypull_requesthave arefs/pull/...ref and are rejected by a branch filter; skip the push for those runs, e.g. with kaniko's--no-push.
Inputs
| Input | Required | Default | Description |
|---|---|---|---|
audience |
yes | Audience passed to the OIDC ID token request, e.g. u:2:<uuid> |
Outputs
| Output | Description |
|---|---|
token |
The job JWT, usable as a bearer token for the Forgejo API and package registries, or as the password for the container registry |
Docker login
Fetches the JWT and stores it as the credentials for the container registry
in the Docker config.json, the file Docker and most other container tools
read registry credentials from. Existing entries for other registries are
kept; an existing entry for the same registry is replaced.
Before writing the file, the action logs in to the registry the way
docker login does (/v2/, then the token endpoint the registry points to).
If the registry rejects the JWT, the step fails right away with the
registry's response and a hint about the likely cause, usually the wrong
audience, instead of a vague invalid username/password or UNAUTHORIZED
from a later build step (see Troubleshooting). This
checks that the token is accepted, not that it may push to a particular
image; a missing write:package grant still only shows up at push time. Set
verify: false to skip the check, e.g. when the registry isn't reachable
from the step that logs in.
The docker-login action is only available from v3 on.
The file is written to <config-dir>/config.json, where config-dir is the
config-dir input, else $DOCKER_CONFIG, else ~/.docker. The registry key
is the host of server-url (e.g. git.hanse.de) unless registry is set.
permissions:
id-token: write
steps:
- uses: actions/checkout@v4
- name: Log in to the container registry
uses: https://git.hanse.de/hanse/upload-package/docker-login@v3
with:
audience: u:2:50bef203-4429-4694-83b3-a9b4ad683d20
- name: Build and push
run: |
docker build -t git.hanse.de/${{ forgejo.repository }}:latest .
docker push git.hanse.de/${{ forgejo.repository }}:latest
With kaniko, write to the directory kaniko reads its credentials from. Set
config-dir explicitly rather than relying on the image's DOCKER_CONFIG;
when config-dir is set, the action also exports DOCKER_CONFIG to later
steps:
- name: Log in to the container registry
uses: https://git.hanse.de/hanse/upload-package/docker-login@v3
with:
audience: u:2:50bef203-4429-4694-83b3-a9b4ad683d20
config-dir: /kaniko/.docker
- name: Build and push image
run: >-
/kaniko/executor
--context=dir://${{ forgejo.workspace }}
--dockerfile=${{ forgejo.workspace }}/Dockerfile
--destination=git.hanse.de/${{ forgejo.repository }}:latest
If the existing config.json sets credsStore, or credHelpers for the
registry, Docker asks that helper instead of reading the entry written here;
the action warns when it finds either. The token stays in the file after the
job; it expires with the job's JWT, but don't use this on a shared,
persistent runner home directory.
Inputs
| Input | Required | Default | Description |
|---|---|---|---|
audience |
yes | Audience passed to the OIDC ID token request, e.g. u:2:<uuid> |
|
server-url |
no | GITHUB_SERVER_URL |
Base URL of the Forgejo/Gitea instance; its host is the registry |
registry |
no | host of server-url |
Registry host to store credentials for, e.g. git.hanse.de; verified over https unless a scheme is given |
username |
no | GITHUB_ACTOR |
User name stored with the token; the registry doesn't check it |
config-dir |
no | $DOCKER_CONFIG, else ~/.docker |
Directory holding config.json; when set, DOCKER_CONFIG is exported to later steps |
verify |
no | true |
Log in to the registry before writing config.json, failing with a clear error if the token is rejected |
Outputs
| Output | Description |
|---|---|
registry |
Registry host the credentials were stored for |
config-path |
Path of the config.json that was written |
token |
The job JWT that was stored |
Development
Source lives in src/; each action's dist/index.js is a committed esbuild
bundle so consumers don't need an install step. After changing src/, rebuild
all bundles:
npm install
npm run build
Commit the updated upload/dist/index.js, delete/dist/index.js,
upload-deb/dist/index.js, delete-deb/dist/index.js, token/dist/index.js,
and docker-login/dist/index.js along with your source changes.
Tests use Node's built-in test runner (no extra dependency). npm test
rebuilds all bundles first, then runs unit tests for src/lib/ and
integration tests that spawn the real bundled actions against a local mock
registry:
npm test