Skip to content

Blackout Secure Launchpad #63

Blackout Secure Launchpad

Blackout Secure Launchpad #63

# Caller for the hub's `bos-launchpad-release.yml` (Blackout Secure Launchpad) reusable.
#
# monitor (github_release mode → actions/runner) → release (docker → balena → github release)
#
# Required vars: DOCKERHUB_NAMESPACE, BALENA_NAMESPACE
# Required secrets: DOCKERHUB_USERNAME, DOCKERHUB_TOKEN, BALENA_API_TOKEN
#
# `balena.yml` is rendered by the hub on each run from the `balena_*` inputs
# below. See blackoutsecure/bos-automation-hub for the full input reference.
name: Blackout Secure Launchpad
on:
schedule:
- cron: '17 */6 * * *' # stagger off :00 to dodge org cron pile-ups
push:
branches:
- main
paths:
- 'Dockerfile'
- '.dockerignore'
- 'build/**'
- 'root/**'
# scripts/autoscale.sh is COPYed into the image at
# /usr/local/bin/gh-runner-autoscale (Dockerfile line ~66), so
# changes here must rebuild even when nothing under root/** moved.
- 'scripts/**'
- 'docker-compose.yml'
- 'README.md'
- '.github/upstream/**'
- '.github/workflows/bos-launchpad-release.yml'
workflow_dispatch:
inputs:
stage_docker:
description: 'Stage: docker build & push'
type: boolean
default: true
stage_balena:
description: 'Stage: balena block publish'
type: boolean
default: true
stage_github_release:
description: 'Stage: GitHub Release publish'
type: boolean
default: true
# Push events always rebuild via the `with: force_run:`
# expression below; this checkbox only affects on-demand
# `workflow_dispatch` runs.
force_run:
description: 'Force: run pipeline even if upstream unchanged'
type: boolean
default: false
# ---- Security scan controls --------------------------------------
# The consolidated `security_scan` stage is ON by default for
# push + dispatch events. Skipped on `schedule` (cron only polls
# upstream — source hasn't changed, so re-scanning every 6h is
# wasteful). Gates the release by default: error-level findings
# skip the docker/balena stages (override via
# `security_scan_blocks_release: false` on the launchpad call
# below, or set `security_scan_fail_on: never` to keep scan
# always-green and never gate).
stage_security_scan:
description: 'Stage: consolidated security scan (kit composite + CodeQL)'
type: boolean
default: true
security_scan_use_advanced_pat:
description: 'Advanced PAT mode: elevated posture probes (PS001/PS002/PS003) via the org-level `SCANNING_PAT`. Safe to leave on — no-op when the secret is missing (kit falls back to GITHUB_TOKEN).'
type: boolean
default: true
# No top-level `concurrency:` — the hub workflow owns serialization.
# Declaring it on both sides triggers a GHA self-deadlock.
permissions:
contents: read
jobs:
release:
permissions:
contents: write # monitor tracking-file commit + GitHub Release publish
actions: write # nested monitor declares this for `gh workflow run` (validated at workflow-call time)
pull-requests: write # nested Docker Scout PR annotations
security-events: write # nested Docker Scout SARIF upload
models: read # cascades to hub `release.yml` -> `github-release.yml`
# `publish` job for the AI changelog renderer step.
# Validated at workflow-load time regardless of whether
# AI changelog is enabled by the caller.
uses: blackoutsecure/bos-automation-hub/.github/workflows/bos-launchpad-release.yml@main
with:
# Schedule + push runs use the defaults; manual runs honour the
# dispatch checkboxes. Every launchpad stage defaults to `false`
# in the hub — opt in explicitly.
docker: ${{ github.event_name != 'workflow_dispatch' || inputs.stage_docker }}
balena: ${{ github.event_name != 'workflow_dispatch' || inputs.stage_balena }}
github_release: ${{ github.event_name != 'workflow_dispatch' || inputs.stage_github_release }}
upstream_repo: actions/runner
track_file: .github/upstream/actions-runner.json
# Push always rebuilds; schedule only when upstream moves.
force_run: ${{ github.event_name == 'push' || (github.event_name == 'workflow_dispatch' && inputs.force_run) }}
# When force_run is a manual dispatch, also bypass downstream caches
# so the rebuild ACTUALLY produces a fresh image + balena release.
# Without these, docker re-uses GHA layer cache (same digest) and balena
# returns the existing release for the same source SHA — the "force"
# ends up being a no-op deploy from the device's perspective.
docker_force: ${{ github.event_name == 'workflow_dispatch' && inputs.force_run }}
balena_force: ${{ github.event_name == 'workflow_dispatch' && inputs.force_run }}
image_name: github-runner
dockerhub_namespace: ${{ vars.DOCKERHUB_NAMESPACE }}
# The autoscaler ships in the same image as the runner — sidecar role is
# selected at deploy time via `RUNNER_ROLE=autoscaler` (or the
# `autoscaler` command argument, or an explicit `entrypoint:` override).
# No `companion_*` inputs are needed; we publish exactly one image.
balena_namespace: ${{ vars.BALENA_NAMESPACE }}
# Render balena.yml dynamically — no checked-in copy.
balena_generate_yml: true
# Preserve the historical device-type matrix from the old balena.yml.
balena_default_device_type: raspberrypi3-64
balena_supported_device_types: |
raspberrypi3
raspberrypi3-64
raspberrypi4
raspberrypi4-64
raspberrypi400
raspberrypi400-64
intel-nuc
genericx86-64-ext
balena_description: >-
Containerized GitHub Actions self-hosted runner with s6 process supervision,
non-root defaults, and ephemeral mode support. Features automatic token
generation via GitHub PAT, Docker-in-Docker support, and built-in health
monitoring for CI/CD workloads on IoT and edge devices.
balena_post_provisioning: |
## Usage instructions
Include this snippet in your docker-compose.yml file under 'services':
```
gh-runner:
image: docker.io/${{ vars.DOCKERHUB_NAMESPACE }}/github-runner:latest
restart: unless-stopped
environment:
- TZ=Etc/UTC
- RUNNER_URL=https://github.com/OWNER/REPO
- RUNNER_TOKEN=YOUR_REGISTRATION_TOKEN
- RUNNER_NAME=my-runner
- LOG_LEVEL=info
volumes:
- '/path/to/runner/config:/config'
- '/var/run/docker.sock:/var/run/docker.sock'
security_opt:
- 'no-new-privileges:true'
```
Repository: https://github.com/${{ github.repository }}
Upstream application: https://github.com/actions/runner
# ---- Consolidated security scan ---------------------------------
# The shell-script payload under `root/` is covered by the kit
# composite's bundled shellcheck; workflow YAML is covered by
# actionlint + CodeQL default setup. Skipped on `schedule` since
# source doesn't change between upstream-version polls. Findings
# surface in the Security tab AND (by default) gate the
# docker/balena release: error-level results from the kit
# composite skip the release job (hub default
# `security_scan_fail_on: fail` + `security_scan_blocks_release:
# true`). Either knob can be flipped here to opt back into
# advisory-only behaviour.
#
# CodeQL coverage of the workflow YAML (`actions` language) is
# delegated to GitHub's first-party **default setup** (Settings →
# Code security → Code scanning → CodeQL → Default setup). GitHub
# disallows running an advanced CodeQL workflow against a repo
# that has default setup enabled — the SARIF upload from the
# advanced path fails with:
#
# CodeQL analyses from advanced configurations cannot be
# processed when the default setup is enabled
#
# So we explicitly skip the launchpad's CodeQL leg here
# (`security_scan_codeql_languages: ''`) and let default setup
# own the `actions` analysis. The launchpad's kit composite
# (posture / actionlint / gitleaks / shellcheck) still runs and
# uploads under its own SARIF category, independent of CodeQL.
enable_security_scan: ${{ github.event_name == 'push' || (github.event_name == 'workflow_dispatch' && inputs.stage_security_scan) }}
security_scan_enable_kit_composite: true
security_scan_enable_posture: true
security_scan_enable_scanners: true
security_scan_enable_upload: true
security_scan_codeql_languages: ''
security_scan_codeql_queries: security-and-quality
security_scan_use_advanced_pat: ${{ github.event_name != 'workflow_dispatch' || inputs.security_scan_use_advanced_pat }}
secrets:
DOCKERHUB_USERNAME: ${{ secrets.DOCKERHUB_USERNAME }}
DOCKERHUB_TOKEN: ${{ secrets.DOCKERHUB_TOKEN }}
BALENA_API_TOKEN: ${{ secrets.BALENA_API_TOKEN }}
# Forwarded to the launchpad `security_scan` stage — only consulted
# when `security_scan_use_advanced_pat: true` AND non-empty. Safe to
# forward unconditionally (kit falls back to GITHUB_TOKEN when empty).
scanning_pat: ${{ secrets.SCANNING_PAT }}