Files
socktop-webterm/kubernetes/03-deployment.yaml
T
jasonwitty 1114482046
Build and Deploy to K3s / test (push) Successful in 2m6s
Build and Deploy to K3s / lint (push) Successful in 1m2s
Build and Deploy to K3s / build-and-push (push) Successful in 9m23s
Build and Deploy to K3s / deploy (push) Successful in 2m13s
Fix actix worker wedge on session teardown; grant CAP_KILL; bump to 0.3.12
Terminal::stopping did child.kill() (error ignored) and then a blocking
child.wait() on the actix worker thread. Since the 0.3.11 privilege drop,
sessions run as `demo` and the server had no CAP_KILL, so kill() failed
with EPERM and wait() blocked forever. With two workers, exactly every
other request to :8082 then timed out — 5 days of readiness/liveness
flapping on socktop.io and orphaned restricted-shell/socktop processes
piling up in the pods.

- Signal the session's whole process group (portable-pty setsid()s the
  child), not just the shell: a non-interactive bash defers signals while
  a foreground command runs, so the old SIGHUP/SIGKILL to the shell alone
  orphaned socktop anyway.
- Reap on a dedicated thread: SIGHUP, 2 s grace, SIGKILL, 10 s deadline,
  then a blocking wait on that thread only. Signal failures are logged.
- Drop the pty handles before handing off the child.
- kubernetes/03-deployment.yaml: add CAP_KILL (sessions still lose every
  cap via setpriv). CI never applies the manifest — it was patched live.
- tests/reaper_tests.rs pins the non-blocking return, process-group kill,
  and SIGHUP→SIGKILL escalation.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-29 17:53:53 -07:00

216 lines
7.6 KiB
YAML

