Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 32 additions & 10 deletions automation/cloud-pipelines/ai-workflows.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -92,20 +92,42 @@ Per the [CI/CD policy](/infrastructure/cicd/policy#dependency-versioning), Jacob

## Authentication

Reusable `.yml` workflows call [`anthropics/claude-code-action@v1`](https://github.com/anthropics/claude-code-action) through a shared wrapper action. The runtime contract is AI-agnostic:
Reusable `.yml` workflows use a **provider-agnostic `GH_ACTION_AI_*` namespace** so you can swap providers, endpoints, or models at the org level without editing any caller.

- `secrets.AI_TOKEN` — provider credential
- `vars.AI_PROVIDER` — defaults to `claude_oauth`
- `vars.AI_BASE_URL` or `secrets.AI_BASE_URL` — required only for OpenRouter or another Anthropic-compatible router
- `vars.AI_MODEL*` — defaults to Claude `sonnet`
| Name | Kind | Purpose |
| --- | --- | --- |
| `GH_ACTION_AI_API_KEY` | Secret | Your provider's API key. Required by all workflows. |
| `GH_ACTION_AI_BASE_URL` | Variable | Provider endpoint. Leave **empty** for direct Anthropic. |
| `GH_ACTION_AI_MODEL` | Variable | Global default model name. |
| `GH_ACTION_AI_MODEL_CODE` | Variable | Code-generation tier (falls back to `GH_ACTION_AI_MODEL`). |
| `GH_ACTION_AI_MODEL_ISSUES` | Variable | Issue-management tier (falls back to `GH_ACTION_AI_MODEL`). |
| `GH_ACTION_AI_MODEL_PLAN` | Variable | Deep-planning tier (falls back to `GH_ACTION_AI_MODEL`). |

Set them at org level with the GitHub CLI:

```bash
gh secret set GH_ACTION_AI_API_KEY --org <your-org> # paste your key
gh variable set GH_ACTION_AI_BASE_URL --org <your-org> -b "" # empty = direct Anthropic
gh variable set GH_ACTION_AI_MODEL --org <your-org> -b "claude-sonnet-4-6"
```

The same workflows run unchanged against any provider — only the org-level values change:

OpenRouter is still supported, but it is not hardcoded: set `AI_PROVIDER=openrouter`, put the key in `AI_TOKEN`, set `AI_BASE_URL`, and choose the model with `AI_MODEL`.
| Provider | `GH_ACTION_AI_API_KEY` | `GH_ACTION_AI_BASE_URL` | Example model |
| --- | --- | --- | --- |
| Direct Anthropic | standard API key | *(empty)* | `claude-sonnet-4-6` |
| OpenRouter | OpenRouter key | `https://openrouter.ai/api/v1` | `anthropic/claude-sonnet-4` |
| Chutes.ai | Chutes key | Chutes endpoint | provider-specific name |

GH-AW imports are different. The current `public-docs-updater` wrapper uses the Copilot engine because GH-AW Claude does not support Claude OAuth tokens on the pinned compiler path. The provider matrix and GH-AW caveats live in [AUTHENTICATION.md](https://github.com/JacobPEvans/ai-workflows/blob/main/docs/AUTHENTICATION.md).
> **OAuth tokens are prohibited in unattended CI.** A Claude Code subscription token (`CLAUDE_CODE_OAUTH_TOKEN`) used inside a GitHub Actions workflow violates the [Claude Code Terms of Service](https://www.anthropic.com/legal/terms) and risks an account ban — the subscription is intended for interactive sessions only. Use a standard API key (`GH_ACTION_AI_API_KEY`) instead; it is purpose-built for programmatic access with no ToS concerns.

GH-AW imports use the Copilot engine (the current `public-docs-updater` wrapper). Provider details and model configuration live in [AUTHENTICATION.md](https://github.com/JacobPEvans/ai-workflows/blob/main/docs/AUTHENTICATION.md).

## Commit signing

Every PR-writing reusable workflow mints a `JacobPEvans-claude` GitHub App installation token immediately before calling the action, then hands it in as `github_token` with `use_commit_signing: true`. Commits land web-flow-signed and attributed to the bot. The App credentials (`GH_APP_CLAUDE_BOT_PRIVATE_KEY`, `GH_APP_CLAUDE_BOT_ID`) are distributed by `secrets-sync` to every repo in the `_github_app_repos` anchor.
`cc-ci-fix` (the CI auto-fixer) uses GitHub's `createCommitOnBranch` GraphQL mutation to push fix commits — no local clone, no custom signing script. The mutation runs as the Actions bot (`${{ github.token }}`), so every fix commit lands web-flow-signed and attributed to `github-actions[bot]`. This works without any App credential on the consumer repo, and is compatible with repos that enforce signed commits.

Other PR-writing workflows (`issue-resolver`, `code-simplifier`, `post-merge-*`) mint a `JacobPEvans-claude` GitHub App installation token and rely on `use_commit_signing: true` in the action. The App credentials (`GH_APP_CLAUDE_BOT_PRIVATE_KEY`, `GH_APP_CLAUDE_BOT_ID`) are distributed by `secrets-sync` to every repo in the `_github_app_repos` anchor.
Comment thread
JacobPEvans-personal marked this conversation as resolved.

## Where to go next

Expand All @@ -117,7 +139,7 @@ Every PR-writing reusable workflow mints a `JacobPEvans-claude` GitHub App insta
The post-merge dispatch pattern, bot guards, and other recurring shapes.
</Card>
<Card title="Authentication" icon="key" href="https://github.com/JacobPEvans/ai-workflows/blob/main/docs/AUTHENTICATION.md">
`AI_TOKEN`, provider routing, model variables, and GH-AW engine caveats.
Full `GH_ACTION_AI_*` reference, provider routing, model variables, and GH-AW engine caveats.
</Card>
<Card title="Verification" icon="check-double" href="https://github.com/JacobPEvans/ai-workflows/blob/main/docs/VERIFICATION.md">
The e2e runbook for checking a freshly-wired repo end to end.
Expand All @@ -126,6 +148,6 @@ Every PR-writing reusable workflow mints a `JacobPEvans-claude` GitHub App insta
Exactly which six callers are wired on `JacobPEvans/docs` and why.
</Card>
<Card title="Secret distribution" icon="lock" href="/security/secrets-sync">
How `AI_TOKEN` and the App credentials land on each consumer repo.
How `GH_ACTION_AI_API_KEY` and the App credentials land on each consumer repo.
</Card>
</CardGroup>
52 changes: 52 additions & 0 deletions conventions/org-gitignore.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
---
title: "Org-default .gitignore"
description: "A canonical .gitignore baseline shipped from dryvist/.github that every repo adopts. Prevents secrets, credentials, and AI-assistant local state from ever reaching git history."
tier: 1
---

> One gitignore to rule them all. Never commit secrets or AI local state — not by accident, not by habit.

The `dryvist/.github` repo ships a canonical `.gitignore` baseline at `configs/gitignore`. Every new repo adopts it by appending (not overwriting) to the repo's own `.gitignore`, preserving repo-specific entries while inheriting the org-wide safety floor.

## What it covers

The baseline targets two categories of files that must never reach git history:

**Secrets and credentials**
- `.env`, `.env.*` (except `.env.example`) — never commit real env files
- `*.pem`, `*.key`, `*.p12`, `*.pfx` — private keys
- `terraform.tfvars` (unencrypted), `credentials.json`, `secrets.yaml` (unencrypted)

**AI-assistant local / machine state**
- `.claude/mcp_settings.json` — MCP server list (can contain tokens)
- `CLAUDE.local.md`, `AGENTS.local.md`, `.envrc.local` — local overrides (gitignored by convention)
- `*.local.md` — any local-only markdown

**Intentional carve-outs** (do NOT add these back to `.gitignore`):

| Path | Why it's committed |
| --- | --- |
| `.envrc` | `use flake` directive; the `SOPS_AGE_KEY_FILE` path is not a secret |
| `*.sops.yaml` / `*.sops.yml` | SOPS-encrypted ciphertext — safe to commit |
| `.terraform.lock.hcl` | Provider lock file — committed by convention |
| `.claude/settings.json` | Project AI config — committed on purpose |
| `.claude/rules/`, committed skills/agents | Project Claude Code config |
| `CLAUDE.md`, `AGENTS.md` | Project AI instructions |

## Adopting in a new repo

```bash
gh api repos/dryvist/.github/contents/configs/gitignore \
-H "Accept: application/vnd.github.raw" >> .gitignore
Comment thread
JacobPEvans-personal marked this conversation as resolved.
```

Use `>>` (append) so repo-specific entries are preserved. De-duplicate afterward if needed.

## Scope

The baseline covers secrets and AI state only — it is not a comprehensive language gitignore. Pair it with a language-specific template (GitHub's `.gitignore` templates, `gitignore.io`) for full coverage.

## Future work

- Automate adoption in the repo scaffold / copier template so new repos inherit the baseline at creation time rather than manually.
- Add a pre-commit hook that checks for `.env` files without the example suffix before every commit.
1 change: 1 addition & 0 deletions docs.json
Original file line number Diff line number Diff line change
Expand Up @@ -222,6 +222,7 @@
"conventions/branch-conventions",
"conventions/pr-conventions",
"conventions/readme-conventions",
"conventions/org-gitignore",
"conventions/git-transport",
"conventions/no-scripts",
"conventions/diagramming",
Expand Down
10 changes: 10 additions & 0 deletions infrastructure/repos/tofu-proxmox.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import { RepoMeta, RepoFit } from "/snippets/repo-summary.mdx";
- Declares per-node ZFS storage (`node_storage`) that Ansible provisions — OpenTofu references the datastore by id and never creates the pool itself (`zpool create` is an OS-level operation)
- Uses Terragrunt to share variables across `prod`, `staging`, and one-off environments
- Outputs a list of provisioned hosts that Ansible inventories consume directly
- Provisions the **object_storage** module: RustFS LXC (S3-compatible, active) alongside MinIO (being migrated from); both run concurrently during the transition

## How it fits

Expand All @@ -32,6 +33,15 @@ import { RepoMeta, RepoFit } from "/snippets/repo-summary.mdx";
Provisioning only. Anything that runs *inside* a host belongs in `ansible-proxmox` or `ansible-proxmox-apps`.
</RepoFit>

## Object storage migration

The homelab is migrating from **MinIO** to **RustFS** for S3-compatible object storage. Both LXCs are declared in the `object_storage` module and run in parallel during the migration:

- **RustFS** — the target; Splunkbase sync and new data flows write here
- **MinIO** — the source; decommissioned once all consumers have cut over

`ansible-proxmox` backs up the RustFS data volume via `sanoid`/`syncoid`. `ansible-splunk` repoints Splunkbase app sync to the RustFS endpoint. After all consumers migrate, the MinIO LXC will be removed from the module.

## Getting started

<Steps>
Expand Down
24 changes: 24 additions & 0 deletions nix/nix-ai.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,27 @@ import { RepoMeta, RepoFit } from "/snippets/repo-summary.mdx";
Anything that's "an AI tool I want everywhere" belongs here. Single-project AI experiments go in a project-local `nix-devenv`-style flake.
</RepoFit>

## Configuration surface

`nix-ai` ships as a reusable flake module with a single knob surface — `modules/maintainer-profile.nix`. This is the one file a consumer touches to adapt the stack:

- **Identity** fields (`user.fullName`, `user.trustedOrgs`) have maintainer defaults so derived values (e.g. Claude auto-mode trusted-org list) stay internally consistent; override them for your own identity.
- **Infrastructure** fields (`homelab.*`, `telemetry.*`, `trustedProjectDirs`) default to clean/off/empty, so the module evaluates to a neutral config with zero consumer input — no personal context ships in the flake.

Modules read `userConfig` via `_module.args` so nothing outside `maintainer-profile.nix` needs to import it directly. A consumer overrides values either in their own `nix-darwin` config (via `extraSpecialArgs`) or by importing the module and setting options.

### Secrets and injection

Runtime secrets (API keys, tokens, Doppler credentials) are never stored in Nix store paths or committed files. Three injection patterns are in use:

| Pattern | Used by | Mechanism |
| --- | --- | --- |
| Doppler subprocess wrapper | Google Workspace MCP, Splunk MCP | `doppler run -p <project> -c prd -- <binary>` |
| macOS Keychain via shell init | HuggingFace token, GitHub PAT | `security find-generic-password` exported in `~/.zshrc` |
| Kubernetes Doppler Operator | Bifrost, Cribl pods | In-cluster operator syncs Doppler → K8s Secrets |

The variable catalog (required vs optional, purpose, source) lives in `.env.example` at the repo root; a real `.env` is an opt-in convenience — direct injection is preferred. The local injection runbook is `AGENTS.local.md` (gitignored).

## Getting started

<Steps>
Expand All @@ -42,6 +63,9 @@ Anything that's "an AI tool I want everywhere" belongs here. Single-project AI e
<Step title="Import into nix-darwin">
Add `nix-ai` as a flake input, then include `nix-ai.overlays.default` in your nixpkgs config. The README has the boilerplate.
</Step>
<Step title="Override the maintainer profile">
Set `userConfig.user.fullName` and any `homelab.*` / `telemetry.*` options in your own config to adapt the stack to your environment.
</Step>
</Steps>

## Related repos
Expand Down
Loading