Make the SBOM actions cover opsroom's flow (image scan + webapp filesystem scan, merged into one BOM, uploaded to DT), not just rahla's single-image case: trivy-sbom: - scan-type input (image|fs) + path input -> supports `trivy fs <path>`. - server-url input -> client/server mode (--server) for a remote Trivy. - image is now required only when scan-type=image. sbom-merge (new): thin cyclonedx-cli wrapper that merges N CycloneDX BOMs. - Passes each file as its own --input-files flag (cyclonedx-cli treats a space-joined value as a single filename). - Sets the .NET CLI opt-out env + a writable DOTNET_CLI_HOME to avoid the first-run dotnet-suggest exception that can abort a cold merge, and guards that the merge actually produced a file. - Verified live end-to-end (fs scan + merge of two real CycloneDX 1.6 BOMs -> 453 components). dtrack-upload already covers opsroom's upload (and fixes its manual Content-Type: multipart/form-data header, which clobbers curl's boundary). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|---|---|---|
| dtrack-upload | ||
| notify-talk | ||
| sbom-merge | ||
| trivy-sbom | ||
| README.md | ||
forgejo-actions
Shared composite actions for Forgejo / Codeberg CI.
notify-talk— post a build/run status into a Nextcloud Talk channeltrivy-sbom— CycloneDX SBOM of a container image or filesystemsbom-merge— merge several CycloneDX SBOMs into onedtrack-upload— publish a BOM to Dependency-Track
notify-talk — Nextcloud Talk build/run notification
Posts a Markdown status message into a Nextcloud Talk channel. Use it as a final if: always() step so it reports both success and failure.
- name: Notify Nextcloud Talk
if: always()
uses: https://codeberg.org/datatactics/forgejo-actions/notify-talk@main
with:
status: ${{ job.status }}
env:
TALK_BOT_PASSWORD: ${{ secrets.TALK_BOT_PASSWORD }}
TALK_ROOM: ${{ vars.TALK_ROOM }}
TALK_USER: ${{ vars.TALK_USER }}
Inputs
| Input | Required | Default | Description |
|---|---|---|---|
status |
yes | — | Job result; pass ${{ job.status }} → renders ✅ / ❌ / ⚪ / ❓ |
title |
no | Build |
Header text → ### <icon> <title> |
message |
no | commit message | Body line; falls back to the push commit message when empty |
Required environment (provided by the caller)
The action embeds no credentials and no instance config — the calling job supplies everything via env::
| Env | Suggested source | Value |
|---|---|---|
TALK_BOT_PASSWORD |
repository/org secret | Nextcloud app-password of the bot user |
TALK_ROOM |
variable | Talk conversation token (the last path segment of the chat URL) |
TALK_USER |
variable | Nextcloud bot username |
The action fails fast (set -eu) if any of the three is unset. TALK_ROOM accepts either the bare token or a full chat URL (the last path segment is used).
Custom message example
- name: Notify Nextcloud Talk
if: always()
uses: https://codeberg.org/datatactics/forgejo-actions/notify-talk@main
with:
status: ${{ needs.deploy.result }}
title: "Deploy"
message: "Release ${{ github.ref_name }} deployed (${{ needs.deploy.result }})"
env:
TALK_BOT_PASSWORD: ${{ secrets.TALK_BOT_PASSWORD }}
TALK_ROOM: ${{ vars.TALK_ROOM }}
TALK_USER: ${{ vars.TALK_USER }}
Rendered into Talk:
### ✅ Deploy
**Repo:** <owner>/<repo> **Branch:** <branch> **Author:** <actor>
Release v1.4.0 deployed (success)
[View run](<server>/<owner>/<repo>/actions/runs/<n>)
trivy-sbom — CycloneDX SBOM of a container image or filesystem
Scans a container image or a filesystem path with Trivy (standalone or client/server mode), writes a CycloneDX SBOM, optionally strips licenses, and fails the job if the BOM has no components (guards against silently emitting an empty BOM after a transient scanner/registry hiccup). Pairs with sbom-merge and dtrack-upload.
- name: Trivy SBOM
id: sbom
uses: https://codeberg.org/datatactics/forgejo-actions/trivy-sbom@main
with:
image: docker.io/datatactics/rahla:${{ steps.meta.outputs.version }}
output: sbom_rahla_${{ steps.meta.outputs.version }}.json
env: # optional — only for a private registry
TRIVY_USERNAME: ${{ vars.DOCKERHUB_USER }}
TRIVY_PASSWORD: ${{ secrets.DOCKERHUB_PASS }}
Filesystem scan against a remote Trivy server:
- uses: https://codeberg.org/datatactics/forgejo-actions/trivy-sbom@main
with:
scan-type: fs
path: webapp/
server-url: http://trivy.dependencytrack.svc.cluster.local
output: sbom_webapp.json
Inputs
| Input | Required | Default | Description |
|---|---|---|---|
scan-type |
no | image |
image (a container image) or fs (a filesystem path) |
image |
when scan-type=image |
"" |
Image ref to scan |
path |
no | . |
Filesystem path to scan (used when scan-type=fs) |
server-url |
no | "" |
Trivy client/server URL — when set, scans in client mode (--server) |
output |
no | sbom.json |
SBOM filename |
format |
no | cyclonedx |
Trivy output format |
scanners |
no | vuln |
Trivy scanners |
severity |
no | MEDIUM,HIGH,CRITICAL |
Trivy severities |
trivy-version |
no | v0.58.1 |
Trivy release tag to install (only when trivy is not already on PATH) |
strip-licenses |
no | false |
Delete .licenses from components + metadata.component. Dependency-Track v4 rejected Trivy's license format; v5.0.2 ingests it fine (verified 2026-07) — set true only if you still upload to a DT v4 instance |
min-components |
no | 1 |
Fail if the SBOM has fewer than this many components |
Outputs
| Output | Description |
|---|---|
sbom-file |
Path to the generated SBOM |
component-count |
Number of components in the SBOM |
Registry credentials are not action inputs — Trivy reads TRIVY_USERNAME / TRIVY_PASSWORD (and other TRIVY_*) natively, so pass them via the caller's env:.
sbom-merge — merge CycloneDX SBOMs
Merges two or more CycloneDX SBOMs into one with the CycloneDX CLI — e.g. combine a container-image scan with a filesystem scan before uploading a single project to Dependency-Track.
- name: Scan image
uses: https://codeberg.org/datatactics/forgejo-actions/trivy-sbom@main
with: { image: "${{ env.IMAGE_REF }}", server-url: "${{ env.TRIVY_SERVER }}", output: sbom_image.json }
- name: Scan webapp/
uses: https://codeberg.org/datatactics/forgejo-actions/trivy-sbom@main
with: { scan-type: fs, path: webapp/, server-url: "${{ env.TRIVY_SERVER }}", output: sbom_webapp.json }
- name: Merge
id: sbom
uses: https://codeberg.org/datatactics/forgejo-actions/sbom-merge@main
with:
input-files: sbom_image.json sbom_webapp.json
output: sbom_merged.json
- name: Upload
uses: https://codeberg.org/datatactics/forgejo-actions/dtrack-upload@main
with: { bom: "${{ steps.sbom.outputs.sbom-file }}", project-name: opsroom, project-version: "${{ github.ref_name }}" }
env: { DTRACK_URL: "${{ vars.DTRACK_URL }}", DTRACK_API_KEY: "${{ secrets.DTRACK_API_KEY }}" }
Inputs
| Input | Required | Default | Description |
|---|---|---|---|
input-files |
yes | — | Space-separated list of BOM files to merge |
output |
no | sbom_merged.json |
Merged SBOM filename |
output-version |
no | v1_6 |
CycloneDX output spec version |
cyclonedx-version |
no | v0.32.0 |
CycloneDX CLI release tag to install (only when cyclonedx is not on PATH) |
Outputs
| Output | Description |
|---|---|
sbom-file |
Path to the merged SBOM |
component-count |
Number of components in the merged SBOM |
dtrack-upload — publish a BOM to Dependency-Track
POSTs a BOM to a Dependency-Track instance (multipart bom upload, autoCreate), then verifies DT returned a processing token — a rejected upload fails the job instead of going green. Optionally polls until DT finishes processing.
- name: Upload SBOM to Dependency-Track
uses: https://codeberg.org/datatactics/forgejo-actions/dtrack-upload@main
with:
bom: ${{ steps.sbom.outputs.sbom-file }}
project-name: datatactics/rahla
project-version: ${{ steps.meta.outputs.version }}
parent-uuid: cff07b40-c95c-47e9-9367-cb9e8df75dcd
env:
DTRACK_URL: ${{ vars.DTRACK_URL }} # the .../api/v1/bom endpoint
DTRACK_API_KEY: ${{ secrets.DTRACK_API_KEY }}
Inputs
| Input | Required | Default | Description |
|---|---|---|---|
bom |
yes | — | Path to the BOM file to upload |
project-name |
yes | — | Dependency-Track projectName |
project-version |
yes | — | Dependency-Track projectVersion |
parent-uuid |
no | "" |
Parent project UUID (field omitted when empty) |
auto-create |
no | true |
Create the project if it does not exist |
verify-non-empty |
no | true |
Refuse to upload a BOM with 0 components |
wait-for-processing |
no | false |
Poll DT until the BOM finishes processing |
timeout-seconds |
no | 120 |
Max wait when wait-for-processing: true |
Outputs
| Output | Description |
|---|---|
token |
Dependency-Track processing token |
Required environment (provided by the caller)
| Env | Suggested source | Value |
|---|---|---|
DTRACK_URL |
variable | The .../api/v1/bom endpoint |
DTRACK_API_KEY |
secret | DT API key with BOM_UPLOAD (+ PROJECT_CREATION_UPLOAD when auto-create: true) permission |
Setup (notify-talk)
The credentials and channel config live outside the action:
- A secret
TALK_BOT_PASSWORD— the Nextcloud app-password of the bot user (repository- or organization-level). - Variables
TALK_ROOM(channel token) andTALK_USER(bot username) — repository-, organization-, or instance-level so they can be shared.
The Nextcloud base URL is configured inside the action.
Conventions
- Use the full action URL. A bare
owner/repo@refresolves against the default actions registry, not the repo you expect — always reference the fullhttps://codeberg.org/...URL. - Pin the ref.
@maintracks latest; pin a tag (e.g.@v1) once releases are tagged for reproducible builds.