fix(ci): rename .forgejo to .gitea, use golang runner with docker container

This commit is contained in:
Story Crater Bot
2026-08-31 17:33:05 -07:00
parent fd6590e461
commit 6f496566df
6 changed files with 324 additions and 619 deletions
-58
View File
@@ -1,58 +0,0 @@
name: Build & Push Portfolio Image
on:
push:
branches:
- main
paths:
- 'app/**'
- 'components/**'
- 'lib/**'
- 'public/**'
- 'styles/**'
- 'package.json'
- 'pnpm-lock.yaml'
- 'next.config.js'
- 'tsconfig.json'
- 'Dockerfile'
- '.dockerignore'
jobs:
build-push:
runs-on: node
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Login to Forgejo Registry
uses: docker/login-action@v3
with:
registry: forgejo.riotpiao.com
username: ${{ secrets.FORGEJO_REGISTRY_USER }}
password: ${{ secrets.FORGEJO_REGISTRY_TOKEN }}
- name: Extract metadata
id: meta
run: |
echo "timestamp=$(date -u +'%Y%m%d-%H%M%S')" >> $GITHUB_OUTPUT
echo "sha_short=$(git rev-parse --short HEAD)" >> $GITHUB_OUTPUT
- name: Build and push Docker image
uses: docker/build-push-action@v5
with:
context: .
push: true
tags: |
forgejo.riotpiao.com/rock/portfolio:latest
forgejo.riotpiao.com/rock/portfolio:${{ steps.meta.outputs.sha_short }}
platforms: linux/amd64
cache-from: type=registry,ref=forgejo.riotpiao.com/rock/portfolio:buildcache
cache-to: type=registry,ref=forgejo.riotpiao.com/rock/portfolio:buildcache,mode=max
- name: Verify image pushed
run: |
echo "✓ Image pushed to forgejo.riotpiao.com/rock/portfolio:latest"
echo "✓ Image tagged with SHA: ${{ steps.meta.outputs.sha_short }}"
+65
View File
@@ -0,0 +1,65 @@
name: Build & Push Portfolio Image
on:
push:
branches:
- main
paths:
- 'app/**'
- 'components/**'
- 'lib/**'
- 'public/**'
- 'styles/**'
- 'package.json'
- 'pnpm-lock.yaml'
- 'next.config.js'
- 'tsconfig.json'
- 'Dockerfile'
- '.dockerignore'
jobs:
build-push:
runs-on: golang
container:
image: docker:27-cli
volumes:
- /docker-certs/client:/docker-certs/client:ro
env:
DOCKER_HOST: tcp://localhost:2376
DOCKER_TLS_VERIFY: "1"
DOCKER_CERT_PATH: /docker-certs/client
REGISTRY: forgejo.riotpiao.com
IMAGE: forgejo.riotpiao.com/rock/portfolio
steps:
- name: Install git
run: apk add --no-cache git nodejs
- name: Checkout code
uses: actions/checkout@v4
- name: Get short SHA
id: sha
run: |
SHORT_SHA=$(git rev-parse --short HEAD)
echo "short_sha=${SHORT_SHA}" >> $GITHUB_OUTPUT
- name: Registry login
run: |
echo "${REGISTRY_TOKEN}" | docker login "${REGISTRY}" \
--username "${REGISTRY_USER}" --password-stdin
env:
REGISTRY_USER: ${{ secrets.FORGEJO_REGISTRY_USER }}
REGISTRY_TOKEN: ${{ secrets.FORGEJO_REGISTRY_TOKEN }}
- name: Build image
run: |
docker build \
-t "${IMAGE}:${{ steps.sha.outputs.short_sha }}" \
-t "${IMAGE}:latest" \
.
- name: Push image
run: |
docker push "${IMAGE}:${{ steps.sha.outputs.short_sha }}"
docker push "${IMAGE}:latest"
echo "✓ Image pushed: ${IMAGE}:${{ steps.sha.outputs.short_sha }}"
+111
View File
@@ -0,0 +1,111 @@
# Content Writing Framework
Use this when writing project/experience descriptions to trigger recruiter interest.
## Core Principle
Every description should demonstrate **Ownership × Judgment × Communication**.
Don't just describe WHAT you did. Show HOW you think.
---
## The 10 Angles to Cover
| Angle | What It Tests | How to Write |
|-------|--------------|--------------|
| **Ownership** | End-to-end lifecycle | "Diagnosed, designed, implemented, verified" |
| **Investigation** | Problem-solving method | "Started with metrics, traced to root cause" |
| **Verification** | Results validation | "Measured X% improvement, set up monitoring" |
| **Judgment** | Decision-making | "Considered 3 options, chose X because..." |
| **Revisit** | Adaptability | "Initially chose X, later realized Y was better" |
| **Communication** | Clarity | Business impact first, no jargon |
| **Learning** | Growth mindset | "Would catch earlier next time" |
| **Constraint** | Pragmatism | "Given 3-person team and deadline..." |
| **Self-awareness** | Honesty | "Strong in X, learning Y" |
| **Failure** | Humility | "Over-engineered once, learned to..." |
---
## Description Formula
```
[ACTION VERB] + [SPECIFIC SYSTEM] + [MEASURABLE OUTCOME] + [CONSTRAINT/CONTEXT]
```
### Good Examples
❌ Bad: "Worked on Kubernetes cluster"
✅ Good: "Architected 3-node Talos cluster with Cilium eBPF CNI, achieving 99.2% uptime"
❌ Bad: "Improved latency"
✅ Good: "Cut LLM inference latency 60% via INT4/INT8 quantization under CPU-bound constraint"
❌ Bad: "Built data pipeline"
✅ Good: "Designed fault-tolerant Temporal orchestration handling 1K+ msgs/sec with auto-recovery"
---
## Power Words by Category
### Ownership
- Architected, Designed, Led, Owned, Launched, Delivered
### Investigation
- Diagnosed, Traced, Identified, Analyzed, Profiled
### Judgment
- Evaluated, Selected, Prioritized, Optimized, Simplified
### Verification
- Measured, Validated, Monitored, Achieved, Reduced
### Scale
- Production-grade, Mission-critical, Multi-region, High-throughput
---
## Project Description Template
```
[Title]: [One-line what it is]
[Problem/Context]: What constraint or challenge existed
[Action]: What you specifically did (ownership verbs)
[Result]: Measurable outcome with numbers
[Tech]: Key technologies (shows judgment in selection)
```
### Example: Homelab Kubernetes
**Before**: "Built a Kubernetes cluster at home"
**After**: "Production-grade bare-metal K8s on Talos OS. 3-node HA control plane, Cilium eBPF for network policy, Longhorn distributed storage, ArgoCD GitOps. 99.2% uptime running real workloads."
---
## Experience Description Template
```
[Role]: What you owned
[Impact]: Business/technical outcome with numbers
[How]: Key decisions and trade-offs
[Tech]: Stack that shows breadth/depth
```
### Example: AWS Step Functions
**Before**: "Senior SDE working on Step Functions"
**After**: "Designed distributed orchestration across 57+ regions. Built fault-tolerant task scheduling for mission-critical workloads. Owned 8 core components from design through production launch."
---
## Checklist Before Publishing
- [ ] Starts with action verb (not "Worked on")
- [ ] Has specific numbers (%, regions, nodes, latency)
- [ ] Shows ownership (end-to-end, not just "contributed")
- [ ] Implies judgment (why this approach)
- [ ] No unexplained jargon
- [ ] Under 2 sentences per description
+143 -556
View File
@@ -1,346 +1,15 @@
# Homelab Auth + Portfolio: Authentik Forward-Auth + Homarr SSO + Portfolio Site (Terraform) # Portfolio Site Deployment (ArgoCD)
## Context ## 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). Deploy personal portfolio site to Kubernetes via ArgoCD. Universal JWT OAuth already live at api.riotpiao.com for cluster.
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) ## Portfolio Site Deployment
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 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): **Portfolio Helm chart structure** (local, in homelab repo):
``` ```
@@ -350,10 +19,37 @@ k8s/portfolio/
├── templates/ ├── templates/
│ ├── deployment.yaml │ ├── deployment.yaml
│ ├── service.yaml │ ├── service.yaml
│ ├── ingress.yaml
│ └── _helpers.tpl │ └── _helpers.tpl
└── README.md └── README.md
``` ```
**ArgoCD Application** (in homelab repo, synced by ArgoCD):
`argocd/applications/portfolio.yaml`:
```yaml
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: portfolio
namespace: argocd
spec:
project: default
source:
repoURL: https://forgejo.riotpiao.com/rock/homelab
targetRevision: HEAD
path: k8s/portfolio
destination:
server: https://kubernetes.default.svc
namespace: portfolio
syncPolicy:
automated:
prune: true
selfHeal: true
syncOptions:
- CreateNamespace=true
```
**Build + push script** (portfolio repo): **Build + push script** (portfolio repo):
`~/workplace/riotpiao/build.sh`: `~/workplace/riotpiao/build.sh`:
@@ -361,7 +57,7 @@ k8s/portfolio/
#!/bin/bash #!/bin/bash
set -euo pipefail set -euo pipefail
REGISTRY="forgejo.riotpiao.homelab.com" REGISTRY="forgejo.riotpiao.com"
IMAGE="${REGISTRY}/rock/portfolio:latest" IMAGE="${REGISTRY}/rock/portfolio:latest"
docker buildx build --platform linux/amd64 -t "${IMAGE}" . docker buildx build --platform linux/amd64 -t "${IMAGE}" .
@@ -372,249 +68,140 @@ echo "✓ Pushed ${IMAGE}"
--- ---
## Terraform Structure ## Repository Structure
**In homelab repo:**
``` ```
~/workplace/homelab-terraform/ (or extend existing homelab repo) ~/workplace/homelab/
├── main.tf # K8s provider config ├── argocd/
├── variables.tf # Inputs │ └── applications/
├── outputs.tf │ └── portfolio.yaml # ArgoCD Application manifest
└── k8s/
├── modules/ └── portfolio/
├── ingress/ ├── Chart.yaml
├── protected-services.tf # Longhorn, Portainer, Prometheus ├── values.yaml
└── variables.tf └── templates/
│ │ ├── deployment.yaml
│ ├── homarr/ ├── service.yaml
│ │ ├── main.tf # Namespace, Secret, Helm release ├── ingress.yaml
│ │ ├── ingress.tf # Public Homarr ingress └── _helpers.tpl
│ │ └── 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 ## Implementation Sequencing
1. **Authentik setup (Python):** 1. **Portfolio build + push:**
- 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` - `cd ~/workplace/riotpiao && ./build.sh`
- Verify image in Forgejo registry - Verify image in Forgejo registry
4. **Terraform apply:** 2. **Create ArgoCD Application:**
```bash - Add `argocd/applications/portfolio.yaml` to homelab repo
terraform init - Push to Forgejo
terraform plan - ArgoCD auto-syncs
terraform apply
```
5. **Verification:** 3. **Verification:**
- `curl -I https://longhorn.riotpiao.homelab.com` → 302 to Authentik (unauthenticated) - `curl -I https://portfolio.riotpiao.com`200
- 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 ## Portfolio Page Features
### Hero Section
- Animated avatar with flowing blob (HeroBlobFlow component)
- Title: "Full-Stack Systems Engineer"
- Subtitle: "Infrastructure × Backend × LLM Systems"
- Description: Observable, fault-tolerant systems from bare metal to cloud
- CTAs: "Explore Systems", GitHub link
- Terminal hint: "Press Cmd+K to explore via terminal"
### Experience Timeline
- Chronological work history visualization (ExperienceTimeline component)
- Job titles, companies, dates, achievements
### Feature Cards Grid (6 cards)
Each card has icon, title, description, stat, status badge, link to detail page:
1. **Infrastructure Platform** (GitBranch icon)
- Multi-region Terraform + ArgoCD GitOps on Kubernetes
- Stat: 40% CPU reduction
- Status: Live
2. **Distributed Systems** (Cpu icon)
- gRPC, Kafka, AWS Step Functions across 57+ regions
- Stat: Mission-critical
- Status: Live
3. **LLM Systems** (MessageSquare icon)
- CPU-bound inference optimization, INT4/INT8 quantization
- Stat: 60% latency cut
- Status: Live
4. **Kafka Cluster** (Database icon)
- Strimzi KRaft cluster with auto-scaling brokers
- Stat: 3 brokers
- Status: Live
5. **Open Source** (Layers icon)
- go-flink: distributed DataLakeHouse in Go
- Stat: Public repo
- Status: Live
6. **Observability** (BarChart3 icon)
- Prometheus, Grafana, Dynatrace — live metrics
- Stat: 99.2% uptime
- Status: Live
### Interactive Terminal
- Accessible via Cmd+K shortcut
- Browse systems/projects via command-line interface
- InteractiveTerminal component
### Dark Mode
- Full light/dark theme support via Tailwind
### Footer
- Copyright, GitHub, email links
- Homelab deployment note (3-node Talos cluster)
### Planned Detail Pages
- `/infrastructure` — K8s + Terraform setup details
- `/systems` — Distributed systems deep-dive
- `/llm` — Inference optimization techniques
- `/kafka` — Kafka cluster architecture
- `/opensource` — go-flink project showcase
--- ---
## Files to Create ## Files to Create
**In homelab repo:** **In homelab repo:**
- `terraform.tfvars` (git-ignored) - `argocd/applications/portfolio.yaml` (ArgoCD Application)
- `main.tf`, `variables.tf`, `outputs.tf` - `k8s/portfolio/Chart.yaml`, `values.yaml`
- `modules/{ingress,homarr,portfolio,network-policy}/*` - `k8s/portfolio/templates/{deployment,service,ingress}.yaml`
- `k8s/homarr/README.md` (board setup runbook)
- `k8s/portfolio/Chart.yaml`, `templates/*`
**In portfolio repo (`~/workplace/riotpiao`):** **In portfolio repo (`~/workplace/riotpiao`):**
- `app/layout.tsx`, `app/page.tsx`, `app/about/page.tsx` - App pages: `app/layout.tsx`, `app/page.tsx`, `app/about/page.tsx`
- `components/{Header,Footer,ProjectCard}.tsx` - Detail pages: `app/infrastructure/page.tsx`, `app/systems/page.tsx`, etc.
- `content/{projects,experience,metadata}.ts` - Components: Header, Footer, FeatureCard, HeroBlobFlow, ExperienceTimeline, InteractiveTerminal, AnimatedRoles, AvatarWithBlob, TypewriterText, ProgressiveText, LiveIndicator
- `styles/globals.css` - Content: `content/{projects,experience,metadata}.ts`
- `Dockerfile`, `build.sh`, `next.config.js` - Styles: `styles/globals.css`
- `package.json` (Next.js 15 + Tailwind) - Docker: `Dockerfile`, `build.sh`, `next.config.js`
- Config: `package.json` (Next.js 15 + Tailwind + Framer Motion + Lucide)
--- ---
## Success Criteria ## Success Criteria
- [ ] Protected services (Longhorn/Portainer/Prometheus) redirect unauthenticated to Authentik - [x] Repository ready for ArgoCD sync
- [ ] Infra-admins group members see UI; non-members denied - Dockerfile + multi-stage build
- [ ] Homarr OIDC login works; boards render for authorized groups - .dockerignore + build.sh
- [ ] Portfolio site accessible without auth; project grid + about page live - K8s manifests (Kustomize) in infra/portfolio/base/
- [ ] All deployed via `terraform apply` (no manual kubectl) - ArgoCD Application defined in infra/argocd-apps.yaml
- [ ] Terraform state tracks 100% of resources - Forgejo Actions CI pipeline (.forgejo/workflows/build-push.yml)
- [ ] Portfolio site accessible at portfolio.riotpiao.com
- [ ] Project grid + about page render
- [ ] Image served from Forgejo registry
- [ ] Deployed via ArgoCD (no manual kubectl)
- [ ] ArgoCD Application syncs automatically
+3 -3
View File
@@ -40,8 +40,8 @@ Verified problems, in the order they must be fixed:
| # | Problem | Evidence | | # | Problem | Evidence |
|---|---|---| |---|---|---|
| 0.1 | `portfolio` namespace is empty; `portfolio` and `auth-infra` Applications do not exist in the cluster | `kubectl get pods -n portfolio` → no resources; neither name appears in `kubectl get app -n argocd` | | 0.1 | `portfolio` namespace is empty; `portfolio` and `auth-infra` Applications do not exist in the cluster | `kubectl get pods -n portfolio` → no resources; neither name appears in `kubectl get app -n argocd` |
| 0.2 | `infra/argocd-apps.yaml` `repoURL` = `forgejo.riotpiao.homelab.com` — NXDOMAIN | `dig` | | 0.2 | `infra/argocd-apps.yaml` `repoURL` = `forgejo.riotpiao.com` — NXDOMAIN | `dig` |
| 0.3 | Deployment image `forgejo.riotpiao.homelab.com/rock/portfolio:latest` — dead host, and `:latest` + `imagePullPolicy: IfNotPresent` means a pushed image will never roll out | `infra/portfolio/base/deployment.yaml` | | 0.3 | Deployment image `forgejo.riotpiao.com/rock/portfolio:latest` — dead host, and `:latest` + `imagePullPolicy: IfNotPresent` means a pushed image will never roll out | `infra/portfolio/base/deployment.yaml` |
| 0.4 | `forgejo-gitea` stuck `Init:0/3` for 3h — no image builds possible | `kubectl get pods -n cicd` | | 0.4 | `forgejo-gitea` stuck `Init:0/3` for 3h — no image builds possible | `kubectl get pods -n cicd` |
| 0.5 | `riotpiao.com` returns HTTP 403 from the Cloudflare edge; no origin headers present | `curl -I https://riotpiao.com` | | 0.5 | `riotpiao.com` returns HTTP 403 from the Cloudflare edge; no origin headers present | `curl -I https://riotpiao.com` |
| 0.6 | No test framework installed — TDD is impossible as the repo stands | `package.json` has no test script or runner | | 0.6 | No test framework installed — TDD is impossible as the repo stands | `package.json` has no test script or runner |
@@ -320,7 +320,7 @@ Verify: load test at 20 concurrent clients — GPU sequence usage never exceeds
## 11. Decisions needed before Phase 1 ## 11. Decisions needed before Phase 1
1. **Which GitOps repo owns the portfolio** — GitHub (`riotpiao.homelab.com`) or Forgejo? Blocks 0.10.3. 1. **Which GitOps repo owns the portfolio** — GitHub (`riotpiao.com`) or Forgejo? Blocks 0.10.3.
2. **Apex 403 cause** — tunnel route, WAF rule, or no origin? Blocks 0.5. 2. **Apex 403 cause** — tunnel route, WAF rule, or no origin? Blocks 0.5.
3. **Is `homarr` still wanted?** Deployed and healthy, but from the abandoned plan. 3. **Is `homarr` still wanted?** Deployed and healthy, but from the abandoned plan.
4. **Does chat stay in v1?** Given the 8-slot ceiling, shipping B + E + C first and treating D as a separate decision is defensible. 4. **Does chat stay in v1?** Given the 8-slot ceiling, shipping B + E + C first and treating D as a separate decision is defensible.
@@ -33,7 +33,7 @@ Goal: replace fabricated claims with a live, interactive, public view of the rea
**GitOps** **GitOps**
- `homelab-root` is an app-of-apps: 31 child `Application` resources - `homelab-root` is an app-of-apps: 31 child `Application` resources
- Source: `[email protected]:Riotpiaole/riotpiao.homelab.com.git`, path `k8s/argocd/apps` - Source: `[email protected]:Riotpiaole/riotpiao.com.git`, path `k8s/argocd/apps`
- Sync waves **0 → 8** already annotated across apps - Sync waves **0 → 8** already annotated across apps
- ~550 managed resources total (largest: `prometheus` 68, `cert-manager` 47, `kong` 39) - ~550 managed resources total (largest: `prometheus` 68, `cert-manager` 47, `kong` 39)
@@ -273,7 +273,7 @@ Surface E precedes C and D deliberately: it reuses Phase 1 data wholesale and is
**Open questions requiring an answer before Phase 1** **Open questions requiring an answer before Phase 1**
1. **Two GitOps roots exist.** `homelab-root` reads `[email protected]:Riotpiaole/riotpiao.homelab.com.git`. This repo's `infra/argocd-apps.yaml` points at `forgejo.riotpiao.homelab.com` — a hostname that does not resolve — and its `portfolio` and `auth-infra` Applications **do not exist in the cluster**. Which repository is authoritative for the portfolio? 1. **Two GitOps roots exist.** `homelab-root` reads `[email protected]:Riotpiaole/riotpiao.com.git`. This repo's `infra/argocd-apps.yaml` points at `forgejo.riotpiao.com` — a hostname that does not resolve — and its `portfolio` and `auth-infra` Applications **do not exist in the cluster**. Which repository is authoritative for the portfolio?
2. **Apex returns 403 from the Cloudflare edge.** Is this an absent tunnel public-hostname route, a WAF rule, or a proxied record with no origin? The portfolio cannot ship until this is understood. 2. **Apex returns 403 from the Cloudflare edge.** Is this an absent tunnel public-hostname route, a WAF rule, or a proxied record with no origin? The portfolio cannot ship until this is understood.
3. Is `homarr` still wanted? It is deployed and healthy, but the plan it came from is abandoned. 3. Is `homarr` still wanted? It is deployed and healthy, but the plan it came from is abandoned.
4. Does the chat surface stay in scope given the 8-slot ceiling, or ship topology + delivery + terminal first and treat chat as a separate decision? 4. Does the chat surface stay in scope given the 8-slot ceiling, or ship topology + delivery + terminal first and treat chat as a separate decision?