feat: add IDE choice (VSCode, Google Antigravity, SSH) #20
+22
-3
@@ -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
|
||||||
|
|||||||
@@ -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
@@ -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"
|
||||||
|
|||||||
@@ -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: {}
|
||||||
|
|||||||
@@ -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 }}
|
||||||
|
|||||||
@@ -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"
|
||||||
|
|||||||
@@ -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
@@ -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
|
||||||
|
|||||||
Reference in New Issue
Block a user