diff --git a/config.tfvars b/config.tfvars index 651fed74..ca9a8141 100644 Binary files a/config.tfvars and b/config.tfvars differ diff --git a/docs/plans/2026-03-07-k8s-portal-onboarding-plan.md b/docs/plans/2026-03-07-k8s-portal-onboarding-plan.md new file mode 100644 index 00000000..7d002b33 --- /dev/null +++ b/docs/plans/2026-03-07-k8s-portal-onboarding-plan.md @@ -0,0 +1,210 @@ +# K8s Portal Onboarding Hub — Implementation Plan (v2) + +## Goals +1. Fix broken kubeconfig/OIDC setup script (users can't connect) +2. Add markdown-driven onboarding hub for non-technical users +3. Complete contributor onboarding (git, PR workflow, Codex setup) + +--- + +## Part 1: Fix Setup Script Bugs + +### Bug 1 — Empty CA cert (CRITICAL) +**Root cause**: ConfigMap `k8s-portal-config` has `ca.crt = ""`. The kubeconfig gets empty `certificate-authority-data`, causing TLS failures. + +**Fix**: +1. Extract K8s API CA cert: `kubectl get configmap -n kube-system kube-root-ca.crt -o jsonpath='{.data.ca\.crt}'` +2. Verify it matches the API server cert: `openssl s_client -connect 10.0.20.100:6443 -showcerts 2>/dev/null | openssl x509 -issuer -noout` — compare issuer with CA cert subject +3. Add `variable "k8s_ca_cert" { type = string }` to `main.tf` +4. Add the cert value to `config.tfvars` (it's public, not a secret) +5. Use in ConfigMap: `"ca.crt" = var.k8s_ca_cert` +6. Pass through `stacks/platform/main.tf` module call + +**Double-base64 risk**: The Node.js code does `Buffer.from(caCert).toString('base64')` on the PEM text. This creates base64-of-PEM, which kubectl accepts (kubectl handles both base64(PEM) and base64(DER)). Verified: this is the standard kubeconfig format used by `kubectl config set-cluster --certificate-authority`. + +### Bug 2 — Missing VPN prerequisite +**Root cause**: Kubeconfig points to `https://10.0.20.100:6443` (internal IP). No VPN = no connection. + +**Fix**: Add VPN setup as step 0 in both: +- The existing homepage (`+page.svelte`) — prominent callout box +- The new onboarding page — full enrollment instructions + +### Bug 3 — Headscale enrollment is admin-gated +**Fix**: Document the complete flow: +1. User installs Tailscale app +2. User runs `tailscale login --login-server https://headscale.viktorbarzin.me` +3. User sends the registration URL to Viktor (via Slack/email — provide contact) +4. Viktor approves on Headscale +5. User is now on the VPN + +### Bug 4 — `kubectl get pods` vs `kubectl get namespaces` +**Fix**: Change homepage `+page.svelte` to say `kubectl get namespaces` (consistent with setup script). + +### Bug 5 — Unused `openid` scope fix +**NOT a bug**: kubelogin always adds `openid` automatically. Remove from the plan. The real investigation is: verify Authentik's `kubernetes` OIDC provider returns `groups` claim in the ID token. + +### Bug 6 — Heredoc quoting no-op +**Fix**: Remove the useless `escapedKubeconfig` replace on line 49 of `script/+server.ts` — the quoted heredoc delimiter makes it irrelevant. + +### Files to Modify +- `stacks/platform/modules/k8s-portal/main.tf` — add `k8s_ca_cert` variable, update ConfigMap +- `stacks/platform/main.tf` — pass `k8s_ca_cert` to module +- `config.tfvars` — add the CA cert value +- `files/src/routes/setup/script/+server.ts` — remove useless quote escaping +- `files/src/routes/download/+server.ts` — same CA cert fix applies here (identical code) +- `files/src/routes/+page.svelte` — add VPN callout, fix verification command + +--- + +## Part 2: Content System — Skip mdsvex, Use Direct Svelte + +### Why NOT mdsvex +- Svelte 5.53.0 broke mdsvex (unresolved as of today) +- Requires pinning Svelte to <5.53, which conflicts with security updates +- Runes mode in layouts is broken in mdsvex +- The content is 5 small pages authored by one person — mdsvex is overkill +- Build complexity and image size increase for minimal benefit + +### Alternative: Write content directly in Svelte components +Each content page is a Svelte component with inline HTML/text: +```svelte + +
+

