Files
nora/.github/workflows/release.yml
devitway 7df118d488 security: harden OpenSSF Scorecard compliance
- Pin all GitHub Actions by SHA hash (Pinned-Dependencies)
- Add top-level permissions: read-all (Token-Permissions)
- Add explicit job-level permissions (least privilege)
- Add OpenSSF Scorecard workflow with weekly schedule
- Publish scorecard results to scorecard.dev and GitHub Security tab
2026-03-17 10:30:15 +00:00

273 lines
9.9 KiB
YAML

name: Release
on:
push:
tags: ['v*']
permissions: read-all
env:
REGISTRY: ghcr.io
NORA: localhost:5000
IMAGE_NAME: ${{ github.repository }}
jobs:
build:
name: Build & Push
runs-on: [self-hosted, nora]
permissions:
contents: read
packages: write
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
- name: Set up Rust
run: |
echo "/home/github-runner/.cargo/bin" >> $GITHUB_PATH
echo "RUSTUP_HOME=/home/github-runner/.rustup" >> $GITHUB_ENV
echo "CARGO_HOME=/home/github-runner/.cargo" >> $GITHUB_ENV
- name: Build release binary (musl static)
run: |
cargo build --release --target x86_64-unknown-linux-musl --package nora-registry
cp target/x86_64-unknown-linux-musl/release/nora ./nora
- name: Upload binary artifact
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7
with:
name: nora-binary-${{ github.run_id }}
path: ./nora
retention-days: 1
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd # v4
with:
driver-opts: network=host
- name: Log in to GitHub Container Registry
uses: docker/login-action@b45d80f862d83dbcd57f89517bcf500b2ab88fb2 # v4
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
# ── Alpine ───────────────────────────────────────────────────────────────
- name: Extract metadata (alpine)
id: meta-alpine
uses: docker/metadata-action@030e881283bb7a6894de51c315a6bfe6a94e05cf # v6
with:
images: |
${{ env.NORA }}/${{ env.IMAGE_NAME }}
${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
tags: |
type=semver,pattern={{version}}
type=semver,pattern={{major}}.{{minor}}
type=raw,value=latest
- name: Build and push (alpine)
uses: docker/build-push-action@d08e5c354a6adb9ed34480a06d141179aa583294 # v7
with:
context: .
file: Dockerfile
platforms: linux/amd64
push: true
tags: ${{ steps.meta-alpine.outputs.tags }}
labels: ${{ steps.meta-alpine.outputs.labels }}
cache-from: type=registry,ref=${{ env.NORA }}/${{ env.IMAGE_NAME }}-cache:alpine,ignore-error=true
cache-to: type=registry,ref=${{ env.NORA }}/${{ env.IMAGE_NAME }}-cache:alpine,mode=max
# ── RED OS ───────────────────────────────────────────────────────────────
- name: Extract metadata (redos)
id: meta-redos
uses: docker/metadata-action@030e881283bb7a6894de51c315a6bfe6a94e05cf # v6
with:
images: |
${{ env.NORA }}/${{ env.IMAGE_NAME }}
${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
flavor: suffix=-redos,onlatest=true
tags: |
type=semver,pattern={{version}}
type=semver,pattern={{major}}.{{minor}}
type=raw,value=redos
- name: Build and push (redos)
uses: docker/build-push-action@d08e5c354a6adb9ed34480a06d141179aa583294 # v7
with:
context: .
file: Dockerfile.redos
platforms: linux/amd64
push: true
tags: ${{ steps.meta-redos.outputs.tags }}
labels: ${{ steps.meta-redos.outputs.labels }}
cache-from: type=registry,ref=${{ env.NORA }}/${{ env.IMAGE_NAME }}-cache:redos,ignore-error=true
cache-to: type=registry,ref=${{ env.NORA }}/${{ env.IMAGE_NAME }}-cache:redos,mode=max
# ── Astra Linux SE ───────────────────────────────────────────────────────
- name: Extract metadata (astra)
id: meta-astra
uses: docker/metadata-action@030e881283bb7a6894de51c315a6bfe6a94e05cf # v6
with:
images: |
${{ env.NORA }}/${{ env.IMAGE_NAME }}
${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
flavor: suffix=-astra,onlatest=true
tags: |
type=semver,pattern={{version}}
type=semver,pattern={{major}}.{{minor}}
type=raw,value=astra
- name: Build and push (astra)
uses: docker/build-push-action@d08e5c354a6adb9ed34480a06d141179aa583294 # v7
with:
context: .
file: Dockerfile.astra
platforms: linux/amd64
push: true
tags: ${{ steps.meta-astra.outputs.tags }}
labels: ${{ steps.meta-astra.outputs.labels }}
cache-from: type=registry,ref=${{ env.NORA }}/${{ env.IMAGE_NAME }}-cache:astra,ignore-error=true
cache-to: type=registry,ref=${{ env.NORA }}/${{ env.IMAGE_NAME }}-cache:astra,mode=max
# ── Smoke test ──────────────────────────────────────────────────────────
- name: Smoke test — verify alpine image starts and responds
run: |
docker rm -f nora-smoke 2>/dev/null || true
docker run --rm -d --name nora-smoke -p 5555:4000 -e NORA_HOST=0.0.0.0 \
${{ env.NORA }}/${{ env.IMAGE_NAME }}:latest
for i in $(seq 1 10); do
curl -sf http://localhost:5555/health && break || sleep 2
done
curl -sf http://localhost:5555/health
docker stop nora-smoke
scan:
name: Scan (${{ matrix.name }})
runs-on: [self-hosted, nora]
needs: build
permissions:
contents: read
packages: read
security-events: write
strategy:
fail-fast: false
matrix:
include:
- name: alpine
suffix: ""
- name: redos
suffix: "-redos"
- name: astra
suffix: "-astra"
steps:
- name: Set version tag (strip leading v)
id: ver
run: echo "tag=${GITHUB_REF_NAME#v}" >> $GITHUB_OUTPUT
- name: Trivy — image scan (${{ matrix.name }})
uses: aquasecurity/trivy-action@57a97c7e7821a5776cebc9bb87c984fa69cba8f1 # 0.35.0
with:
scan-type: image
image-ref: ${{ env.NORA }}/${{ env.IMAGE_NAME }}:${{ steps.ver.outputs.tag }}${{ matrix.suffix }}
format: sarif
output: trivy-image-${{ matrix.name }}.sarif
severity: HIGH,CRITICAL
exit-code: 1
- name: Upload Trivy image results to GitHub Security tab
uses: github/codeql-action/upload-sarif@a60c4df7a135c7317c1e9ddf9b5a9b07a910dda9 # v4
if: always()
with:
sarif_file: trivy-image-${{ matrix.name }}.sarif
category: trivy-image-${{ matrix.name }}
release:
name: GitHub Release
runs-on: [self-hosted, nora]
needs: [build, scan]
permissions:
contents: write
packages: read
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
- name: Set version tag (strip leading v)
id: ver
run: echo "tag=${GITHUB_REF_NAME#v}" >> $GITHUB_OUTPUT
- name: Download binary artifact
uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4
with:
name: nora-binary-${{ github.run_id }}
path: ./artifacts
- name: Prepare binary
run: |
cp ./artifacts/nora ./nora-linux-amd64
chmod +x ./nora-linux-amd64
sha256sum ./nora-linux-amd64 > nora-linux-amd64.sha256
echo "Binary size: $(du -sh nora-linux-amd64 | cut -f1)"
cat nora-linux-amd64.sha256
- name: Generate SBOM (SPDX)
uses: anchore/sbom-action@57aae528053a48a3f6235f2d9461b05fbcb7366d # v0
with:
image: ${{ env.NORA }}/${{ env.IMAGE_NAME }}:${{ steps.ver.outputs.tag }}
format: spdx-json
output-file: nora-${{ github.ref_name }}.sbom.spdx.json
- name: Generate SBOM (CycloneDX)
uses: anchore/sbom-action@57aae528053a48a3f6235f2d9461b05fbcb7366d # v0
with:
image: ${{ env.NORA }}/${{ env.IMAGE_NAME }}:${{ steps.ver.outputs.tag }}
format: cyclonedx-json
output-file: nora-${{ github.ref_name }}.sbom.cdx.json
- name: Create Release
uses: softprops/action-gh-release@153bb8e04406b158c6c84fc1615b65b24149a1fe # v2
with:
generate_release_notes: true
files: |
nora-linux-amd64
nora-linux-amd64.sha256
nora-${{ github.ref_name }}.sbom.spdx.json
nora-${{ github.ref_name }}.sbom.cdx.json
body: |
## Install
```bash
curl -fsSL https://getnora.io/install.sh | sh
```
Or download the binary directly:
```bash
curl -LO https://github.com/${{ github.repository }}/releases/download/${{ github.ref_name }}/nora-linux-amd64
chmod +x nora-linux-amd64
sudo mv nora-linux-amd64 /usr/local/bin/nora
```
## Docker
**Alpine (standard):**
```bash
docker pull ghcr.io/${{ github.repository }}:${{ github.ref_name }}
```
**RED OS:**
```bash
docker pull ghcr.io/${{ github.repository }}:${{ github.ref_name }}-redos
```
**Astra Linux SE:**
```bash
docker pull ghcr.io/${{ github.repository }}:${{ github.ref_name }}-astra
```
## Changelog
See [CHANGELOG.md](https://github.com/${{ github.repository }}/blob/main/CHANGELOG.md)