@@ -0,0 +1,167 @@
|
||||
# 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
|
||||
```
|
||||
Reference in New Issue
Block a user