Getting Started

+

Welcome! Follow these steps...

+ ... +
+``` + +**Advantages**: +- Zero new dependencies +- Works with any Svelte 5 version +- Content is still just HTML/text in clearly named files +- Can add Svelte interactivity later (copy buttons, progress tracking) + +**Trade-off**: Content edits require touching `.svelte` files instead of `.md`. For 5 pages maintained by one person (or an AI), this is fine. If content grows significantly, revisit mdsvex later when Svelte 5 compatibility is stable. + +### Shared Content Styling +Create `src/lib/content.css` with the docs-style layout: +```css +.content { max-width: 768px; margin: 2rem auto; font-family: system-ui; line-height: 1.6; } +.content h1 { border-bottom: 1px solid #e0e0e0; padding-bottom: 0.5rem; } +.content pre { background: #1e1e1e; color: #d4d4d4; padding: 1rem; border-radius: 6px; } +.content code { background: #f0f0f0; padding: 2px 6px; border-radius: 3px; } +.content .callout { background: #fff3cd; border-left: 4px solid #ffc107; padding: 1rem; margin: 1rem 0; } +.content .danger { background: #f8d7da; border-left: 4px solid #dc3545; } +``` + +--- + +## Part 3: Route Structure + +``` +src/routes/ +├── +layout.svelte ← Nav bar (Home, Onboarding, Architecture, Services, Contributing, Troubleshooting) +├── +page.svelte ← Identity + VPN callout + Get Started (UPDATED) +├── onboarding/+page.svelte ← Step-by-step guide +├── architecture/+page.svelte ← How the cluster works +├── services/+page.svelte ← Service catalog +├── contributing/+page.svelte ← PR workflow +├── troubleshooting/+page.svelte ← Common issues +├── setup/+page.svelte ← Existing kubectl install +├── setup/script/+server.ts ← Existing auto-setup (FIXED) +└── download/+server.ts ← Existing kubeconfig download (FIXED) +``` + +### Navigation Layout (`+layout.svelte`) +Simple horizontal nav, active page highlighted: +```svelte + + +``` + +--- + +## Part 4: Page Content + +### `/onboarding` — Getting Started (non-technical, step-by-step) + +**Step 0 — Join the VPN** +- "The cluster is on a private network. You need VPN access first." +- Install Tailscale: link to tailscale.com/download +- Run: `tailscale login --login-server https://headscale.viktorbarzin.me` +- "This will open a browser with a registration URL. Send that URL to Viktor via [Slack/email]. He'll approve your device within a few hours." +- "Once approved, you're connected! Test: `ping 10.0.20.100`" + +**Step 1 — Log in to the portal** +- "Visit https://k8s-portal.viktorbarzin.me and sign in with your Authentik account" +- "If you don't have an account, ask Viktor to create one" + +**Step 2 — Set up kubectl** +- macOS: `bash <(curl -fsSL https://k8s-portal.viktorbarzin.me/setup/script?os=mac)` +- Linux: `bash <(curl -fsSL https://k8s-portal.viktorbarzin.me/setup/script?os=linux)` +- Windows: "Use WSL2 and follow the Linux instructions" +- macOS prerequisite: "Requires Homebrew. Install it first if you don't have it: [link]" + +**Step 3 — Verify access** +- Run: `kubectl get namespaces` +- "This will open a browser for you to log in. After login, you should see a list of namespaces." +- Show expected output example + +**Step 4 — Clone the repo** +- `git clone https://github.com/ViktorBarzin/infra.git` + +**Step 5 — Install your AI assistant (optional)** +- Install Codex: `npm install -g @openai/codex` +- "Codex reads AGENTS.md from the repo and knows how to work with the cluster" + +**Step 6 — Your first change** +- Walk-through: create branch, edit a file, push, open PR, watch CI + +### `/architecture` — How It Works +- Simplified: "Proxmox runs VMs → VMs form a K8s cluster → services run as pods" +- Storage, networking, DNS in plain English +- Tier system: "critical services restart first, optional services restart last" + +### `/services` — What's Running +- Table: service name, URL, what it does +- Top services highlighted (Nextcloud, Grafana, Uptime Kuma, etc.) + +### `/contributing` — How to Contribute +- Branch → edit → PR → review → CI applies +- "What you CAN change" vs "what needs Viktor's review" +- The NEVER list (kubectl apply, secrets in plaintext, NFS restart) + +### `/troubleshooting` — Common Issues +- "Can't connect to the cluster" → VPN + KUBECONFIG +- "Permission denied on kubectl" → namespace access +- "Pod is crashing" → check logs +- "PR CI failed" → read Woodpecker logs +- "Need a new secret" → ask Viktor + +--- + +## Part 5: Build & Deploy + +1. Make code changes (bug fixes + new pages) +2. Build locally: `cd files && npm install && npm run dev` — verify all pages +3. Test kubeconfig: verify CA cert is present and valid +4. Build Docker image: `docker build -t viktorbarzin/k8s-portal:latest .` +5. Push to registry +6. `terragrunt apply` to deploy +7. End-to-end test on a fresh machine + +--- + +## Implementation Order +1. Fix CA cert (immediate — unblocks setup script) +2. Fix homepage (VPN callout, correct verification command) +3. Remove useless heredoc escaping +4. Add nav layout +5. Create 5 content pages (onboarding, architecture, services, contributing, troubleshooting) +6. Build, push, deploy +7. End-to-end test diff --git a/stacks/hackmd/main.tf b/stacks/hackmd/main.tf index debe9a5a..d6eef4ac 100644 --- a/stacks/hackmd/main.tf +++ b/stacks/hackmd/main.tf @@ -4,7 +4,6 @@ variable "hackmd_db_password" { } variable "tls_secret_name" { type = string - sensitive = true } variable "nfs_server" { type = string } variable "mysql_host" { type = string } diff --git a/stacks/k8s-dashboard/main.tf b/stacks/k8s-dashboard/main.tf index 27f90858..09f29c4a 100644 --- a/stacks/k8s-dashboard/main.tf +++ b/stacks/k8s-dashboard/main.tf @@ -4,7 +4,6 @@ variable "tls_secret_name" { } variable "client_certificate_secret_name" { type = string - sensitive = true } diff --git a/stacks/platform/main.tf b/stacks/platform/main.tf index 847ec9bf..f3f3aea8 100644 --- a/stacks/platform/main.tf +++ b/stacks/platform/main.tf @@ -24,7 +24,6 @@ # --- Core --- variable "tls_secret_name" { type = string - sensitive = true } variable "nfs_server" { type = string } variable "redis_host" { type = string } @@ -75,6 +74,10 @@ variable "homepage_credentials" { # --- headscale --- variable "headscale_config" { type = string } variable "headscale_acl" { type = string } +variable "k8s_ca_cert" { + type = string + default = "" +} # --- authentik / rbac / k8s-portal --- variable "authentik_secret_key" { @@ -317,6 +320,7 @@ module "k8s-portal" { source = "./modules/k8s-portal" tier = local.tiers.edge tls_secret_name = var.tls_secret_name + k8s_ca_cert = var.k8s_ca_cert } # ----------------------------------------------------------------------------- diff --git a/stacks/platform/modules/k8s-portal/files/src/routes/+layout.svelte b/stacks/platform/modules/k8s-portal/files/src/routes/+layout.svelte index 9cebde54..d412c4d6 100644 --- a/stacks/platform/modules/k8s-portal/files/src/routes/+layout.svelte +++ b/stacks/platform/modules/k8s-portal/files/src/routes/+layout.svelte @@ -1,5 +1,6 @@ @@ -8,4 +9,56 @@ + + {@render children()} + + diff --git a/stacks/platform/modules/k8s-portal/files/src/routes/+page.svelte b/stacks/platform/modules/k8s-portal/files/src/routes/+page.svelte index b4783b6f..961011cd 100644 --- a/stacks/platform/modules/k8s-portal/files/src/routes/+page.svelte +++ b/stacks/platform/modules/k8s-portal/files/src/routes/+page.svelte @@ -5,6 +5,11 @@

Kubernetes Access Portal

+
+ VPN Required — The cluster is on a private network. You need Headscale VPN access before kubectl will work. + See the Getting Started guide for VPN setup instructions. +
+

Your Identity

Username: {data.username}

@@ -18,18 +23,31 @@

Get Started

    +
  1. Complete the onboarding guide (VPN, kubectl, git)
  2. Install kubectl and kubelogin
  3. Download your kubeconfig
  4. -
  5. Run kubectl get pods to verify access
  6. +
  7. Run kubectl get namespaces to verify access
+ +
+

Resources

+ +
diff --git a/stacks/platform/modules/k8s-portal/files/src/routes/architecture/+page.svelte b/stacks/platform/modules/k8s-portal/files/src/routes/architecture/+page.svelte new file mode 100644 index 00000000..0f6e0734 --- /dev/null +++ b/stacks/platform/modules/k8s-portal/files/src/routes/architecture/+page.svelte @@ -0,0 +1,73 @@ +
+

Architecture

+ +
+

Overview

+

The infrastructure runs on a single Dell R730 server (22 CPU cores, 142GB RAM) using Proxmox to manage virtual machines. Five of those VMs form a Kubernetes cluster that runs 70+ services.

+
+Proxmox (Dell R730)
+ ├── k8s-master  (10.0.20.100) — control plane
+ ├── k8s-node1   (10.0.20.101) — GPU node (Tesla T4)
+ ├── k8s-node2   (10.0.20.102) — worker
+ ├── k8s-node3   (10.0.20.103) — worker
+ ├── k8s-node4   (10.0.20.104) — worker
+ ├── TrueNAS     (10.0.10.15)  — storage (NFS + iSCSI)
+ └── pfSense     (10.0.20.1)   — firewall + gateway
+
+ +
+

Networking

+
    +
  • Public domain: viktorbarzin.me — managed by Cloudflare
  • +
  • Internal domain: viktorbarzin.lan — managed by Technitium DNS
  • +
  • Ingress: Cloudflare → Traefik → services
  • +
  • VPN: Headscale (self-hosted Tailscale)
  • +
+
+ +
+

Storage

+
    +
  • NFS (nfs-truenas) — for app data (files, configs, media). Stored on TrueNAS.
  • +
  • iSCSI (iscsi-truenas) — for databases (PostgreSQL, MySQL). Block storage.
  • +
+
+ +
+

Service Tiers

+

Services are organized into tiers that control resource limits and restart priority:

+ + + + + + + +
TierExamplesPriority
0-coreTraefik, DNS, VPN, AuthHighest — never evicted
1-clusterRedis, Prometheus, CrowdSecHigh
2-gpuOllama, Immich ML, WhisperMedium
3-edgeNextcloud, Paperless, GrafanaNormal
4-auxDashy, PrivateBin, CyberChefLow — evicted first under pressure
+
+ +
+

Infrastructure as Code

+

Everything is managed with Terraform (via Terragrunt). Each service has its own stack:

+
stacks/
+ ├── platform/       ← core infra (22 modules)
+ ├── url/            ← URL shortener (Shlink)
+ ├── immich/         ← photo library
+ ├── nextcloud/      ← file storage
+ └── ... (70+ more)
+

Changes go through git: branch → PR → review → merge → CI applies automatically.

+
+
+ + diff --git a/stacks/platform/modules/k8s-portal/files/src/routes/contributing/+page.svelte b/stacks/platform/modules/k8s-portal/files/src/routes/contributing/+page.svelte new file mode 100644 index 00000000..6f0d1903 --- /dev/null +++ b/stacks/platform/modules/k8s-portal/files/src/routes/contributing/+page.svelte @@ -0,0 +1,62 @@ +
+

How to Contribute

+ +
+

Workflow

+
    +
  1. Create a branch: git checkout -b fix/my-change
  2. +
  3. Make your changes in stacks/<service>/main.tf
  4. +
  5. Push and open a PR: git push -u origin fix/my-change
  6. +
  7. Viktor reviews and merges
  8. +
  9. CI applies automatically — Slack notification when done
  10. +
+
+ +
+

What you CAN change

+
    +
  • Service configurations (image tags, environment variables, resource limits)
  • +
  • New services (add a new stack under stacks/)
  • +
  • Ingress routes, health probes, replica counts
  • +
+
+ +
+

What needs Viktor's review

+
    +
  • CI pipeline changes (.woodpecker/)
  • +
  • Terragrunt configuration (terragrunt.hcl)
  • +
  • Secrets configuration (.sops.yaml)
  • +
  • Core platform modules (stacks/platform/)
  • +
+
+ +
+

NEVER do these

+
+
    +
  • Never kubectl apply/edit/patch — all changes go through Terraform
  • +
  • Never put secrets in code — ask Viktor to add them to the encrypted secrets file
  • +
  • Never restart NFS on TrueNAS — causes cluster-wide mount failures
  • +
  • Never push directly to master — always use a PR
  • +
+
+
+ +
+

Need a new secret?

+

Comment on your PR: "I need a database password for my-service." Viktor will add it to the encrypted secrets file and push to your branch.

+

Then reference it in your Terraform: var.my_service_db_password

+
+
+ + diff --git a/stacks/platform/modules/k8s-portal/files/src/routes/onboarding/+page.svelte b/stacks/platform/modules/k8s-portal/files/src/routes/onboarding/+page.svelte new file mode 100644 index 00000000..8d8fc3d2 --- /dev/null +++ b/stacks/platform/modules/k8s-portal/files/src/routes/onboarding/+page.svelte @@ -0,0 +1,89 @@ +
+

Getting Started

+

Welcome! Follow these steps to get access to the home Kubernetes cluster.

+ +
+

Step 0 — Join the VPN

+

The cluster is on a private network (10.0.20.0/24). You need VPN access first.

+
    +
  1. Install Tailscale for your OS
  2. +
  3. Run this in your terminal: +
    tailscale login --login-server https://headscale.viktorbarzin.me
    +
  4. +
  5. A browser window will open with a registration URL
  6. +
  7. Send that URL to Viktor via email (vbarzin@gmail.com) or Slack
  8. +
  9. Wait for approval (usually within a few hours)
  10. +
  11. Once approved, test:
    ping 10.0.20.100
  12. +
+
+ +
+

Step 1 — Log in to the portal

+

Visit k8s-portal.viktorbarzin.me and sign in with your Authentik account.

+

If you don't have an account yet, ask Viktor to create one.

+
+ +
+

Step 2 — Set up kubectl

+

Run one of these commands in your terminal to install everything automatically:

+

macOS

+

Requires Homebrew. Install it first if you don't have it.

+
bash <(curl -fsSL https://k8s-portal.viktorbarzin.me/setup/script?os=mac)
+

Linux

+
bash <(curl -fsSL https://k8s-portal.viktorbarzin.me/setup/script?os=linux)
+

Windows

+

Use WSL2 and follow the Linux instructions.

+
+ +
+

Step 3 — Verify access

+

Run this command. It will open your browser for login the first time:

+
kubectl get namespaces
+

You should see output like:

+
NAME              STATUS   AGE
+default           Active   200d
+kube-system       Active   200d
+monitoring        Active   200d
+...
+

If you get a connection error, make sure your VPN is connected (tailscale status).

+
+ +
+

Step 4 — Clone the repo

+
git clone https://github.com/ViktorBarzin/infra.git
+cd infra
+

This is where all the infrastructure configuration lives.

+
+ +
+

Step 5 — Install your AI assistant (optional)

+

Install Codex CLI for AI-assisted cluster management:

+
npm install -g @openai/codex
+

Codex reads the AGENTS.md file in the repo and knows how to work with the cluster.

+
+ +
+

Step 6 — Your first change

+
    +
  1. Create a branch:
    git checkout -b my-first-change
  2. +
  3. Edit a service file (e.g., change an image tag in stacks/echo/main.tf)
  4. +
  5. Commit and push:
    git add . && git commit -m "my first change" && git push -u origin my-first-change
  6. +
  7. Open a Pull Request on GitHub
  8. +
  9. Viktor reviews and merges
  10. +
  11. Woodpecker CI automatically applies the change to the cluster
  12. +
  13. Slack notification confirms it worked
  14. +
+
+
+ + diff --git a/stacks/platform/modules/k8s-portal/files/src/routes/services/+page.svelte b/stacks/platform/modules/k8s-portal/files/src/routes/services/+page.svelte new file mode 100644 index 00000000..2d603dfb --- /dev/null +++ b/stacks/platform/modules/k8s-portal/files/src/routes/services/+page.svelte @@ -0,0 +1,52 @@ +
+

Service Catalog

+

70+ services running on the cluster. Here are the most commonly used:

+ +
+

Core Services

+ + + + + + +
ServiceURLDescription
Grafanagrafana.viktorbarzin.meMonitoring dashboards
Uptime Kumauptime.viktorbarzin.meService uptime monitoring
Authentikauthentik.viktorbarzin.meIdentity provider (SSO)
Woodpecker CIci.viktorbarzin.meCI/CD pipeline
+
+ +
+

User-Facing Services

+ + + + + + + + + +
ServiceURLDescription
Nextcloudnextcloud.viktorbarzin.meFile storage, calendar, contacts
Immichimmich.viktorbarzin.mePhoto library (Google Photos alternative)
Vaultwardenvault.viktorbarzin.mePassword manager
Paperless-ngxpdf.viktorbarzin.meDocument management
Navidromemusic.viktorbarzin.meMusic streaming
Tandoorrecipes.viktorbarzin.meRecipe manager
Linkwardenbookmarks.viktorbarzin.meBookmark manager
+
+ +
+

Developer Tools

+ + + + + + + +
ServiceURLDescription
Forgejoforgejo.viktorbarzin.meGit server (Gitea fork)
CyberChefcyberchef.viktorbarzin.meData transformation tool
Excalidrawdraw.viktorbarzin.meWhiteboard drawing
PrivateBinpaste.viktorbarzin.meEncrypted paste bin
JSON Crackjsoncrack.viktorbarzin.meJSON visualizer
+
+
+ + diff --git a/stacks/platform/modules/k8s-portal/files/src/routes/setup/script/+server.ts b/stacks/platform/modules/k8s-portal/files/src/routes/setup/script/+server.ts index 7e512020..696ab6dc 100644 --- a/stacks/platform/modules/k8s-portal/files/src/routes/setup/script/+server.ts +++ b/stacks/platform/modules/k8s-portal/files/src/routes/setup/script/+server.ts @@ -4,7 +4,6 @@ import { readFileSync } from 'fs'; const CLUSTER_SERVER = 'https://10.0.20.100:6443'; const OIDC_ISSUER = 'https://authentik.viktorbarzin.me/application/o/kubernetes/'; const OIDC_CLIENT_ID = 'kubernetes'; -const PORTAL_URL = 'https://k8s-portal.viktorbarzin.me'; export const GET: RequestHandler = async ({ url }) => { const os = url.searchParams.get('os') || 'mac'; @@ -46,8 +45,6 @@ users: - --oidc-extra-scope=groups interactiveMode: IfAvailable`; - const escapedKubeconfig = kubeconfigContent.replace(/'/g, "'\\''"); - let script: string; if (os === 'linux') { @@ -98,7 +95,7 @@ fi # Write kubeconfig mkdir -p ~/.kube cat > ~/.kube/config-home << 'KUBECONFIG_EOF' -${escapedKubeconfig} +${kubeconfigContent} KUBECONFIG_EOF echo "[OK] Kubeconfig written to ~/.kube/config-home" @@ -152,7 +149,7 @@ fi # Write kubeconfig mkdir -p ~/.kube cat > ~/.kube/config-home << 'KUBECONFIG_EOF' -${escapedKubeconfig} +${kubeconfigContent} KUBECONFIG_EOF echo "[OK] Kubeconfig written to ~/.kube/config-home" diff --git a/stacks/platform/modules/k8s-portal/files/src/routes/troubleshooting/+page.svelte b/stacks/platform/modules/k8s-portal/files/src/routes/troubleshooting/+page.svelte new file mode 100644 index 00000000..17ac2e5a --- /dev/null +++ b/stacks/platform/modules/k8s-portal/files/src/routes/troubleshooting/+page.svelte @@ -0,0 +1,63 @@ +
+

Troubleshooting

+ +
+

"kubectl can't connect to the server"

+
    +
  1. Check your VPN: tailscale status — should show "connected"
  2. +
  3. Check KUBECONFIG: echo $KUBECONFIG — should be ~/.kube/config-home
  4. +
  5. Test connectivity: ping 10.0.20.100
  6. +
  7. If ping works but kubectl doesn't, re-run the setup script
  8. +
+
+ +
+

"Forbidden" or "Permission denied"

+

You may not have access to that namespace. Your access is scoped to specific namespaces.

+

Try: kubectl get namespaces to see which namespaces you can access.

+

Need access to another namespace? Ask Viktor.

+
+ +
+

"Pod is CrashLoopBackOff"

+
    +
  1. Check pod logs: kubectl logs -n <namespace> <pod-name> --tail=50
  2. +
  3. Check previous crash: kubectl logs -n <namespace> <pod-name> --previous
  4. +
  5. Check events: kubectl describe pod -n <namespace> <pod-name>
  6. +
  7. Common causes: OOMKilled (need more memory), bad config, database connection failure
  8. +
+
+ +
+

"PR CI failed"

+
    +
  1. Check the Woodpecker CI dashboard: ci.viktorbarzin.me
  2. +
  3. Read the build logs — the error is usually at the bottom
  4. +
  5. Fix the issue, commit, and push — CI will re-run
  6. +
+
+ +
+

"I need a new secret / database password"

+

Secrets are managed by Viktor in an encrypted file. You cannot add them yourself.

+
    +
  1. Comment on your PR: "Need DB password for <service>"
  2. +
  3. Viktor adds the secret and pushes to your branch
  4. +
  5. Reference it as var.<service>_db_password in your Terraform
  6. +
+
+ +
+

Still stuck?

+

Email Viktor at vbarzin@gmail.com or message on Slack.

+
+
+ + diff --git a/stacks/platform/modules/k8s-portal/main.tf b/stacks/platform/modules/k8s-portal/main.tf index 40217516..bab83dab 100644 --- a/stacks/platform/modules/k8s-portal/main.tf +++ b/stacks/platform/modules/k8s-portal/main.tf @@ -1,5 +1,9 @@ variable "tls_secret_name" {} variable "tier" { type = string } +variable "k8s_ca_cert" { + type = string + default = "" +} resource "kubernetes_namespace" "k8s_portal" { metadata { @@ -23,8 +27,7 @@ resource "kubernetes_config_map" "k8s_portal_config" { } data = { - # CA cert extracted from kubeconfig — will be populated with cluster CA cert - "ca.crt" = "" + "ca.crt" = var.k8s_ca_cert } } diff --git a/stacks/platform/modules/reverse_proxy/factory/main.tf b/stacks/platform/modules/reverse_proxy/factory/main.tf index cfbcf9c2..1af42844 100644 --- a/stacks/platform/modules/reverse_proxy/factory/main.tf +++ b/stacks/platform/modules/reverse_proxy/factory/main.tf @@ -17,7 +17,6 @@ variable "protected" { variable "ingress_path" { type = list(string) default = ["/"] - sensitive = true } variable "max_body_size" { type = string