Add ttyd app

This commit is contained in:
dvirlabs 2026-03-25 17:06:10 +02:00
parent c2331dc5d1
commit 9bf49b956c
11 changed files with 447 additions and 0 deletions

25
argocd-apps/ttyd.yaml Normal file
View File

@ -0,0 +1,25 @@
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: ttyd
namespace: argocd
labels:
env: infra
spec:
project: infra
source:
repoURL: 'ssh://git@gitea-ssh.dev-tools.svc.cluster.local:2222/dvirlabs/infra.git'
targetRevision: HEAD
path: charts/ttyd
helm:
valueFiles:
- ../../manifests/ttyd/values.yaml
destination:
server: https://kubernetes.default.svc
namespace: infra
syncPolicy:
automated:
prune: true
selfHeal: true
syncOptions:
- CreateNamespace=true

16
charts/ttyd/Chart.yaml Normal file
View File

@ -0,0 +1,16 @@
apiVersion: v2
name: ttyd
description: Browser-based terminal via ttyd, managed by ArgoCD
type: application
version: 0.1.0
appVersion: "latest"
keywords:
- terminal
- ttyd
- kubectl
- web-terminal
maintainers:
- name: dvirlabs
home: https://github.com/tsl0922/ttyd
sources:
- https://github.com/tsl0922/ttyd

View File

@ -0,0 +1,62 @@
{{/*
Expand the name of the chart.
*/}}
{{- define "ttyd.name" -}}
{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }}
{{- end }}
{{/*
Create a default fully qualified app name.
We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec).
If release name contains chart name it will be used as a full name.
*/}}
{{- define "ttyd.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 "ttyd.chart" -}}
{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }}
{{- end }}
{{/*
Common labels
*/}}
{{- define "ttyd.labels" -}}
helm.sh/chart: {{ include "ttyd.chart" . }}
{{ include "ttyd.selectorLabels" . }}
{{- if .Chart.AppVersion }}
app.kubernetes.io/version: {{ .Chart.AppVersion | quote }}
{{- end }}
app.kubernetes.io/managed-by: {{ .Release.Service }}
{{- end }}
{{/*
Selector labels
*/}}
{{- define "ttyd.selectorLabels" -}}
app.kubernetes.io/name: {{ include "ttyd.name" . }}
app.kubernetes.io/instance: {{ .Release.Name }}
{{- end }}
{{/*
Create the name of the service account to use
*/}}
{{- define "ttyd.serviceAccountName" -}}
{{- if .Values.serviceAccount.create }}
{{- default (include "ttyd.fullname" .) .Values.serviceAccount.name }}
{{- else }}
{{- default "default" .Values.serviceAccount.name }}
{{- end }}
{{- end }}

View File

@ -0,0 +1,94 @@
{{- if .Values.serviceAccount.create -}}
# WARNING: This ClusterRole grants broad read + exec access across the cluster.
# It is intentionally permissive for lab/troubleshooting use.
# Review and restrict these permissions before using in a production environment.
#
# Future auth integration note:
# When oauth2-proxy is added in front of ttyd, consider scoping this role
# further to match the actual user's identity or group permissions.
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: {{ include "ttyd.fullname" . }}
labels:
{{- include "ttyd.labels" . | nindent 4 }}
rules:
# Core workload resources — read + basic management for kubectl troubleshooting
- apiGroups: [""]
resources:
- pods
- pods/log
- services
- endpoints
- configmaps
- secrets # WARNING: includes secret read access; tighten in production
- events
- namespaces
- nodes
- persistentvolumeclaims
- persistentvolumes
- replicationcontrollers
- serviceaccounts
verbs: ["get", "list", "watch"]
# Pod exec and log streaming (needed for `kubectl exec` and `kubectl logs -f`)
- apiGroups: [""]
resources:
- pods/exec
- pods/attach
- pods/portforward
verbs: ["create"]
# Pod and service management (basic ops for lab use)
- apiGroups: [""]
resources:
- pods
- services
- configmaps
verbs: ["delete", "patch", "update"]
# Apps resources
- apiGroups: ["apps"]
resources:
- deployments
- replicasets
- statefulsets
- daemonsets
verbs: ["get", "list", "watch", "patch", "delete"]
# Batch resources
- apiGroups: ["batch"]
resources:
- jobs
- cronjobs
verbs: ["get", "list", "watch", "delete"]
# Networking resources
- apiGroups: ["networking.k8s.io"]
resources:
- ingresses
- ingressclasses
verbs: ["get", "list", "watch"]
# RBAC resources (read-only, for inspection purposes)
- apiGroups: ["rbac.authorization.k8s.io"]
resources:
- roles
- rolebindings
- clusterroles
- clusterrolebindings
verbs: ["get", "list", "watch"]
# Storage classes (read-only)
- apiGroups: ["storage.k8s.io"]
resources:
- storageclasses
verbs: ["get", "list", "watch"]
# Metrics (optional, useful for `kubectl top`)
- apiGroups: ["metrics.k8s.io"]
resources:
- pods
- nodes
verbs: ["get", "list", "watch"]
{{- end }}

View File

@ -0,0 +1,18 @@
{{- if .Values.serviceAccount.create -}}
# Binds the ttyd ClusterRole to its dedicated ServiceAccount.
# WARNING: This grants cluster-wide permissions. See clusterrole.yaml for details.
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: {{ include "ttyd.fullname" . }}
labels:
{{- include "ttyd.labels" . | nindent 4 }}
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: {{ include "ttyd.fullname" . }}
subjects:
- kind: ServiceAccount
name: {{ include "ttyd.serviceAccountName" . }}
namespace: {{ .Release.Namespace }}
{{- end }}

View File

@ -0,0 +1,72 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: {{ include "ttyd.fullname" . }}
labels:
{{- include "ttyd.labels" . | nindent 4 }}
spec:
replicas: {{ .Values.replicaCount }}
selector:
matchLabels:
{{- include "ttyd.selectorLabels" . | nindent 6 }}
template:
metadata:
{{- with .Values.podAnnotations }}
annotations:
{{- toYaml . | nindent 8 }}
{{- end }}
labels:
{{- include "ttyd.selectorLabels" . | nindent 8 }}
spec:
{{- with .Values.imagePullSecrets }}
imagePullSecrets:
{{- toYaml . | nindent 8 }}
{{- end }}
serviceAccountName: {{ include "ttyd.serviceAccountName" . }}
securityContext:
{{- toYaml .Values.podSecurityContext | nindent 8 }}
containers:
- name: {{ .Chart.Name }}
securityContext:
{{- toYaml .Values.securityContext | nindent 12 }}
image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}"
imagePullPolicy: {{ .Values.image.pullPolicy }}
# ttyd args: bind port, then pass the shell command to execute in the browser terminal.
# To use kubectl, switch image.repository to a custom image that bundles ttyd + kubectl.
args:
- "--port={{ .Values.ttyd.port }}"
- {{ .Values.ttyd.command | quote }}
ports:
- name: http
containerPort: {{ .Values.ttyd.port }}
protocol: TCP
livenessProbe:
httpGet:
path: /
port: http
initialDelaySeconds: 10
periodSeconds: 20
timeoutSeconds: 5
failureThreshold: 3
readinessProbe:
httpGet:
path: /
port: http
initialDelaySeconds: 5
periodSeconds: 10
timeoutSeconds: 3
failureThreshold: 3
resources:
{{- toYaml .Values.resources | nindent 12 }}
{{- with .Values.nodeSelector }}
nodeSelector:
{{- toYaml . | nindent 8 }}
{{- end }}
{{- with .Values.affinity }}
affinity:
{{- toYaml . | nindent 8 }}
{{- end }}
{{- with .Values.tolerations }}
tolerations:
{{- toYaml . | nindent 8 }}
{{- end }}

