k8s-portal: document all three cluster-access paths in onboarding

The Getting Started portal only walked through the heaviest path (local VPN + kubectl + Vault + sops install) and never mentioned the two zero-setup routes that users actually reach first. Restructure onboarding to lead with all three, recommendation first: (A) the t3 web terminal, which drops you into a ready shell with kubectl/Vault/repos preinstalled; (B) the k8s web dashboard, auto-authenticated per user; and (C) the existing own-machine setup. Flag the dashboard/terminal as the fallback when CLI OIDC login is unavailable, reframe the misleading home-page 'VPN required' banner (only path C needs it), add the access endpoints to the service catalog, and fix a stale Vaultwarden URL (was vault.viktorbarzin.me, which is actually HashiCorp Vault; Vaultwarden is vaultwarden.viktorbarzin.me).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Viktor Barzin 2026-06-27 16:34:36 +00:00
parent d4f564e8d5
commit fca948a23d
4 changed files with 330 additions and 201 deletions

View file

@ -5,9 +5,11 @@
<main> <main>
<h1>Kubernetes Access Portal</h1> <h1>Kubernetes Access Portal</h1>
<div class="callout warning"> <div class="callout info">
<strong>VPN Required</strong> — The cluster is on a private network. You need Headscale VPN access before kubectl will work. <strong>Fastest way in:</strong> open the <a href="https://t3.viktorbarzin.me">web terminal</a> or the
<a href="/onboarding">See the Getting Started guide</a> for VPN setup instructions. <a href="https://k8s.viktorbarzin.me">dashboard</a> and sign in — no install, no VPN needed. Prefer your
own machine? The <a href="/onboarding#path-laptop">local-setup guide</a> covers VPN + kubectl, and the
<a href="/onboarding">Getting Started page</a> compares all three access paths.
</div> </div>
<section> <section>
@ -26,6 +28,7 @@
<p><strong>Assigned namespaces:</strong> {data.namespaces.join(', ')}</p> <p><strong>Assigned namespaces:</strong> {data.namespaces.join(', ')}</p>
<h3>Quick Commands</h3> <h3>Quick Commands</h3>
<p>Run these as-is in the <a href="https://t3.viktorbarzin.me">web terminal</a> — it's already signed in as you.</p>
<pre> <pre>
# Check your pods # Check your pods
kubectl get pods -n {data.namespaces[0]} kubectl get pods -n {data.namespaces[0]}
@ -47,16 +50,23 @@ vault write kubernetes/creds/{data.namespaces[0]}-deployer \
<section> <section>
<h2>Get Started</h2> <h2>Get Started</h2>
<h3>No setup — start now</h3>
<ol>
<li><a href="https://t3.viktorbarzin.me">Open the web terminal</a> — a ready shell with kubectl, Vault and your repos already set up</li>
<li><a href="https://k8s.viktorbarzin.me">Open the dashboard</a> — point-and-click view of your workloads</li>
</ol>
<h3>On your own machine</h3>
<ol> <ol>
{#if data.role === 'namespace-owner'} {#if data.role === 'namespace-owner'}
<li><a href="/onboarding?role=namespace-owner">Complete the namespace-owner onboarding guide</a></li> <li><a href="/onboarding?role=namespace-owner#path-laptop">Follow the namespace-owner setup</a> (VPN, kubectl, Vault, encrypted state)</li>
{:else} {:else}
<li><a href="/onboarding">Complete the onboarding guide</a> (VPN, kubectl, git)</li> <li><a href="/onboarding#path-laptop">Follow the local setup</a> (VPN, kubectl, git)</li>
{/if} {/if}
<li><a href="/setup">Install kubectl and kubelogin</a></li> <li><a href="/setup">Install kubectl and kubelogin</a></li>
<li><a href="/download">Download your kubeconfig</a></li> <li><a href="/download">Download your kubeconfig</a></li>
<li>Run <code>kubectl get namespaces</code> to verify access</li> <li>Run <code>kubectl get namespaces</code> to verify access</li>
</ol> </ol>
<p><a href="/onboarding">Compare all three access paths →</a></p>
</section> </section>
<section> <section>
@ -91,12 +101,12 @@ vault write kubernetes/creds/{data.namespaces[0]}-deployer \
border-radius: 6px; border-radius: 6px;
margin: 1rem 0; margin: 1rem 0;
} }
.callout.warning { .callout.info {
background: #fff3cd; background: #e8f4fd;
border-left: 4px solid #ffc107; border-left: 4px solid #2196f3;
} }
.callout a { .callout a {
color: #856404; color: #0d47a1;
font-weight: 600; font-weight: 600;
} }
</style> </style>

View file

