chore: add CI validation workflow, widen secrets-scanner scope, document validation commands

Stage 9 of REFACTOR_PLAN.md.

- New .gitea/workflows/validate.yaml: runs yamllint/ansible-lint/
  ansible-playbook --syntax-check/kubeconform on push and PR via the
  self-hosted act_runner. Read-only lint pass, no cluster access, no
  apply/deploy step. This is a new automation surface -- review before
  relying on it; it only takes effect once pushed and the runner picks it up.
- Widen secrets-leak-scanner.md's stated scope to explicitly cover config/**
  -- the Dashy API key (finding #4) slipped through previously because the
  scanner's description read as manifests/Ansible/Helm-values-centric and
  didn't call out app config directories.
- Add a "Validation commands" section to CLAUDE.md with the exact
  yamllint/ansible-lint/kubeconform invocations, so future sessions run them
  before claiming a change is done instead of just asserting success.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
Nik Afiq 2026-07-23 18:22:38 +09:00
parent 992a00c2db
commit 7435b7583f
3 changed files with 73 additions and 1 deletions

View File

@ -1,6 +1,6 @@
--- ---
name: secrets-leak-scanner name: secrets-leak-scanner
description: Scans staged/diffed files in this homelab repo for plaintext secrets that should instead come from .env or be sealed via kubeseal. Use before committing changes to manifests, Ansible vars, or Helm values. description: Scans staged/diffed files in this homelab repo for plaintext secrets that should instead come from .env or be sealed via kubeseal. Use before committing changes to manifests, Ansible vars, Helm values, or config/**.
tools: Read, Grep, Glob, Bash tools: Read, Grep, Glob, Bash
model: sonnet model: sonnet
--- ---
@ -9,6 +9,12 @@ You scan changes in this repo for secrets that are about to be committed in
plaintext. You have read-only Bash access (`git diff`, `git status`, `grep`) plaintext. You have read-only Bash access (`git diff`, `git status`, `grep`)
— never modify or stage files yourself. — never modify or stage files yourself.
Scope explicitly includes `config/**` (e.g. `config/dashy/conf.yaml`), not
just `manifests/`, `values/`, and Ansible vars — a live weather-widget API key
previously slipped through there precisely because it read as app config
rather than infra config. If it's committed to git and reaches a live
service, it's in scope regardless of which top-level directory it lives in.
## What "should never be plaintext in git" looks like here ## What "should never be plaintext in git" looks like here
Cross-reference `.env.example` for the full list of secret-shaped variable Cross-reference `.env.example` for the full list of secret-shaped variable

View File

@ -0,0 +1,44 @@
# Config for: Gitea Actions CI
# Applied by: the self-hosted act_runner (ansible/roles/gitea-runner) on push
# Description: Read-only lint/validate pass -- no cluster access, no apply/deploy.
name: validate
on:
push:
branches:
- main
pull_request:
jobs:
lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install validation tools
run: |
pip install yamllint ansible-lint
curl -L https://github.com/yannh/kubeconform/releases/latest/download/kubeconform-linux-amd64.tar.gz \
| tar xz kubeconform
sudo mv kubeconform /usr/local/bin/
- name: yamllint
run: yamllint -c .yamllint.yml .
- name: ansible-lint
run: |
ansible-galaxy collection install -r ansible/requirements.yml
ansible-lint ansible/
- name: ansible-playbook --syntax-check
run: |
for pb in ansible/playbooks/*.yaml; do
ansible-playbook --syntax-check -i ansible/inventory.yaml "$pb"
done
- name: kubeconform
run: |
kubeconform -summary -ignore-missing-schemas -kubernetes-version 1.32.0 \
-schema-location default \
-schema-location 'https://raw.githubusercontent.com/datreeio/CRDs-catalog/main/{{.Group}}/{{.ResourceKind}}_{{.ResourceAPIVersion}}.json' \
$(find manifests -name "*.yaml" -not -name "*-sealed.yaml") argocd/apps/*.yaml

View File

@ -107,6 +107,28 @@ Never commit plaintext secrets. Two patterns coexist, both listed in
`ansible/group_vars/all/vault.yaml` holds Ansible-side secrets (e.g. `ansible/group_vars/all/vault.yaml` holds Ansible-side secrets (e.g.
`vault_k3s_node_token`) referenced from `host_vars`. `vault_k3s_node_token`) referenced from `host_vars`.
## Validation commands
No cluster access is required for any of these — run them before reporting a
change done, and show the actual output, not just a claim it passed.
```bash
yamllint -c .yamllint.yml .
ansible-lint ansible/
for pb in ansible/playbooks/*.yaml; do
ansible-playbook --syntax-check -i ansible/inventory.yaml "$pb"
done
kubeconform -summary -ignore-missing-schemas -kubernetes-version 1.32.0 \
-schema-location default \
-schema-location 'https://raw.githubusercontent.com/datreeio/CRDs-catalog/main/{{.Group}}/{{.ResourceKind}}_{{.ResourceAPIVersion}}.json' \
$(find manifests -name "*.yaml" -not -name "*-sealed.yaml") argocd/apps/*.yaml
```
`ansible-lint` needs the collections in `ansible/requirements.yml` installed
first (`ansible-galaxy collection install -r ansible/requirements.yml`),
otherwise it reports spurious `unknown-module` errors for `community.general`/
`community.docker` modules that are actually fine.
## Key gotchas (see README.md "Gotchas" for the full list) ## Key gotchas (see README.md "Gotchas" for the full list)
- Pi-hole has no wildcard DNS — add every new `home.arpa` hostname to both - Pi-hole has no wildcard DNS — add every new `home.arpa` hostname to both