k8s/aux: add cert-manager longhorn dashboard forge dev-tools and shadowsocks

- cert-manager ClusterIssuers (LetsEncrypt + homelab-ca)
- Longhorn storage dashboard
- Portainer dashboard config
- Forgejo git service
- Claude terminal remote access
- Shadowsocks tunnel for remote access
This commit is contained in:
Story Crater Bot
2026-08-18 15:08:00 -07:00
parent f64cde0687
commit 1eef4711e5
18 changed files with 1201 additions and 0 deletions
+6
View File
@@ -0,0 +1,6 @@
apiVersion: v2
name: claude-terminal
description: Persistent Claude CLI terminal running in tmux with web access via gotty
type: application
version: 1.0.0
appVersion: "1.0"
+31
View File
@@ -0,0 +1,31 @@
FROM --platform=linux/amd64 ubuntu:24.04
RUN apt-get update && apt-get install -y \
tmux \
curl \
git \
build-essential \
nodejs \
npm \
bash \
&& rm -rf /var/lib/apt/lists/*
# Install gotty (web terminal access)
RUN curl -sL https://github.com/sorenisanerd/gotty/releases/download/v1.5.0/gotty_linux_amd64.tar.gz | \
tar xz -C /usr/local/bin && chmod +x /usr/local/bin/gotty
# Install Claude CLI
RUN npm install -g claude-code-cli 2>&1 || echo "Note: Claude CLI will be available after NPM package is published"
WORKDIR /root
# Create persistent storage dir
RUN mkdir -p /root/.claude /root/.config /root/.cache
# Entrypoint: start tmux session and gotty
COPY entrypoint.sh /entrypoint.sh
RUN chmod +x /entrypoint.sh
EXPOSE 8080
ENTRYPOINT ["/entrypoint.sh"]
+49
View File
@@ -0,0 +1,49 @@
# Claude Terminal — Persistent Remote Dev Environment
Runs Claude CLI in a persistent tmux session with web-based terminal access via gotty.
## Building the Image
Build for `linux/amd64`:
```bash
cd homelab
docker buildx build --platform linux/amd64 \
-t forgejo.riotpiao.homelab.com/rock/claude-terminal:latest \
-f k8s/dev-tools/Dockerfile \
k8s/dev-tools
# Log in to Forgejo registry
docker login forgejo.riotpiao.homelab.com \
--username ci-bot \
--password "$(talos get cluster/iam/agents/ci-bot --key token)"
# Push
docker push forgejo.riotpiao.homelab.com/rock/claude-terminal:latest
```
Or use the provided build script:
```bash
./k8s/dev-tools/build.sh
```
## Deployment
Update `values.yaml` if needed, then deploy via helmfile:
```bash
helmfile apply -l name=claude-terminal
```
Access the terminal at: **https://claude.riotpiao.homelab.com**
## Persistent Storage
- All Claude configuration stored in `/root/.claude` (persistent PVC, 10Gi Longhorn)
- Survives pod restarts and node reboots
- Accessible immediately after reconnecting
## SSH Access (Optional)
To add SSH access, extend the Dockerfile to include openssh-server and mount the PVC as home directory.
+24
View File
@@ -0,0 +1,24 @@
#!/bin/bash
set -euo pipefail
REGISTRY="forgejo.riotpiao.homelab.com"
IMAGE_NAME="rock/claude-terminal"
TAG="latest"
FULL_IMAGE="${REGISTRY}/${IMAGE_NAME}:${TAG}"
echo "🔨 Building Claude Terminal image for linux/amd64..."
docker buildx build --platform linux/amd64 \
-t "${FULL_IMAGE}" \
-f Dockerfile \
. || { echo "❌ Build failed"; exit 1; }
echo "🔓 Logging in to Forgejo registry..."
REGISTRY_TOKEN=$(talos get cluster/iam/agents/ci-bot --key token)
echo "${REGISTRY_TOKEN}" | docker login "${REGISTRY}" \
--username ci-bot \
--password-stdin || { echo "❌ Login failed"; exit 1; }
echo "📤 Pushing image to registry..."
docker push "${FULL_IMAGE}" || { echo "❌ Push failed"; exit 1; }
echo "✅ Successfully pushed ${FULL_IMAGE}"
+13
View File
@@ -0,0 +1,13 @@
#!/bin/bash
set -e
# Start tmux server in background
tmux new-session -d -s claude -c /root "bash"
# Give tmux a moment to stabilize
sleep 1
# Start gotty serving the tmux session
# -w: allow write (make terminal interactive)
# -p 8080: listen on port 8080
exec gotty -p 8080 -w tmux attach-session -t claude
+49
View File
@@ -0,0 +1,49 @@
{{/*
Expand the name of the chart.
*/}}
{{- define "claude-terminal.name" -}}
{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }}
{{- end }}
{{/*
Create a default fully qualified app name.
*/}}
{{- define "claude-terminal.fullname" -}}
{{- if .Values.fullnameOverride }}
{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }}
{{- else }}
{{- $name := default .Chart.Name .Values.nameOverride }}
{{- if contains $name .Release.Name }}
{{- .Release.Name | trunc 63 | trimSuffix "-" }}
{{- else }}
{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" }}
{{- end }}
{{- end }}
{{- end }}
{{/*
Create chart name and version as used by the chart label.
*/}}
{{- define "claude-terminal.chart" -}}
{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }}
{{- end }}
{{/*
Common labels
*/}}
{{- define "claude-terminal.labels" -}}
helm.sh/chart: {{ include "claude-terminal.chart" . }}
{{ include "claude-terminal.selectorLabels" . }}
{{- if .Chart.AppVersion }}
app.kubernetes.io/version: {{ .Chart.AppVersion | quote }}
{{- end }}
app.kubernetes.io/managed-by: {{ .Release.Service }}
{{- end }}
{{/*
Selector labels
*/}}
{{- define "claude-terminal.selectorLabels" -}}
app.kubernetes.io/name: {{ include "claude-terminal.name" . }}
app.kubernetes.io/instance: {{ .Release.Name }}
{{- end }}
+57
View File
@@ -0,0 +1,57 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: {{ include "claude-terminal.fullname" . }}
labels:
{{- include "claude-terminal.labels" . | nindent 4 }}
spec:
replicas: {{ .Values.replicaCount }}
selector:
matchLabels:
{{- include "claude-terminal.selectorLabels" . | nindent 6 }}
template:
metadata:
labels:
{{- include "claude-terminal.selectorLabels" . | nindent 8 }}
spec:
containers:
- name: claude-terminal
image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}"
imagePullPolicy: {{ .Values.image.pullPolicy }}
ports:
- name: http
containerPort: 8080
protocol: TCP
livenessProbe:
httpGet:
path: /
port: http
initialDelaySeconds: 30
periodSeconds: 10
readinessProbe:
httpGet:
path: /
port: http
initialDelaySeconds: 5
periodSeconds: 5
resources:
{{- toYaml .Values.resources | nindent 12 }}
volumeMounts:
- name: claude-storage
mountPath: {{ .Values.persistence.mountPath }}
volumes:
- name: claude-storage
persistentVolumeClaim:
claimName: {{ include "claude-terminal.fullname" . }}-pvc
{{- with .Values.nodeSelector }}
nodeSelector:
{{- toYaml . | nindent 8 }}
{{- end }}
{{- with .Values.affinity }}
affinity:
{{- toYaml . | nindent 8 }}
{{- end }}
{{- with .Values.tolerations }}
tolerations:
{{- toYaml . | nindent 8 }}
{{- end }}
+41
View File
@@ -0,0 +1,41 @@
{{- if .Values.ingress.enabled }}
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: {{ include "claude-terminal.fullname" . }}
labels:
{{- include "claude-terminal.labels" . | nindent 4 }}
{{- with .Values.ingress.annotations }}
annotations:
{{- toYaml . | nindent 4 }}
{{- end }}
spec:
{{- if .Values.ingress.className }}
ingressClassName: {{ .Values.ingress.className }}
{{- end }}
{{- if .Values.ingress.tls }}
tls:
{{- range .Values.ingress.tls }}
- hosts:
{{- range .hosts }}
- {{ . | quote }}
{{- end }}
secretName: {{ .secretName }}
{{- end }}
{{- end }}
rules:
{{- range .Values.ingress.hosts }}
- host: {{ .host | quote }}
http:
paths:
{{- range .paths }}
- path: {{ .path }}
pathType: {{ .pathType }}
backend:
service:
name: {{ include "claude-terminal.fullname" $ }}
port:
number: {{ $.Values.service.port }}
{{- end }}
{{- end }}
{{- end }}
+15
View File
@@ -0,0 +1,15 @@
{{- if .Values.persistence.enabled }}
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: {{ include "claude-terminal.fullname" . }}-pvc
labels:
{{- include "claude-terminal.labels" . | nindent 4 }}
spec:
accessModes:
- ReadWriteOnce
storageClassName: {{ .Values.persistence.storageClass }}
resources:
requests:
storage: {{ .Values.persistence.size }}
{{- end }}
+15
View File
@@ -0,0 +1,15 @@
apiVersion: v1
kind: Service
metadata:
name: {{ include "claude-terminal.fullname" . }}
labels:
{{- include "claude-terminal.labels" . | nindent 4 }}
spec:
type: {{ .Values.service.type }}
ports:
- port: {{ .Values.service.port }}
targetPort: http
protocol: TCP
name: http
selector:
{{- include "claude-terminal.selectorLabels" . | nindent 4 }}
+45
View File
@@ -0,0 +1,45 @@
replicaCount: 1
image:
repository: localhost:5000/claude-terminal
pullPolicy: IfNotPresent
tag: latest
service:
type: ClusterIP
port: 8080
ingress:
enabled: true
className: nginx
annotations:
cert-manager.io/cluster-issuer: homelab-ca
hosts:
- host: claude.riotpiao.homelab.com
paths:
- path: /
pathType: Prefix
tls:
- secretName: claude-terminal-tls
hosts:
- claude.riotpiao.homelab.com
persistence:
enabled: true
storageClass: longhorn
size: 10Gi
mountPath: /root/.claude
resources:
requests:
cpu: 100m
memory: 256Mi
limits:
cpu: 500m
memory: 512Mi
nodeSelector: {}
tolerations: []
affinity: {}