Skip to content

CI/CD Pipelines

Two GitHub Actions workflows handle everything: ci.yml (test → gated deploy) and docs.yml (docs publishing). All run on GitHub-hosted runners — no self-hosted runner needed.

CI Pipeline (ci.yml)

One pipeline on every push to main (plus PRs + manual dispatch). Deploys are gated on tests — a broken config can never reach the live instance.

changes  → detect which areas changed (paths-filter): ha_config / grafana / lovelace
test     → ALWAYS runs: yamllint + pytest (Tiers 0/2/3) + containerized check_config (Tier 1)
deploy-ha       → needs: test, only if ha_config changed   (Tailscale → scp → reload/restart)
deploy-grafana  → needs: test, only if grafana changed     (scp dashboard + provider; verify via Grafana API)
deploy-lovelace → needs: test, only if lovelace changed    (race-free stop → swap → start)
  • Test job is the gate: if it fails, every deploy job is skipped. It runs the same suite as make test (see the Testing section in CLAUDE.md and the design at docs/superpowers/specs/2026-06-07-ha-test-suite-design.md). Tier 1 runs check_config on the official HA image pinned to the live version (HA_VERSION in ci.yml).
  • On pull requests, only changes + test run — PRs are validated, never deployed.
  • deploy-ha connects via Tailscale (ephemeral node, OAuth tag:ci), SCPs only changed files, then: only automations.yamlautomation.reload (no restart); any other file → ha core restart + health-check wait.
  • deploy-grafana SCPs the provisioned dashboard + provider config (auto-reloads ~30s), then verifies via the Grafana API (GRAFANA_TOKEN) that the dashboard provisioned with resolved datasources.
  • deploy-lovelace stages the file, then ha core stop → mv → ha core start (race-free; storage-mode dashboards have no hot-reload service).

Docs (docs.yml)

Triggers on pushes to main that change files under docs/ or mkdocs.yml.

Builds the MkDocs Material site with --strict (fails on warnings) and deploys to Cloudflare Pages via cloudflare/pages-action.

Published at: blairmont.pages.dev

Required GitHub Secrets

Secret Purpose
HAOS_HOST HAOS Tailscale IP (100.x.x.x)
HAOS_SSH_KEY Ed25519 private key for SCP to HAOS
HA_TOKEN HA long-lived access token for REST API calls (reload/restart, health checks, entity snapshot)
GRAFANA_TOKEN Grafana service-account token (Viewer) for post-deploy dashboard verification
TAILSCALE_OAUTH_CLIENT_ID OAuth client ID (tag: tag:ci)
TAILSCALE_OAUTH_CLIENT_SECRET OAuth client secret
CLOUDFLARE_ACCOUNT_ID Cloudflare account ID
CLOUDFLARE_API_TOKEN Cloudflare API token with Pages Edit permission

Infrastructure Decisions

Private repo — home automation config reveals occupancy patterns, camera placements, and alarm zones. Kept private; the docs site is the public-facing portfolio artifact.

Tailscale over self-hosted runner — GitHub warns against self-hosted runners on public repos (fork PRs can execute arbitrary code). Tailscale lets GitHub-hosted runners reach the local network without that risk, and provides secure remote HAOS access as a side benefit.

Cloudflare Pages over GitHub Pages — GitHub Pages on private repos requires a paid plan. Cloudflare Pages is free and the published docs site is the resume link, not the raw repo.