Skip to content

chore: update dependency node to v24.21.0 - autoclosed #361

chore: update dependency node to v24.21.0 - autoclosed

chore: update dependency node to v24.21.0 - autoclosed #361

Workflow file for this run

name: Docker Build & Publish
# All events build + push to both registries (Docker Hub + GHCR):
# - Pull requests: validated with a native amd64 build only (--load, no
# push) — PRs aren't a published artifact.
# - Push to main: tagged latest (+ short sha for traceability)
# - Push to development/nightly/feature/**: tagged with the (sanitized)
# branch name, e.g. development, nightly, feature-foo
# - Release published: tagged with the released semver (and latest, since a
# fresh release should be latest)
#
# Multi-arch publishing builds each platform on its own NATIVE runner
# (ubuntu-latest for amd64, ubuntu-24.04-arm for arm64) rather than
# cross-building arm64 under QEMU emulation on an amd64 runner. On
# 2026-08-09 a QEMU-emulated `npm ci --legacy-peer-deps` reliably crashed with "qemu: uncaught
# target signal 4 (Illegal instruction) - core dumped" while running one of
# esbuild's postinstall steps — deterministic on every retry, not a
# transient hang. GitHub's Linux arm64 hosted runners are free for public
# repos and sidestep emulation entirely for the actual build; QEMU is no
# longer used anywhere in this workflow. Each arch pushes its image by
# digest, then a merge job combines the digests into one multi-arch
# manifest per tag and pushes that to both registries.
#
# Every published build is scanned for image vulnerabilities (Trivy) and
# supply-chain risk (Syft SBOM + Grype), uploaded to the Security tab as
# SARIF. Neither blocks the build right now — this is meant for visibility
# (know where and when to patch) rather than enforcement; revisit once
# there's a branch strategy to gate against. Known findings with no fix yet
# are suppressed via .trivyignore / .grype.yaml — see SECURITY.md for why,
# and the review date each suppression expires.
on:
pull_request:
push:
branches: [main, development, nightly, 'feature/**']
release:
types: [published]
concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: true
permissions:
contents: read
env:
REGISTRY_IMAGE: ghcr.io/wikid82/hestia
jobs:
# PR builds validate the Dockerfile builds cleanly on native amd64 — no
# push, no arm64 leg (self-hosters pull from the push/release builds
# below, not from a PR).
build-pr:
if: github.event_name == 'pull_request' && github.event.pull_request.user.login != 'github-actions[bot]'
runs-on: ubuntu-latest
timeout-minutes: 20
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
- uses: docker/setup-buildx-action@f87e5991a6d7451dcb8d9637bfbc97413f497069 # v4.4.1
- name: Free disk space
uses: jlumbroso/free-disk-space@54081f138730dfa15788a46383842cd2f914a1be # v1.3.1
with:
android: true
dotnet: true
haskell: true
large-packages: true
docker-images: false
swap-storage: true
tool-cache: false
- name: Build (validate only)
uses: docker/build-push-action@c3c9e263c25d99ce0380d002d59b67737d91b0dc # v7.4.0
with:
context: .
push: false
load: true
tags: hestia:pr-${{ github.event.pull_request.number }}
- name: Trivy vulnerability scan (table)
uses: aquasecurity/trivy-action@ed142fd0673e97e23eac54620cfb913e5ce36c25 # v0.36.0
with:
image-ref: hestia:pr-${{ github.event.pull_request.number }}
format: table
severity: CRITICAL,HIGH
exit-code: '0'
trivyignores: .trivyignore
# Push/release builds: one native runner per architecture, each pushing
# its single-platform image by digest. See header comment for why this
# replaced a single cross-platform QEMU build.
build:
if: github.event_name != 'pull_request'
strategy:
fail-fast: false
matrix:
include:
- platform: linux/amd64
runner: ubuntu-latest
- platform: linux/arm64
runner: ubuntu-24.04-arm
runs-on: ${{ matrix.runner }}
timeout-minutes: 20
permissions:
contents: read
packages: write
steps:
- name: Checkout (release)
if: github.event_name == 'release'
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
with:
ref: ${{ github.event.release.tag_name }}
- name: Checkout
if: github.event_name != 'release'
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
- uses: docker/setup-buildx-action@f87e5991a6d7451dcb8d9637bfbc97413f497069 # v4.4.1
- name: Log in to GHCR
uses: nick-fields/retry@ad984534de44a9489a53aefd81eb77f87c70dc60 # v4
with:
max_attempts: 3
timeout_minutes: 2
retry_wait_seconds: 10
command: echo "${{ secrets.GITHUB_TOKEN }}" | docker login ghcr.io -u ${{ github.actor }} --password-stdin
- name: Free disk space
uses: jlumbroso/free-disk-space@54081f138730dfa15788a46383842cd2f914a1be # v1.3.1
with:
android: true
dotnet: true
haskell: true
large-packages: true
docker-images: false
swap-storage: true
tool-cache: false
- name: Build and push by digest
id: build
uses: docker/build-push-action@c3c9e263c25d99ce0380d002d59b67737d91b0dc # v7.4.0
with:
context: .
platforms: ${{ matrix.platform }}
# provenance: false — otherwise buildx wraps even a single-platform
# push in a 2-entry OCI index (image manifest + attestation
# manifest), so the exported digest points at an index rather than
# the plain image manifest. That breaks anything resolving the
# digest directly (confirmed: Trivy's remote fetcher errors with
# "no child with platform linux/amd64 in index ..." trying to scan
# it) and serves no purpose here since digest-merge already
# produces one clean multi-arch manifest in the step below.
provenance: false
outputs: type=image,name=${{ env.REGISTRY_IMAGE }},push-by-digest=true,name-canonical=true,push=true
- name: Export digest
run: |
mkdir -p /tmp/digests
digest="${{ steps.build.outputs.digest }}"
touch "/tmp/digests/${digest#sha256:}"
- name: Sanitize platform for artifact name
id: platform-slug
run: echo "slug=$(echo '${{ matrix.platform }}' | tr '/' '-')" >> "$GITHUB_OUTPUT"
- name: Upload digest
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
with:
name: digests-${{ steps.platform-slug.outputs.slug }}
path: /tmp/digests/*
if-no-files-found: error
retention-days: 1
# Combine the per-architecture digests into one multi-arch manifest per
# tag, pushed to both registries, then scan the result.
merge:
if: github.event_name != 'pull_request'
needs: build
runs-on: ubuntu-latest
timeout-minutes: 20
permissions:
contents: read
packages: write
security-events: write
steps:
- name: Checkout (release)
if: github.event_name == 'release'
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
with:
ref: ${{ github.event.release.tag_name }}
- name: Checkout
if: github.event_name != 'release'
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
- name: Download all digests
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8
with:
path: /tmp/digests
pattern: digests-*
merge-multiple: true
- uses: docker/setup-buildx-action@f87e5991a6d7451dcb8d9637bfbc97413f497069 # v4.4.1
- name: Log in to GHCR
uses: nick-fields/retry@ad984534de44a9489a53aefd81eb77f87c70dc60 # v4
with:
max_attempts: 3
timeout_minutes: 2
retry_wait_seconds: 10
command: echo "${{ secrets.GITHUB_TOKEN }}" | docker login ghcr.io -u ${{ github.actor }} --password-stdin
- name: Log in to Docker Hub
uses: nick-fields/retry@ad984534de44a9489a53aefd81eb77f87c70dc60 # v4
with:
max_attempts: 3
timeout_minutes: 2
retry_wait_seconds: 10
command: echo "${{ secrets.DOCKERHUB_TOKEN }}" | docker login -u ${{ secrets.DOCKERHUB_USERNAME }} --password-stdin
- name: Docker metadata
id: meta
uses: docker/metadata-action@dc802804100637a589fabce1cb79ff13a1411302 # v6
with:
images: |
wikid82/hestia
ghcr.io/wikid82/hestia
tags: |
type=raw,value=latest,enable={{is_default_branch}}
type=sha,format=short,enable={{is_default_branch}}
type=ref,event=branch
type=semver,pattern={{version}}
type=semver,pattern={{major}}.{{minor}}
type=semver,pattern={{major}}
- name: Create manifest list and push
working-directory: /tmp/digests
run: |
docker buildx imagetools create \
$(jq -cr '.tags | map("-t " + .) | join(" ")' <<< "$DOCKER_METADATA_OUTPUT_JSON") \
$(printf "${{ env.REGISTRY_IMAGE }}@sha256:%s " *)
env:
DOCKER_METADATA_OUTPUT_JSON: ${{ steps.meta.outputs.json }}
- name: Determine digest for scanning
id: scan
run: |
# Either single-platform digest is representative for scanning —
# OS package and npm dependency findings don't differ by arch.
echo "digest=sha256:$(ls /tmp/digests | head -n1)" >> "$GITHUB_OUTPUT"
- name: Trivy vulnerability scan (table)
uses: aquasecurity/trivy-action@ed142fd0673e97e23eac54620cfb913e5ce36c25 # v0.36.0
with:
image-ref: ${{ env.REGISTRY_IMAGE }}@${{ steps.scan.outputs.digest }}
format: table
severity: CRITICAL,HIGH
exit-code: '0'
trivyignores: .trivyignore
- name: Trivy vulnerability scan (SARIF)
uses: aquasecurity/trivy-action@ed142fd0673e97e23eac54620cfb913e5ce36c25 # v0.36.0
continue-on-error: true
with:
image-ref: ${{ env.REGISTRY_IMAGE }}@${{ steps.scan.outputs.digest }}
format: sarif
output: trivy-results.sarif
severity: CRITICAL,HIGH
trivyignores: .trivyignore
- name: Upload Trivy results to Security tab
uses: github/codeql-action/upload-sarif@b96794f015dfd88f77b49b1c93e0fa7110f94c63 # v4
continue-on-error: true
with:
sarif_file: trivy-results.sarif
category: trivy-image-scan
# anchore/sbom-action uploads the SBOM it generates as a workflow
# artifact itself (and attaches it to the GitHub Release on release
# events), so there is no separate upload-artifact step. Grype below
# scans the pushed image directly rather than consuming this SBOM
# file: sbom-action's `output-file` input only reliably writes to a
# temp dir (v0.24.1 stopped writing it into the workspace at all),
# and chaining the two tools through an on-disk file that a patch
# bump can silently relocate is exactly what broke this job. Both
# scanners now take the image digest independently, like the Trivy
# steps above.
- name: Generate SBOM
uses: anchore/sbom-action@3ad7283483fc7af8ff2b4ea19663c2d5ca935e26 # v0.24.2
with:
image: ${{ env.REGISTRY_IMAGE }}@${{ steps.scan.outputs.digest }}
format: cyclonedx-json
artifact-name: sbom-${{ github.sha }}.cyclonedx.json
- name: Scan image for vulnerabilities (Grype)
id: grype
uses: anchore/scan-action@e1165082ffb1fe366ebaf02d8526e7c4989ea9d2 # v7
with:
image: ${{ env.REGISTRY_IMAGE }}@${{ steps.scan.outputs.digest }}
output-format: sarif
severity-cutoff: high
fail-build: false
- name: Upload Grype results to Security tab
uses: github/codeql-action/upload-sarif@b96794f015dfd88f77b49b1c93e0fa7110f94c63 # v4
continue-on-error: true
with:
sarif_file: ${{ steps.grype.outputs.sarif }}
category: supply-chain-sbom