Files
kforge/CLAUDE.md
nate.lubitz 4ee1b9e13c
Publish Action Image / build (push) Successful in 1m8s
more updates
2026-06-29 15:14:55 +10:00

168 lines
5.8 KiB
Markdown

# kforge
**kforge** generates production-ready Kubernetes manifests from a single `kforge.yml` in your repository root. Designed for self-hosted homelab deployments on MicroK8s with Gitea Actions CI/CD.
## Core concept
```
kforge.yml → kforge generate → kubectl apply → cluster
```
Only `kforge.yml` and generated workflow files are committed. Generated manifests are applied and discarded each CI run.
## Build and test
```sh
go build -o kforge .
go test ./...
```
## Commands
| Command | Purpose |
|---|---|
| `kforge validate` | Validate kforge.yml, list required secrets |
| `kforge generate [--env E]` | Generate Kubernetes manifests to `.kforge-out/` |
| `kforge generate --pr-number N` | Generate manifests for a PR preview environment |
| `kforge secrets apply --env E` | Generate and apply random credentials to cluster |
| `kforge secrets apply --pr-number N` | Apply credentials for a PR preview environment |
| `kforge gitea-actions` | Generate `.gitea/workflows/deploy.yml` |
| `kforge gitea-preview` | Generate `.gitea/workflows/preview.yml` for PR previews |
## Architecture
### Config system (`internal/config/`)
- `types.go` — all config structs mirroring `kforge.yml`
- `loader.go` — YAML parsing and structural validation
- `defaults.go``ApplyDefaults()`, `ResolveEnvironment()`, `SynthesizePreviewEnvironment()`
### Generator system (`internal/generator/`)
- `manifests.go` — Service, Deployment, Ingress (with external-dns annotations), Certificate, CronJob
- `infrastructure.go` — CNPG Database CR + db-init Job, Valkey, Minio, NATS, Meilisearch
- `gitea_actions.go` — deploy workflow (`GenerateGiteaActions`) + preview workflow (`GeneratePreviewActions`)
### CLI (`cmd/`)
- Uses [Cobra](https://github.com/spf13/cobra) for subcommands
- `root.go` — shared `loadConfig()` helper
- Each command calls `loadConfig()` then delegates to generators
### Token interpolation (`pkg/interpolate/`)
- Built-in: `${name}`, `${tenant}`, `${env}`, `${env_prefix}`, `${full_name}`, `${namespace}`, `${image_tag}`
- Falls back to `os.Getenv()` — CI secrets (e.g. `${KFORGE_NODE_IP}`) are injected this way
- `PGIdentifier()` converts a kforge name to a valid unquoted PostgreSQL identifier
## DNS and TLS
DNS is managed by **external-dns** running in the cluster — no Cloudflare API calls from kforge.
When a host has `dns_record: true`, kforge adds to the Ingress:
```yaml
annotations:
external-dns.alpha.kubernetes.io/hostname: "app.example.com"
external-dns.alpha.kubernetes.io/target: "<dns.target>"
```
`dns.target` in `kforge.yml` sets the target value (your node IP or a static hostname). Supports `${KFORGE_NODE_IP}` token which is resolved from the CI environment.
TLS is managed by **cert-manager** via a `ClusterIssuer`. A `Certificate` CR is generated for each host with `tls: true`. No separate DNS step needed in the workflow.
## CNPG database (centralized cluster)
For a centralized CNPG cluster, kforge generates:
1. A CNPG `Database` CR — declaratively manages the database lifecycle
2. A `db-init` Kubernetes Job — creates the PostgreSQL role and syncs its password on every deploy
`kforge secrets apply` creates a `${full_name}-db-credentials` Secret containing an alphanumeric `username` and random `password` **before** the Job runs. The password is alphanumeric-only so it can be safely used in shell commands within the Job.
Required cluster resources:
- CNPG superuser Secret named by `cluster.cnpg.superuser_secret` (default: `cnpg-main-superuser`)
- CNPG cluster named by `cluster.cnpg.cluster_name` (default: `cnpg-main`)
## PR Preview environments
Add a `preview:` block to `kforge.yml` and run `kforge gitea-preview` to generate `.gitea/workflows/preview.yml`.
The preview workflow:
- **PR open / sync**: Creates namespace `preview-pr-{N}`, builds image tagged `:pr-{N}`, deploys app
- **PR close**: Deletes the namespace (removes all preview resources)
`kforge generate --pr-number N` and `kforge secrets apply --pr-number N` synthesize the preview environment at runtime using `preview.base_environment` settings with namespace/hostname overrides.
A `Namespace` manifest is included in the generated output so `kubectl apply` creates it automatically.
## Required secrets (Gitea)
### Org-level
- `DOCKER_USERNAME`, `DOCKER_PASSWORD` — registry auth
- `KFORGE_NODE_IP` — cluster node IP (used as external-dns target)
### Repo-level
- `KUBE_HOST`, `KUBE_TOKEN`, `KUBE_CERTIFICATE` — kubectl auth
## kforge.yml reference
```yaml
meta:
name: my-app
tenant: my-tenant
registry:
url: registry.example.com
pull_secret: regcred # default
dns:
target: ${KFORGE_NODE_IP} # external-dns annotation target
cluster:
tls_issuer: letsencrypt-prod # default
ingress_class: nginx # default
cnpg:
host: cnpg-main-rw.default.svc.cluster.local # default
cluster_name: cnpg-main # default
superuser_secret: cnpg-main-superuser # default
defaults:
port: 3000
health_check:
path: /healthcheck
resources:
requests: { cpu: 100m, memory: 128Mi }
limits: { cpu: 500m, memory: 512Mi }
infrastructure:
database:
provider: cnpg
cache:
provider: valkey
mode: standalone
preview:
enabled: true
base_environment: staging
namespace_prefix: preview-pr # namespace = preview-pr-{N}
hostname_template: "pr-${PR_NUMBER}.${name}.example.com"
environments:
staging:
namespace: staging
image_tag: latest
ingress:
hosts:
- hostname: app-staging.example.com
tls: true
dns_record: true
production:
namespace: production
image_tag: latest
ingress:
hosts:
- hostname: app.example.com
tls: true
dns_record: true
infrastructure:
cache:
mode: cluster
replicas: 3
```