From 298a1ce6eca0029976435b332de8aded1f45de68 Mon Sep 17 00:00:00 2001 From: Chris Farhood Date: Fri, 20 Feb 2026 13:49:31 -0500 Subject: [PATCH] =?UTF-8?q?feat:=20add=20IDE=20choice=20=E2=80=94=20VSCode?= =?UTF-8?q?=20(default),=20Google=20Antigravity,=20SSH?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add `ide` Helm value with options: vscode, antigravity, ssh - Dockerfile: install Google Antigravity via apt and openssh-server - scripts/startapp.sh: branch on IDE env var to launch the right app - scripts/cont-init-sshd.sh: start sshd as root in SSH mode, set up authorized_keys from SSH_AUTHORIZED_KEYS env var - chart/templates/deployment.yaml: pass IDE env var, conditional ports and probes (HTTP for VNC modes, TCP socket for SSH mode) - chart/templates/service.yaml: expose port 5800 (VNC) or 22 (SSH) - chart/values.yaml: add ide field with documentation - README.md: document IDE choice, fix stale happyHomeDir references - chart/Chart.yaml: bump to 0.1.5 Closes #10 Co-Authored-By: Claude Sonnet 4.6 --- Dockerfile | 25 +++++++++++++++++--- README.md | 42 +++++++++++++++++++++++++++------ chart/Chart.yaml | 2 +- chart/templates/deployment.yaml | 21 +++++++++++++++++ chart/templates/service.yaml | 7 ++++++ chart/values.yaml | 8 +++++++ scripts/cont-init-sshd.sh | 27 +++++++++++++++++++++ scripts/startapp.sh | 21 +++++++++++++---- 8 files changed, 138 insertions(+), 15 deletions(-) create mode 100644 scripts/cont-init-sshd.sh diff --git a/Dockerfile b/Dockerfile index 8439d92..337f828 100644 --- a/Dockerfile +++ b/Dockerfile @@ -46,15 +46,33 @@ RUN curl -fsSL https://deb.nodesource.com/setup_lts.x | bash - && \ # Install Happy Coder and Claude Code globally RUN npm install -g happy-coder @anthropic-ai/claude-code -# Install Antigravity (Google's Project IDX / Cloud Code alternative) -# Note: Antigravity might be packaged differently - adjust as needed -# For now, we'll use VSCode with Project IDX extensions as a placeholder +# Install VSCode RUN wget -qO- https://packages.microsoft.com/keys/microsoft.asc | gpg --dearmor -o /usr/share/keyrings/packages.microsoft.gpg && \ echo "deb [arch=amd64 signed-by=/usr/share/keyrings/packages.microsoft.gpg] https://packages.microsoft.com/repos/code stable main" > /etc/apt/sources.list.d/vscode.list && \ apt-get update && \ apt-get install -y code && \ rm -rf /var/lib/apt/lists/* +# Install Google Antigravity IDE +RUN mkdir -p /etc/apt/keyrings && \ + curl -fsSL https://us-central1-apt.pkg.dev/doc/repo-signing-key.gpg | \ + gpg --dearmor --yes -o /etc/apt/keyrings/antigravity-repo-key.gpg && \ + echo "deb [signed-by=/etc/apt/keyrings/antigravity-repo-key.gpg] https://us-central1-apt.pkg.dev/projects/antigravity-auto-updater-dev/ antigravity-debian main" \ + > /etc/apt/sources.list.d/antigravity.list && \ + apt-get update && \ + apt-get install -y antigravity && \ + rm -rf /var/lib/apt/lists/* + +# Install OpenSSH server (for SSH IDE mode) +RUN apt-get update && \ + apt-get install -y openssh-server && \ + rm -rf /var/lib/apt/lists/* && \ + mkdir -p /var/run/sshd && \ + sed -i 's/#PubkeyAuthentication yes/PubkeyAuthentication yes/' /etc/ssh/sshd_config && \ + sed -i 's/#PasswordAuthentication yes/PasswordAuthentication no/' /etc/ssh/sshd_config && \ + sed -i 's/PasswordAuthentication yes/PasswordAuthentication no/' /etc/ssh/sshd_config && \ + echo "PermitRootLogin no" >> /etc/ssh/sshd_config + # Create user user with specific UID/GID RUN groupadd -g 1000 user && \ useradd -u 1000 -g 1000 -m -s /bin/bash user && \ @@ -69,6 +87,7 @@ COPY --chmod=755 scripts/startapp.sh /startapp.sh COPY --chmod=755 scripts/init-repo.sh /usr/local/bin/init-repo # Fix app user shell after baseimage-gui creates it at runtime COPY --chmod=755 scripts/cont-init-user.sh /etc/cont-init.d/20-fix-user-shell.sh +COPY --chmod=755 scripts/cont-init-sshd.sh /etc/cont-init.d/25-start-sshd.sh # Set working directory WORKDIR /workspace diff --git a/README.md b/README.md index ecf8f84..29a6a87 100644 --- a/README.md +++ b/README.md @@ -75,16 +75,40 @@ A Chrome browser window will open inside VNC for the Claude Max OAuth login. Cre |-------|---------|-------------| | `name` | `""` | Instance name — used in all resource names (`devcontainer-{name}`) | | `githubRepo` | `""` | Repository to clone into `/workspace` on startup | +| `ide` | `vscode` | IDE to launch — `vscode`, `antigravity`, or `ssh` (see below) | | `image.repository` | `ghcr.io/cpfarhood/devcontainer` | Container image | | `image.tag` | `latest` | Image tag | +### IDE choice + +| Value | Port | Description | +|-------|------|-------------| +| `vscode` (default) | 5800 (VNC) | VSCode desktop via browser-based VNC | +| `antigravity` | 5800 (VNC) | Google Antigravity (VSCode fork with AI) via VNC | +| `ssh` | 22 (SSH) | OpenSSH server — no VNC GUI, connect via SSH | + +For `ssh` mode, add your public key to the env secret: + +```bash +kubectl create secret generic devcontainer-mydev-secrets-env \ + --from-literal=GITHUB_TOKEN='ghp_...' \ + --from-literal=SSH_AUTHORIZED_KEYS='ssh-ed25519 AAAA...' +``` + +Then connect with: + +```bash +kubectl port-forward deployment/devcontainer-mydev 2222:22 +ssh -p 2222 user@localhost +``` + ### Happy Coder | Value | Default | Description | |-------|---------|-------------| | `happyServerUrl` | `https://happy.farh.net` | Happy Coder server endpoint | | `happyWebappUrl` | `https://happy-coder.farh.net` | Happy Coder webapp URL | -| `happyHomeDir` | `/workspace/.happy` | Happy runtime state directory (ephemeral — lives in emptyDir) | +| `happyHomeDir` | `/home/user/.happy` | Happy runtime state directory (persists on the home PVC) | | `happyExperimental` | `true` | Enable experimental Happy features | ### Kubernetes cluster access @@ -133,13 +157,17 @@ With any non-`none` value, a `ServiceAccount` named `devcontainer-{name}` is cre ### Startup flow ``` -Container start (as app user, UID 1000) +Container start → cont-init.d/20-fix-user-shell.sh — fix shell/home on baseimage-gui app user - → /startapp.sh + → cont-init.d/25-start-sshd.sh — start sshd if IDE=ssh + → /startapp.sh (runs as app user, UID 1000) → init-repo.sh → clone / pull GITHUB_REPO into /workspace/{repo} - → happy daemon start — starts Happy Coder background daemon - → code --new-window /workspace/{repo} — opens VSCode in VNC + → rm daemon.state.json.lock — clear stale Happy lock + → happy daemon start — starts Happy Coder background daemon + → IDE=vscode: code --new-window --wait /workspace/{repo} + IDE=antigravity: antigravity --new-window --wait /workspace/{repo} + IDE=ssh: sleep infinity (sshd already running as root) ``` ### Storage @@ -149,7 +177,7 @@ Container start (as app user, UID 1000) | `/home` | ReadWriteMany PVC (`userhome-{name}`) | Survives pod restarts — stores Claude credentials, dotfiles, git config | | `/workspace` | `emptyDir` | Ephemeral — repo is re-cloned on each pod start | -Happy Coder's runtime state (`HAPPY_HOME_DIR`) is kept in `/workspace/.happy` so stale lock files never survive a pod restart. +Happy Coder's runtime state (`HAPPY_HOME_DIR`) is kept in `/home/user/.happy` on the persistent home PVC, so auth credentials and settings survive pod restarts. A stale lock file (`daemon.state.json.lock`) is removed automatically on each startup. --- @@ -165,7 +193,7 @@ happy daemon status happy daemon start # View daemon logs -ls ~/.happy/logs/ || ls /workspace/.happy/logs/ +ls ~/.happy/logs/ ``` ### Claude not authenticated diff --git a/chart/Chart.yaml b/chart/Chart.yaml index 9c8b35d..a7ab285 100644 --- a/chart/Chart.yaml +++ b/chart/Chart.yaml @@ -2,5 +2,5 @@ apiVersion: v2 name: devcontainer description: Antigravity Dev Container with Happy Coder AI assistant type: application -version: 0.1.4 +version: 0.1.5 appVersion: "latest" diff --git a/chart/templates/deployment.yaml b/chart/templates/deployment.yaml index c49f7bc..77bd7ef 100644 --- a/chart/templates/deployment.yaml +++ b/chart/templates/deployment.yaml @@ -25,10 +25,18 @@ spec: image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}" imagePullPolicy: {{ .Values.image.pullPolicy }} ports: + {{- if eq (.Values.ide | default "vscode") "ssh" }} + - containerPort: 22 + name: ssh + protocol: TCP + {{- else }} - containerPort: 5800 name: vnc-web protocol: TCP + {{- end }} env: + - name: IDE + value: {{ .Values.ide | default "vscode" | quote }} - name: USER_ID value: {{ .Values.userId | quote }} - name: GROUP_ID @@ -60,6 +68,7 @@ spec: mountPath: /home - name: workspace mountPath: /workspace + {{- if ne (.Values.ide | default "vscode") "ssh" }} livenessProbe: httpGet: path: / @@ -72,6 +81,18 @@ spec: port: 5800 initialDelaySeconds: 10 periodSeconds: 5 + {{- else }} + livenessProbe: + tcpSocket: + port: 22 + initialDelaySeconds: 15 + periodSeconds: 10 + readinessProbe: + tcpSocket: + port: 22 + initialDelaySeconds: 5 + periodSeconds: 5 + {{- end }} volumes: - name: workspace emptyDir: {} diff --git a/chart/templates/service.yaml b/chart/templates/service.yaml index 5fe275b..3620c47 100644 --- a/chart/templates/service.yaml +++ b/chart/templates/service.yaml @@ -6,9 +6,16 @@ metadata: {{- include "antigravity.labels" . | nindent 4 }} spec: ports: + {{- if eq (.Values.ide | default "vscode") "ssh" }} + - port: 22 + name: ssh + protocol: TCP + targetPort: ssh + {{- else }} - port: 5800 name: vnc-web protocol: TCP targetPort: vnc-web + {{- end }} selector: {{- include "antigravity.labels" . | nindent 4 }} diff --git a/chart/values.yaml b/chart/values.yaml index 34a7d38..6092977 100644 --- a/chart/values.yaml +++ b/chart/values.yaml @@ -9,6 +9,14 @@ image: # GitHub repository to clone into /workspace githubRepo: "" +# IDE to launch inside the container. +# Options: +# vscode — VSCode via VNC browser UI on port 5800 (default) +# antigravity — Google Antigravity (VSCode fork) via VNC on port 5800 +# ssh — OpenSSH server on port 22 (no VNC GUI) +# Requires SSH_AUTHORIZED_KEYS in the env secret. +ide: vscode + # Happy Coder endpoints happyServerUrl: "https://happy.farh.net" happyWebappUrl: "https://happy-coder.farh.net" diff --git a/scripts/cont-init-sshd.sh b/scripts/cont-init-sshd.sh new file mode 100644 index 0000000..ec0d7af --- /dev/null +++ b/scripts/cont-init-sshd.sh @@ -0,0 +1,27 @@ +#!/bin/sh +# Start OpenSSH server for SSH IDE mode. +# Runs as root during container initialisation (cont-init.d). +[ "${IDE:-vscode}" = "ssh" ] || exit 0 + +echo "=== SSH IDE mode: starting sshd ===" + +# Generate host keys if missing (first boot or ephemeral /etc/ssh) +ssh-keygen -A 2>/dev/null || true + +# Populate authorized_keys from env var (injected via Kubernetes secret) +if [ -n "$SSH_AUTHORIZED_KEYS" ]; then + HOME_DIR="/home/user" + mkdir -p "$HOME_DIR/.ssh" + chmod 700 "$HOME_DIR/.ssh" + printf '%s\n' "$SSH_AUTHORIZED_KEYS" > "$HOME_DIR/.ssh/authorized_keys" + chmod 600 "$HOME_DIR/.ssh/authorized_keys" + chown -R 1000:1000 "$HOME_DIR/.ssh" + echo "SSH authorized keys configured." +else + echo "WARNING: SSH_AUTHORIZED_KEYS not set — you will not be able to log in." +fi + +# Start sshd in background (root required to bind :22 and fork sessions) +/usr/sbin/sshd -D & + +echo "sshd started (PID $!)" diff --git a/scripts/startapp.sh b/scripts/startapp.sh index 3002e63..07afe7a 100644 --- a/scripts/startapp.sh +++ b/scripts/startapp.sh @@ -14,8 +14,21 @@ else WORKSPACE_DIR="/workspace/default" fi -echo "Opening Antigravity in: $WORKSPACE_DIR" +IDE="${IDE:-vscode}" +echo "IDE mode: $IDE" +echo "Workspace: $WORKSPACE_DIR" -# Start Antigravity (VSCode) in the workspace directory as claude user -# The baseimage-gui will handle the GUI display -exec code --new-window --wait "$WORKSPACE_DIR" +case "$IDE" in + antigravity) + echo "Opening Google Antigravity in: $WORKSPACE_DIR" + exec antigravity --new-window --wait "$WORKSPACE_DIR" + ;; + ssh) + echo "SSH mode: sshd started by cont-init. Keeping container alive." + exec sleep infinity + ;; + *) + echo "Opening VSCode in: $WORKSPACE_DIR" + exec code --new-window --wait "$WORKSPACE_DIR" + ;; +esac