Repository navigation
Blackout Secure Launchpad #63
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| # 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 }} |