@ -5,87 +5,175 @@
<main class="content"> <main class="content">
<h1>Getting Started</h1> <h1>Getting Started</h1>
<p>Welcome! Follow these steps to get access to the home Kubernetes cluster.</p> <p>
Welcome! There are three ways to reach the home Kubernetes cluster. Pick the one that fits —
<div class="role-tabs"> the first two need <strong>zero setup</strong> and open right in your browser.
<a href="/onboarding" class:active={!showNamespaceOwner}>General User</a> </p>
<a href="/onboarding?role=namespace-owner" class:active={showNamespaceOwner}>Namespace Owner</a>
</div>
<section> <section>
<h2>Step 0 — Join the VPN</h2> <h2>Three ways in</h2>
<p>The cluster is on a private network (<code>10.0.20.0/24</code>). You need VPN access first.</p> <table>
<thead><tr><th>Path</th><th>Best for</th><th>Setup</th></tr></thead>
<tbody>
<tr>
<td><a href="#path-terminal"><strong>A — Web terminal</strong></a></td>
<td>Just want to start working now</td>
<td>None — opens in your browser</td>
</tr>
<tr>
<td><a href="#path-dashboard"><strong>B — Web dashboard</strong></a></td>
<td>Click around, watch your app, read logs</td>
<td>None — opens in your browser</td>
</tr>
<tr>
<td><a href="#path-laptop"><strong>C — Your own machine</strong></a></td>
<td>kubectl / Terraform locally, full control</td>
<td>VPN + one-line installer</td>
</tr>
</tbody>
</table>
<div class="callout info">
<strong>Not sure?</strong> Start with the <a href="#path-terminal">web terminal (Path A)</a>.
Everything is already installed and your repos are already cloned — you can run your first
<code>kubectl</code> command within a minute, from any device.
</div>
</section>
<section id="path-terminal" class="path">
<h2>Path A — Web terminal <span class="badge rec">Recommended</span> <span class="badge none">No setup</span></h2>
<p>
A full terminal that runs in your browser — nothing to install, works from any device
(even a tablet). It drops you into your own account on the shared workstation, with every
tool already set up.
</p>
<ol> <ol>
<li>Install <a href="https://tailscale.com/download" target="_blank">Tailscale</a> for your OS</li> <li>Open <a href="https://t3.viktorbarzin.me" target="_blank">t3.viktorbarzin.me</a></li>
<li>Run this in your terminal: <li>Sign in with your Authentik account (the same SSO login as this portal)</li>
<pre>tailscale login --login-server https://headscale.viktorbarzin.me</pre> <li>You land in a ready-to-use shell. Try it:
<pre>kubectl get pods -n YOUR_NAMESPACE</pre>
</li> </li>
<li>A browser window will open with a registration URL</li>
<li>Send that URL to Viktor via email (<a href="mailto:vbarzin@gmail.com">vbarzin@gmail.com</a>) or Slack</li>
<li>Wait for approval (usually within a few hours)</li>
<li>Once approved, test: <pre>ping 10.0.20.100</pre></li>
</ol> </ol>
<div class="callout info">
<strong>Already done for you</strong> on the workstation:
<ul>
<li><code>kubectl</code> + your kubeconfig, scoped to your namespaces (no login dance)</li>
<li><code>vault</code>, <code>terragrunt</code>, <code>terraform</code>, <code>sops</code>, <code>kubeseal</code></li>
<li>Your repos cloned under <code>~/code</code> — the <code>infra</code> repo plus your own project repos</li>
<li>Claude Code, ready to pair with you on changes</li>
</ul>
</div>
<div class="callout warning">
<strong>No access yet?</strong> The workstation is provisioned per person. If
<code>t3.viktorbarzin.me</code> says you're not authorized, ask Viktor to add you
(<a href="mailto:vbarzin@gmail.com">vbarzin@gmail.com</a> or Slack).
</div>
</section> </section>
<section> <section id="path-dashboard" class="path">
<h2>Step 1 — Log in to the portal</h2> <h2>Path B — Web dashboard <span class="badge none">No setup</span></h2>
<p>Visit <a href="https://k8s-portal.viktorbarzin.me">k8s-portal.viktorbarzin.me</a> and sign in with your Authentik account.</p> <p>
<p>If you don't have an account yet, ask Viktor to create one.</p> A point-and-click view of the cluster — browse your pods, read logs, restart a deployment,
check events. Nothing to install.
</p>
<ol>
<li>Open <a href="https://k8s.viktorbarzin.me" target="_blank">k8s.viktorbarzin.me</a></li>
<li>Sign in with your Authentik account</li>
<li>
You're dropped straight into the Kubernetes Dashboard, already authenticated as you —
<strong>no token to paste</strong>. The portal injects your personal access token for you.
</li>
</ol>
<div class="callout info">
Scoped to your namespace(s): you can see and manage your own workloads, but not other
tenants'. This path uses a per-user token that does <em>not</em> depend on CLI login, so it
keeps working even if <code>kubectl</code> OIDC login is having a bad day — making it the
reliable fallback for Path C.
</div>
</section> </section>
<section> <section id="path-laptop" class="path c">
<h2>Step 2 — Set up kubectl</h2> <h2>Path C — From your own machine</h2>
<p>Run one of these commands in your terminal to install everything automatically:</p> <p>
<h3>macOS</h3> For running <code>kubectl</code>, <code>vault</code> and Terraform locally. This is the most
<p class="prereq">Requires <a href="https://brew.sh" target="_blank">Homebrew</a>. Install it first if you don't have it.</p> powerful path and the one to use for infrastructure changes — it just needs a bit more setup
<pre>bash &lt;(curl -fsSL https://k8s-portal.viktorbarzin.me/setup/script?os=mac)</pre> because the cluster API lives on a private network.
<h3>Linux</h3> </p>
<pre>bash &lt;(curl -fsSL https://k8s-portal.viktorbarzin.me/setup/script?os=linux)</pre>
<h3>Windows</h3> <div class="role-tabs">
<p>Use <a href="https://learn.microsoft.com/en-us/windows/wsl/install" target="_blank">WSL2</a> and follow the Linux instructions.</p> <a href="/onboarding?role=general#path-laptop" class:active={!showNamespaceOwner}>General User</a>
</section> <a href="/onboarding?role=namespace-owner#path-laptop" class:active={showNamespaceOwner}>Namespace Owner</a>
</div>
<p class="prereq">
{#if showNamespaceOwner}
Namespace owner — you'll also set up Vault and encrypted Terraform state so you can deploy
your own app stacks.
{:else}
General user — VPN, kubectl and git access. (Managing your own app stack? Switch to the
<strong>Namespace Owner</strong> tab above.)
{/if}
</p>
{#if showNamespaceOwner}
<section> <section>
<h2>Step 3 — Log into Vault</h2> <h3>Step 1 — Join the VPN</h3>
<p>Vault manages your secrets and issues dynamic Kubernetes credentials.</p> <p>The cluster API is on a private network (<code>10.0.20.0/24</code>), so you need VPN access first.</p>
<pre>vault login -method=oidc</pre> <ol>
<p>This opens your browser for Authentik SSO. After login, your token is saved to <code>~/.vault-token</code>.</p> <li>Install <a href="https://tailscale.com/download" target="_blank">Tailscale</a> for your OS</li>
<li>Run this in your terminal:
<pre>tailscale login --login-server https://headscale.viktorbarzin.me</pre>
</li>
<li>A browser window opens with a registration URL</li>
<li>Send that URL to Viktor via email (<a href="mailto:vbarzin@gmail.com">vbarzin@gmail.com</a>) or Slack</li>
<li>Wait for approval (usually within a few hours)</li>
<li>Once approved, test: <pre>ping 10.0.20.100</pre></li>
</ol>
</section> </section>
<section> <section>
<h2>Step 4 — Verify kubectl access</h2> <h3>Step 2 — Install the tools</h3>
<p>Run this command. It will open your browser for OIDC login the first time:</p> <p>Run one of these to install everything automatically (kubectl, kubelogin, vault, terragrunt, terraform, kubeseal) and write your kubeconfig to <code>~/.kube/config-home</code>:</p>
<pre>kubectl get pods -n YOUR_NAMESPACE</pre> <h4>macOS</h4>
<p>You should see an empty list (no resources) or your running pods.</p> <p class="prereq">Requires <a href="https://brew.sh" target="_blank">Homebrew</a>. Install it first if you don't have it.</p>
<pre>bash &lt;(curl -fsSL https://k8s-portal.viktorbarzin.me/setup/script?os=mac)</pre>
<h4>Linux</h4>
<pre>bash &lt;(curl -fsSL https://k8s-portal.viktorbarzin.me/setup/script?os=linux)</pre>
<h4>Windows</h4>
<p>Use <a href="https://learn.microsoft.com/en-us/windows/wsl/install" target="_blank">WSL2</a> and follow the Linux instructions.</p>
</section> </section>
<section> <section>
<h2>Step 5 — Clone the infra repo</h2> <h3>Step 3 — Verify access</h3>
<pre>git clone https://github.com/ViktorBarzin/infra.git <p>Run this. The first time, it opens your browser for SSO login:</p>
<pre>kubectl get {showNamespaceOwner ? 'pods -n YOUR_NAMESPACE' : 'namespaces'}</pre>
<p>You should see your resources (or an empty list if you haven't deployed anything yet).</p>
<div class="callout warning">
<strong>Browser login loops, or kubectl says "Unauthorized"?</strong> Command-line SSO
(OIDC) can occasionally be unavailable. When that happens, use the
<a href="#path-dashboard">web dashboard (Path B)</a> or the
<a href="#path-terminal">web terminal (Path A)</a> — both authenticate a different way and
keep working — and let Viktor know.
</div>
<p class="prereq">Connection error instead? Make sure the VPN is up: <code>tailscale status</code>.</p>
</section>
{#if showNamespaceOwner}
<section>
<h3>Step 4 — Log into Vault</h3>
<p>Vault manages your secrets and issues dynamic Kubernetes credentials.</p>
<pre>vault login -method=oidc</pre>
<p>This opens your browser for Authentik SSO. After login, your token is saved to <code>~/.vault-token</code>.</p>
</section>
<section>
<h3>Step 5 — Clone the infra repo</h3>
<pre>git clone https://github.com/ViktorBarzin/infra.git
cd infra</pre> cd infra</pre>
<p>This is where all the infrastructure configuration lives. Terraform state is committed as encrypted files.</p> <p>This is where all the infrastructure configuration lives. Terraform state is committed as encrypted files.</p>
</section> </section>
<section> <section>
<h2>Step 6 — Install tools</h2> <h3>Step 6 — Decrypt your state</h3>
<p>You need <code>sops</code> and <code>terragrunt</code> to work with infrastructure state:</p> <p>Terraform state is encrypted with SOPS. Your Vault login gives you access to <strong>only your stacks</strong>.</p>
<h3>macOS</h3> <pre># Make sure you're logged into Vault
<pre>brew install sops terragrunt</pre>
<h3>Linux</h3>
<pre># sops
curl -LO https://github.com/getsops/sops/releases/latest/download/sops-v3.9.4.linux.amd64
sudo mv sops-*.linux.amd64 /usr/local/bin/sops && sudo chmod +x /usr/local/bin/sops
# terragrunt
curl -LO https://github.com/gruntwork-io/terragrunt/releases/latest/download/terragrunt_linux_amd64
sudo mv terragrunt_linux_amd64 /usr/local/bin/terragrunt && sudo chmod +x /usr/local/bin/terragrunt</pre>
</section>
<section>
<h2>Step 7 — Decrypt your state</h2>
<p>Terraform state is encrypted with SOPS. Your Vault login gives you access to <strong>only your stacks</strong>.</p>
<pre># Make sure you're logged into Vault
vault login -method=oidc vault login -method=oidc
# Decrypt your stack's state # Decrypt your stack's state
@ -95,160 +183,157 @@ scripts/state-sync decrypt YOUR_NAMESPACE
cd stacks/YOUR_NAMESPACE cd stacks/YOUR_NAMESPACE
../../scripts/tg plan</pre> ../../scripts/tg plan</pre>
<div class="diagram"> <div class="diagram">
<h3>How state encryption works</h3> <h3>How state encryption works</h3>
<div class="flow-diagram"> <div class="flow-diagram">
<div class="flow-row"> <div class="flow-row">
<div class="flow-box accent">vault login -method=oidc</div> <div class="flow-box accent">vault login -method=oidc</div>
<div class="flow-arrow"></div> <div class="flow-arrow"></div>
<div class="flow-box">Authentik SSO</div> <div class="flow-box">Authentik SSO</div>
<div class="flow-arrow"></div> <div class="flow-arrow"></div>
<div class="flow-box accent">~/.vault-token</div> <div class="flow-box accent">~/.vault-token</div>
</div> </div>
<div class="flow-separator"></div> <div class="flow-separator"></div>
<div class="flow-row"> <div class="flow-row">
<div class="flow-box accent">scripts/tg plan</div> <div class="flow-box accent">scripts/tg plan</div>
<div class="flow-arrow"></div> <div class="flow-arrow"></div>
<div class="flow-box">state-sync decrypt</div> <div class="flow-box">state-sync decrypt</div>
<div class="flow-arrow"></div> <div class="flow-arrow"></div>
<div class="flow-box">Vault Transit<br/><small>sops-state-YOUR_NS</small></div> <div class="flow-box">Vault Transit<br/><small>sops-state-YOUR_NS</small></div>
</div> </div>
<div class="flow-separator"></div> <div class="flow-separator"></div>
<div class="flow-row"> <div class="flow-row">
<div class="flow-box">terragrunt plan/apply</div> <div class="flow-box">terragrunt plan/apply</div>
<div class="flow-arrow"></div> <div class="flow-arrow"></div>
<div class="flow-box">state-sync encrypt</div> <div class="flow-box">state-sync encrypt</div>
<div class="flow-arrow"></div> <div class="flow-arrow"></div>
<div class="flow-box accent">git commit + push</div> <div class="flow-box accent">git commit + push</div>
</div>
</div> </div>
</div> </div>
</div>
<div class="callout info"> <div class="callout info">
<strong>Access control:</strong> You can only decrypt state for your own namespaces. <strong>Access control:</strong> You can only decrypt state for your own namespaces.
Each namespace has its own Vault Transit encryption key. Your Vault policy Each namespace has its own Vault Transit encryption key. Your Vault policy
(<code>sops-user-YOUR_USERNAME</code>) only grants access to your keys. (<code>sops-user-YOUR_USERNAME</code>) only grants access to your keys.
</div> </div>
</section> </section>
<section> <section>
<h2>Step 8 — Create your first app stack</h2> <h3>Step 7 — Create your first app stack</h3>
<ol> <ol>
<li>Copy the template: <pre>cp -r stacks/_template stacks/myapp <li>Copy the template: <pre>cp -r stacks/_template stacks/myapp
mv stacks/myapp/main.tf.example stacks/myapp/main.tf</pre></li> mv stacks/myapp/main.tf.example stacks/myapp/main.tf</pre></li>
<li>Edit <code>stacks/myapp/main.tf</code> — replace all <code>&lt;placeholders&gt;</code></li> <li>Edit <code>stacks/myapp/main.tf</code> — replace all <code>&lt;placeholders&gt;</code></li>
<li>Store secrets in Vault: <li>Store secrets in Vault:
<pre>vault kv put secret/YOUR_USERNAME/myapp DB_PASSWORD=secret123</pre> <pre>vault kv put secret/YOUR_USERNAME/myapp DB_PASSWORD=secret123</pre>
</li> </li>
<li>Apply your stack: <li>Apply your stack:
<pre>cd stacks/myapp && ../../scripts/tg apply</pre> <pre>cd stacks/myapp && ../../scripts/tg apply</pre>
</li> </li>
<li>Commit encrypted state: <li>Commit encrypted state:
<pre>cd ../.. <pre>cd ../..
git add stacks/myapp/ state/stacks/myapp/terraform.tfstate.enc git add stacks/myapp/ state/stacks/myapp/terraform.tfstate.enc
git commit -m "add myapp stack" git commit -m "add myapp stack"
git push</pre> git push</pre>
</li> </li>
</ol> </ol>
</section> </section>
<section> <section>
<h2>Architecture Overview</h2> <h3>Architecture Overview</h3>
<p>Here's how your changes flow through the system:</p> <p>Here's how your changes flow through the system:</p>
<div class="diagram"> <div class="diagram">
<h3>Apply workflow</h3> <h3>Apply workflow</h3>
<div class="arch-grid"> <div class="arch-grid">
<div class="arch-col"> <div class="arch-col">
<div class="arch-header">Your Machine</div> <div class="arch-header">Your Machine</div>
<div class="arch-box">git pull</div> <div class="arch-box">git pull</div>
<div class="arch-arrow"></div> <div class="arch-arrow"></div>
<div class="arch-box">scripts/tg plan</div> <div class="arch-box">scripts/tg plan</div>
<div class="arch-arrow"><small>auto-decrypt</small></div> <div class="arch-arrow"><small>auto-decrypt</small></div>
<div class="arch-box">scripts/tg apply</div> <div class="arch-box">scripts/tg apply</div>
<div class="arch-arrow"><small>auto-encrypt</small></div> <div class="arch-arrow"><small>auto-encrypt</small></div>
<div class="arch-box">git push</div> <div class="arch-box">git push</div>
</div> </div>
<div class="arch-col"> <div class="arch-col">
<div class="arch-header">Vault</div> <div class="arch-header">Vault</div>
<div class="arch-box small">OIDC auth<br/><small>Authentik SSO</small></div> <div class="arch-box small">OIDC auth<br/><small>Authentik SSO</small></div>
<div class="arch-arrow"></div> <div class="arch-arrow"></div>
<div class="arch-box small">Transit decrypt<br/><small>sops-state-*</small></div> <div class="arch-box small">Transit decrypt<br/><small>sops-state-*</small></div>
<div class="arch-arrow"></div> <div class="arch-arrow"></div>
<div class="arch-box small">Transit encrypt<br/><small>per-stack key</small></div> <div class="arch-box small">Transit encrypt<br/><small>per-stack key</small></div>
</div> </div>
<div class="arch-col"> <div class="arch-col">
<div class="arch-header">Cluster</div> <div class="arch-header">Cluster</div>
<div class="arch-box small">K8s API</div> <div class="arch-box small">K8s API</div>
<div class="arch-arrow"></div> <div class="arch-arrow"></div>
<div class="arch-box small">Your namespace<br/><small>pods, services</small></div> <div class="arch-box small">Your namespace<br/><small>pods, services</small></div>
<div class="arch-arrow"></div> <div class="arch-arrow"></div>
<div class="arch-box small">Traefik ingress<br/><small>*.viktorbarzin.me</small></div> <div class="arch-box small">Traefik ingress<br/><small>*.viktorbarzin.me</small></div>
</div>
</div> </div>
</div> </div>
</div>
<div class="diagram"> <div class="diagram">
<h3>Security model</h3> <h3>Security model</h3>
<table> <table>
<thead><tr><th>Layer</th><th>What</th><th>How</th></tr></thead> <thead><tr><th>Layer</th><th>What</th><th>How</th></tr></thead>
<tbody> <tbody>
<tr><td>Authentication</td><td>Who are you?</td><td>Authentik SSO (OIDC) → Vault token</td></tr> <tr><td>Authentication</td><td>Who are you?</td><td>Authentik SSO (OIDC) → Vault token</td></tr>
<tr><td>Authorization</td><td>What can you access?</td><td>Vault policy (<code>sops-user-*</code>) scoped to your namespaces</td></tr> <tr><td>Authorization</td><td>What can you access?</td><td>Vault policy (<code>sops-user-*</code>) scoped to your namespaces</td></tr>
<tr><td>Encryption at rest</td><td>State in git</td><td>SOPS + Vault Transit (per-stack key)</td></tr> <tr><td>Encryption at rest</td><td>State in git</td><td>SOPS + Vault Transit (per-stack key)</td></tr>
<tr><td>Encryption fallback</td><td>Bootstrap / DR</td><td>age keys (admin only)</td></tr> <tr><td>Encryption fallback</td><td>Bootstrap / DR</td><td>age keys (admin only)</td></tr>
<tr><td>Network</td><td>Cluster access</td><td>Headscale VPN (private 10.0.20.0/24)</td></tr> <tr><td>Network</td><td>Cluster access</td><td>Headscale VPN (private 10.0.20.0/24)</td></tr>
</tbody> </tbody>
</table> </table>
</div> </div>
</section> </section>
{:else} {:else}
<section> <section>
<h2>Step 3 — Verify access</h2> <h3>Step 4 — Clone the repo</h3>
<p>Run this command. It will open your browser for login the first time:</p> <pre>git clone https://github.com/ViktorBarzin/infra.git
<pre>kubectl get namespaces</pre>
<p>You should see output like:</p>
<pre class="output">NAME STATUS AGE
default Active 200d
kube-system Active 200d
monitoring Active 200d
...</pre>
<p>If you get a connection error, make sure your VPN is connected (<code>tailscale status</code>).</p>
</section>
<section>
<h2>Step 4 — Clone the repo</h2>
<pre>git clone https://github.com/ViktorBarzin/infra.git
cd infra</pre> cd infra</pre>
<p>This is where all the infrastructure configuration lives.</p> <p>This is where all the infrastructure configuration lives.</p>
</section> </section>
<section> <section>
<h2>Step 5 — Your first change</h2> <h3>Step 5 — Your first change</h3>
<ol> <ol>
<li>Create a branch: <pre>git checkout -b my-first-change</pre></li> <li>Create a branch: <pre>git checkout -b my-first-change</pre></li>
<li>Edit a service file (e.g., change an image tag in <code>stacks/echo/main.tf</code>)</li> <li>Edit a service file (e.g., change an image tag in <code>stacks/echo/main.tf</code>)</li>
<li>Commit and push: <pre>git add . && git commit -m "my first change" && git push -u origin my-first-change</pre></li> <li>Commit and push: <pre>git add . &amp;&amp; git commit -m "my first change" &amp;&amp; git push -u origin my-first-change</pre></li>
<li>Open a Pull Request on GitHub</li> <li>Open a Pull Request on GitHub</li>
<li>Viktor reviews and merges</li> <li>Viktor reviews and merges</li>
<li>Woodpecker CI automatically applies the change to the cluster</li> <li>Woodpecker CI automatically applies the change to the cluster</li>
<li>Slack notification confirms it worked</li> <li>Slack notification confirms it worked</li>
</ol> </ol>
</section> </section>
{/if} {/if}
</section>
</main> </main>
<style> <style>
.content { max-width: 768px; margin: 2rem auto; padding: 0 1rem; font-family: system-ui, -apple-system, sans-serif; line-height: 1.6; } .content { max-width: 768px; margin: 2rem auto; padding: 0 1rem; font-family: system-ui, -apple-system, sans-serif; line-height: 1.6; }
.content h1 { border-bottom: 1px solid #e0e0e0; padding-bottom: 0.5rem; } .content h1 { border-bottom: 1px solid #e0e0e0; padding-bottom: 0.5rem; }
.content h2 { margin-top: 2rem; color: #333; } .content h2 { margin-top: 2rem; color: #333; }
.content h3 { color: #666; margin: 1rem 0 0.25rem; } .content h3 { color: #444; margin: 1.25rem 0 0.25rem; }
.content h4 { color: #666; margin: 0.75rem 0 0.25rem; }
.content pre { background: #1e1e1e; color: #d4d4d4; padding: 1rem; border-radius: 6px; overflow-x: auto; } .content pre { background: #1e1e1e; color: #d4d4d4; padding: 1rem; border-radius: 6px; overflow-x: auto; }
.content pre.output { background: #f5f5f5; color: #333; }
.content code { background: #f0f0f0; padding: 2px 6px; border-radius: 3px; } .content code { background: #f0f0f0; padding: 2px 6px; border-radius: 3px; }
.content .prereq { font-size: 0.9rem; color: #666; font-style: italic; } .content .prereq { font-size: 0.9rem; color: #666; font-style: italic; }
section { margin: 2rem 0; } section { margin: 2rem 0; }
.role-tabs { display: flex; gap: 0; margin: 1.5rem 0; border-bottom: 2px solid #e0e0e0; } section section { margin: 1.25rem 0; }
.path { border-left: 4px solid #4fc3f7; padding-left: 1.25rem; scroll-margin-top: 4rem; }
.path.c { border-left-color: #bbb; }
.badge { display: inline-block; font-size: 0.65rem; font-weight: 700; text-transform: uppercase; letter-spacing: 0.5px; padding: 0.15rem 0.5rem; border-radius: 4px; vertical-align: middle; margin-left: 0.4rem; }
.badge.rec { background: #d4f8d4; color: #1b5e20; }
.badge.none { background: #e3f2fd; color: #0d47a1; }
.role-tabs { display: flex; gap: 0; margin: 1.5rem 0 0.5rem; border-bottom: 2px solid #e0e0e0; }
.role-tabs a { padding: 0.5rem 1.5rem; text-decoration: none; color: #666; border-bottom: 2px solid transparent; margin-bottom: -2px; } .role-tabs a { padding: 0.5rem 1.5rem; text-decoration: none; color: #666; border-bottom: 2px solid transparent; margin-bottom: -2px; }
.role-tabs a.active { color: #333; border-bottom-color: #333; font-weight: 600; } .role-tabs a.active { color: #333; border-bottom-color: #333; font-weight: 600; }
table { border-collapse: collapse; width: 100%; margin: 0.5rem 0; } table { border-collapse: collapse; width: 100%; margin: 0.5rem 0; }
@ -258,6 +343,7 @@ cd infra</pre>
.callout { padding: 1rem; border-radius: 6px; margin: 1rem 0; } .callout { padding: 1rem; border-radius: 6px; margin: 1rem 0; }
.callout.info { background: #e8f4fd; border-left: 4px solid #2196f3; } .callout.info { background: #e8f4fd; border-left: 4px solid #2196f3; }
.callout.warning { background: #fff3cd; border-left: 4px solid #ffc107; } .callout.warning { background: #fff3cd; border-left: 4px solid #ffc107; }
.callout ul { margin: 0.5rem 0 0; padding-left: 1.25rem; }
.diagram { background: #fafafa; border: 1px solid #e0e0e0; border-radius: 8px; padding: 1.5rem; margin: 1.5rem 0; } .diagram { background: #fafafa; border: 1px solid #e0e0e0; border-radius: 8px; padding: 1.5rem; margin: 1.5rem 0; }
.diagram h3 { margin: 0 0 1rem 0; color: #333; font-size: 0.95rem; text-transform: uppercase; letter-spacing: 0.5px; } .diagram h3 { margin: 0 0 1rem 0; color: #333; font-size: 0.95rem; text-transform: uppercase; letter-spacing: 0.5px; }

View file

@ -2,6 +2,19 @@
<h1>Service Catalog</h1> <h1>Service Catalog</h1>
<p>70+ services running on the cluster. Here are the most commonly used:</p> <p>70+ services running on the cluster. Here are the most commonly used:</p>
<section>
<h2>Cluster Access</h2>
<table>
<thead><tr><th>Service</th><th>URL</th><th>Description</th></tr></thead>
<tbody>
<tr><td>Web Terminal</td><td><a href="https://t3.viktorbarzin.me">t3.viktorbarzin.me</a></td><td>Browser shell on the shared workstation — kubectl, Vault &amp; your repos preinstalled (zero setup)</td></tr>
<tr><td>Kubernetes Dashboard</td><td><a href="https://k8s.viktorbarzin.me">k8s.viktorbarzin.me</a></td><td>Point-and-click view of your workloads, auto-authenticated (zero setup)</td></tr>
<tr><td>Access Portal</td><td><a href="https://k8s-portal.viktorbarzin.me">k8s-portal.viktorbarzin.me</a></td><td>This portal — onboarding, kubeconfig download, setup script</td></tr>
<tr><td>Vault</td><td><a href="https://vault.viktorbarzin.me">vault.viktorbarzin.me</a></td><td>Secrets &amp; dynamic credentials — <code>vault login -method=oidc</code></td></tr>
</tbody>
</table>
</section>
<section> <section>
<h2>Core Services</h2> <h2>Core Services</h2>
<table> <table>
@ -22,7 +35,7 @@
<tbody> <tbody>
<tr><td>Nextcloud</td><td><a href="https://nextcloud.viktorbarzin.me">nextcloud.viktorbarzin.me</a></td><td>File storage, calendar, contacts</td></tr> <tr><td>Nextcloud</td><td><a href="https://nextcloud.viktorbarzin.me">nextcloud.viktorbarzin.me</a></td><td>File storage, calendar, contacts</td></tr>
<tr><td>Immich</td><td><a href="https://immich.viktorbarzin.me">immich.viktorbarzin.me</a></td><td>Photo library (Google Photos alternative)</td></tr> <tr><td>Immich</td><td><a href="https://immich.viktorbarzin.me">immich.viktorbarzin.me</a></td><td>Photo library (Google Photos alternative)</td></tr>
<tr><td>Vaultwarden</td><td><a href="https://vault.viktorbarzin.me">vault.viktorbarzin.me</a></td><td>Password manager</td></tr> <tr><td>Vaultwarden</td><td><a href="https://vaultwarden.viktorbarzin.me">vaultwarden.viktorbarzin.me</a></td><td>Password manager</td></tr>
<tr><td>Paperless-ngx</td><td><a href="https://pdf.viktorbarzin.me">pdf.viktorbarzin.me</a></td><td>Document management</td></tr> <tr><td>Paperless-ngx</td><td><a href="https://pdf.viktorbarzin.me">pdf.viktorbarzin.me</a></td><td>Document management</td></tr>
<tr><td>Navidrome</td><td><a href="https://music.viktorbarzin.me">music.viktorbarzin.me</a></td><td>Music streaming</td></tr> <tr><td>Navidrome</td><td><a href="https://music.viktorbarzin.me">music.viktorbarzin.me</a></td><td>Music streaming</td></tr>
<tr><td>Tandoor</td><td><a href="https://recipes.viktorbarzin.me">recipes.viktorbarzin.me</a></td><td>Recipe manager</td></tr> <tr><td>Tandoor</td><td><a href="https://recipes.viktorbarzin.me">recipes.viktorbarzin.me</a></td><td>Recipe manager</td></tr>

View file

@ -11,6 +11,26 @@
</ol> </ol>
</section> </section>
<section>
<h2>Browser login loops, or kubectl says "Unauthorized"</h2>
<p>Command-line SSO (OIDC) login can occasionally be unavailable. You don't have to wait for it — these authenticate a different way and keep working:</p>
<ul>
<li><a href="https://k8s.viktorbarzin.me">Web dashboard</a> — auto-authenticated, no token to paste</li>
<li><a href="https://t3.viktorbarzin.me">Web terminal</a> — its kubectl is already wired up</li>
</ul>
<p>Let Viktor know so the CLI login path gets fixed.</p>
</section>
<section>
<h2>Don't want to set up a local machine at all?</h2>
<p>Skip the VPN and CLI install entirely:</p>
<ul>
<li><a href="https://t3.viktorbarzin.me">t3.viktorbarzin.me</a> — a browser shell with everything preinstalled</li>
<li><a href="https://k8s.viktorbarzin.me">k8s.viktorbarzin.me</a> — a point-and-click dashboard</li>
</ul>
<p>Both just need your Authentik login. See the <a href="/onboarding">Getting Started</a> guide.</p>
</section>
<section> <section>
<h2>"Forbidden" or "Permission denied"</h2> <h2>"Forbidden" or "Permission denied"</h2>
<p>You may not have access to that namespace. Your access is scoped to specific namespaces.</p> <p>You may not have access to that namespace. Your access is scoped to specific namespaces.</p>