apiVersion: apps/v1
kind: Deployment
metadata:
name: socktop-webterm
labels:
app: socktop-webterm
spec:
replicas: 3
selector:
matchLabels:
app: socktop-webterm
template:
metadata:
labels:
app: socktop-webterm
spec:
# Use standard pod networking
hostNetwork: false
dnsPolicy: ClusterFirst
# Security context for the pod
securityContext:
runAsUser: 100
runAsGroup: 101
fsGroup: 101
# Init container to set up configuration
initContainers:
- name: init-config
image: gt.wittyoneoff.com/jason/socktop-webterm:0.2.2
imagePullPolicy: Always
command: ["/bin/bash", "-c"]
args:
- |
set -e
echo "Setting up configuration directories..."
mkdir -p /var/lib/socktop/.config/socktop/certs
mkdir -p /var/lib/socktop/.config/alacritty
if [ -f "/home/socktop/.config/socktop/profiles.json" ]; then
cp /home/socktop/.config/socktop/profiles.json /var/lib/socktop/.config/socktop/profiles.json
echo "Copied profiles.json"
fi
if [ -f "/home/socktop/.config/alacritty/alacritty.toml" ]; then
cp /home/socktop/.config/alacritty/alacritty.toml /var/lib/socktop/.config/alacritty/alacritty.toml
echo "Copied alacritty.toml"
fi
if [ -f "/home/socktop/.config/alacritty/catppuccin-frappe.toml" ]; then
cp /home/socktop/.config/alacritty/catppuccin-frappe.toml /var/lib/socktop/.config/alacritty/catppuccin-frappe.toml
echo "Copied catppuccin-frappe.toml"
fi
if [ -d "/home/socktop/.config/socktop/certs" ]; then
cp /home/socktop/.config/socktop/certs/*.pem /var/lib/socktop/.config/socktop/certs/ 2>/dev/null || true
echo "Copied certificates"
fi
# Fix paths in profiles.json
if [ -f "/var/lib/socktop/.config/socktop/profiles.json" ]; then
sed -i 's|/home/socktop/.config/socktop/rpi-|/var/lib/socktop/.config/socktop/certs/rpi-|g' /var/lib/socktop/.config/socktop/profiles.json
echo "Updated certificate paths"
fi
echo "Configuration setup complete"
volumeMounts:
- name: config
mountPath: /home/socktop/.config/socktop/profiles.json
subPath: profiles.json
- name: config
mountPath: /home/socktop/.config/alacritty/alacritty.toml
subPath: alacritty.toml
- name: config
mountPath: /home/socktop/.config/alacritty/catppuccin-frappe.toml
subPath: catppuccin-frappe.toml
- name: certs
mountPath: /home/socktop/.config/socktop/certs
readOnly: true
- name: socktop-home
mountPath: /var/lib/socktop
securityContext:
runAsUser: 100
runAsGroup: 101
containers:
- name: webterm
image: gt.wittyoneoff.com/jason/socktop-webterm:0.3.12
imagePullPolicy: Always
command: ["/docker-entrypoint.sh"]
args:
[
"webterm-server",
"--host",
"0.0.0.0",
"--port",
"8082",
"--command",
"/usr/local/bin/session-shell.sh",
]
ports:
- name: http
containerPort: 8082
protocol: TCP
- name: agent
containerPort: 3001
protocol: TCP
env:
- name: TERM
value: "xterm-256color"
- name: TZ
value: "America/New_York"
- name: RUST_LOG
value: "info"
# Disable socktop's local process-kill feature for every socktop
# launched anywhere under this pod, regardless of command line.
# The restricted shell also passes --no-kill, but this is the layer
# a visitor cannot route around (no way to unset env from the
# restricted shell).
- name: SOCKTOP_NO_KILL
value: "1"
resources:
limits:
cpu: "2000m"
memory: "1Gi"
requests:
cpu: "500m"
memory: "256Mi"
livenessProbe:
httpGet:
path: /
port: 8082
initialDelaySeconds: 10
periodSeconds: 30
timeoutSeconds: 5
failureThreshold: 3
readinessProbe:
httpGet:
path: /
port: 8082
initialDelaySeconds: 5
periodSeconds: 10
timeoutSeconds: 3
failureThreshold: 3
volumeMounts:
- name: config
mountPath: /home/socktop/.config/socktop/profiles.json
subPath: profiles.json
- name: config
mountPath: /home/socktop/.config/alacritty/alacritty.toml
subPath: alacritty.toml
- name: config
mountPath: /home/socktop/.config/alacritty/catppuccin-frappe.toml
subPath: catppuccin-frappe.toml
- name: certs
mountPath: /home/socktop/.config/socktop/certs
readOnly: true
- name: socktop-home
mountPath: /var/lib/socktop
# webterm-server runs as in-container root holding only the caps
# listed below (everything else dropped, no privilege escalation),
# so it can drop each websocket session to the unprivileged `demo`
# user via session-shell.sh. The kernel then refuses any signal a
# session aims at the server, the agent (running as `socktop`), or
# another session's UID — the kill feature's UI gating stops being
# the only line of defense.
securityContext:
allowPrivilegeEscalation: false
capabilities:
drop:
- ALL
add:
- SETUID
- SETGID
# The server must be able to signal the sessions it spawned,
# which run as `demo` — a different uid — so root needs
# CAP_KILL for that. Without it every idle-timeout teardown
# got EPERM, the session lived on, and (0.3.11) the actix
# worker blocked in wait() on it: half of all requests hung.
# Sessions still cannot signal anything: setpriv drops every
# cap (including this one) before the restricted shell runs.
- KILL
# prepare_demo_home (entrypoint.sh) writes into and re-owns
# /home/demo, which the image ships as demo-owned 700. With
# ALL dropped, uid 0 has no implicit file privilege, so the
# three file caps must come back or the entrypoint crashes on
# mkdir/chown/chmod. Sessions still get zero caps — session-
# shell.sh drops them all via setpriv --inh-caps -all.
- CHOWN
- DAC_OVERRIDE
- FOWNER
readOnlyRootFilesystem: false
runAsUser: 0
runAsGroup: 0
volumes:
- name: config
configMap:
name: socktop-webterm-config
- name: certs
secret:
secretName: socktop-webterm-certs
optional: true
- name: socktop-home
emptyDir: {}
restartPolicy: Always