feat: add IDE choice (VSCode, Google Antigravity, SSH) #20

Merged
cpfarhood merged 3 commits from feat/ide-choice into main 2026-02-20 19:21:12 +00:00
8 changed files with 162 additions and 15 deletions
+22 -3
View File
@@ -46,15 +46,33 @@ RUN curl -fsSL https://deb.nodesource.com/setup_lts.x | bash - && \
# Install Happy Coder and Claude Code globally # Install Happy Coder and Claude Code globally
RUN npm install -g happy-coder @anthropic-ai/claude-code RUN npm install -g happy-coder @anthropic-ai/claude-code
# Install Antigravity (Google's Project IDX / Cloud Code alternative) # Install VSCode
# Note: Antigravity might be packaged differently - adjust as needed
# For now, we'll use VSCode with Project IDX extensions as a placeholder
RUN wget -qO- https://packages.microsoft.com/keys/microsoft.asc | gpg --dearmor -o /usr/share/keyrings/packages.microsoft.gpg && \ 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 && \ 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 update && \
apt-get install -y code && \ apt-get install -y code && \
rm -rf /var/lib/apt/lists/* 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 # Create user user with specific UID/GID
RUN groupadd -g 1000 user && \ RUN groupadd -g 1000 user && \
useradd -u 1000 -g 1000 -m -s /bin/bash 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 COPY --chmod=755 scripts/init-repo.sh /usr/local/bin/init-repo
# Fix app user shell after baseimage-gui creates it at runtime # 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-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 # Set working directory
WORKDIR /workspace WORKDIR /workspace
+52 -7
View File
@@ -20,6 +20,7 @@ The secret is picked up automatically via `envFrom`. Keys recognised:
| `GITHUB_TOKEN` | PAT for private repo access (`repo` scope) | | `GITHUB_TOKEN` | PAT for private repo access (`repo` scope) |
| `VNC_PASSWORD` | Password for the VNC web UI | | `VNC_PASSWORD` | Password for the VNC web UI |
| `ANTHROPIC_API_KEY` | API key — alternative to browser-based Claude login | | `ANTHROPIC_API_KEY` | API key — alternative to browser-based Claude login |
| `SSH_AUTHORIZED_KEYS` | Public key(s) for SSH access (required when `ssh: true`) |
```bash ```bash
kubectl create secret generic devcontainer-mydev-secrets-env \ kubectl create secret generic devcontainer-mydev-secrets-env \
@@ -75,16 +76,55 @@ 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}`) | | `name` | `""` | Instance name — used in all resource names (`devcontainer-{name}`) |
| `githubRepo` | `""` | Repository to clone into `/workspace` on startup | | `githubRepo` | `""` | Repository to clone into `/workspace` on startup |
| `ide` | `vscode` | IDE to launch — `vscode`, `antigravity`, or `none` (see below) |
| `ssh` | `false` | Also start an OpenSSH server on port 22 (additive, any `ide`) |
| `image.repository` | `ghcr.io/cpfarhood/devcontainer` | Container image | | `image.repository` | `ghcr.io/cpfarhood/devcontainer` | Container image |
| `image.tag` | `latest` | Image tag | | `image.tag` | `latest` | Image tag |
### IDE choice
`ide` controls what GUI is launched in the VNC session:
| Value | Port | Description |
|-------|------|-------------|
| `vscode` (default) | 5800 (VNC) | VSCode desktop via browser-based VNC |
| `antigravity` | 5800 (VNC) | Google Antigravity (VSCode fork with AI) via VNC |
| `none` | — | No IDE; container stays alive (useful when `ssh: true`) |
### SSH access
`ssh: true` starts OpenSSH on port 22 **in addition to** the IDE. It works with any `ide` value:
```bash
# SSH-only (no VNC)
helm install mydev ./chart --set name=mydev --set ide=none --set ssh=true
# VSCode in VNC + SSH access at the same time
helm install mydev ./chart --set name=mydev --set ssh=true
```
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:
```bash
kubectl port-forward deployment/devcontainer-mydev 2222:22
ssh -p 2222 user@localhost
```
### Happy Coder ### Happy Coder
| Value | Default | Description | | Value | Default | Description |
|-------|---------|-------------| |-------|---------|-------------|
| `happyServerUrl` | `https://happy.farh.net` | Happy Coder server endpoint | | `happyServerUrl` | `https://happy.farh.net` | Happy Coder server endpoint |
| `happyWebappUrl` | `https://happy-coder.farh.net` | Happy Coder webapp URL | | `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 | | `happyExperimental` | `true` | Enable experimental Happy features |
### Kubernetes cluster access ### Kubernetes cluster access
@@ -133,13 +173,18 @@ With any non-`none` value, a `ServiceAccount` named `devcontainer-{name}` is cre
### Startup flow ### 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 → 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 SSH=true
→ /startapp.sh (runs as app user, UID 1000)
→ init-repo.sh → init-repo.sh
→ clone / pull GITHUB_REPO into /workspace/{repo} → clone / pull GITHUB_REPO into /workspace/{repo}
happy daemon start — starts Happy Coder background daemon rm daemon.state.json.lock — clear stale Happy lock
→ code --new-window /workspace/{repo} — opens VSCode in VNC → 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=none: sleep infinity
(SSH=true: sshd also running as root on port 22)
``` ```
### Storage ### Storage
@@ -149,7 +194,7 @@ Container start (as app user, UID 1000)
| `/home` | ReadWriteMany PVC (`userhome-{name}`) | Survives pod restarts — stores Claude credentials, dotfiles, git config | | `/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 | | `/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 +210,7 @@ happy daemon status
happy daemon start happy daemon start
# View daemon logs # View daemon logs
ls ~/.happy/logs/ || ls /workspace/.happy/logs/ ls ~/.happy/logs/
``` ```
### Claude not authenticated ### Claude not authenticated
+1 -1
View File
@@ -2,5 +2,5 @@ apiVersion: v2
name: devcontainer name: devcontainer
description: Antigravity Dev Container with Happy Coder AI assistant description: Antigravity Dev Container with Happy Coder AI assistant
type: application type: application
version: 0.1.4 version: 0.1.6
appVersion: "latest" appVersion: "latest"
+24
View File
@@ -25,10 +25,21 @@ spec:
image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}" image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}"
imagePullPolicy: {{ .Values.image.pullPolicy }} imagePullPolicy: {{ .Values.image.pullPolicy }}
ports: ports:
{{- if ne (.Values.ide | default "vscode") "none" }}
- containerPort: 5800 - containerPort: 5800
name: vnc-web name: vnc-web
protocol: TCP protocol: TCP
{{- end }}
{{- if .Values.ssh }}
- containerPort: 22
name: ssh
protocol: TCP
{{- end }}
env: env:
- name: IDE
value: {{ .Values.ide | default "vscode" | quote }}
- name: SSH
value: {{ .Values.ssh | toString | quote }}
- name: USER_ID - name: USER_ID
value: {{ .Values.userId | quote }} value: {{ .Values.userId | quote }}
- name: GROUP_ID - name: GROUP_ID
@@ -60,6 +71,7 @@ spec:
mountPath: /home mountPath: /home
- name: workspace - name: workspace
mountPath: /workspace mountPath: /workspace
{{- if ne (.Values.ide | default "vscode") "none" }}
livenessProbe: livenessProbe:
httpGet: httpGet:
path: / path: /
@@ -72,6 +84,18 @@ spec:
port: 5800 port: 5800
initialDelaySeconds: 10 initialDelaySeconds: 10
periodSeconds: 5 periodSeconds: 5
{{- else if .Values.ssh }}
livenessProbe:
tcpSocket:
port: 22
initialDelaySeconds: 15
periodSeconds: 10
readinessProbe:
tcpSocket:
port: 22
initialDelaySeconds: 5
periodSeconds: 5
{{- end }}
volumes: volumes:
- name: workspace - name: workspace
emptyDir: {} emptyDir: {}
+8
View File
@@ -6,9 +6,17 @@ metadata:
{{- include "antigravity.labels" . | nindent 4 }} {{- include "antigravity.labels" . | nindent 4 }}
spec: spec:
ports: ports:
{{- if ne (.Values.ide | default "vscode") "none" }}
- port: 5800 - port: 5800
name: vnc-web name: vnc-web
protocol: TCP protocol: TCP
targetPort: vnc-web targetPort: vnc-web
{{- end }}
{{- if .Values.ssh }}
- port: 22
name: ssh
protocol: TCP
targetPort: ssh
{{- end }}
selector: selector:
{{- include "antigravity.labels" . | nindent 4 }} {{- include "antigravity.labels" . | nindent 4 }}
+11
View File
@@ -9,6 +9,17 @@ image:
# GitHub repository to clone into /workspace # GitHub repository to clone into /workspace
githubRepo: "" 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
# none — no IDE; useful when ssh: true is the sole access method
ide: vscode
# Start an OpenSSH server on port 22 in addition to the IDE.
# Set SSH_AUTHORIZED_KEYS in the env secret to allow key-based login.
ssh: false
# Happy Coder endpoints # Happy Coder endpoints
happyServerUrl: "https://happy.farh.net" happyServerUrl: "https://happy.farh.net"
happyWebappUrl: "https://happy-coder.farh.net" happyWebappUrl: "https://happy-coder.farh.net"
+27
View File
@@ -0,0 +1,27 @@
#!/bin/sh
# Start OpenSSH server when SSH=true.
# Runs as root during container initialisation (cont-init.d).
[ "${SSH:-false}" = "true" ] || exit 0
echo "=== SSH enabled: 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 $!)"
+17 -4
View File
@@ -14,8 +14,21 @@ else
WORKSPACE_DIR="/workspace/default" WORKSPACE_DIR="/workspace/default"
fi 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 case "$IDE" in
# The baseimage-gui will handle the GUI display antigravity)
exec code --new-window --wait "$WORKSPACE_DIR" echo "Opening Google Antigravity in: $WORKSPACE_DIR"
exec antigravity --new-window --wait "$WORKSPACE_DIR"
;;
none)
echo "IDE=none: no IDE launched, keeping container alive."
exec sleep infinity
;;
*)
echo "Opening VSCode in: $WORKSPACE_DIR"
exec code --new-window --wait "$WORKSPACE_DIR"
;;
esac