Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| f1c93b81d1 |
+6
-32
@@ -25,19 +25,13 @@ RUN apt-get update && apt-get install -y \
|
|||||||
sudo \
|
sudo \
|
||||||
&& rm -rf /var/lib/apt/lists/*
|
&& rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
# Install Chrome and xdg-utils (needed for xdg-open to work in VNC)
|
# Install Chrome
|
||||||
RUN wget -q -O - https://dl.google.com/linux/linux_signing_key.pub | gpg --dearmor -o /usr/share/keyrings/google-chrome-keyring.gpg && \
|
RUN wget -q -O - https://dl.google.com/linux/linux_signing_key.pub | gpg --dearmor -o /usr/share/keyrings/google-chrome-keyring.gpg && \
|
||||||
echo "deb [arch=amd64 signed-by=/usr/share/keyrings/google-chrome-keyring.gpg] http://dl.google.com/linux/chrome/deb/ stable main" > /etc/apt/sources.list.d/google-chrome.list && \
|
echo "deb [arch=amd64 signed-by=/usr/share/keyrings/google-chrome-keyring.gpg] http://dl.google.com/linux/chrome/deb/ stable main" > /etc/apt/sources.list.d/google-chrome.list && \
|
||||||
apt-get update && \
|
apt-get update && \
|
||||||
apt-get install -y google-chrome-stable xdg-utils && \
|
apt-get install -y google-chrome-stable && \
|
||||||
rm -rf /var/lib/apt/lists/*
|
rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
# Chrome wrapper: adds flags required for running inside a Docker container.
|
|
||||||
# xdg-open (used by Claude Code on Linux) respects $BROWSER, so pointing it
|
|
||||||
# here ensures the OAuth popup works without manual --no-sandbox invocations.
|
|
||||||
RUN printf '#!/bin/bash\nexec /usr/bin/google-chrome-stable \\\n --no-sandbox \\\n --disable-dev-shm-usage \\\n --disable-gpu \\\n "$@"\n' > /usr/local/bin/google-chrome && \
|
|
||||||
chmod +x /usr/local/bin/google-chrome
|
|
||||||
|
|
||||||
# Install Node.js (LTS version for Happy Coder)
|
# Install Node.js (LTS version for Happy Coder)
|
||||||
RUN curl -fsSL https://deb.nodesource.com/setup_lts.x | bash - && \
|
RUN curl -fsSL https://deb.nodesource.com/setup_lts.x | bash - && \
|
||||||
apt-get install -y nodejs && \
|
apt-get install -y nodejs && \
|
||||||
@@ -46,33 +40,15 @@ 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 VSCode
|
# 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
|
||||||
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 && \
|
||||||
@@ -87,15 +63,13 @@ 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
|
||||||
|
|
||||||
# Configure container to run as user user
|
# Configure container to run as user user
|
||||||
ENV HOME=/home/user \
|
ENV HOME=/home/user \
|
||||||
USER=user \
|
USER=user
|
||||||
BROWSER=/usr/local/bin/google-chrome
|
|
||||||
|
|
||||||
# Expose VNC port (baseimage-gui default)
|
# Expose VNC port (baseimage-gui default)
|
||||||
EXPOSE 5800
|
EXPOSE 5800
|
||||||
|
|||||||
@@ -2,272 +2,366 @@
|
|||||||
|
|
||||||

|

|
||||||
|
|
||||||
A containerized cloud development environment with web-based GUI access, featuring:
|
A containerized development environment with GUI access, featuring:
|
||||||
- **VSCode** via browser-based VNC (port 5800)
|
- **Antigravity** (VSCode/Cloud IDE) via web browser
|
||||||
- **Happy Coder** AI assistant backed by Claude
|
- **Happy Coder** - AI-powered development assistant
|
||||||
- **Automatic GitHub repo cloning** on startup
|
- **Automatic GitHub repo cloning**
|
||||||
- **Persistent home directory** via ReadWriteMany PVC
|
- **Persistent user home directory**
|
||||||
- **Kubernetes-native** Helm chart deployment
|
- **Secure non-root execution**
|
||||||
|
|
||||||
|
## Features
|
||||||
|
|
||||||
|
### GUI Access
|
||||||
|
- Web-based VNC interface (port 5800)
|
||||||
|
- Full desktop environment in your browser
|
||||||
|
- Secure connections with optional password protection
|
||||||
|
|
||||||
|
### Development Tools
|
||||||
|
- Antigravity IDE (VSCode-based)
|
||||||
|
- Happy Coder AI assistant
|
||||||
|
- Git integration
|
||||||
|
- Node.js and npm
|
||||||
|
- Python 3
|
||||||
|
- Chrome browser
|
||||||
|
|
||||||
|
### Security
|
||||||
|
- Runs as non-root user `claude` (UID 1000, GID 1000)
|
||||||
|
- Secure VNC connections
|
||||||
|
- Token-based GitHub authentication
|
||||||
|
- Isolated workspace
|
||||||
|
|
||||||
|
### Persistence
|
||||||
|
- ReadWriteMany PVC for `/home` (user data persists)
|
||||||
|
- Workspace mounted at `/workspace`
|
||||||
|
- Repository cloned on first startup
|
||||||
|
|
||||||
|
## Documentation
|
||||||
|
|
||||||
|
- **[DEPLOYMENT.md](DEPLOYMENT.md)** - Complete deployment guide with step-by-step instructions
|
||||||
|
- **[VARIABLES.md](VARIABLES.md)** - Reference for all configuration variables
|
||||||
|
- **[README.md](README.md)** - This file (overview and quick start)
|
||||||
|
|
||||||
## Quick Start
|
## Quick Start
|
||||||
|
|
||||||
### 1. Create a secret
|
**👉 For detailed deployment instructions, see [DEPLOYMENT.md](DEPLOYMENT.md)**
|
||||||
|
|
||||||
The secret is picked up automatically via `envFrom`. Keys recognised:
|
### 1. Get the Image
|
||||||
|
|
||||||
| Key | Purpose |
|
The image is automatically built and published to GitHub Container Registry on every push to main.
|
||||||
|-----|---------|
|
|
||||||
| `GITHUB_TOKEN` | PAT for private repo access (`repo` scope) |
|
|
||||||
| `VNC_PASSWORD` | Password for the VNC web UI |
|
|
||||||
| `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 \
|
# Pull the latest image
|
||||||
--from-literal=GITHUB_TOKEN='ghp_...' \
|
docker pull ghcr.io/cpfarhood/devcontainer:latest
|
||||||
--from-literal=VNC_PASSWORD='changeme'
|
|
||||||
|
# Or pull a specific version
|
||||||
|
docker pull ghcr.io/cpfarhood/devcontainer:v1.0.0
|
||||||
```
|
```
|
||||||
|
|
||||||
Or use SealedSecrets:
|
**Building locally (optional):**
|
||||||
|
|
||||||
```bash
|
|
||||||
kubectl create secret generic devcontainer-mydev-secrets-env \
|
|
||||||
--from-literal=GITHUB_TOKEN='ghp_...' \
|
|
||||||
--from-literal=VNC_PASSWORD='changeme' \
|
|
||||||
--dry-run=client -o yaml | \
|
|
||||||
kubeseal --format=yaml | kubectl apply -f -
|
|
||||||
```
|
|
||||||
|
|
||||||
### 2. Deploy with Helm
|
|
||||||
|
|
||||||
```bash
|
|
||||||
helm install mydev ./chart \
|
|
||||||
--set name=mydev \
|
|
||||||
--set githubRepo=https://github.com/youruser/yourrepo
|
|
||||||
```
|
|
||||||
|
|
||||||
### 3. Access
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Local port-forward
|
|
||||||
kubectl port-forward deployment/devcontainer-mydev 5800:5800
|
|
||||||
open http://localhost:5800
|
|
||||||
```
|
|
||||||
|
|
||||||
Or configure an ingress / Gateway API HTTPRoute pointing at port 5800.
|
|
||||||
|
|
||||||
### 4. Authenticate Claude
|
|
||||||
|
|
||||||
On first launch, open a terminal in the VSCode GUI and run:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
claude
|
|
||||||
```
|
|
||||||
|
|
||||||
A Chrome browser window will open inside VNC for the Claude Max OAuth login. Credentials are stored on the home PVC and persist across pod restarts.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Helm Chart Reference
|
|
||||||
|
|
||||||
### Core values
|
|
||||||
|
|
||||||
| Value | Default | Description |
|
|
||||||
|-------|---------|-------------|
|
|
||||||
| `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 `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.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
|
|
||||||
|
|
||||||
| Value | Default | Description |
|
|
||||||
|-------|---------|-------------|
|
|
||||||
| `happyServerUrl` | `https://happy.farh.net` | Happy Coder server endpoint |
|
|
||||||
| `happyWebappUrl` | `https://happy-coder.farh.net` | Happy Coder webapp URL |
|
|
||||||
| `happyHomeDir` | `/home/user/.happy` | Happy runtime state directory (persists on the home PVC) |
|
|
||||||
| `happyExperimental` | `true` | Enable experimental Happy features |
|
|
||||||
|
|
||||||
### Kubernetes cluster access
|
|
||||||
|
|
||||||
The `clusterAccess` value provisions a ServiceAccount, Role/ClusterRole, and binding so the devcontainer pod can interact with the Kubernetes API. The default is `none` — no RBAC resources are created.
|
|
||||||
|
|
||||||
| Value | Scope | Verbs |
|
|
||||||
|-------|-------|-------|
|
|
||||||
| `none` (default) | — | no access |
|
|
||||||
| `readonlyns` | release namespace | `get`, `list`, `watch` |
|
|
||||||
| `readwritens` | release namespace | `*` |
|
|
||||||
| `readonly` | cluster-wide | `get`, `list`, `watch` |
|
|
||||||
| `readwrite` | cluster-wide | `*` |
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Give the pod read-only access to its own namespace
|
|
||||||
helm install mydev ./chart \
|
|
||||||
--set name=mydev \
|
|
||||||
--set githubRepo=https://github.com/youruser/yourrepo \
|
|
||||||
--set clusterAccess=readonlyns
|
|
||||||
```
|
|
||||||
|
|
||||||
With any non-`none` value, a `ServiceAccount` named `devcontainer-{name}` is created and set as the pod's `serviceAccountName`, so `kubectl` and any in-cluster API calls use it automatically.
|
|
||||||
|
|
||||||
### Display and resources
|
|
||||||
|
|
||||||
| Value | Default | Description |
|
|
||||||
|-------|---------|-------------|
|
|
||||||
| `display.width` | `1920` | VNC width (px) |
|
|
||||||
| `display.height` | `1080` | VNC height (px) |
|
|
||||||
| `secureConnection` | `0` | Set to `1` if TLS is not terminated upstream |
|
|
||||||
| `userId` | `1000` | UID for the app user |
|
|
||||||
| `groupId` | `1000` | GID for the app user |
|
|
||||||
| `storage.size` | `32Gi` | Home PVC size |
|
|
||||||
| `storage.className` | `ceph-filesystem` | StorageClass (must be ReadWriteMany) |
|
|
||||||
| `resources.requests.memory` | `2Gi` | |
|
|
||||||
| `resources.requests.cpu` | `1000m` | |
|
|
||||||
| `resources.limits.memory` | `8Gi` | |
|
|
||||||
| `resources.limits.cpu` | `4000m` | |
|
|
||||||
| `envSecretName` | `devcontainer-{name}-secrets-env` | Override the secret name |
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Architecture
|
|
||||||
|
|
||||||
### Startup flow
|
|
||||||
|
|
||||||
```
|
|
||||||
Container start
|
|
||||||
→ cont-init.d/20-fix-user-shell.sh — fix shell/home on baseimage-gui app user
|
|
||||||
→ cont-init.d/25-start-sshd.sh — start sshd if SSH=true
|
|
||||||
→ /startapp.sh (runs as app user, UID 1000)
|
|
||||||
→ init-repo.sh
|
|
||||||
→ clone / pull GITHUB_REPO into /workspace/{repo}
|
|
||||||
→ 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=none: sleep infinity
|
|
||||||
(SSH=true: sshd also running as root on port 22)
|
|
||||||
```
|
|
||||||
|
|
||||||
### Storage
|
|
||||||
|
|
||||||
| Mount | Source | Persistence |
|
|
||||||
|-------|--------|-------------|
|
|
||||||
| `/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 `/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.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Troubleshooting
|
|
||||||
|
|
||||||
### Happy Coder daemon not starting
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Check daemon status
|
|
||||||
happy daemon status
|
|
||||||
|
|
||||||
# Start manually (also clears any stale lock)
|
|
||||||
happy daemon start
|
|
||||||
|
|
||||||
# View daemon logs
|
|
||||||
ls ~/.happy/logs/
|
|
||||||
```
|
|
||||||
|
|
||||||
### Claude not authenticated
|
|
||||||
|
|
||||||
Browser-based OAuth login is the primary method (works inside VNC via the Chrome wrapper). If you prefer API key auth:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
kubectl patch secret devcontainer-mydev-secrets-env \
|
|
||||||
--type='json' \
|
|
||||||
-p='[{"op":"add","path":"/data/ANTHROPIC_API_KEY","value":"'$(echo -n "sk-ant-..." | base64)'"}]'
|
|
||||||
```
|
|
||||||
|
|
||||||
Then restart the pod to pick up the new env var.
|
|
||||||
|
|
||||||
### VNC not loading
|
|
||||||
|
|
||||||
```bash
|
|
||||||
kubectl port-forward deployment/devcontainer-mydev 5800:5800
|
|
||||||
kubectl logs deployment/devcontainer-mydev
|
|
||||||
kubectl describe pod -l instance=mydev
|
|
||||||
```
|
|
||||||
|
|
||||||
### Repository not cloning
|
|
||||||
|
|
||||||
```bash
|
|
||||||
kubectl logs deployment/devcontainer-mydev | grep "Repository Initialization"
|
|
||||||
kubectl exec deployment/devcontainer-mydev -- env | grep GITHUB
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Local Docker run
|
|
||||||
|
|
||||||
```bash
|
|
||||||
docker run -d \
|
|
||||||
-p 5800:5800 \
|
|
||||||
-e GITHUB_REPO="https://github.com/youruser/yourrepo" \
|
|
||||||
-e GITHUB_TOKEN="ghp_..." \
|
|
||||||
-e VNC_PASSWORD="changeme" \
|
|
||||||
-v $(pwd)/home:/home \
|
|
||||||
ghcr.io/cpfarhood/devcontainer:latest
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Building
|
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
docker build -t ghcr.io/cpfarhood/devcontainer:latest .
|
docker build -t ghcr.io/cpfarhood/devcontainer:latest .
|
||||||
docker push ghcr.io/cpfarhood/devcontainer:latest
|
docker push ghcr.io/cpfarhood/devcontainer:latest
|
||||||
```
|
```
|
||||||
|
|
||||||
The image is also built and pushed automatically by CI on every push to `main` and on version tags (`v*`).
|
### 2. Configure Secrets
|
||||||
|
|
||||||
---
|
Edit `k8s/secrets-example.yaml` and create a sealed secret:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
kubectl create secret generic antigravity-secrets \
|
||||||
|
--from-literal=github-token='ghp_your_token' \
|
||||||
|
--from-literal=vnc-password='your_password' \
|
||||||
|
--dry-run=client -o yaml | \
|
||||||
|
kubeseal --format=yaml > k8s/sealedsecrets.yaml
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. Configure Repository
|
||||||
|
|
||||||
|
Edit `k8s/configmap.yaml`:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
data:
|
||||||
|
github-repo: "https://github.com/yourusername/yourrepo"
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4. Deploy to Kubernetes
|
||||||
|
|
||||||
|
```bash
|
||||||
|
kubectl apply -k k8s/
|
||||||
|
```
|
||||||
|
|
||||||
|
### 5. Access the Interface
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Port forward for local access
|
||||||
|
kubectl port-forward statefulset/antigravity 5800:5800
|
||||||
|
|
||||||
|
# Open in browser
|
||||||
|
open http://localhost:5800
|
||||||
|
```
|
||||||
|
|
||||||
|
Or configure HTTPRoute (Gateway API) for external access via your domain.
|
||||||
|
|
||||||
|
## Environment Variables
|
||||||
|
|
||||||
|
### Required
|
||||||
|
- `GITHUB_REPO` - GitHub repository URL to clone
|
||||||
|
|
||||||
|
### Optional
|
||||||
|
- `GITHUB_TOKEN` - GitHub Personal Access Token (for private repos)
|
||||||
|
- `VNC_PASSWORD` - Password for VNC access
|
||||||
|
- `USER_ID` - UID for claude user (default: 1000)
|
||||||
|
- `GROUP_ID` - GID for claude user (default: 1000)
|
||||||
|
- `DISPLAY_WIDTH` - VNC display width (default: 1920)
|
||||||
|
- `DISPLAY_HEIGHT` - VNC display height (default: 1080)
|
||||||
|
|
||||||
|
### Happy Coder Configuration (Optional)
|
||||||
|
- `HAPPY_SERVER_URL` - Custom Happy server URL (default: https://api.cluster-fluster.com)
|
||||||
|
- `HAPPY_WEBAPP_URL` - Custom Happy webapp URL (default: https://app.happy.engineering)
|
||||||
|
- `HAPPY_HOME_DIR` - Happy data directory (default: /home/claude/.happy)
|
||||||
|
- `HAPPY_EXPERIMENTAL` - Enable experimental features (default: true in container)
|
||||||
|
|
||||||
|
## Architecture
|
||||||
|
|
||||||
|
```
|
||||||
|
┌─────────────────────────────────────┐
|
||||||
|
│ Web Browser (Port 5800) │
|
||||||
|
└──────────────┬──────────────────────┘
|
||||||
|
│
|
||||||
|
▼
|
||||||
|
┌─────────────────────────────────────┐
|
||||||
|
│ VNC Web Interface │
|
||||||
|
│ (jlesage/baseimage-gui) │
|
||||||
|
└──────────────┬──────────────────────┘
|
||||||
|
│
|
||||||
|
▼
|
||||||
|
┌─────────────────────────────────────┐
|
||||||
|
│ Antigravity IDE │
|
||||||
|
│ (VSCode + Extensions) │
|
||||||
|
│ Running as user: claude (1000) │
|
||||||
|
└──────────────┬──────────────────────┘
|
||||||
|
│
|
||||||
|
▼
|
||||||
|
┌─────────────────────────────────────┐
|
||||||
|
│ Happy Coder (Background Process) │
|
||||||
|
│ AI Development Assistant │
|
||||||
|
└─────────────────────────────────────┘
|
||||||
|
│
|
||||||
|
▼
|
||||||
|
┌─────────────────────────────────────┐
|
||||||
|
│ Workspace: /workspace/{repo} │
|
||||||
|
│ Home: /home/claude (RWX PVC) │
|
||||||
|
└─────────────────────────────────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
## Startup Flow
|
||||||
|
|
||||||
|
1. **Container starts** - baseimage-gui initializes
|
||||||
|
2. **init-repo.sh runs**:
|
||||||
|
- Checks for `GITHUB_REPO` environment variable
|
||||||
|
- Clones repository to `/workspace/{repo-name}` if not exists
|
||||||
|
- Configures git credentials with `GITHUB_TOKEN`
|
||||||
|
- Starts Happy Coder in background
|
||||||
|
3. **startapp.sh runs**:
|
||||||
|
- Opens Antigravity IDE in the cloned repository
|
||||||
|
- Happy Coder is already running and accessible
|
||||||
|
|
||||||
|
## Happy Coder Integration
|
||||||
|
|
||||||
|
Happy Coder runs as a background service and is accessible within the IDE:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Check Happy Coder status
|
||||||
|
ps aux | grep happy-coder
|
||||||
|
|
||||||
|
# View logs
|
||||||
|
cat /tmp/happy-coder.log
|
||||||
|
|
||||||
|
# Restart Happy Coder
|
||||||
|
sudo -u claude bash -c "cd /workspace/your-repo && happy-coder &"
|
||||||
|
```
|
||||||
|
|
||||||
|
## Local Development
|
||||||
|
|
||||||
|
### Run with Docker Compose
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
version: '3.8'
|
||||||
|
services:
|
||||||
|
antigravity:
|
||||||
|
build: .
|
||||||
|
ports:
|
||||||
|
- "5800:5800"
|
||||||
|
environment:
|
||||||
|
- GITHUB_REPO=https://github.com/yourusername/yourrepo
|
||||||
|
- GITHUB_TOKEN=ghp_your_token
|
||||||
|
- VNC_PASSWORD=yourpassword
|
||||||
|
- HAPPY_EXPERIMENTAL=true
|
||||||
|
volumes:
|
||||||
|
- ./home:/home
|
||||||
|
- ./workspace:/workspace
|
||||||
|
```
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker-compose up
|
||||||
|
```
|
||||||
|
|
||||||
|
### Run with Docker
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker run -d \
|
||||||
|
-p 5800:5800 \
|
||||||
|
-e GITHUB_REPO="https://github.com/yourusername/yourrepo" \
|
||||||
|
-e GITHUB_TOKEN="ghp_your_token" \
|
||||||
|
-e VNC_PASSWORD="yourpassword" \
|
||||||
|
-e HAPPY_EXPERIMENTAL="true" \
|
||||||
|
-v $(pwd)/home:/home \
|
||||||
|
-v $(pwd)/workspace:/workspace \
|
||||||
|
ghcr.io/cpfarhood/antigravity:latest
|
||||||
|
```
|
||||||
|
|
||||||
|
## Kubernetes Deployment
|
||||||
|
|
||||||
|
### With Flux
|
||||||
|
|
||||||
|
See the animaniacs cluster configuration for GitOps deployment patterns.
|
||||||
|
|
||||||
|
### Standalone
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Apply manifests
|
||||||
|
kubectl apply -k k8s/
|
||||||
|
|
||||||
|
# Check status
|
||||||
|
kubectl get statefulset antigravity
|
||||||
|
kubectl get pods -l app=antigravity
|
||||||
|
|
||||||
|
# Access logs
|
||||||
|
kubectl logs antigravity-0
|
||||||
|
|
||||||
|
# Access shell
|
||||||
|
kubectl exec -it antigravity-0 -- bash
|
||||||
|
```
|
||||||
|
|
||||||
|
## Troubleshooting
|
||||||
|
|
||||||
|
### Repository not cloning
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Check logs
|
||||||
|
kubectl logs antigravity-0 | grep "Repository Initialization"
|
||||||
|
|
||||||
|
# Verify GITHUB_REPO is set
|
||||||
|
kubectl exec antigravity-0 -- env | grep GITHUB
|
||||||
|
|
||||||
|
# Check git credentials
|
||||||
|
kubectl exec antigravity-0 -- cat /home/claude/.git-credentials
|
||||||
|
```
|
||||||
|
|
||||||
|
### Happy Coder not starting
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Check Happy Coder logs
|
||||||
|
kubectl exec antigravity-0 -- cat /tmp/happy-coder.log
|
||||||
|
|
||||||
|
# Verify API key
|
||||||
|
kubectl exec antigravity-0 -- env | grep HAPPY_CODER
|
||||||
|
|
||||||
|
# Restart Happy Coder
|
||||||
|
kubectl exec antigravity-0 -- sudo -u claude bash -c "cd /workspace/repo && happy-coder &"
|
||||||
|
```
|
||||||
|
|
||||||
|
### VNC not accessible
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Check port forwarding
|
||||||
|
kubectl port-forward antigravity-0 5800:5800
|
||||||
|
|
||||||
|
# Verify service
|
||||||
|
kubectl get svc antigravity
|
||||||
|
|
||||||
|
# Check pod status
|
||||||
|
kubectl describe pod antigravity-0
|
||||||
|
```
|
||||||
|
|
||||||
|
### Permission issues
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Check ownership
|
||||||
|
kubectl exec antigravity-0 -- ls -la /home/claude
|
||||||
|
kubectl exec antigravity-0 -- ls -la /workspace
|
||||||
|
|
||||||
|
# Fix ownership
|
||||||
|
kubectl exec antigravity-0 -- chown -R claude:claude /home/claude
|
||||||
|
kubectl exec antigravity-0 -- chown -R claude:claude /workspace
|
||||||
|
```
|
||||||
|
|
||||||
|
## Security Considerations
|
||||||
|
|
||||||
|
1. **Secrets Management**: Use SealedSecrets or external secret managers
|
||||||
|
2. **Network Policies**: Restrict ingress/egress as needed
|
||||||
|
3. **RBAC**: Limit who can access the namespace
|
||||||
|
4. **VNC Password**: Always set a strong VNC password
|
||||||
|
5. **GitHub Token**: Use fine-grained tokens with minimal permissions
|
||||||
|
6. **Container Security**: Runs as non-root user (claude:1000)
|
||||||
|
|
||||||
|
## Storage
|
||||||
|
|
||||||
|
### Home Directory (`/home`)
|
||||||
|
- Mounted from ReadWriteMany PVC (`userhome`)
|
||||||
|
- Persists user settings, credentials, history
|
||||||
|
- Survives pod restarts
|
||||||
|
|
||||||
|
### Workspace (`/workspace`)
|
||||||
|
- ephemeral emptyDir (can be changed to PVC)
|
||||||
|
- Contains cloned repository
|
||||||
|
- Rebuild on pod restart
|
||||||
|
|
||||||
|
To persist workspace:
|
||||||
|
1. Create a PVC for workspace
|
||||||
|
2. Update `statefulset.yaml` to use PVC instead of emptyDir
|
||||||
|
|
||||||
|
## Customization
|
||||||
|
|
||||||
|
### Add More Tools
|
||||||
|
|
||||||
|
Edit `Dockerfile`:
|
||||||
|
|
||||||
|
```dockerfile
|
||||||
|
RUN apt-get update && apt-get install -y \
|
||||||
|
your-package-here \
|
||||||
|
&& rm -rf /var/lib/apt/lists/*
|
||||||
|
```
|
||||||
|
|
||||||
|
### Change Display Resolution
|
||||||
|
|
||||||
|
Set environment variables:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
env:
|
||||||
|
- name: DISPLAY_WIDTH
|
||||||
|
value: "2560"
|
||||||
|
- name: DISPLAY_HEIGHT
|
||||||
|
value: "1440"
|
||||||
|
```
|
||||||
|
|
||||||
|
### Auto-clone Multiple Repos
|
||||||
|
|
||||||
|
Modify `init-repo.sh` to support `GITHUB_REPOS` (comma-separated):
|
||||||
|
|
||||||
|
```bash
|
||||||
|
IFS=',' read -ra REPOS <<< "$GITHUB_REPOS"
|
||||||
|
for repo in "${REPOS[@]}"; do
|
||||||
|
# Clone each repo
|
||||||
|
done
|
||||||
|
```
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
MIT
|
||||||
|
|
||||||
## Credits
|
## Credits
|
||||||
|
|
||||||
- Base image: [jlesage/docker-baseimage-gui](https://github.com/jlesage/docker-baseimage-gui)
|
- Built on [jlesage/baseimage-gui](https://github.com/jlesage/docker-baseimage-gui)
|
||||||
- AI assistant: [Happy Coder](https://happy.engineering) + [Claude](https://claude.ai)
|
- Uses [Happy Coder](https://happy.engineering)
|
||||||
|
- Inspired by Google's Project IDX
|
||||||
|
|||||||
+13
-3
@@ -59,9 +59,18 @@ These MUST be configured before deployment:
|
|||||||
- **Format:** `ghp_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx`
|
- **Format:** `ghp_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx`
|
||||||
- **Scopes:** `repo`
|
- **Scopes:** `repo`
|
||||||
|
|
||||||
|
### Anthropic API Key
|
||||||
|
- **Variable:** `ANTHROPIC_API_KEY`
|
||||||
|
- **File:** Kubernetes Secret (referenced by `envSecretName`)
|
||||||
|
- **Type:** String (Anthropic API key)
|
||||||
|
- **Description:** API key for Claude Code / Happy Coder authentication. Browser-based OAuth login does not work inside the VNC session, so this key is **required** for Happy Coder to function.
|
||||||
|
- **Required:** Yes (for Happy Coder / Claude Code)
|
||||||
|
- **Format:** `sk-ant-api03-...`
|
||||||
|
- **How to get:** https://console.anthropic.com/settings/keys
|
||||||
|
|
||||||
### VNC Password
|
### VNC Password
|
||||||
- **Variable:** `vnc-password`
|
- **Variable:** `vnc-password`
|
||||||
- **File:** Sealed Secret
|
- **File:** Kubernetes Secret (referenced by `envSecretName`)
|
||||||
- **Type:** String
|
- **Type:** String
|
||||||
- **Description:** Password for VNC web interface
|
- **Description:** Password for VNC web interface
|
||||||
- **Required:** Recommended for security
|
- **Required:** Recommended for security
|
||||||
@@ -286,8 +295,9 @@ hostnames:
|
|||||||
### With Secrets
|
### With Secrets
|
||||||
```bash
|
```bash
|
||||||
kubectl create secret generic antigravity-secrets \
|
kubectl create secret generic antigravity-secrets \
|
||||||
--from-literal=github-token='CHANGE_ME' \
|
--from-literal=GITHUB_TOKEN='CHANGE_ME' \
|
||||||
--from-literal=vnc-password='CHANGE_ME' \
|
--from-literal=VNC_PASSWORD='CHANGE_ME' \
|
||||||
|
--from-literal=ANTHROPIC_API_KEY='sk-ant-api03-...' \
|
||||||
--dry-run=client -o yaml | \
|
--dry-run=client -o yaml | \
|
||||||
kubeseal --format=yaml > k8s/sealedsecrets.yaml
|
kubeseal --format=yaml > k8s/sealedsecrets.yaml
|
||||||
```
|
```
|
||||||
|
|||||||
+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.11
|
version: 0.1.1
|
||||||
appVersion: "latest"
|
appVersion: "latest"
|
||||||
|
|||||||
@@ -14,9 +14,6 @@ spec:
|
|||||||
labels:
|
labels:
|
||||||
{{- include "antigravity.labels" . | nindent 8 }}
|
{{- include "antigravity.labels" . | nindent 8 }}
|
||||||
spec:
|
spec:
|
||||||
{{- if ne (.Values.clusterAccess | default "none") "none" }}
|
|
||||||
serviceAccountName: {{ include "antigravity.fullname" . }}
|
|
||||||
{{- end }}
|
|
||||||
securityContext:
|
securityContext:
|
||||||
fsGroup: 1000
|
fsGroup: 1000
|
||||||
fsGroupChangePolicy: "OnRootMismatch"
|
fsGroupChangePolicy: "OnRootMismatch"
|
||||||
@@ -25,21 +22,10 @@ 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
|
||||||
@@ -71,9 +57,6 @@ spec:
|
|||||||
mountPath: /home
|
mountPath: /home
|
||||||
- name: workspace
|
- name: workspace
|
||||||
mountPath: /workspace
|
mountPath: /workspace
|
||||||
- name: shm
|
|
||||||
mountPath: /dev/shm
|
|
||||||
{{- if ne (.Values.ide | default "vscode") "none" }}
|
|
||||||
livenessProbe:
|
livenessProbe:
|
||||||
httpGet:
|
httpGet:
|
||||||
path: /
|
path: /
|
||||||
@@ -86,25 +69,9 @@ 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: {}
|
||||||
- name: shm
|
|
||||||
emptyDir:
|
|
||||||
medium: Memory
|
|
||||||
sizeLimit: {{ .Values.shm.sizeLimit }}
|
|
||||||
- name: userhome
|
- name: userhome
|
||||||
persistentVolumeClaim:
|
persistentVolumeClaim:
|
||||||
claimName: {{ include "antigravity.pvcName" . }}
|
claimName: {{ include "antigravity.pvcName" . }}
|
||||||
|
|||||||
@@ -1,97 +0,0 @@
|
|||||||
{{- $access := .Values.clusterAccess | default "none" }}
|
|
||||||
{{- $name := include "antigravity.fullname" . }}
|
|
||||||
{{- $ns := .Release.Namespace }}
|
|
||||||
{{- $labels := include "antigravity.labels" . }}
|
|
||||||
|
|
||||||
{{- if ne $access "none" }}
|
|
||||||
---
|
|
||||||
apiVersion: v1
|
|
||||||
kind: ServiceAccount
|
|
||||||
metadata:
|
|
||||||
name: {{ $name }}
|
|
||||||
namespace: {{ $ns }}
|
|
||||||
labels:
|
|
||||||
{{- $labels | nindent 4 }}
|
|
||||||
|
|
||||||
{{- if or (eq $access "readonlyns") (eq $access "readwritens") }}
|
|
||||||
---
|
|
||||||
apiVersion: rbac.authorization.k8s.io/v1
|
|
||||||
kind: Role
|
|
||||||
metadata:
|
|
||||||
name: {{ $name }}
|
|
||||||
namespace: {{ $ns }}
|
|
||||||
labels:
|
|
||||||
{{- $labels | nindent 4 }}
|
|
||||||
rules:
|
|
||||||
- apiGroups: ["*"]
|
|
||||||
resources: ["*"]
|
|
||||||
verbs:
|
|
||||||
{{- if eq $access "readonlyns" }}
|
|
||||||
- get
|
|
||||||
- list
|
|
||||||
- watch
|
|
||||||
{{- else }}
|
|
||||||
- "*"
|
|
||||||
{{- end }}
|
|
||||||
---
|
|
||||||
apiVersion: rbac.authorization.k8s.io/v1
|
|
||||||
kind: RoleBinding
|
|
||||||
metadata:
|
|
||||||
name: {{ $name }}
|
|
||||||
namespace: {{ $ns }}
|
|
||||||
labels:
|
|
||||||
{{- $labels | nindent 4 }}
|
|
||||||
subjects:
|
|
||||||
- kind: ServiceAccount
|
|
||||||
name: {{ $name }}
|
|
||||||
namespace: {{ $ns }}
|
|
||||||
roleRef:
|
|
||||||
kind: Role
|
|
||||||
name: {{ $name }}
|
|
||||||
apiGroup: rbac.authorization.k8s.io
|
|
||||||
{{- end }}
|
|
||||||
|
|
||||||
{{- if or (eq $access "readonly") (eq $access "readwrite") }}
|
|
||||||
---
|
|
||||||
apiVersion: rbac.authorization.k8s.io/v1
|
|
||||||
kind: ClusterRole
|
|
||||||
metadata:
|
|
||||||
name: {{ $name }}
|
|
||||||
labels:
|
|
||||||
{{- $labels | nindent 4 }}
|
|
||||||
rules:
|
|
||||||
- apiGroups: ["*"]
|
|
||||||
resources: ["*"]
|
|
||||||
verbs:
|
|
||||||
{{- if eq $access "readonly" }}
|
|
||||||
- get
|
|
||||||
- list
|
|
||||||
- watch
|
|
||||||
{{- else }}
|
|
||||||
- "*"
|
|
||||||
{{- end }}
|
|
||||||
- nonResourceURLs: ["*"]
|
|
||||||
verbs:
|
|
||||||
{{- if eq $access "readonly" }}
|
|
||||||
- get
|
|
||||||
{{- else }}
|
|
||||||
- "*"
|
|
||||||
{{- end }}
|
|
||||||
---
|
|
||||||
apiVersion: rbac.authorization.k8s.io/v1
|
|
||||||
kind: ClusterRoleBinding
|
|
||||||
metadata:
|
|
||||||
name: {{ $name }}
|
|
||||||
labels:
|
|
||||||
{{- $labels | nindent 4 }}
|
|
||||||
subjects:
|
|
||||||
- kind: ServiceAccount
|
|
||||||
name: {{ $name }}
|
|
||||||
namespace: {{ $ns }}
|
|
||||||
roleRef:
|
|
||||||
kind: ClusterRole
|
|
||||||
name: {{ $name }}
|
|
||||||
apiGroup: rbac.authorization.k8s.io
|
|
||||||
{{- end }}
|
|
||||||
|
|
||||||
{{- end }}
|
|
||||||
@@ -6,17 +6,9 @@ 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 }}
|
||||||
|
|||||||
+5
-27
@@ -9,17 +9,6 @@ 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"
|
||||||
@@ -41,11 +30,6 @@ storage:
|
|||||||
size: 32Gi
|
size: 32Gi
|
||||||
className: ceph-filesystem
|
className: ceph-filesystem
|
||||||
|
|
||||||
# Shared memory size — mounted at /dev/shm as a memory-backed emptyDir.
|
|
||||||
# Electron apps (Antigravity, Chrome) use /dev/shm for GPU/IPC buffers.
|
|
||||||
shm:
|
|
||||||
sizeLimit: 2Gi
|
|
||||||
|
|
||||||
resources:
|
resources:
|
||||||
requests:
|
requests:
|
||||||
memory: "2Gi"
|
memory: "2Gi"
|
||||||
@@ -54,15 +38,9 @@ resources:
|
|||||||
memory: "8Gi"
|
memory: "8Gi"
|
||||||
cpu: "4000m"
|
cpu: "4000m"
|
||||||
|
|
||||||
# Kubernetes cluster access granted to the devcontainer pod via RBAC.
|
# Name of existing Secret containing env vars. Defaults to: devcontainer-{name}-secrets-env
|
||||||
# Options:
|
# Recognized keys:
|
||||||
# none — no cluster access (default)
|
# GITHUB_TOKEN — PAT for private repo access
|
||||||
# readonlyns — get/list/watch all resources in the release namespace
|
# VNC_PASSWORD — password for the VNC web UI
|
||||||
# readwritens — full access to all resources in the release namespace
|
# ANTHROPIC_API_KEY — required for Claude Code / Happy Coder auth (browser login won't work in VNC)
|
||||||
# readonly — get/list/watch all resources cluster-wide
|
|
||||||
# readwrite — full access to all resources cluster-wide
|
|
||||||
clusterAccess: none
|
|
||||||
|
|
||||||
# Name of existing Secret containing env vars (GITHUB_TOKEN, VNC_PASSWORD, etc.)
|
|
||||||
# Defaults to: devcontainer-{name}-secrets-env
|
|
||||||
envSecretName: ""
|
envSecretName: ""
|
||||||
|
|||||||
@@ -1,45 +0,0 @@
|
|||||||
#!/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 ==="
|
|
||||||
|
|
||||||
HOME_DIR="/home/user"
|
|
||||||
HOST_KEY_STORE="$HOME_DIR/.ssh/host_keys"
|
|
||||||
|
|
||||||
# Persist host keys on the home PVC so clients don't see a "host key
|
|
||||||
# changed" warning after pod restarts.
|
|
||||||
if [ -d "$HOST_KEY_STORE" ] && [ -n "$(ls "$HOST_KEY_STORE"/ssh_host_* 2>/dev/null)" ]; then
|
|
||||||
# Restore previously generated host keys
|
|
||||||
echo "Restoring SSH host keys from PVC..."
|
|
||||||
cp "$HOST_KEY_STORE"/ssh_host_* /etc/ssh/
|
|
||||||
chmod 600 /etc/ssh/ssh_host_*_key
|
|
||||||
chmod 644 /etc/ssh/ssh_host_*_key.pub
|
|
||||||
else
|
|
||||||
# First boot: generate and save host keys to PVC
|
|
||||||
echo "Generating SSH host keys (first boot)..."
|
|
||||||
ssh-keygen -A 2>/dev/null || true
|
|
||||||
mkdir -p "$HOST_KEY_STORE"
|
|
||||||
cp /etc/ssh/ssh_host_* "$HOST_KEY_STORE/"
|
|
||||||
chmod 700 "$HOST_KEY_STORE"
|
|
||||||
chown -R 1000:1000 "$HOST_KEY_STORE"
|
|
||||||
echo "SSH host keys saved to PVC."
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Populate authorized_keys from env var (injected via Kubernetes secret)
|
|
||||||
if [ -n "$SSH_AUTHORIZED_KEYS" ]; then
|
|
||||||
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 $!)"
|
|
||||||
@@ -59,15 +59,17 @@ chown -R "$RUN_UID:$RUN_GID" "$WORKSPACE_DIR"
|
|||||||
mkdir -p "$HOME"
|
mkdir -p "$HOME"
|
||||||
chown "$RUN_UID:$RUN_GID" "$HOME"
|
chown "$RUN_UID:$RUN_GID" "$HOME"
|
||||||
|
|
||||||
# Start Happy Coder daemon. startapp.sh already runs as the app user (UID 1000),
|
# Warn if ANTHROPIC_API_KEY is not set — browser-based Claude login won't work in VNC
|
||||||
# so no sudo needed — Happy/Claude Code will find credentials in the correct home dir.
|
if [ -z "$ANTHROPIC_API_KEY" ]; then
|
||||||
|
echo "WARNING: ANTHROPIC_API_KEY is not set."
|
||||||
|
echo " Claude Code cannot authenticate via browser inside this container."
|
||||||
|
echo " Add ANTHROPIC_API_KEY to your Kubernetes secret to enable Happy Coder."
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Start Happy Coder daemon
|
||||||
echo "Starting Happy Coder..."
|
echo "Starting Happy Coder..."
|
||||||
|
|
||||||
# Remove stale lock file. HAPPY_HOME_DIR lives on the home PVC so it survives
|
|
||||||
# pod restarts — without this cleanup the daemon refuses to start after a crash.
|
|
||||||
rm -f "${HAPPY_HOME_DIR:-$HOME/.happy}/daemon.state.json.lock"
|
|
||||||
|
|
||||||
cd "$WORKSPACE_DIR"
|
cd "$WORKSPACE_DIR"
|
||||||
|
|
||||||
happy daemon start || echo "Happy Coder daemon failed to start, continuing anyway..."
|
happy daemon start || echo "Happy Coder daemon failed to start, continuing anyway..."
|
||||||
|
|
||||||
echo "Happy Coder daemon started"
|
echo "Happy Coder daemon started"
|
||||||
|
|||||||
+4
-23
@@ -14,27 +14,8 @@ else
|
|||||||
WORKSPACE_DIR="/workspace/default"
|
WORKSPACE_DIR="/workspace/default"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
IDE="${IDE:-vscode}"
|
echo "Opening Antigravity in: $WORKSPACE_DIR"
|
||||||
echo "IDE mode: $IDE"
|
|
||||||
echo "Workspace: $WORKSPACE_DIR"
|
|
||||||
|
|
||||||
case "$IDE" in
|
# Start Antigravity (VSCode) in the workspace directory as claude user
|
||||||
antigravity)
|
# The baseimage-gui will handle the GUI display
|
||||||
echo "Opening Google Antigravity in: $WORKSPACE_DIR"
|
exec code --new-window --wait "$WORKSPACE_DIR"
|
||||||
# --no-sandbox is required for Electron apps in Docker (no kernel sandbox available).
|
|
||||||
# Explicit --user-data-dir and --extensions-dir pin config to the home PVC so
|
|
||||||
# settings and the setup wizard state survive pod restarts.
|
|
||||||
exec antigravity --no-sandbox \
|
|
||||||
--user-data-dir "$HOME/.config/antigravity" \
|
|
||||||
--extensions-dir "$HOME/.antigravity/extensions" \
|
|
||||||
--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