168 lines
5.8 KiB
Markdown
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
|
|
```
|