View File

@ -0,0 +1,35 @@
{{- if .Values.ingress.enabled -}}
{{- $fullName := include "ttyd.fullname" . -}}
{{- $svcPort := .Values.service.port -}}
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: {{ $fullName }}
labels:
{{- include "ttyd.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.enabled }}
tls:
- hosts:
- {{ .Values.ingress.host | quote }}
secretName: {{ .Values.ingress.tls.secretName }}
{{- end }}
rules:
- host: {{ .Values.ingress.host | quote }}
http:
paths:
- path: {{ .Values.ingress.path }}
pathType: {{ .Values.ingress.pathType }}
backend:
service:
name: {{ $fullName }}
port:
number: {{ $svcPort }}
{{- end }}

View File

@ -0,0 +1,15 @@
apiVersion: v1
kind: Service
metadata:
name: {{ include "ttyd.fullname" . }}
labels:
{{- include "ttyd.labels" . | nindent 4 }}
spec:
type: {{ .Values.service.type }}
ports:
- port: {{ .Values.service.port }}
targetPort: http
protocol: TCP
name: http
selector:
{{- include "ttyd.selectorLabels" . | nindent 4 }}

View File

@ -0,0 +1,8 @@
{{- if .Values.serviceAccount.create -}}
apiVersion: v1
kind: ServiceAccount
metadata:
name: {{ include "ttyd.serviceAccountName" . }}
labels:
{{- include "ttyd.labels" . | nindent 4 }}
{{- end }}

54
charts/ttyd/values.yaml Normal file
View File

@ -0,0 +1,54 @@
replicaCount: 1
image:
repository: tsl0922/ttyd
tag: latest
pullPolicy: IfNotPresent
nameOverride: ""
fullnameOverride: ""
serviceAccount:
create: true
name: ""
service:
type: ClusterIP
port: 7681
ttyd:
port: 7681
# Shell command passed to ttyd. Switch to a custom image with kubectl for full functionality.
command: "/bin/sh"
ingress:
enabled: true
className: traefik
annotations:
traefik.ingress.kubernetes.io/router.entrypoints: websecure
host: kctl.dvirlabs.com
path: /
pathType: Prefix
tls:
enabled: true
secretName: tls-ttyd-ingress
resources:
requests:
cpu: 50m
memory: 64Mi
limits:
cpu: 200m
memory: 128Mi
podAnnotations: {}
podSecurityContext: {}
securityContext: {}
nodeSelector: {}
tolerations: []
affinity: {}

View File

@ -0,0 +1,48 @@
# ttyd environment-specific values
# Overrides charts/ttyd/values.yaml defaults
replicaCount: 1
image:
# Switch to a custom image that bundles ttyd + kubectl for full kubectl support.
# Example: repository: registry.dvirlabs.com/ttyd-kubectl
repository: tsl0922/ttyd
tag: latest
pullPolicy: IfNotPresent
serviceAccount:
create: true
name: ""
service:
port: 7681
ttyd:
port: 7681
# Shell to launch in the browser terminal.
# Change to /bin/bash if using a custom image that includes bash + kubectl.
command: "/bin/sh"
ingress:
enabled: true
className: traefik
annotations:
traefik.ingress.kubernetes.io/router.entrypoints: websecure
host: kctl.dvirlabs.com
path: /
pathType: Prefix
tls:
enabled: true
secretName: tls-ttyd-ingress
resources:
requests:
cpu: 50m
memory: 64Mi
limits:
cpu: 200m
memory: 128Mi
nodeSelector: {}
tolerations: []
affinity: {}