Initial commit

This commit is contained in:
sarodz
2026-02-14 10:46:57 -05:00
commit 4426274448
30 changed files with 1149 additions and 0 deletions

54
infrastructure/README.md Normal file
View File

@@ -0,0 +1,54 @@
# Infrastructure
Core cluster services that apps depend on. These are installed before any apps via Flux `dependsOn` ordering.
## Dependency Chain
```
MetalLB Install ──▶ MetalLB Config ──▶ Traefik Install ──▶ Routes
Cert-Manager Install ──▶ Cert-Manager Issuer │
Apps depend on ─┘
```
## Components
| Directory | What it does |
|-----------|-------------|
| `metallb-install/` | Installs MetalLB via Helm — gives LoadBalancer services real LAN IPs |
| `metallb-config/` | Configures the IP address pool and L2 advertisement |
| `traefik-install/` | Installs Traefik via Helm — reverse proxy and ingress controller |
| `cert-manager-install/` | Installs cert-manager via Helm — automates TLS certificate provisioning |
| `cert-manager-issuer/` | Configures Let's Encrypt ClusterIssuer with DNS-01 challenge |
| `routes/` | Traefik IngressRoutes — one file per app defining how traffic reaches it |
## How Helm Releases Work Here
Each Helm-based service follows the same pattern:
1. **HelmRelease** (`helmrelease.yaml`) — Points to a chart and version from a HelmRepository defined in `bootstrap/repositories/`
2. **ConfigMap override** (`*-override.yaml`) — Contains chart values as a YAML string under `data.values.yaml`. Referenced via `valuesFrom` in the HelmRelease.
This pattern keeps chart values separate from the release definition, making them easier to review and modify.
## Adding a New Infrastructure Service
1. Create a HelmRepository in `bootstrap/repositories/` (if the chart source is new)
2. Create a directory under `infrastructure/` (e.g. `infrastructure/my-service-install/`)
3. Add a `helmrelease.yaml` and optionally a ConfigMap override
4. Create a Flux Kustomization in `bootstrap/kustomization/infrastructure/` pointing to your new directory
5. Set `dependsOn` appropriately (most infra services should depend on MetalLB being configured)
6. Commit and push — Flux handles the rest
## Adding SOPS Secret Encryption
The `cert-manager-issuer/secret.yaml` file currently contains a plain-text secret. To encrypt it:
1. Install [age](https://github.com/FiloSottile/age) and generate a key pair
2. Create a `.sops.yaml` at the repo root with creation rules for your paths
3. Encrypt secret files: `sops --encrypt --in-place infrastructure/cert-manager-issuer/secret.yaml`
4. Add `spec.decryption` to the relevant Flux Kustomizations in `bootstrap/kustomization/`
5. Create a `sops-age` Secret in `flux-system` namespace with your age private key
See the [Flux SOPS guide](https://fluxcd.io/flux/guides/mozilla-sops/) for full instructions.

View File

@@ -0,0 +1,10 @@
apiVersion: v1
kind: ConfigMap
metadata:
name: cert-manager-chart-overrides
namespace: infrastructure
data:
values.yaml: |-
namespace: infrastructure
crds:
enabled: true

View File

@@ -0,0 +1,19 @@
apiVersion: helm.toolkit.fluxcd.io/v2
kind: HelmRelease
metadata:
name: cert-manager-release
namespace: infrastructure
spec:
chart:
spec:
chart: cert-manager
version: 1.19.2
sourceRef:
kind: HelmRepository
name: jetstack
namespace: flux-system
interval: 10m
valuesFrom:
- kind: ConfigMap
name: cert-manager-chart-overrides
valuesKey: values.yaml

View File

@@ -0,0 +1,22 @@
# ClusterIssuer for Let's Encrypt using DNS-01 challenge.
# This example uses Cloudflare as the DNS provider. If you use a different
# provider, see: https://cert-manager.io/docs/configuration/acme/dns01/
#
# Replace <YOUR_EMAIL> with your email address for Let's Encrypt notifications.
apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
name: cert-issuer
namespace: infrastructure
spec:
acme:
server: https://acme-v02.api.letsencrypt.org/directory
email: <YOUR_EMAIL>
privateKeySecretRef:
name: letsencrypt-dns-key
solvers:
- dns01:
cloudflare:
apiTokenSecretRef:
name: cloudflare-credentials
key: api-token

View File

@@ -0,0 +1,13 @@
# DNS provider API token for cert-manager DNS-01 challenge.
# Replace <YOUR_DNS_API_TOKEN> with your Cloudflare API token
# (or adjust for your DNS provider).
#
# Encrypt this file with: sops --encrypt --in-place secrets.yaml
apiVersion: v1
kind: Secret
metadata:
name: cloudflare-credentials
namespace: infrastructure
type: Opaque
stringData:
api-token: <YOUR_DNS_API_TOKEN>

View File

@@ -0,0 +1,13 @@
# MetalLB IP Address Pool
# Replace <YOUR_IP_RANGE> with a range of unused IPs on your LAN.
# These IPs will be assigned to LoadBalancer services (e.g. Traefik).
# Example: 192.168.1.200-192.168.1.210
apiVersion: metallb.io/v1beta1
kind: IPAddressPool
metadata:
name: metallb-pool
namespace: infrastructure
spec:
addresses:
- <YOUR_IP_RANGE>
autoAssign: true

View File

@@ -0,0 +1,8 @@
apiVersion: metallb.io/v1beta1
kind: L2Advertisement
metadata:
name: metallb-l2
namespace: infrastructure
spec:
ipAddressPools:
- metallb-pool

View File

@@ -0,0 +1,17 @@
apiVersion: helm.toolkit.fluxcd.io/v2beta1
kind: HelmRelease
metadata:
name: metallb-release
namespace: infrastructure
spec:
chart:
spec:
chart: metallb
version: 0.14.9
sourceRef:
kind: HelmRepository
name: metallb
namespace: flux-system
interval: 15m
timeout: 10m
releaseName: metallb

View File

@@ -0,0 +1,56 @@
# Gitea ingress routes via Traefik.
# Replace <YOUR_DOMAIN> with your domain (e.g. git.example.com).
---
apiVersion: traefik.io/v1alpha1
kind: Middleware
metadata:
name: redirect-https
namespace: gitea
spec:
redirectScheme:
scheme: https
permanent: true
---
# HTTP entrypoint — redirects all traffic to HTTPS
apiVersion: traefik.io/v1alpha1
kind: IngressRoute
metadata:
name: gitea-ingress-http
namespace: gitea
spec:
entryPoints:
- web
routes:
- match: Host(`<YOUR_DOMAIN>`)
kind: Rule
middlewares:
- name: redirect-https
services:
- name: gitea-http
namespace: gitea
port: 3000
---
# HTTPS entrypoint — serves Gitea with TLS
apiVersion: traefik.io/v1alpha1
kind: IngressRoute
metadata:
name: gitea-ingress
namespace: gitea
annotations:
cert-manager.io/issuer: "cert-issuer"
spec:
entryPoints:
- websecure
routes:
- match: Host(`<YOUR_DOMAIN>`)
kind: Rule
services:
- name: gitea-http
namespace: gitea
port: 3000
tls:
secretName: gitea-tls
domains:
- main: <YOUR_DOMAIN>
sans:
- <YOUR_DOMAIN>

View File

@@ -0,0 +1,21 @@
apiVersion: helm.toolkit.fluxcd.io/v2beta1
kind: HelmRelease
metadata:
name: traefik-release
namespace: infrastructure
spec:
chart:
spec:
chart: traefik
version: 39.0.0
sourceRef:
kind: HelmRepository
name: traefik
namespace: flux-system
interval: 15m
timeout: 10m
releaseName: traefik
valuesFrom:
- kind: ConfigMap
name: traefik-chart-overrides
valuesKey: values.yaml

View File

@@ -0,0 +1,25 @@
# Traefik Helm chart value overrides.
# Replace <YOUR_LAN_CIDR> with your local network range (e.g. 192.168.1.0/24).
apiVersion: v1
kind: ConfigMap
metadata:
name: traefik-chart-overrides
namespace: infrastructure
data:
values.yaml: |-
deployment:
enabled: true
replicas: 1
ingressClass:
enabled: true
isDefaultClass: true
service:
type: LoadBalancer
ports:
websecure:
forwardedHeaders:
trustedIPs:
- <YOUR_LAN_CIDR>
additionalArguments:
- "--api.dashboard=true"
- "--api.insecure=true"