No description
Find a file
2026-06-03 14:16:59 +01:00
actions publish-deb: inputs via env (injection-safe) + per-file summary; add forgejo-release composite (notes-only, non-fatal warning) 2026-06-03 13:08:19 +01:00
README.md docs: document both composites (publish-deb + forgejo-release), security model, cross-repo usage 2026-06-03 14:16:59 +01:00

ci-actions

Shared Forgejo Actions composite actions for the bit-crafts org. Keeping them here (DRY) means every repo's release.yml calls the same audited, injection-safe publish/release logic instead of copy-pasting shell.

Two actions:

Action Purpose
actions/publish-deb Upload .deb package(s) to this instance's Debian registry (idempotent delete-then-upload).
actions/forgejo-release Create a notes-only Forgejo Release for the current tag (marker only — binaries live in the registry).

Using these actions (cross-repo)

Forgejo resolves cross-repo uses: by full URL. Reference an action by its absolute URL on this instance and pin a ref:

- uses: https://git.bit-crafts.com/bit-crafts/ci-actions/actions/publish-deb@main

Pinning: consumers currently track @main (mutable). For reproducible releases you can tag this repo (vN) and switch consumers to @vN or a commit SHA. Tracked as a TODO.

Security model (both actions)

All user-supplied inputs are bound to environment variables in the step's env: block and referenced as "$VAR" inside run: — they are never interpolated into the shell body (no ${{ inputs.x }} directly in run:). This blocks shell/command injection through crafted input values. Both actions run with set -euo pipefail. forgejo-release builds its JSON payload with python3 -c 'json.dumps(...)' reading from os.environ, so the body cannot break out of the JSON. Endpoints are derived from ${{ github.server_url }} (the runner's own instance URL) — no hardcoded IPs.


actions/publish-deb

Publishes one or more .deb files to the Debian package registry of this Forgejo instance. For each matched file it DELETEs the existing (package, version, arch) (tolerating 404) then uploads it — so re-running a release overwrites rather than 409-conflicting.

Inputs

Input Required Default Description
token yes PAT with write:package scope (e.g. secrets.PKG_TOKEN). The built-in github.token does not carry the package scope.
path no dist/*.deb Glob of .deb files to publish.
owner no bit-crafts Registry owner/org.
user no younes Auth username for the registry.
distribution no trixie Debian distribution (apt dists/<distribution>).
component no main Debian component.

Fails the job if no file matches path. Prints a per-file delete/upload -> HTTP code summary.

Example

- name: Publish .deb to registry
  uses: https://git.bit-crafts.com/bit-crafts/ci-actions/actions/publish-deb@main
  with:
    token: ${{ secrets.PKG_TOKEN }}
    path: "dist/*.deb"

Consumers (apt): the registry is served at https://pkgs.bit-crafts.comdeb https://pkgs.bit-crafts.com/api/packages/bit-crafts/debian trixie main (key at .../debian/repository.key; one-line .list, not DEB822).


actions/forgejo-release

Creates a notes-only Forgejo Release for the tag that triggered the run. It attaches no binary assets — the canonical artifacts are the .debs in the Debian registry; the Release object is just a human-facing marker/changelog. It is non-fatal: on a non-2xx/409 response it emits ::warning:: and the job still succeeds (the release is not load-bearing).

Inputs

Input Required Default Description
token yes Token with repo write. Pass the built-in ${{ github.token }}.
user no younes Auth username.
body no Automated release. Binaries are published to the Debian package registry. Release notes body.

Behavior by HTTP status: 2xx → created · 409 → already exists (ok) · anything else → ::warning:: (job continues).

Example

- name: Create Forgejo release marker
  uses: https://git.bit-crafts.com/bit-crafts/ci-actions/actions/forgejo-release@main
  with:
    token: ${{ github.token }}
    body: "Automated release — my-project (.deb). Binary is published to the Debian package registry."

Combined release.yml (typical consumer)

name: release
on:
  push:
    tags: ["v*"]
concurrency:
  group: release-${{ github.ref }}
  cancel-in-progress: false        # serialize the delete-then-upload to the registry
jobs:
  package-publish:
    runs-on: build-large
    container: git.bit-crafts.com/bit-crafts/ci-base-c@sha256:<digest>   # pin by digest for reproducibility
    steps:
      - uses: actions/checkout@v4
      - run: |
          set -euo pipefail
          bash packaging/build-deb.sh "${GITHUB_REF_NAME#v}"   # produces dist/*.deb
      - uses: https://git.bit-crafts.com/bit-crafts/ci-actions/actions/publish-deb@main
        with:
          token: ${{ secrets.PKG_TOKEN }}
          path: "dist/*.deb"
      - uses: https://git.bit-crafts.com/bit-crafts/ci-actions/actions/forgejo-release@main
        with:
          token: ${{ github.token }}

Required secrets (org bit-crafts)

Secret Used by Scope
PKG_TOKEN publish-deb PAT write:package
DEPLOY_KEY site deploy workflows (not these actions) per-container SSH deploy key

The release token (github.token) is provided automatically per run — no secret needed for forgejo-release.