621 lines
14 KiB
Markdown
621 lines
14 KiB
Markdown
# Homelab Auth + Portfolio: Authentik Forward-Auth + Homarr SSO + Portfolio Site (Terraform)
|
|||
|
|
|
||
|
|
## Context
|
||
|
|
|
||
|
|
Three services (Longhorn, Portainer, Prometheus) currently zero-auth. Goal: rebuild with Authentik forward-auth + Homarr dashboard + personal portfolio site. Infrastructure as Terraform (not Helm).
|
||
|
|
|
||
|
|
Authentik bootstrap (Proxy Providers, groups) via Python helper. Homarr + Portfolio deployed as K8s manifests via Terraform modules.
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## Track A — Authentik Forward-Auth (Setup via Python, Ingress via TF)
|
||
|
|
|
||
|
|
Authentik cluster already running (deployed separately). Python helper script registers Proxy Providers + groups:
|
||
|
|
|
||
|
|
**`k8s/talos-iam/register_proxy_app.py`** (mirrors `register_oauth_app.py`):
|
||
|
|
- Queries embedded outpost, creates `/providers/proxy/` (mode: `forward_single`)
|
||
|
|
- Creates `/core/applications/` entry
|
||
|
|
- Creates/binds Authentik group
|
||
|
|
- Patches outpost `providers` list
|
||
|
|
|
||
|
|
```bash
|
||
|
|
python3 k8s/talos-iam/register_proxy_app.py \
|
||
|
|
--service-name longhorn --namespace longhorn-system \
|
||
|
|
--external-host longhorn.riotpiao.homelab.com \
|
||
|
|
--add-group infra-admins
|
||
|
|
# Repeat for portainer, prometheus
|
||
|
|
```
|
||
|
|
|
||
|
|
**Ingress wiring (Terraform):**
|
||
|
|
|
||
|
|
`modules/ingress/protected-services.tf`:
|
||
|
|
```hcl
|
||
|
|
resource "kubernetes_ingress_v1" "protected_services" {
|
||
|
|
for_each = var.protected_services
|
||
|
|
|
||
|
|
metadata {
|
||
|
|
name = each.key
|
||
|
|
namespace = each.value.namespace
|
||
|
|
annotations = {
|
||
|
|
"nginx.ingress.kubernetes.io/auth-url" = "http://authentik-server.iam.svc.cluster.local/outpost.goauthentik.io/auth/nginx"
|
||
|
|
"nginx.ingress.kubernetes.io/auth-signin" = "https://authentik.riotpiao.homelab.com/outpost.goauthentik.io/start?rd=$scheme://$http_host$escaped_request_uri"
|
||
|
|
"nginx.ingress.kubernetes.io/auth-response-headers" = "Set-Cookie,X-authentik-username,X-authentik-groups,X-authentik-email,X-authentik-name,X-authentik-uid"
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
spec {
|
||
|
|
ingress_class_name = "nginx"
|
||
|
|
rule {
|
||
|
|
host = each.value.host
|
||
|
|
http {
|
||
|
|
path {
|
||
|
|
path = "/"
|
||
|
|
path_type = "Prefix"
|
||
|
|
backend {
|
||
|
|
service {
|
||
|
|
name = each.value.service_name
|
||
|
|
port {
|
||
|
|
number = each.value.service_port
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
**NetworkPolicy (per-service, Terraform):**
|
||
|
|
|
||
|
|
`modules/network-policy/backend-isolation.tf`:
|
||
|
|
```hcl
|
||
|
|
resource "kubernetes_network_policy" "backend_only_from_ingress" {
|
||
|
|
for_each = var.isolated_services
|
||
|
|
|
||
|
|
metadata {
|
||
|
|
name = "${each.key}-deny-except-ingress"
|
||
|
|
namespace = each.value.namespace
|
||
|
|
}
|
||
|
|
|
||
|
|
spec {
|
||
|
|
pod_selector {
|
||
|
|
match_labels = each.value.pod_selector
|
||
|
|
}
|
||
|
|
policy_types = ["Ingress"]
|
||
|
|
ingress {
|
||
|
|
from {
|
||
|
|
namespace_selector {
|
||
|
|
match_labels = {
|
||
|
|
name = "ingress-nginx"
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## Track B — Homarr (Terraform Deployment)
|
||
|
|
|
||
|
|
**Homarr Helm chart wrapper (Terraform):**
|
||
|
|
|
||
|
|
`modules/homarr/main.tf`:
|
||
|
|
```hcl
|
||
|
|
resource "kubernetes_namespace" "homarr" {
|
||
|
|
metadata {
|
||
|
|
name = "dashboard"
|
||
|
|
labels = {
|
||
|
|
"managed_by" = "terraform"
|
||
|
|
"reloader" = "enabled"
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
resource "kubernetes_secret" "homarr_oidc" {
|
||
|
|
metadata {
|
||
|
|
name = "homarr-oidc"
|
||
|
|
namespace = kubernetes_namespace.homarr.metadata[0].name
|
||
|
|
}
|
||
|
|
|
||
|
|
data = {
|
||
|
|
AUTH_OIDC_CLIENT_SECRET = var.homarr_oidc_client_secret
|
||
|
|
}
|
||
|
|
|
||
|
|
type = "Opaque"
|
||
|
|
}
|
||
|
|
|
||
|
|
resource "helm_release" "homarr" {
|
||
|
|
name = "homarr"
|
||
|
|
namespace = kubernetes_namespace.homarr.metadata[0].name
|
||
|
|
chart = "homarr"
|
||
|
|
repository = "https://homarr-labs.github.io/charts/"
|
||
|
|
version = var.homarr_version
|
||
|
|
|
||
|
|
set {
|
||
|
|
name = "env.AUTH_PROVIDERS"
|
||
|
|
value = "oidc"
|
||
|
|
}
|
||
|
|
|
||
|
|
set {
|
||
|
|
name = "env.AUTH_OIDC_CLIENT_ID"
|
||
|
|
value = "homarr"
|
||
|
|
}
|
||
|
|
|
||
|
|
set {
|
||
|
|
name = "env.AUTH_OIDC_ISSUER"
|
||
|
|
value = "https://authentik.riotpiao.homelab.com/application/o/homarr/"
|
||
|
|
}
|
||
|
|
|
||
|
|
set {
|
||
|
|
name = "env.AUTH_OIDC_URI"
|
||
|
|
value = "https://authentik.riotpiao.homelab.com/application/o/homarr/.well-known/openid-configuration"
|
||
|
|
}
|
||
|
|
|
||
|
|
set {
|
||
|
|
name = "env.AUTH_OIDC_GROUPS_ATTRIBUTE"
|
||
|
|
value = "groups"
|
||
|
|
}
|
||
|
|
|
||
|
|
set {
|
||
|
|
name = "envFrom[0].secretRef.name"
|
||
|
|
value = kubernetes_secret.homarr_oidc.metadata[0].name
|
||
|
|
}
|
||
|
|
|
||
|
|
set {
|
||
|
|
name = "persistence.enabled"
|
||
|
|
value = "true"
|
||
|
|
}
|
||
|
|
|
||
|
|
set {
|
||
|
|
name = "persistence.storageClass"
|
||
|
|
value = "longhorn"
|
||
|
|
}
|
||
|
|
|
||
|
|
set {
|
||
|
|
name = "persistence.size"
|
||
|
|
value = "2Gi"
|
||
|
|
}
|
||
|
|
|
||
|
|
depends_on = [kubernetes_secret.homarr_oidc]
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
**Homarr Ingress (no auth, Homarr handles login):**
|
||
|
|
|
||
|
|
`modules/homarr/ingress.tf`:
|
||
|
|
```hcl
|
||
|
|
resource "kubernetes_ingress_v1" "homarr" {
|
||
|
|
metadata {
|
||
|
|
name = "homarr"
|
||
|
|
namespace = kubernetes_namespace.homarr.metadata[0].name
|
||
|
|
annotations = {
|
||
|
|
"cert-manager.io/cluster-issuer" = "letsencrypt-prod"
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
spec {
|
||
|
|
ingress_class_name = "nginx"
|
||
|
|
tls {
|
||
|
|
hosts = ["homarr.riotpiao.homelab.com"]
|
||
|
|
secret_name = "homarr-tls"
|
||
|
|
}
|
||
|
|
rule {
|
||
|
|
host = "homarr.riotpiao.homelab.com"
|
||
|
|
http {
|
||
|
|
path {
|
||
|
|
path = "/"
|
||
|
|
path_type = "Prefix"
|
||
|
|
backend {
|
||
|
|
service {
|
||
|
|
name = helm_release.homarr.name
|
||
|
|
port {
|
||
|
|
number = 3000
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
**Authentik setup for Homarr (Python):**
|
||
|
|
|
||
|
|
```bash
|
||
|
|
python3 k8s/talos-iam/register_oauth_app.py \
|
||
|
|
--service-name homarr \
|
||
|
|
--namespace dashboard \
|
||
|
|
--redirect-uri "https://homarr.riotpiao.homelab.com/api/auth/callback/oidc" \
|
||
|
|
--add-group homarr-infra-admins
|
||
|
|
```
|
||
|
|
|
||
|
|
Then attach `groups` scope mapping (manual Authentik UI or separate Terraform provider if available).
|
||
|
|
|
||
|
|
**Homarr board setup (manual runbook in `k8s/homarr/README.md`):**
|
||
|
|
- Create groups: Infra (Longhorn/Portainer/Prometheus), Platform (Grafana/Vault), Workflows (Temporal/kmsvc)
|
||
|
|
- Each tile deep-links to existing ingress
|
||
|
|
- Restrict Infra group visibility to `infra-admins`
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## Track C — Portfolio Site (Terraform Deployment)
|
||
|
|
|
||
|
|
Portfolio source repo: `~/workplace/riotpiao` (Next.js, standalone output).
|
||
|
|
|
||
|
|
**Portfolio Helm chart (Terraform wrapper):**
|
||
|
|
|
||
|
|
`modules/portfolio/main.tf`:
|
||
|
|
```hcl
|
||
|
|
resource "kubernetes_namespace" "portfolio" {
|
||
|
|
metadata {
|
||
|
|
name = "portfolio"
|
||
|
|
labels = {
|
||
|
|
"managed_by" = "terraform"
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
resource "helm_release" "portfolio" {
|
||
|
|
name = "portfolio"
|
||
|
|
namespace = kubernetes_namespace.portfolio.metadata[0].name
|
||
|
|
chart = "./k8s/portfolio" # Local chart from homelab repo
|
||
|
|
|
||
|
|
set {
|
||
|
|
name = "image.repository"
|
||
|
|
value = var.portfolio_image_repo
|
||
|
|
}
|
||
|
|
|
||
|
|
set {
|
||
|
|
name = "image.tag"
|
||
|
|
value = var.portfolio_image_tag
|
||
|
|
}
|
||
|
|
|
||
|
|
set {
|
||
|
|
name = "replicaCount"
|
||
|
|
value = 2
|
||
|
|
}
|
||
|
|
|
||
|
|
set {
|
||
|
|
name = "resources.requests.cpu"
|
||
|
|
value = "100m"
|
||
|
|
}
|
||
|
|
|
||
|
|
set {
|
||
|
|
name = "resources.requests.memory"
|
||
|
|
value = "128Mi"
|
||
|
|
}
|
||
|
|
|
||
|
|
set {
|
||
|
|
name = "resources.limits.cpu"
|
||
|
|
value = "500m"
|
||
|
|
}
|
||
|
|
|
||
|
|
set {
|
||
|
|
name = "resources.limits.memory"
|
||
|
|
value = "512Mi"
|
||
|
|
}
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
**Portfolio Ingress (public, no auth):**
|
||
|
|
|
||
|
|
`modules/portfolio/ingress.tf`:
|
||
|
|
```hcl
|
||
|
|
resource "kubernetes_ingress_v1" "portfolio" {
|
||
|
|
metadata {
|
||
|
|
name = "portfolio"
|
||
|
|
namespace = kubernetes_namespace.portfolio.metadata[0].name
|
||
|
|
annotations = {
|
||
|
|
"cert-manager.io/cluster-issuer" = "letsencrypt-prod"
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
spec {
|
||
|
|
ingress_class_name = "nginx"
|
||
|
|
tls {
|
||
|
|
hosts = ["portfolio.riotpiao.homelab.com"]
|
||
|
|
secret_name = "portfolio-tls"
|
||
|
|
}
|
||
|
|
rule {
|
||
|
|
host = "portfolio.riotpiao.homelab.com"
|
||
|
|
http {
|
||
|
|
path {
|
||
|
|
path = "/"
|
||
|
|
path_type = "Prefix"
|
||
|
|
backend {
|
||
|
|
service {
|
||
|
|
name = helm_release.portfolio.name
|
||
|
|
port {
|
||
|
|
number = 3000
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
**Portfolio Helm chart structure** (local, in homelab repo):
|
||
|
|
|
||
|
|
```
|
||
|
|
k8s/portfolio/
|
||
|
|
├── Chart.yaml
|
||
|
|
├── values.yaml
|
||
|
|
├── templates/
|
||
|
|
│ ├── deployment.yaml
|
||
|
|
│ ├── service.yaml
|
||
|
|
│ └── _helpers.tpl
|
||
|
|
└── README.md
|
||
|
|
```
|
||
|
|
|
||
|
|
**Build + push script** (portfolio repo):
|
||
|
|
|
||
|
|
`~/workplace/riotpiao/build.sh`:
|
||
|
|
```bash
|
||
|
|
#!/bin/bash
|
||
|
|
set -euo pipefail
|
||
|
|
|
||
|
|
REGISTRY="forgejo.riotpiao.homelab.com"
|
||
|
|
IMAGE="${REGISTRY}/rock/portfolio:latest"
|
||
|
|
|
||
|
|
docker buildx build --platform linux/amd64 -t "${IMAGE}" .
|
||
|
|
docker push "${IMAGE}"
|
||
|
|
|
||
|
|
echo "✓ Pushed ${IMAGE}"
|
||
|
|
```
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## Terraform Structure
|
||
|
|
|
||
|
|
```
|
||
|
|
~/workplace/homelab-terraform/ (or extend existing homelab repo)
|
||
|
|
├── main.tf # K8s provider config
|
||
|
|
├── variables.tf # Inputs
|
||
|
|
├── outputs.tf
|
||
|
|
│
|
||
|
|
├── modules/
|
||
|
|
│ ├── ingress/
|
||
|
|
│ │ ├── protected-services.tf # Longhorn, Portainer, Prometheus
|
||
|
|
│ │ └── variables.tf
|
||
|
|
│ │
|
||
|
|
│ ├── homarr/
|
||
|
|
│ │ ├── main.tf # Namespace, Secret, Helm release
|
||
|
|
│ │ ├── ingress.tf # Public Homarr ingress
|
||
|
|
│ │ └── variables.tf
|
||
|
|
│ │
|
||
|
|
│ ├── portfolio/
|
||
|
|
│ │ ├── main.tf # Namespace, Helm release
|
||
|
|
│ │ ├── ingress.tf # Public portfolio ingress
|
||
|
|
│ │ └── variables.tf
|
||
|
|
│ │
|
||
|
|
│ └── network-policy/
|
||
|
|
│ └── backend-isolation.tf # Deny ingress except nginx
|
||
|
|
│
|
||
|
|
├── environments/
|
||
|
|
│ └── prod.tfvars # Domain, image repos, versions
|
||
|
|
│
|
||
|
|
└── .terraform.lock.hcl
|
||
|
|
```
|
||
|
|
|
||
|
|
**Main entry point:**
|
||
|
|
|
||
|
|
`main.tf`:
|
||
|
|
```hcl
|
||
|
|
terraform {
|
||
|
|
required_providers {
|
||
|
|
kubernetes = {
|
||
|
|
source = "hashicorp/kubernetes"
|
||
|
|
version = "~> 2.25"
|
||
|
|
}
|
||
|
|
helm = {
|
||
|
|
source = "hashicorp/helm"
|
||
|
|
version = "~> 2.12"
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
backend "local" {
|
||
|
|
path = "terraform.tfstate"
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
provider "kubernetes" {
|
||
|
|
host = var.cluster_endpoint
|
||
|
|
cluster_ca_certificate = base64decode(var.cluster_ca_cert)
|
||
|
|
token = var.cluster_token
|
||
|
|
}
|
||
|
|
|
||
|
|
provider "helm" {
|
||
|
|
kubernetes {
|
||
|
|
host = var.cluster_endpoint
|
||
|
|
cluster_ca_certificate = base64decode(var.cluster_ca_cert)
|
||
|
|
token = var.cluster_token
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
module "ingress" {
|
||
|
|
source = "./modules/ingress"
|
||
|
|
protected_services = var.protected_services
|
||
|
|
}
|
||
|
|
|
||
|
|
module "homarr" {
|
||
|
|
source = "./modules/homarr"
|
||
|
|
homarr_version = var.homarr_version
|
||
|
|
homarr_oidc_client_secret = var.homarr_oidc_client_secret
|
||
|
|
}
|
||
|
|
|
||
|
|
module "portfolio" {
|
||
|
|
source = "./modules/portfolio"
|
||
|
|
portfolio_image_repo = var.portfolio_image_repo
|
||
|
|
portfolio_image_tag = var.portfolio_image_tag
|
||
|
|
}
|
||
|
|
|
||
|
|
module "network_policy" {
|
||
|
|
source = "./modules/network-policy"
|
||
|
|
isolated_services = var.isolated_services
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
**`variables.tf`:**
|
||
|
|
```hcl
|
||
|
|
variable "cluster_endpoint" {
|
||
|
|
type = string
|
||
|
|
description = "K8s API endpoint"
|
||
|
|
}
|
||
|
|
|
||
|
|
variable "cluster_ca_cert" {
|
||
|
|
type = string
|
||
|
|
sensitive = true
|
||
|
|
}
|
||
|
|
|
||
|
|
variable "cluster_token" {
|
||
|
|
type = string
|
||
|
|
sensitive = true
|
||
|
|
}
|
||
|
|
|
||
|
|
variable "protected_services" {
|
||
|
|
type = map(object({
|
||
|
|
host = string
|
||
|
|
namespace = string
|
||
|
|
service_name = string
|
||
|
|
service_port = number
|
||
|
|
}))
|
||
|
|
default = {
|
||
|
|
"longhorn" = {
|
||
|
|
host = "longhorn.riotpiao.homelab.com"
|
||
|
|
namespace = "longhorn-system"
|
||
|
|
service_name = "longhorn-frontend"
|
||
|
|
service_port = 80
|
||
|
|
}
|
||
|
|
"portainer" = {
|
||
|
|
host = "portainer.riotpiao.homelab.com"
|
||
|
|
namespace = "dashboard"
|
||
|
|
service_name = "portainer"
|
||
|
|
service_port = 9000
|
||
|
|
}
|
||
|
|
"prometheus" = {
|
||
|
|
host = "prometheus.riotpiao.homelab.com"
|
||
|
|
namespace = "monitoring"
|
||
|
|
service_name = "prometheus-operated"
|
||
|
|
service_port = 9090
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
variable "homarr_version" {
|
||
|
|
type = string
|
||
|
|
default = "~1"
|
||
|
|
}
|
||
|
|
|
||
|
|
variable "homarr_oidc_client_secret" {
|
||
|
|
type = string
|
||
|
|
sensitive = true
|
||
|
|
description = "From Authentik provider"
|
||
|
|
}
|
||
|
|
|
||
|
|
variable "portfolio_image_repo" {
|
||
|
|
type = string
|
||
|
|
default = "forgejo.riotpiao.homelab.com/rock/portfolio"
|
||
|
|
}
|
||
|
|
|
||
|
|
variable "portfolio_image_tag" {
|
||
|
|
type = string
|
||
|
|
default = "latest"
|
||
|
|
}
|
||
|
|
|
||
|
|
variable "isolated_services" {
|
||
|
|
type = map(object({
|
||
|
|
namespace = string
|
||
|
|
pod_selector = map(string)
|
||
|
|
}))
|
||
|
|
default = {
|
||
|
|
"longhorn" = {
|
||
|
|
namespace = "longhorn-system"
|
||
|
|
pod_selector = {
|
||
|
|
"app.kubernetes.io/name" = "longhorn"
|
||
|
|
}
|
||
|
|
}
|
||
|
|
"portainer" = {
|
||
|
|
namespace = "dashboard"
|
||
|
|
pod_selector = {
|
||
|
|
"app" = "portainer"
|
||
|
|
}
|
||
|
|
}
|
||
|
|
"prometheus" = {
|
||
|
|
namespace = "monitoring"
|
||
|
|
pod_selector = {
|
||
|
|
"app.kubernetes.io/name" = "prometheus"
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## Implementation Sequencing
|
||
|
|
|
||
|
|
1. **Authentik setup (Python):**
|
||
|
|
- Register Proxy Providers (Longhorn, Portainer, Prometheus)
|
||
|
|
- Create groups (infra-admins, homarr-infra-admins)
|
||
|
|
- Verify `/outpost.goauthentik.io/auth/nginx` endpoint accessible
|
||
|
|
|
||
|
|
2. **Terraform deploy (parallel):**
|
||
|
|
- Ingress + NetworkPolicies (protected services)
|
||
|
|
- Homarr (depends on Authentik, has own OIDC)
|
||
|
|
- Portfolio (independent, public)
|
||
|
|
|
||
|
|
3. **Portfolio build + push:**
|
||
|
|
- `cd ~/workplace/riotpiao && ./build.sh`
|
||
|
|
- Verify image in Forgejo registry
|
||
|
|
|
||
|
|
4. **Terraform apply:**
|
||
|
|
```bash
|
||
|
|
terraform init
|
||
|
|
terraform plan
|
||
|
|
terraform apply
|
||
|
|
```
|
||
|
|
|
||
|
|
5. **Verification:**
|
||
|
|
- `curl -I https://longhorn.riotpiao.homelab.com` → 302 to Authentik (unauthenticated)
|
||
|
|
- Login as `infra-admins` member → Longhorn UI loads
|
||
|
|
- `curl -I https://homarr.riotpiao.homelab.com` → OIDC sign-in page
|
||
|
|
- Login → Homarr boards visible
|
||
|
|
- `curl -I https://portfolio.riotpiao.homelab.com` → 200, no redirect
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## Files to Create
|
||
|
|
|
||
|
|
**In homelab repo:**
|
||
|
|
- `terraform.tfvars` (git-ignored)
|
||
|
|
- `main.tf`, `variables.tf`, `outputs.tf`
|
||
|
|
- `modules/{ingress,homarr,portfolio,network-policy}/*`
|
||
|
|
- `k8s/homarr/README.md` (board setup runbook)
|
||
|
|
- `k8s/portfolio/Chart.yaml`, `templates/*`
|
||
|
|
|
||
|
|
**In portfolio repo (`~/workplace/riotpiao`):**
|
||
|
|
- `app/layout.tsx`, `app/page.tsx`, `app/about/page.tsx`
|
||
|
|
- `components/{Header,Footer,ProjectCard}.tsx`
|
||
|
|
- `content/{projects,experience,metadata}.ts`
|
||
|
|
- `styles/globals.css`
|
||
|
|
- `Dockerfile`, `build.sh`, `next.config.js`
|
||
|
|
- `package.json` (Next.js 15 + Tailwind)
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## Success Criteria
|
||
|
|
|
||
|
|
- [ ] Protected services (Longhorn/Portainer/Prometheus) redirect unauthenticated to Authentik
|
||
|
|
- [ ] Infra-admins group members see UI; non-members denied
|
||
|
|
- [ ] Homarr OIDC login works; boards render for authorized groups
|
||
|
|
- [ ] Portfolio site accessible without auth; project grid + about page live
|
||
|
|
- [ ] All deployed via `terraform apply` (no manual kubectl)
|
||
|
|
- [ ] Terraform state tracks 100% of resources
|