Compare commits
191 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 70e74ab2d2 | |||
| 673554b393 | |||
| 609752e7dc | |||
| fcd959ae1f | |||
| 149120ff6c | |||
| 50770b6e5f | |||
| 01d5ebbdb8 | |||
| d5338ab836 | |||
| a378c0f913 | |||
| c1f3fdbb90 | |||
| 60f96fc8da | |||
| a300e8e810 | |||
| b5ee7c84de | |||
| 02c4f864f7 | |||
| ea966fadab | |||
| f0e70438db | |||
| 0fe568a7d6 | |||
| 7235d2dc67 | |||
| 7940e80cf0 | |||
| f6cbec05f6 | |||
| 9175d48844 | |||
| cb60f2a428 | |||
| 1179897cba | |||
| 46dc486cb4 | |||
| 41ec70c7da | |||
| e3f751240a | |||
| e6c3b7f7bf | |||
| 41e270ec32 | |||
| 05b06d1d90 | |||
| 8736d5b500 | |||
| 713e5eebe6 | |||
| 276477e245 | |||
| 2136976b8e | |||
| e269e19f23 | |||
| 3109de7e2e | |||
| 2b9350c86d | |||
| 5d62842aec | |||
| 58719cf262 | |||
| c066aa49be | |||
| 204a673b3d | |||
| 04ed52bc8d | |||
| c670dd124f | |||
| 219af987ae | |||
| c70352dc41 | |||
| f689b27b78 | |||
| a978b505d0 | |||
| 69497b1ec6 | |||
| 698c5810a0 | |||
| 2582c1d824 | |||
| 6dd560f2ad | |||
| 19d47da079 | |||
| 12d3444cc5 | |||
| eeb995e1fc | |||
| d26b69c587 | |||
| da40d57e07 | |||
| e99ec65cd9 | |||
| 38e481484e | |||
| cc38a07168 | |||
| 5c3600a424 | |||
| 5565354127 | |||
| b69cd80cae | |||
| 3e46bf5ec1 | |||
| c8a7bbcd6e | |||
| adb2ee4817 | |||
| 3637a0a6fc | |||
| f67066823b | |||
| 50560652cb | |||
| 0fc4ff503b | |||
| 04203e4efb | |||
| b710daac05 | |||
| 52a29da38d | |||
| ea71f71c74 | |||
| f6eceb4d94 | |||
| 84bf7841c3 | |||
| c823a30c2a | |||
| 27af9dc9c4 | |||
| 0944dcec1c | |||
| 60a2689658 | |||
| 53bc4b68a6 | |||
| d526a445fd | |||
| f56b3efb66 | |||
| a778d32b3b | |||
| b48fce97d5 | |||
| 47af7acc5e | |||
| da45415cfe | |||
| 897555b1dc | |||
| df1f4d9b50 | |||
| 2f5a8d65d5 | |||
| 0d8fe1ec64 | |||
| 00638d372c | |||
| 31ec139a8a | |||
| 71c6ca70cc | |||
| b9c30b8e4d | |||
| 794de6d0e5 | |||
| fbcd9c1f72 | |||
| 3be59e56eb | |||
| be9479ef75 | |||
| 065a6534e3 | |||
| d8d83ffa47 | |||
| 9535886945 | |||
| 44f30ec03f | |||
| 76391a8ed0 | |||
| ac1e5074b1 | |||
| 2a63f227f1 | |||
| 5da23def5b | |||
| 5532eee8cd | |||
| d32e453f93 | |||
| f95e8877e8 | |||
| 46267b6e26 | |||
| c4cbd67399 | |||
| a7799dbb16 | |||
| 45b8e5e95e | |||
| a0b409239e | |||
| eacf41302c | |||
| cbdee590bf | |||
| 5570b2c617 | |||
| c3f8421d60 | |||
| 21d8fc73e6 | |||
| 0a63894f6d | |||
| a50a1815e0 | |||
| 131dad8611 | |||
| 581d0737e4 | |||
| 68110d911f | |||
| 427f7a710c | |||
| 745a0cdf59 | |||
| 115907cdc8 | |||
| 0c4f93c077 | |||
| a83d79bc10 | |||
| 2258df4ae3 | |||
| d4b069cbdc | |||
| db7e422b96 | |||
| d5bbf21578 | |||
| 1c3398b178 | |||
| 4e67c48a4c | |||
| df3413f54e | |||
| 6a35f38a8c | |||
| 431b9079ee | |||
| 00d88b16b5 | |||
| c10dd718e1 | |||
| b6bf4b6640 | |||
| c42b47bb56 | |||
| 288c1a4103 | |||
| 2caa8a790f | |||
| 7a6a515b53 | |||
| 4f126a938b | |||
| 4af38a5d2e | |||
| 90350a2090 | |||
| 5b8e6a290b | |||
| e860499757 | |||
| e90a2fe553 | |||
| 897f1409b5 | |||
| 32d4fe4944 | |||
| e8c263a045 | |||
| 927c9f1051 | |||
| 298a1ce6ec | |||
| f33c7e1ae8 | |||
| b0d4b98bb4 | |||
| b5820cfc7f | |||
| bace308394 | |||
| 9c964e7069 | |||
| d7210fb4e5 | |||
| 7a96f5156c | |||
| 8df46d6b6f | |||
| 5d8b1369c3 | |||
| 751402be44 | |||
| 66e0d1f406 | |||
| e89c3040b7 | |||
| 8d7b39f1b5 | |||
| 32e87254d2 | |||
| 66ccee1202 | |||
| 1909c2a3aa | |||
| d078bb1c44 | |||
| 56c648187a | |||
| 8870d60ccc | |||
| d54515244c | |||
| 2918cfde25 | |||
| dd77cf6a48 | |||
| 961a0985b6 | |||
| d3f5e9f185 | |||
| 9aab08b8e4 | |||
| 727487053d | |||
| 47a275d667 | |||
| 7788352995 | |||
| 81a7098c21 | |||
| 3832fd922b | |||
| 4984e5200c | |||
| c8f51beac6 | |||
| ee7a4a0be8 | |||
| 2472dc5b3f | |||
| 17c2d04d70 | |||
| 636b68d263 |
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"enabledPlugins": {
|
||||
"voltagent-dev-exp@voltagent-subagents": true,
|
||||
"voltagent-lang@voltagent-subagents": true
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"enabledMcpjsonServers": [
|
||||
"kubernetes",
|
||||
"flux",
|
||||
"playwright",
|
||||
"github",
|
||||
"pgtuner",
|
||||
"fetch",
|
||||
"sequentialthinking"
|
||||
],
|
||||
"permissions": {
|
||||
"allow": [
|
||||
"Bash(git add .claude/settings.local.json .claude/settings.json && git commit -m \"$\\(cat <<'EOF'\nchore: update Claude Code settings and enable voltagent plugins\n\nAdd fetch and sequentialthinking MCP servers to allowed list, and enable\nvoltagent dev-exp and lang subagent plugins.\n\nCo-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>\nEOF\n\\)\" && git status)"
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -19,7 +19,6 @@
|
||||
- [ ] Built Docker image locally
|
||||
- [ ] Tested container startup
|
||||
- [ ] Tested repository cloning
|
||||
- [ ] Tested Happy Coder integration
|
||||
- [ ] Tested VNC web interface
|
||||
|
||||
## Checklist
|
||||
|
||||
@@ -1,19 +0,0 @@
|
||||
version: 2
|
||||
updates:
|
||||
# Maintain dependencies for GitHub Actions
|
||||
- package-ecosystem: "github-actions"
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: "weekly"
|
||||
labels:
|
||||
- "dependencies"
|
||||
- "github-actions"
|
||||
|
||||
# Maintain dependencies for Docker
|
||||
- package-ecosystem: "docker"
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: "weekly"
|
||||
labels:
|
||||
- "dependencies"
|
||||
- "docker"
|
||||
@@ -0,0 +1,93 @@
|
||||
# CI/CD Pipeline Guide
|
||||
|
||||
## 🚀 Simplified Pipeline - Only 3 Workflows!
|
||||
|
||||
### 1️⃣ For Releases → **Unified Release**
|
||||
Use this for all version releases:
|
||||
1. Go to [Actions → Unified Release](https://github.com/cpfarhood/devcontainer/actions/workflows/release-unified.yaml)
|
||||
2. Click "Run workflow"
|
||||
3. Either:
|
||||
- Enter specific version (e.g., `0.2.1`), OR
|
||||
- Choose release type (patch/minor/major) for auto-increment
|
||||
4. Click "Run workflow"
|
||||
|
||||
**This single workflow does EVERYTHING:**
|
||||
- ✅ Updates chart version
|
||||
- ✅ Creates git tag
|
||||
- ✅ Builds Docker image with all proper tags
|
||||
- ✅ Publishes Helm chart to GitHub Pages (`https://cpfarhood.github.io/devcontainer`)
|
||||
- ✅ Creates GitHub Release with changelog
|
||||
|
||||
### 2️⃣ For Quick Fixes → **Quick Fix Build**
|
||||
Use this for emergency fixes without version changes:
|
||||
1. Go to [Actions → Quick Fix Build](https://github.com/cpfarhood/devcontainer/actions/workflows/quick-fix.yaml)
|
||||
2. Click "Run workflow"
|
||||
3. Enter tag (default: `latest`)
|
||||
4. Click "Run workflow"
|
||||
|
||||
**Just builds and pushes Docker image** - no version bumps, no releases.
|
||||
|
||||
### 3️⃣ Automatic CI → **Build and Push**
|
||||
Runs automatically on:
|
||||
- Pushes to `main` (builds and pushes; skipped for release commits via `[skip ci]`)
|
||||
- Pull requests (builds but doesn't push)
|
||||
- Manual trigger available
|
||||
|
||||
## Workflow Files
|
||||
|
||||
| Workflow | File | Purpose | When to Use |
|
||||
|----------|------|---------|-------------|
|
||||
| **Unified Release** | `release-unified.yaml` | Full release process | New versions |
|
||||
| **Quick Fix Build** | `quick-fix.yaml` | Docker build only | Hotfixes |
|
||||
| **Build and Push** | `build-and-push.yaml` | CI/CD automation | PRs & tags |
|
||||
|
||||
## Examples
|
||||
|
||||
### Release a new version
|
||||
```bash
|
||||
# Via GitHub UI (Recommended):
|
||||
# Go to Actions → Unified Release → Run workflow
|
||||
|
||||
# Via GitHub CLI:
|
||||
gh workflow run release-unified.yaml -f version=0.2.1
|
||||
# OR auto-increment:
|
||||
gh workflow run release-unified.yaml -f release_type=patch
|
||||
```
|
||||
|
||||
### Push a quick fix
|
||||
```bash
|
||||
# Via GitHub UI:
|
||||
# Go to Actions → Quick Fix Build → Run workflow
|
||||
|
||||
# Via GitHub CLI:
|
||||
gh workflow run quick-fix.yaml -f tag=hotfix-1
|
||||
```
|
||||
|
||||
### Check workflow status
|
||||
```bash
|
||||
# List all recent runs
|
||||
gh run list --limit 5
|
||||
|
||||
# Watch a specific workflow
|
||||
gh run watch
|
||||
```
|
||||
|
||||
## Version Strategy
|
||||
|
||||
- **Major** (1.0.0): Breaking changes
|
||||
- **Minor** (0.2.0): New features
|
||||
- **Patch** (0.2.1): Bug fixes
|
||||
|
||||
## What We Fixed
|
||||
|
||||
### Before (Nightmare 😱)
|
||||
- Auto-version-bump with `[skip ci]` prevented Docker builds
|
||||
- 6+ disconnected workflows
|
||||
- Manual tag deletion and re-pushing
|
||||
- Version conflicts everywhere
|
||||
|
||||
### After (Simple! 🎉)
|
||||
- **3 total workflows** (down from 6+)
|
||||
- **1 button** for complete releases
|
||||
- Release builds its own Docker image — `[skip ci]` on the version commit prevents duplicate CI builds
|
||||
- **Clear separation** of concerns
|
||||
@@ -4,8 +4,6 @@ on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
tags:
|
||||
- 'v*'
|
||||
pull_request:
|
||||
branches:
|
||||
- main
|
||||
@@ -18,14 +16,16 @@ env:
|
||||
jobs:
|
||||
build-and-push:
|
||||
runs-on: ubuntu-latest
|
||||
if: >-
|
||||
github.event_name != 'push'
|
||||
|| !contains(github.event.head_commit.message, '[skip ci]')
|
||||
permissions:
|
||||
contents: read
|
||||
packages: write
|
||||
id-token: write
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
@@ -46,19 +46,17 @@ jobs:
|
||||
tags: |
|
||||
type=ref,event=branch
|
||||
type=ref,event=pr
|
||||
type=semver,pattern={{version}}
|
||||
type=semver,pattern={{major}}.{{minor}}
|
||||
type=semver,pattern={{major}}
|
||||
type=sha,prefix=sha-
|
||||
type=raw,value=latest,enable={{is_default_branch}}
|
||||
|
||||
- name: Build and push Docker image
|
||||
uses: docker/build-push-action@v5
|
||||
uses: docker/build-push-action@v6
|
||||
with:
|
||||
context: .
|
||||
push: ${{ github.event_name != 'pull_request' }}
|
||||
tags: ${{ steps.meta.outputs.tags }}
|
||||
labels: ${{ steps.meta.outputs.labels }}
|
||||
build-args: CACHE_BUST=${{ github.sha }}
|
||||
cache-from: type=gha
|
||||
cache-to: type=gha,mode=max
|
||||
platforms: linux/amd64
|
||||
|
||||
@@ -0,0 +1,56 @@
|
||||
name: Quick Fix Build
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
tag:
|
||||
description: 'Tag for the image (defaults to latest)'
|
||||
required: false
|
||||
default: 'latest'
|
||||
type: string
|
||||
|
||||
env:
|
||||
REGISTRY: ghcr.io
|
||||
IMAGE_NAME: ${{ github.repository }}
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
if: github.ref == 'refs/heads/main'
|
||||
permissions:
|
||||
contents: read
|
||||
packages: write
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
|
||||
- name: Log in to GitHub Container Registry
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
registry: ${{ env.REGISTRY }}
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Build and Push
|
||||
uses: docker/build-push-action@v6
|
||||
with:
|
||||
context: .
|
||||
push: true
|
||||
tags: |
|
||||
${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.event.inputs.tag }}
|
||||
${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.sha }}
|
||||
cache-from: type=gha
|
||||
cache-to: type=gha,mode=max
|
||||
platforms: linux/amd64
|
||||
|
||||
- name: Summary
|
||||
run: |
|
||||
echo "## ✅ Quick Fix Build Complete" >> $GITHUB_STEP_SUMMARY
|
||||
echo "" >> $GITHUB_STEP_SUMMARY
|
||||
echo "### Images Published:" >> $GITHUB_STEP_SUMMARY
|
||||
echo "- \`${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.event.inputs.tag }}\`" >> $GITHUB_STEP_SUMMARY
|
||||
echo "- \`${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.sha }}\`" >> $GITHUB_STEP_SUMMARY
|
||||
@@ -0,0 +1,193 @@
|
||||
name: Unified Release
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
version:
|
||||
description: 'Explicit version (e.g., 1.2.3). Leave blank to auto-increment.'
|
||||
required: false
|
||||
type: string
|
||||
release_type:
|
||||
description: 'Release type (used when version is blank)'
|
||||
required: true
|
||||
default: 'patch'
|
||||
type: choice
|
||||
options:
|
||||
- patch
|
||||
- minor
|
||||
- major
|
||||
|
||||
env:
|
||||
REGISTRY: ghcr.io
|
||||
IMAGE_NAME: ${{ github.repository }}
|
||||
|
||||
jobs:
|
||||
release:
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: write
|
||||
packages: write
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
fetch-depth: 0
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
|
||||
- name: Set up Helm
|
||||
uses: azure/setup-helm@v4
|
||||
|
||||
- name: Configure Git
|
||||
run: |
|
||||
git config user.name "github-actions[bot]"
|
||||
git config user.email "github-actions[bot]@users.noreply.github.com"
|
||||
|
||||
- name: Determine Version
|
||||
id: version
|
||||
run: |
|
||||
INPUT_VERSION="${{ github.event.inputs.version }}"
|
||||
if [ -n "$INPUT_VERSION" ]; then
|
||||
VERSION="$INPUT_VERSION"
|
||||
else
|
||||
# Auto-increment based on release_type
|
||||
CURRENT=$(grep '^version:' chart/Chart.yaml | awk '{print $2}')
|
||||
# Strip any pre-release suffix (e.g., 2.0.0-dev -> 2.0.0)
|
||||
CURRENT=$(echo "$CURRENT" | sed 's/-.*//')
|
||||
MAJOR=$(echo "$CURRENT" | cut -d. -f1)
|
||||
MINOR=$(echo "$CURRENT" | cut -d. -f2)
|
||||
PATCH=$(echo "$CURRENT" | cut -d. -f3)
|
||||
|
||||
case "${{ github.event.inputs.release_type }}" in
|
||||
major) VERSION="$((MAJOR + 1)).0.0" ;;
|
||||
minor) VERSION="${MAJOR}.$((MINOR + 1)).0" ;;
|
||||
patch) VERSION="${MAJOR}.${MINOR}.$((PATCH + 1))" ;;
|
||||
esac
|
||||
fi
|
||||
|
||||
echo "version=${VERSION}" >> $GITHUB_OUTPUT
|
||||
echo "tag=v${VERSION}" >> $GITHUB_OUTPUT
|
||||
echo "Releasing version ${VERSION}"
|
||||
|
||||
- name: Update Chart Version
|
||||
run: |
|
||||
sed -i "s/^version: .*/version: ${{ steps.version.outputs.version }}/" chart/Chart.yaml
|
||||
git add chart/Chart.yaml
|
||||
git diff --quiet --staged || git commit -m "chore(release): ${{ steps.version.outputs.version }} [skip ci]"
|
||||
|
||||
- name: Create and Push Tag
|
||||
run: |
|
||||
git tag -a "${{ steps.version.outputs.tag }}" -m "Release ${{ steps.version.outputs.tag }}"
|
||||
git push origin main
|
||||
git push origin "${{ steps.version.outputs.tag }}"
|
||||
|
||||
- name: Log in to GitHub Container Registry
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
registry: ${{ env.REGISTRY }}
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Build and Push Docker Image
|
||||
uses: docker/build-push-action@v6
|
||||
with:
|
||||
context: .
|
||||
push: true
|
||||
build-args: CACHE_BUST=${{ github.sha }}
|
||||
cache-from: type=gha
|
||||
cache-to: type=gha,mode=max
|
||||
tags: |
|
||||
${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.version.outputs.tag }}
|
||||
${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.version.outputs.version }}
|
||||
${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest
|
||||
platforms: linux/amd64
|
||||
|
||||
- name: Publish Helm Chart to GitHub Pages
|
||||
run: |
|
||||
helm package chart/
|
||||
CHART_TGZ="devcontainer-${{ steps.version.outputs.version }}.tgz"
|
||||
|
||||
# Set up gh-pages in a temporary directory
|
||||
PAGES_DIR=$(mktemp -d)
|
||||
if git ls-remote --heads origin gh-pages | grep -q gh-pages; then
|
||||
# gh-pages exists — shallow clone just that branch
|
||||
git clone --single-branch --branch gh-pages \
|
||||
"https://x-access-token:${{ secrets.GITHUB_TOKEN }}@github.com/${{ github.repository }}.git" \
|
||||
"$PAGES_DIR"
|
||||
else
|
||||
# First time — initialize gh-pages
|
||||
git init "$PAGES_DIR"
|
||||
git -C "$PAGES_DIR" checkout --orphan gh-pages
|
||||
git -C "$PAGES_DIR" remote add origin \
|
||||
"https://x-access-token:${{ secrets.GITHUB_TOKEN }}@github.com/${{ github.repository }}.git"
|
||||
cat > "$PAGES_DIR/index.html" <<'HTMLEOF'
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head><title>Dev Container Helm Chart Repository</title></head>
|
||||
<body>
|
||||
<h1>Dev Container Helm Chart Repository</h1>
|
||||
<p>Add this repository to Helm:</p>
|
||||
<pre>helm repo add devcontainer https://cpfarhood.github.io/devcontainer</pre>
|
||||
<p>Install the chart:</p>
|
||||
<pre>helm install mydev devcontainer/devcontainer --set name=mydev</pre>
|
||||
</body>
|
||||
</html>
|
||||
HTMLEOF
|
||||
fi
|
||||
|
||||
git -C "$PAGES_DIR" config user.name "github-actions[bot]"
|
||||
git -C "$PAGES_DIR" config user.email "github-actions[bot]@users.noreply.github.com"
|
||||
|
||||
# Copy chart package and rebuild index
|
||||
cp "$CHART_TGZ" "$PAGES_DIR/"
|
||||
if [ -f "$PAGES_DIR/index.yaml" ]; then
|
||||
helm repo index "$PAGES_DIR" --url https://cpfarhood.github.io/devcontainer --merge "$PAGES_DIR/index.yaml"
|
||||
else
|
||||
helm repo index "$PAGES_DIR" --url https://cpfarhood.github.io/devcontainer
|
||||
fi
|
||||
|
||||
# Commit and push
|
||||
git -C "$PAGES_DIR" add .
|
||||
git -C "$PAGES_DIR" commit -m "Publish chart ${{ steps.version.outputs.version }}"
|
||||
git -C "$PAGES_DIR" push origin gh-pages
|
||||
|
||||
- name: Create GitHub Release
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
VERSION: ${{ steps.version.outputs.version }}
|
||||
TAG: ${{ steps.version.outputs.tag }}
|
||||
IMAGE: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.version.outputs.tag }}
|
||||
run: |
|
||||
PREV_TAG=$(git describe --tags --abbrev=0 HEAD~1 2>/dev/null || echo "")
|
||||
if [ -z "$PREV_TAG" ]; then
|
||||
COMMITS=$(git log --pretty=format:"- %s (%h)" HEAD)
|
||||
else
|
||||
COMMITS=$(git log --pretty=format:"- %s (%h)" "${PREV_TAG}..HEAD")
|
||||
fi
|
||||
|
||||
cat > release-notes.md <<NOTESEOF
|
||||
## Release ${VERSION}
|
||||
|
||||
### Changes
|
||||
${COMMITS}
|
||||
|
||||
### Docker Image
|
||||
\`\`\`bash
|
||||
docker pull ${IMAGE}
|
||||
\`\`\`
|
||||
|
||||
### Helm Chart
|
||||
\`\`\`bash
|
||||
helm repo add devcontainer https://cpfarhood.github.io/devcontainer
|
||||
helm repo update
|
||||
helm install mydev devcontainer/devcontainer --version ${VERSION} --set name=mydev
|
||||
\`\`\`
|
||||
NOTESEOF
|
||||
sed -i 's/^ //' release-notes.md
|
||||
|
||||
gh release create "${TAG}" \
|
||||
--title "Release ${TAG}" \
|
||||
--notes-file release-notes.md
|
||||
@@ -1,51 +0,0 @@
|
||||
name: Release
|
||||
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- 'v*'
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
packages: write
|
||||
|
||||
jobs:
|
||||
release:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Generate Release Notes
|
||||
id: notes
|
||||
run: |
|
||||
# Get the tag message or generate from commits
|
||||
TAG_MESSAGE=$(git tag -l --format='%(contents)' ${{ github.ref_name }})
|
||||
if [ -z "$TAG_MESSAGE" ]; then
|
||||
# Generate from commit messages since last tag
|
||||
PREV_TAG=$(git describe --tags --abbrev=0 ${{ github.ref_name }}^ 2>/dev/null || echo "")
|
||||
if [ -z "$PREV_TAG" ]; then
|
||||
COMMITS=$(git log --pretty=format:"- %s (%h)" ${{ github.ref_name }})
|
||||
else
|
||||
COMMITS=$(git log --pretty=format:"- %s (%h)" ${PREV_TAG}..${{ github.ref_name }})
|
||||
fi
|
||||
NOTES="## Changes\n\n${COMMITS}\n\n## Docker Image\n\n\`\`\`bash\ndocker pull ghcr.io/${{ github.repository }}:${{ github.ref_name }}\n\`\`\`"
|
||||
else
|
||||
NOTES="${TAG_MESSAGE}\n\n## Docker Image\n\n\`\`\`bash\ndocker pull ghcr.io/${{ github.repository }}:${{ github.ref_name }}\n\`\`\`"
|
||||
fi
|
||||
echo "notes<<EOF" >> $GITHUB_OUTPUT
|
||||
echo -e "$NOTES" >> $GITHUB_OUTPUT
|
||||
echo "EOF" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Create Release
|
||||
uses: actions/create-release@v1
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
tag_name: ${{ github.ref_name }}
|
||||
release_name: Release ${{ github.ref_name }}
|
||||
body: ${{ steps.notes.outputs.notes }}
|
||||
draft: false
|
||||
prerelease: false
|
||||
@@ -1,259 +0,0 @@
|
||||
# Release Process
|
||||
|
||||
This document describes how to create releases for this project.
|
||||
|
||||
## Semantic Versioning
|
||||
|
||||
We follow [Semantic Versioning 2.0.0](https://semver.org/):
|
||||
|
||||
- **MAJOR** version (v2.0.0): Incompatible API/breaking changes
|
||||
- **MINOR** version (v1.1.0): New features, backwards compatible
|
||||
- **PATCH** version (v1.0.1): Bug fixes, backwards compatible
|
||||
|
||||
## Creating a Release
|
||||
|
||||
### Method 1: Using GitHub CLI (Recommended)
|
||||
|
||||
```bash
|
||||
# Ensure you're on main branch and up to date
|
||||
git checkout main
|
||||
git pull
|
||||
|
||||
# Create and push a tag
|
||||
VERSION="v1.0.0" # Change this
|
||||
git tag -a "$VERSION" -m "Release $VERSION
|
||||
|
||||
## What's New
|
||||
- Feature 1
|
||||
- Feature 2
|
||||
- Bug fix 1
|
||||
|
||||
## Docker Image
|
||||
\`\`\`bash
|
||||
docker pull ghcr.io/cpfarhood/devcontainer:$VERSION
|
||||
\`\`\`
|
||||
"
|
||||
|
||||
git push origin "$VERSION"
|
||||
|
||||
# The GitHub Actions workflow will automatically:
|
||||
# 1. Build the Docker image
|
||||
# 2. Push to ghcr.io with multiple tags
|
||||
# 3. Create a GitHub release with notes
|
||||
```
|
||||
|
||||
### Method 2: Using Git Tags Only
|
||||
|
||||
```bash
|
||||
git checkout main
|
||||
git pull
|
||||
|
||||
# Create annotated tag
|
||||
git tag -a v1.0.0 -m "Release v1.0.0"
|
||||
|
||||
# Push tag
|
||||
git push origin v1.0.0
|
||||
```
|
||||
|
||||
### Method 3: Using GitHub Web UI
|
||||
|
||||
1. Go to https://github.com/cpfarhood/devcontainer/releases
|
||||
2. Click "Draft a new release"
|
||||
3. Click "Choose a tag"
|
||||
4. Type the new version (e.g., `v1.0.0`)
|
||||
5. Click "Create new tag on publish"
|
||||
6. Fill in the release title and description
|
||||
7. Click "Publish release"
|
||||
|
||||
## What Happens Automatically
|
||||
|
||||
When you push a version tag (`v*`), GitHub Actions will:
|
||||
|
||||
1. **Build Docker image** with multiple tags:
|
||||
- `ghcr.io/cpfarhood/devcontainer:v1.2.3` (exact version)
|
||||
- `ghcr.io/cpfarhood/devcontainer:1.2` (minor version)
|
||||
- `ghcr.io/cpfarhood/devcontainer:1` (major version)
|
||||
- `ghcr.io/cpfarhood/devcontainer:latest` (if on default branch)
|
||||
|
||||
2. **Create GitHub Release** with:
|
||||
- Auto-generated release notes from commits
|
||||
- Docker pull command in the description
|
||||
|
||||
## Version Bump Guidelines
|
||||
|
||||
### Patch Release (v1.0.X)
|
||||
- Bug fixes
|
||||
- Documentation updates
|
||||
- Minor dependency updates
|
||||
- No new features
|
||||
- No breaking changes
|
||||
|
||||
**Example:** v1.0.1
|
||||
```bash
|
||||
git tag -a v1.0.1 -m "Release v1.0.1 - Bug fixes"
|
||||
git push origin v1.0.1
|
||||
```
|
||||
|
||||
### Minor Release (v1.X.0)
|
||||
- New features
|
||||
- New optional configuration variables
|
||||
- Enhancements to existing features
|
||||
- Backwards compatible
|
||||
- No breaking changes
|
||||
|
||||
**Example:** v1.1.0
|
||||
```bash
|
||||
git tag -a v1.1.0 -m "Release v1.1.0 - New Happy Coder features"
|
||||
git push origin v1.1.0
|
||||
```
|
||||
|
||||
### Major Release (vX.0.0)
|
||||
- Breaking changes
|
||||
- Required configuration changes
|
||||
- Removal of deprecated features
|
||||
- Incompatible API changes
|
||||
|
||||
**Example:** v2.0.0
|
||||
```bash
|
||||
git tag -a v2.0.0 -m "Release v2.0.0 - Breaking: New storage architecture"
|
||||
git push origin v2.0.0
|
||||
```
|
||||
|
||||
## Pre-releases
|
||||
|
||||
For alpha, beta, or release candidates:
|
||||
|
||||
```bash
|
||||
# Alpha
|
||||
git tag -a v1.1.0-alpha.1 -m "Release v1.1.0-alpha.1"
|
||||
git push origin v1.1.0-alpha.1
|
||||
|
||||
# Beta
|
||||
git tag -a v1.1.0-beta.1 -m "Release v1.1.0-beta.1"
|
||||
git push origin v1.1.0-beta.1
|
||||
|
||||
# Release Candidate
|
||||
git tag -a v1.1.0-rc.1 -m "Release v1.1.0-rc.1"
|
||||
git push origin v1.1.0-rc.1
|
||||
```
|
||||
|
||||
## Release Checklist
|
||||
|
||||
Before creating a release:
|
||||
|
||||
- [ ] All tests pass
|
||||
- [ ] Documentation is up to date
|
||||
- [ ] CHANGELOG.md is updated (if you maintain one)
|
||||
- [ ] Version number follows semver
|
||||
- [ ] On main/master branch
|
||||
- [ ] All changes are committed
|
||||
- [ ] Tag message includes release notes
|
||||
|
||||
## Docker Image Tags
|
||||
|
||||
Each release creates multiple Docker tags for flexibility:
|
||||
|
||||
| Git Tag | Docker Tags Created |
|
||||
|---------|---------------------|
|
||||
| v1.2.3 | `:v1.2.3`, `:1.2`, `:1`, `:latest` |
|
||||
| v2.0.0 | `:v2.0.0`, `:2.0`, `:2`, `:latest` |
|
||||
| v1.2.4-beta.1 | `:v1.2.4-beta.1`, `:1.2-beta` |
|
||||
|
||||
**Usage examples:**
|
||||
```bash
|
||||
# Specific version (recommended for production)
|
||||
docker pull ghcr.io/cpfarhood/devcontainer:v1.2.3
|
||||
|
||||
# Minor version (gets patches automatically)
|
||||
docker pull ghcr.io/cpfarhood/devcontainer:1.2
|
||||
|
||||
# Major version (gets minor updates and patches)
|
||||
docker pull ghcr.io/cpfarhood/devcontainer:1
|
||||
|
||||
# Latest (always gets newest stable release)
|
||||
docker pull ghcr.io/cpfarhood/devcontainer:latest
|
||||
```
|
||||
|
||||
## Viewing Releases
|
||||
|
||||
- **GitHub Releases:** https://github.com/cpfarhood/devcontainer/releases
|
||||
- **Docker Images:** https://github.com/cpfarhood/devcontainer/pkgs/container/devcontainer
|
||||
- **Git Tags:** `git tag -l`
|
||||
|
||||
## Deleting a Release
|
||||
|
||||
If you need to delete a bad release:
|
||||
|
||||
```bash
|
||||
# Delete local tag
|
||||
git tag -d v1.0.0
|
||||
|
||||
# Delete remote tag
|
||||
git push origin :refs/tags/v1.0.0
|
||||
|
||||
# Delete GitHub release (use web UI or gh CLI)
|
||||
gh release delete v1.0.0
|
||||
```
|
||||
|
||||
**Note:** Docker images pushed to ghcr.io cannot be easily deleted. It's better to create a new patch version.
|
||||
|
||||
## First Release
|
||||
|
||||
For the initial v1.0.0 release:
|
||||
|
||||
```bash
|
||||
git checkout main
|
||||
git pull
|
||||
|
||||
git tag -a v1.0.0 -m "Release v1.0.0 - Initial Release
|
||||
|
||||
## Features
|
||||
- Antigravity IDE with web-based VNC access
|
||||
- Happy Coder AI assistant integration
|
||||
- Automatic GitHub repository cloning
|
||||
- Persistent home directory with ReadWriteMany PVC
|
||||
- Secure non-root execution (claude user, UID 1000)
|
||||
- Support for private repositories with GitHub token
|
||||
- HTTPRoute (Gateway API) support
|
||||
- Multi-platform Docker images
|
||||
- Comprehensive deployment documentation
|
||||
|
||||
## Docker Image
|
||||
\`\`\`bash
|
||||
docker pull ghcr.io/cpfarhood/devcontainer:v1.0.0
|
||||
\`\`\`
|
||||
|
||||
## Deployment
|
||||
See DEPLOYMENT.md for complete deployment instructions.
|
||||
"
|
||||
|
||||
git push origin v1.0.0
|
||||
```
|
||||
|
||||
## Example Release Workflow
|
||||
|
||||
```bash
|
||||
# 1. Finish your feature/fix on a branch
|
||||
git checkout feature/new-feature
|
||||
git commit -m "feat: Add new feature"
|
||||
git push
|
||||
|
||||
# 2. Create PR and merge to main
|
||||
gh pr create
|
||||
# ... get approval and merge ...
|
||||
|
||||
# 3. Pull latest main
|
||||
git checkout main
|
||||
git pull
|
||||
|
||||
# 4. Create release tag
|
||||
git tag -a v1.1.0 -m "Release v1.1.0 - New feature"
|
||||
git push origin v1.1.0
|
||||
|
||||
# 5. Wait for GitHub Actions
|
||||
# - Check: https://github.com/cpfarhood/devcontainer/actions
|
||||
|
||||
# 6. Verify release
|
||||
# - GitHub: https://github.com/cpfarhood/devcontainer/releases
|
||||
# - Docker: docker pull ghcr.io/cpfarhood/devcontainer:v1.1.0
|
||||
```
|
||||
@@ -1,29 +1,23 @@
|
||||
{
|
||||
"mcpServers": {
|
||||
"github": {
|
||||
"command": "github-mcp-server",
|
||||
"args": ["stdio"],
|
||||
"env": {
|
||||
"GITHUB_PERSONAL_ACCESS_TOKEN": "${CLAUDE_GITHUB_TOKEN}"
|
||||
"type": "http",
|
||||
"url": "https://api.githubcopilot.com/mcp/",
|
||||
"headers": {
|
||||
"Authorization": "Bearer ${GITHUB_TOKEN}"
|
||||
}
|
||||
},
|
||||
"kubernetes (local)": {
|
||||
"command": "npx",
|
||||
"args": [
|
||||
"-y",
|
||||
"kubernetes-mcp-server@latest"
|
||||
]
|
||||
"kubernetes": {
|
||||
"type": "sse",
|
||||
"url": "http://localhost:8080/sse"
|
||||
},
|
||||
"flux (local)":{
|
||||
"command":"flux-operator-mcp",
|
||||
"args":["serve"],
|
||||
"env":{
|
||||
"KUBECONFIG":"/Users/cpfarhood/.kube/config"
|
||||
}
|
||||
"flux": {
|
||||
"type": "sse",
|
||||
"url": "http://localhost:8081/sse"
|
||||
},
|
||||
"playwright": {
|
||||
"command": "npx",
|
||||
"args": ["-y", "@playwright/mcp@latest"]
|
||||
"type": "sse",
|
||||
"url": "http://localhost:8086/sse"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -4,11 +4,13 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co
|
||||
|
||||
## Project Overview
|
||||
|
||||
Antigravity is a Docker-based cloud development environment that provides:
|
||||
The Dev Container is a Docker-based cloud development environment that provides:
|
||||
- Web-based GUI IDE (VSCode/Antigravity) via VNC on port 5800
|
||||
- Happy Coder AI assistant integration
|
||||
- Claude Code, OpenCode, and Crush AI coding agents (terminal-based)
|
||||
- Built-in web file manager for uploading/downloading files (optional, via `fileManager.enabled`)
|
||||
- Automatic GitHub repository cloning on startup
|
||||
- Kubernetes-native deployment with persistent home storage
|
||||
- MCP (Model Context Protocol) sidecars for AI assistant integrations
|
||||
|
||||
The stack is primarily **Bash scripts + YAML** — there is no Node.js package, compiled language, or test framework.
|
||||
|
||||
@@ -19,7 +21,7 @@ The stack is primarily **Bash scripts + YAML** — there is no Node.js package,
|
||||
```bash
|
||||
make build # Build Docker image
|
||||
make build REGISTRY=ghcr.io/myuser IMAGE_TAG=v1.0 # Custom registry/tag
|
||||
docker build -t ghcr.io/cpfarhood/antigravity:latest . # Direct build
|
||||
docker build -t ghcr.io/cpfarhood/devcontainer:latest . # Direct build
|
||||
```
|
||||
|
||||
### Running Locally
|
||||
@@ -33,12 +35,14 @@ make clean # Remove volumes
|
||||
### Kubernetes Deployment
|
||||
|
||||
```bash
|
||||
make k8s-deploy # Deploy via kustomize
|
||||
kubectl apply -k k8s/ # Direct kustomize apply
|
||||
make k8s-delete # Tear down
|
||||
make k8s-port-forward # Forward port 5800 to localhost
|
||||
make k8s-logs # Stream container logs
|
||||
make k8s-shell # Open interactive shell in pod
|
||||
GITHUB_REPO="https://github.com/user/repo" make helm-deploy # Deploy with Helm
|
||||
make helm-delete # Tear down Helm release
|
||||
make helm-port-forward # Forward port 5800 to localhost
|
||||
make helm-logs # Stream container logs
|
||||
make helm-shell # Open interactive shell in pod
|
||||
|
||||
# Or use Helm directly
|
||||
helm install mydev ./chart --set name=mydev --set githubRepo=https://github.com/user/repo
|
||||
```
|
||||
|
||||
### Other Useful Targets
|
||||
@@ -55,53 +59,155 @@ make push # Push image to registry (build first)
|
||||
```
|
||||
Container start
|
||||
→ scripts/startapp.sh
|
||||
→ scripts/init-repo.sh (clone GITHUB_REPO, start Happy Coder)
|
||||
→ launch VSCode as user `claude` in /workspace
|
||||
→ scripts/init-repo.sh
|
||||
→ Configure git user & credentials
|
||||
→ Clone GITHUB_REPOS or GITHUB_REPO (if set)
|
||||
→ Generate workspace.code-workspace for multi-repo setups
|
||||
→ Launch VSCode as user `user` in /workspace
|
||||
```
|
||||
|
||||
### Key Files
|
||||
|
||||
| File | Purpose |
|
||||
|------|---------|
|
||||
| `Dockerfile` | Image definition — installs Chrome, Node.js, VSCode, Happy Coder; creates non-root user `claude` (UID 1000) |
|
||||
| `scripts/init-repo.sh` | Clones GitHub repo, authenticates with token, starts Happy Coder background service |
|
||||
| `Dockerfile` | Image definition — installs Chrome, VSCode, Helm, gh CLI, kubeseal, Claude Code, OpenCode, Crush, LSP servers (pyright, typescript-language-server, gopls, clangd, rust-analyzer, lua-language-server, jdtls, kotlin-language-server, intelephense); creates non-root user (UID 1000) |
|
||||
| `scripts/init-repo.sh` | Configures git credentials, clones GitHub repo(s), generates multi-root workspace file |
|
||||
| `scripts/startapp.sh` | Calls init-repo.sh then opens VSCode in the workspace |
|
||||
| `k8s/statefulset.yaml` | StatefulSet + headless Service; mounts `/home` (PVC) and `/workspace` (emptyDir) |
|
||||
| `k8s/configmap.yaml` | `GITHUB_REPO`, `HAPPY_SERVER_URL`, `HAPPY_WEBAPP_URL` |
|
||||
| `k8s/httproute.yaml` | Gateway API HTTPRoute for external browser access |
|
||||
| `k8s/secrets-example.yaml` | Template for SealedSecrets (GitHub token, VNC password) |
|
||||
| `chart/` | Helm chart for Kubernetes deployment |
|
||||
| `chart/templates/deployment.yaml` | Deployment spec — main container + MCP sidecar containers |
|
||||
| `chart/templates/rbac.yaml` | ServiceAccount, Role/ClusterRole based on `clusterAccess` value |
|
||||
| `chart/templates/pvc.yaml` | PersistentVolumeClaim for user home |
|
||||
| `chart/templates/service.yaml` | ClusterIP Service (VNC + optional SSH) |
|
||||
| `chart/values.yaml` | Default Helm values |
|
||||
| `.mcp.json` | MCP server connection config (GitHub Copilot, Kubernetes, Flux, Helm, Fetch, Sequential Thinking, Playwright, pgtuner) |
|
||||
| `Makefile` | Build/deploy automation |
|
||||
|
||||
### MCP Sidecars
|
||||
|
||||
MCP (Model Context Protocol) servers run as sidecar containers in the pod, enabling AI assistants to interact with various services:
|
||||
|
||||
| Sidecar | Image | Version | Port | Endpoint | Default |
|
||||
|---------|-------|---------|------|----------|---------|
|
||||
| `kubernetes-mcp` | `quay.io/containers/kubernetes_mcp_server` | v0.0.57 | 8080 | `http://localhost:8080/sse` | Enabled |
|
||||
| `flux-mcp` | `ghcr.io/controlplaneio-fluxcd/flux-operator-mcp` | v0.41.1 | 8081 | `http://localhost:8081/sse` | Enabled |
|
||||
| `helm-mcp` | `ghcr.io/zekker6/mcp-helm` | v1.3.1 | 8012 | `http://localhost:8012/sse` | Enabled |
|
||||
| `fetch-mcp` | `mcp/fetch` | latest | 8082 | `http://localhost:8082/sse` | Enabled |
|
||||
| `sequentialthinking-mcp` | `mcp/sequentialthinking` | latest | 8083 | `http://localhost:8083/sse` | Enabled |
|
||||
| `homeassistant-mcp` | `ghcr.io/homeassistant-ai/ha-mcp` | stable | 8087 | `http://localhost:8087/sse` | Disabled |
|
||||
| `pgtuner-mcp` | `dog830228/pgtuner_mcp` | latest | 8085 | `http://localhost:8085/sse` | Disabled |
|
||||
| `playwright-mcp` | `mcr.microsoft.com/playwright/mcp` | latest | 8086 | `http://localhost:8086/sse` | Enabled |
|
||||
|
||||
**Note:**
|
||||
- GitHub MCP is accessed via the Copilot API (`https://api.githubcopilot.com/mcp/`), not as a sidecar
|
||||
- Kubernetes and Flux sidecars require `clusterAccess` != `none` to be deployed (they need RBAC permissions)
|
||||
- Kubernetes and Flux sidecars inherit the pod's ServiceAccount RBAC permissions
|
||||
- Helm sidecar enables browsing Helm repositories and chart metadata
|
||||
- Fetch sidecar provides web content fetching capabilities and HTML to markdown conversion
|
||||
- Sequential thinking sidecar enables structured thinking and problem-solving processes
|
||||
- Home Assistant sidecar requires `HOMEASSISTANT_URL` and `HOMEASSISTANT_TOKEN` in the env secret
|
||||
- PostgreSQL tuner sidecar requires `DATABASE_URI` in the env secret (PostgreSQL connection string)
|
||||
- Playwright sidecar provides browser automation and web testing capabilities
|
||||
|
||||
#### Enabling/Disabling MCP Servers
|
||||
|
||||
To control MCP sidecars, set the `enabled` flag in your values override:
|
||||
|
||||
```yaml
|
||||
# Disable all MCP sidecars
|
||||
mcp:
|
||||
sidecars:
|
||||
kubernetes:
|
||||
enabled: false
|
||||
flux:
|
||||
enabled: false
|
||||
helm:
|
||||
enabled: false
|
||||
fetch:
|
||||
enabled: false
|
||||
sequentialthinking:
|
||||
enabled: false
|
||||
homeassistant:
|
||||
enabled: false
|
||||
pgtuner:
|
||||
enabled: false
|
||||
playwright:
|
||||
enabled: false
|
||||
|
||||
# Or selectively enable/disable
|
||||
mcp:
|
||||
sidecars:
|
||||
kubernetes:
|
||||
enabled: true # Keep Kubernetes MCP enabled
|
||||
flux:
|
||||
enabled: false # Disable Flux MCP
|
||||
helm:
|
||||
enabled: true # Enable Helm MCP for chart browsing
|
||||
fetch:
|
||||
enabled: true # Enable Fetch MCP for web content fetching
|
||||
sequentialthinking:
|
||||
enabled: true # Enable Sequential Thinking MCP for problem-solving
|
||||
homeassistant:
|
||||
enabled: true # Enable Home Assistant MCP (requires secrets)
|
||||
pgtuner:
|
||||
enabled: true # Enable PostgreSQL tuner MCP (requires DATABASE_URI)
|
||||
playwright:
|
||||
enabled: true # Enable Playwright MCP for browser automation
|
||||
```
|
||||
|
||||
When deploying via Helm:
|
||||
```bash
|
||||
# Quick start (recommended)
|
||||
cp chart/values-quickstart.yaml my-values.yaml
|
||||
# Edit name and githubRepo in my-values.yaml
|
||||
helm install my-devcontainer ./chart -f my-values.yaml
|
||||
|
||||
# Using --set flags
|
||||
helm install my-devcontainer ./chart \
|
||||
--set name=mydev \
|
||||
--set githubRepo=https://github.com/user/repo \
|
||||
--set mcp.sidecars.kubernetes.enabled=false
|
||||
|
||||
# Full customization
|
||||
helm install my-devcontainer ./chart -f custom-values.yaml
|
||||
```
|
||||
|
||||
### Storage Model
|
||||
|
||||
- `/home` — ReadWriteMany PVC (persists across pod restarts, holds user config/dotfiles)
|
||||
- `/config` — ReadWriteMany PVC (persists across pod restarts, holds user config/dotfiles)
|
||||
- `/workspace` — emptyDir by default (ephemeral; can be changed to PVC)
|
||||
|
||||
### Environment Variables
|
||||
|
||||
**Required:**
|
||||
- `GITHUB_REPO` — URL of repository to clone into `/workspace`
|
||||
**Required (at least one):**
|
||||
- `GITHUB_REPO` — URL of a single repository to clone into `/workspace`
|
||||
- `GITHUB_REPOS` — Comma-separated list of repository URLs to clone (takes precedence over `GITHUB_REPO`). When multiple repos are cloned, a `workspace.code-workspace` file is generated for multi-root IDE support.
|
||||
|
||||
**Optional:**
|
||||
- `GITHUB_TOKEN` — PAT for private repo access
|
||||
- `GITHUB_TOKEN` — PAT for private repo access (automatically configures git credentials)
|
||||
- `GIT_USER_NAME` — Git user name for commits (default: "DevContainer User")
|
||||
- `GIT_USER_EMAIL` — Git user email for commits (default: "devcontainer@example.com")
|
||||
- `GITLAB_HOST` — GitLab hostname if using GitLab with same token
|
||||
- `VNC_PASSWORD` — VNC web interface password
|
||||
- `DISPLAY_WIDTH` / `DISPLAY_HEIGHT` — VNC resolution
|
||||
- `USER_ID` / `GROUP_ID` — Override UID/GID (default 1000)
|
||||
- `HAPPY_SERVER_URL` / `HAPPY_WEBAPP_URL` — Custom Happy Coder endpoints
|
||||
- `HAPPY_HOME_DIR` / `HAPPY_EXPERIMENTAL`
|
||||
- `WEB_FILE_MANAGER` — Set to `1` to enable the built-in web file manager (controlled via `fileManager.enabled` in Helm values)
|
||||
- `WEB_FILE_MANAGER_ALLOWED_PATHS` — Paths accessible by the file manager (default: `/workspace,/config`)
|
||||
- `WEB_FILE_MANAGER_DENIED_PATHS` — Paths to deny access to (takes precedence over allowed)
|
||||
|
||||
### CI/CD
|
||||
|
||||
- **`build-and-push.yaml`** — Builds and pushes to GHCR on every push to `main`, version tags (`v*`), and PRs. Tags: `latest` (main), semver, branch name, commit SHA.
|
||||
- **`release.yaml`** — Creates a GitHub Release with docker pull instructions when a version tag is pushed.
|
||||
- **`release-unified.yaml`** — Manual release workflow: bumps chart version, builds Docker image, publishes Helm chart to GitHub Pages (`https://cpfarhood.github.io/devcontainer`), and creates GitHub Release.
|
||||
- **`dependabot.yml`** — Weekly updates for GitHub Actions and Docker base image.
|
||||
|
||||
Image registry: `ghcr.io/cpfarhood/devcontainer`
|
||||
Helm repo: `https://cpfarhood.github.io/devcontainer`
|
||||
|
||||
## Kubernetes Notes
|
||||
|
||||
- Uses Kustomize (`kubectl apply -k k8s/`)
|
||||
- Storage class is `ceph-filesystem` by default — change in `statefulset.yaml` for other clusters
|
||||
- Deployed via Helm chart (`chart/`), published to GitHub Pages Helm repo, reconciled by Flux
|
||||
- Storage class is `ceph-filesystem` by default — change via `storage.className` in values
|
||||
- Resource limits: 1–4 CPU, 2–8Gi memory
|
||||
- Health checks (liveness/readiness probes) on port 5800
|
||||
- Secrets managed via SealedSecrets (see `k8s/secrets-example.yaml`)
|
||||
- Secrets: optional env Secret (`devcontainer-{name}-secrets-env`) for `GITHUB_TOKEN`, `VNC_PASSWORD`, etc.
|
||||
- RBAC: controlled by `clusterAccess` value (`none`, `readonlyns`, `readwritens`, `readonly`, `readwrite`)
|
||||
|
||||
+365
-364
@@ -1,436 +1,437 @@
|
||||
# Deployment Guide
|
||||
|
||||
This guide provides step-by-step instructions for deploying the Antigravity Dev Container to Kubernetes.
|
||||
This guide provides step-by-step instructions for deploying the Antigravity Dev Container using Helm.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- Kubernetes cluster with Gateway API support
|
||||
- Kubernetes cluster (1.19+)
|
||||
- `kubectl` configured to access your cluster
|
||||
- `helm` CLI installed (3.0+)
|
||||
- ReadWriteMany storage class available (e.g., `ceph-filesystem`, `nfs-client`, `efs-sc`)
|
||||
- Sealed Secrets controller installed (for secret encryption)
|
||||
- GitHub Container Registry access (images are public)
|
||||
|
||||
## Required Configuration Variables
|
||||
## Quick Start
|
||||
|
||||
Before deploying, you need to provide the following configuration:
|
||||
|
||||
### 1. Storage Configuration
|
||||
|
||||
**Variable:** `storageClassName`
|
||||
**Location:** `k8s/statefulset.yaml` (line ~117)
|
||||
**Description:** The ReadWriteMany storage class name in your cluster
|
||||
**Example values:**
|
||||
- `ceph-filesystem` (Rook-Ceph)
|
||||
- `nfs-client` (NFS)
|
||||
- `efs-sc` (AWS EFS)
|
||||
- `azurefile` (Azure Files)
|
||||
- `filestore` (GCP Filestore)
|
||||
|
||||
**How to find your storage class:**
|
||||
```bash
|
||||
kubectl get storageclass
|
||||
```
|
||||
|
||||
Look for a storage class that supports `ReadWriteMany` access mode.
|
||||
|
||||
### 2. GitHub Repository (Required)
|
||||
|
||||
**Variable:** `github-repo`
|
||||
**Location:** `k8s/configmap.yaml` (line ~9)
|
||||
**Description:** The GitHub repository URL to clone on container startup
|
||||
**Format:** `https://github.com/username/repository`
|
||||
**Example:** `https://github.com/cpfarhood/my-project`
|
||||
|
||||
### 3. GitHub Token (Optional, for private repos)
|
||||
|
||||
**Variable:** `github-token`
|
||||
**Location:** `k8s/secrets-example.yaml` (sealed secret)
|
||||
**Description:** GitHub Personal Access Token for cloning private repositories
|
||||
**Format:** `ghp_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx`
|
||||
**Required:** Only if cloning a private repository
|
||||
|
||||
**How to create a GitHub token:**
|
||||
1. Go to https://github.com/settings/tokens
|
||||
2. Click "Generate new token (classic)"
|
||||
3. Select scopes: `repo` (for private repos)
|
||||
4. Generate and copy the token
|
||||
|
||||
### 4. VNC Password (Optional)
|
||||
|
||||
**Variable:** `vnc-password`
|
||||
**Location:** `k8s/secrets-example.yaml` (sealed secret)
|
||||
**Description:** Password for accessing the VNC web interface
|
||||
**Format:** Any string (recommend 12+ characters)
|
||||
**Required:** Optional, but recommended for security
|
||||
|
||||
### 5. Gateway Configuration (Required for external access)
|
||||
|
||||
**Variables:**
|
||||
- `parentRefs.name` - Your Gateway resource name
|
||||
- `parentRefs.namespace` - Namespace where Gateway is deployed
|
||||
- `hostnames` - Domain name for accessing the container
|
||||
|
||||
**Location:** `k8s/httproute.yaml`
|
||||
**Example:**
|
||||
```yaml
|
||||
parentRefs:
|
||||
- name: cilium-gateway # Your Gateway name
|
||||
namespace: kube-system # Your Gateway namespace
|
||||
hostnames:
|
||||
- "devcontainer.example.com" # Your domain
|
||||
```
|
||||
|
||||
### 6. Namespace (Optional)
|
||||
|
||||
**Variable:** `namespace`
|
||||
**Location:** `k8s/kustomization.yaml` (line ~5)
|
||||
**Description:** Kubernetes namespace to deploy into
|
||||
**Default:** `default`
|
||||
**Example:** `devcontainer`, `development`, `team-workspaces`
|
||||
|
||||
### 7. Container Image (Optional)
|
||||
|
||||
**Variable:** `image`
|
||||
**Location:** `k8s/statefulset.yaml` (line ~32)
|
||||
**Description:** Docker image to use
|
||||
**Default:** `ghcr.io/cpfarhood/devcontainer:latest`
|
||||
**Format:** `registry/repository:tag`
|
||||
|
||||
### 8. Resource Limits (Optional)
|
||||
|
||||
**Variables:**
|
||||
- `resources.requests.memory` (default: `2Gi`)
|
||||
- `resources.requests.cpu` (default: `1000m`)
|
||||
- `resources.limits.memory` (default: `8Gi`)
|
||||
- `resources.limits.cpu` (default: `4000m`)
|
||||
|
||||
**Location:** `k8s/statefulset.yaml` (lines ~98-103)
|
||||
|
||||
### 9. Happy Coder Configuration (Optional)
|
||||
|
||||
**Variables:**
|
||||
- `happy-server-url` - Custom Happy server URL
|
||||
- `happy-webapp-url` - Custom Happy webapp URL
|
||||
|
||||
**Location:** `k8s/configmap.yaml` (lines ~12-13, commented out)
|
||||
**Default:** Uses Happy's default servers
|
||||
**When to set:** Only if using a self-hosted Happy instance
|
||||
|
||||
## Deployment Steps
|
||||
|
||||
### Step 1: Clone the Repository
|
||||
### 1. Clone the Repository
|
||||
|
||||
```bash
|
||||
git clone https://github.com/cpfarhood/devcontainer.git
|
||||
cd devcontainer
|
||||
```
|
||||
|
||||
### Step 2: Configure Storage Class
|
||||
### 2. Create Secret (Optional)
|
||||
|
||||
Edit `k8s/statefulset.yaml` and find the `volumeClaimTemplates` section (around line 117):
|
||||
For private repos or VNC password:
|
||||
|
||||
```bash
|
||||
# Find your storage class
|
||||
kubectl get storageclass
|
||||
|
||||
# Edit the file
|
||||
vi k8s/statefulset.yaml
|
||||
kubectl create secret generic devcontainer-mydev-secrets-env \
|
||||
--from-literal=GITHUB_TOKEN='ghp_...' \
|
||||
--from-literal=VNC_PASSWORD='changeme' \
|
||||
--from-literal=ANTHROPIC_API_KEY='sk-ant-...'
|
||||
```
|
||||
|
||||
Change `storageClassName` to match your cluster:
|
||||
```yaml
|
||||
volumeClaimTemplates:
|
||||
- metadata:
|
||||
name: userhome
|
||||
spec:
|
||||
accessModes: [ "ReadWriteMany" ]
|
||||
storageClassName: "ceph-filesystem" # ← Change this
|
||||
resources:
|
||||
requests:
|
||||
storage: 10Gi
|
||||
```
|
||||
|
||||
### Step 3: Configure GitHub Repository
|
||||
|
||||
Edit `k8s/configmap.yaml`:
|
||||
### 3. Deploy with Helm
|
||||
|
||||
```bash
|
||||
vi k8s/configmap.yaml
|
||||
# Basic deployment
|
||||
helm install mydev ./chart \
|
||||
--set name=mydev \
|
||||
--set githubRepo=https://github.com/youruser/yourrepo
|
||||
|
||||
# With custom storage class
|
||||
helm install mydev ./chart \
|
||||
--set name=mydev \
|
||||
--set githubRepo=https://github.com/youruser/yourrepo \
|
||||
--set storage.className=nfs-client
|
||||
|
||||
# With cluster access for kubectl
|
||||
helm install mydev ./chart \
|
||||
--set name=mydev \
|
||||
--set githubRepo=https://github.com/youruser/yourrepo \
|
||||
--set clusterAccess=readwritens
|
||||
```
|
||||
|
||||
Set your repository URL:
|
||||
```yaml
|
||||
data:
|
||||
github-repo: "https://github.com/yourusername/yourrepo"
|
||||
```
|
||||
|
||||
### Step 4: Configure Gateway (HTTPRoute)
|
||||
|
||||
Edit `k8s/httproute.yaml`:
|
||||
### 4. Access the Container
|
||||
|
||||
```bash
|
||||
# Find your Gateway
|
||||
kubectl get gateway -A
|
||||
|
||||
# Edit the file
|
||||
vi k8s/httproute.yaml
|
||||
```
|
||||
|
||||
Update with your Gateway details:
|
||||
```yaml
|
||||
spec:
|
||||
parentRefs:
|
||||
- name: your-gateway-name # ← Change this
|
||||
namespace: your-gateway-namespace # ← Change this
|
||||
hostnames:
|
||||
- "devcontainer.yourdomain.com" # ← Change this
|
||||
```
|
||||
|
||||
### Step 5: Create Secrets
|
||||
|
||||
Create the secrets for GitHub token and VNC password:
|
||||
|
||||
```bash
|
||||
# Create the secret
|
||||
kubectl create secret generic antigravity-secrets \
|
||||
--from-literal=github-token='ghp_your_token_here' \
|
||||
--from-literal=vnc-password='your_vnc_password' \
|
||||
--dry-run=client -o yaml | \
|
||||
kubeseal --format=yaml > k8s/sealedsecrets.yaml
|
||||
|
||||
# Verify the sealed secret was created
|
||||
cat k8s/sealedsecrets.yaml
|
||||
```
|
||||
|
||||
**If you don't have Sealed Secrets controller:**
|
||||
|
||||
Option 1: Install Sealed Secrets
|
||||
```bash
|
||||
kubectl apply -f https://github.com/bitnami-labs/sealed-secrets/releases/download/v0.24.0/controller.yaml
|
||||
```
|
||||
|
||||
Option 2: Use plain secrets (not recommended for production)
|
||||
```bash
|
||||
kubectl create secret generic antigravity-secrets \
|
||||
--from-literal=github-token='ghp_your_token_here' \
|
||||
--from-literal=vnc-password='your_vnc_password'
|
||||
```
|
||||
|
||||
### Step 6: Review Configuration (Optional)
|
||||
|
||||
Review and adjust optional settings:
|
||||
|
||||
**Namespace:**
|
||||
```bash
|
||||
vi k8s/kustomization.yaml
|
||||
# Change line 5: namespace: default
|
||||
```
|
||||
|
||||
**Resource limits:**
|
||||
```bash
|
||||
vi k8s/statefulset.yaml
|
||||
# Adjust lines 98-103 for your needs
|
||||
```
|
||||
|
||||
### Step 7: Deploy to Kubernetes
|
||||
|
||||
```bash
|
||||
# Deploy everything
|
||||
kubectl apply -k k8s/
|
||||
|
||||
# Or if you changed the namespace
|
||||
kubectl apply -k k8s/ -n your-namespace
|
||||
```
|
||||
|
||||
### Step 8: Verify Deployment
|
||||
|
||||
```bash
|
||||
# Check StatefulSet
|
||||
kubectl get statefulset antigravity
|
||||
|
||||
# Check Pod
|
||||
kubectl get pods -l app=antigravity
|
||||
|
||||
# Check PVC
|
||||
kubectl get pvc -l app=antigravity
|
||||
|
||||
# Check HTTPRoute
|
||||
kubectl get httproute antigravity
|
||||
|
||||
# View logs
|
||||
kubectl logs antigravity-0
|
||||
```
|
||||
|
||||
### Step 9: Access the Container
|
||||
|
||||
**Option A: Via HTTPRoute (external access)**
|
||||
```bash
|
||||
# Open in browser
|
||||
open https://devcontainer.yourdomain.com
|
||||
```
|
||||
|
||||
**Option B: Via Port Forward (local access)**
|
||||
```bash
|
||||
# Port forward to localhost
|
||||
kubectl port-forward statefulset/antigravity 5800:5800
|
||||
|
||||
# Open in browser
|
||||
# Port forward
|
||||
kubectl port-forward deployment/devcontainer-mydev 5800:5800
|
||||
open http://localhost:5800
|
||||
```
|
||||
|
||||
## Configuration Summary
|
||||
## Deployment Options
|
||||
|
||||
Here's a quick checklist of all variables you need to set:
|
||||
### Using Values File
|
||||
|
||||
### Required Variables
|
||||
Create a custom `values.yaml`:
|
||||
|
||||
| Variable | File | Line | Example Value |
|
||||
|----------|------|------|---------------|
|
||||
| `storageClassName` | `k8s/statefulset.yaml` | ~117 | `ceph-filesystem` |
|
||||
| `github-repo` | `k8s/configmap.yaml` | ~9 | `https://github.com/user/repo` |
|
||||
| `parentRefs.name` | `k8s/httproute.yaml` | ~8 | `cilium-gateway` |
|
||||
| `parentRefs.namespace` | `k8s/httproute.yaml` | ~9 | `kube-system` |
|
||||
| `hostnames` | `k8s/httproute.yaml` | ~10 | `devcontainer.example.com` |
|
||||
```yaml
|
||||
name: mydev
|
||||
githubRepo: https://github.com/youruser/yourrepo
|
||||
ide: vscode
|
||||
ssh: false
|
||||
|
||||
### Optional Variables
|
||||
# Storage
|
||||
storage:
|
||||
size: 32Gi
|
||||
className: ceph-filesystem
|
||||
|
||||
| Variable | File | Line | Default | When to Change |
|
||||
|----------|------|------|---------|----------------|
|
||||
| `namespace` | `k8s/kustomization.yaml` | ~5 | `default` | If deploying to different namespace |
|
||||
| `github-token` | Sealed secret | N/A | None | For private repos |
|
||||
| `vnc-password` | Sealed secret | N/A | None | For VNC security |
|
||||
| `image` | `k8s/statefulset.yaml` | ~32 | `ghcr.io/cpfarhood/devcontainer:latest` | For specific version or custom build |
|
||||
| `resources.*` | `k8s/statefulset.yaml` | ~98-103 | 2Gi/8Gi RAM, 1/4 CPU | Based on workload needs |
|
||||
| `happy-server-url` | `k8s/configmap.yaml` | ~12 | Default Happy server | For self-hosted Happy |
|
||||
| `happy-webapp-url` | `k8s/configmap.yaml` | ~13 | Default Happy webapp | For self-hosted Happy |
|
||||
# Resources
|
||||
resources:
|
||||
requests:
|
||||
memory: "4Gi"
|
||||
cpu: "2000m"
|
||||
limits:
|
||||
memory: "16Gi"
|
||||
cpu: "8000m"
|
||||
|
||||
# Kubernetes access
|
||||
clusterAccess: readwritens
|
||||
|
||||
# MCP sidecars
|
||||
mcpSidecars:
|
||||
kubernetes:
|
||||
enabled: true
|
||||
flux:
|
||||
enabled: false
|
||||
```
|
||||
|
||||
Deploy:
|
||||
|
||||
```bash
|
||||
helm install mydev ./chart -f values.yaml
|
||||
```
|
||||
|
||||
### SSH Access Setup
|
||||
|
||||
Enable SSH and add your public key:
|
||||
|
||||
```bash
|
||||
# Create secret with SSH key
|
||||
kubectl create secret generic devcontainer-mydev-secrets-env \
|
||||
--from-literal=SSH_AUTHORIZED_KEYS='ssh-ed25519 AAAA...'
|
||||
|
||||
# Deploy with SSH enabled
|
||||
helm install mydev ./chart \
|
||||
--set name=mydev \
|
||||
--set githubRepo=https://github.com/youruser/yourrepo \
|
||||
--set ssh=true
|
||||
|
||||
# Connect via SSH
|
||||
kubectl port-forward deployment/devcontainer-mydev 2222:22
|
||||
ssh -p 2222 user@localhost
|
||||
```
|
||||
|
||||
### MCP Sidecar Configuration
|
||||
|
||||
Control MCP servers for AI-assisted operations.
|
||||
|
||||
**Important:** Kubernetes and Flux MCP sidecars are only deployed when:
|
||||
1. They are enabled in values (`mcpSidecars.<name>.enabled: true`)
|
||||
2. AND `clusterAccess` is not `none` (they need RBAC permissions to function)
|
||||
|
||||
```bash
|
||||
# Disable all MCP sidecars
|
||||
helm install mydev ./chart \
|
||||
--set name=mydev \
|
||||
--set githubRepo=https://github.com/youruser/yourrepo \
|
||||
--set mcpSidecars.kubernetes.enabled=false \
|
||||
--set mcpSidecars.flux.enabled=false \
|
||||
--set mcpSidecars.homeassistant.enabled=false
|
||||
|
||||
# Enable only Kubernetes MCP
|
||||
helm install mydev ./chart \
|
||||
--set name=mydev \
|
||||
--set githubRepo=https://github.com/youruser/yourrepo \
|
||||
--set mcpSidecars.kubernetes.enabled=true \
|
||||
--set mcpSidecars.flux.enabled=false
|
||||
|
||||
# Enable Home Assistant MCP (requires credentials)
|
||||
kubectl create secret generic devcontainer-mydev-secrets-env \
|
||||
--from-literal=homeassistant-url='http://homeassistant.local:8123' \
|
||||
--from-literal=homeassistant-token='your_long_lived_token'
|
||||
|
||||
helm install mydev ./chart \
|
||||
--set name=mydev \
|
||||
--set githubRepo=https://github.com/youruser/yourrepo \
|
||||
--set mcpSidecars.homeassistant.enabled=true
|
||||
```
|
||||
|
||||
### Cluster Access Levels
|
||||
|
||||
Configure Kubernetes RBAC permissions:
|
||||
|
||||
| Value | Scope | Permissions | Use Case |
|
||||
|-------|-------|-------------|----------|
|
||||
| `none` | No access | None | Default, isolated development |
|
||||
| `readonlyns` | Namespace | Read-only | View resources in namespace |
|
||||
| `readwritens` | Namespace | Full access | Deploy apps in namespace |
|
||||
| `readonly` | Cluster-wide | Read-only | View all cluster resources |
|
||||
| `readwrite` | Cluster-wide | Full access | Cluster administration |
|
||||
|
||||
```bash
|
||||
# Example: Full access within namespace
|
||||
helm install mydev ./chart \
|
||||
--set name=mydev \
|
||||
--set githubRepo=https://github.com/youruser/yourrepo \
|
||||
--set clusterAccess=readwritens
|
||||
```
|
||||
|
||||
## Ingress Configuration
|
||||
|
||||
### Using Gateway API HTTPRoute
|
||||
|
||||
Create an HTTPRoute for external access:
|
||||
|
||||
```yaml
|
||||
apiVersion: gateway.networking.k8s.io/v1beta1
|
||||
kind: HTTPRoute
|
||||
metadata:
|
||||
name: devcontainer-mydev
|
||||
spec:
|
||||
parentRefs:
|
||||
- name: your-gateway
|
||||
namespace: your-gateway-namespace
|
||||
hostnames:
|
||||
- devcontainer.example.com
|
||||
rules:
|
||||
- backendRefs:
|
||||
- name: devcontainer-mydev
|
||||
port: 5800
|
||||
```
|
||||
|
||||
### Using Traditional Ingress
|
||||
|
||||
Create an Ingress resource:
|
||||
|
||||
```yaml
|
||||
apiVersion: networking.k8s.io/v1
|
||||
kind: Ingress
|
||||
metadata:
|
||||
name: devcontainer-mydev
|
||||
spec:
|
||||
rules:
|
||||
- host: devcontainer.example.com
|
||||
http:
|
||||
paths:
|
||||
- path: /
|
||||
pathType: Prefix
|
||||
backend:
|
||||
service:
|
||||
name: devcontainer-mydev
|
||||
port:
|
||||
number: 5800
|
||||
```
|
||||
|
||||
## Advanced Configurations
|
||||
|
||||
### Custom Display Resolution
|
||||
|
||||
```bash
|
||||
helm install mydev ./chart \
|
||||
--set name=mydev \
|
||||
--set githubRepo=https://github.com/youruser/yourrepo \
|
||||
--set display.width=2560 \
|
||||
--set display.height=1440
|
||||
```
|
||||
|
||||
### Different IDE Options
|
||||
|
||||
```bash
|
||||
# Use Google Antigravity
|
||||
helm install mydev ./chart \
|
||||
--set name=mydev \
|
||||
--set githubRepo=https://github.com/youruser/yourrepo \
|
||||
--set ide=antigravity
|
||||
|
||||
# SSH-only mode (no GUI)
|
||||
helm install mydev ./chart \
|
||||
--set name=mydev \
|
||||
--set githubRepo=https://github.com/youruser/yourrepo \
|
||||
--set ide=none \
|
||||
--set ssh=true
|
||||
```
|
||||
|
||||
## Helm Operations
|
||||
|
||||
### List Deployments
|
||||
|
||||
```bash
|
||||
helm list
|
||||
```
|
||||
|
||||
### Upgrade Deployment
|
||||
|
||||
```bash
|
||||
# Change values
|
||||
helm upgrade mydev ./chart \
|
||||
--set name=mydev \
|
||||
--set githubRepo=https://github.com/youruser/newrepo
|
||||
|
||||
# Upgrade with new chart version
|
||||
git pull
|
||||
helm upgrade mydev ./chart
|
||||
```
|
||||
|
||||
### Uninstall
|
||||
|
||||
```bash
|
||||
helm uninstall mydev
|
||||
|
||||
# Note: PVC persists by default
|
||||
kubectl delete pvc userhome-mydev
|
||||
```
|
||||
|
||||
### Rollback
|
||||
|
||||
```bash
|
||||
# View history
|
||||
helm history mydev
|
||||
|
||||
# Rollback to previous version
|
||||
helm rollback mydev
|
||||
|
||||
# Rollback to specific revision
|
||||
helm rollback mydev 3
|
||||
```
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Pod not starting
|
||||
### Pod Not Starting
|
||||
|
||||
**Check events:**
|
||||
```bash
|
||||
kubectl describe pod antigravity-0
|
||||
# Check pod status
|
||||
kubectl get pods -l app.kubernetes.io/instance=mydev
|
||||
|
||||
# Describe pod for events
|
||||
kubectl describe pod -l app.kubernetes.io/instance=mydev
|
||||
|
||||
# Check logs
|
||||
kubectl logs deployment/devcontainer-mydev
|
||||
```
|
||||
|
||||
**Common issues:**
|
||||
- Storage class doesn't support ReadWriteMany
|
||||
- PVC not binding (check storage class exists)
|
||||
- Image pull errors (check image name)
|
||||
### Repository Not Cloning
|
||||
|
||||
### Repository not cloning
|
||||
|
||||
**Check logs:**
|
||||
```bash
|
||||
kubectl logs antigravity-0 | grep -A 10 "Repository Initialization"
|
||||
# Check init logs
|
||||
kubectl logs deployment/devcontainer-mydev | grep "Repository Initialization"
|
||||
|
||||
# Verify secret exists
|
||||
kubectl get secret devcontainer-mydev-secrets-env
|
||||
|
||||
# Check environment
|
||||
kubectl exec deployment/devcontainer-mydev -- env | grep GITHUB
|
||||
```
|
||||
|
||||
**Common issues:**
|
||||
- Invalid GitHub URL
|
||||
- Private repo without token
|
||||
- Token doesn't have correct permissions
|
||||
### VNC Not Accessible
|
||||
|
||||
### HTTPRoute not working
|
||||
|
||||
**Check HTTPRoute:**
|
||||
```bash
|
||||
kubectl describe httproute antigravity
|
||||
# Check service
|
||||
kubectl get svc devcontainer-mydev
|
||||
kubectl describe svc devcontainer-mydev
|
||||
|
||||
# Test with port-forward
|
||||
kubectl port-forward deployment/devcontainer-mydev 5800:5800
|
||||
```
|
||||
|
||||
**Common issues:**
|
||||
- Gateway name/namespace incorrect
|
||||
- Domain not pointing to Gateway
|
||||
- TLS certificate not issued
|
||||
### MCP Sidecar Issues
|
||||
|
||||
### VNC not accessible
|
||||
|
||||
**Check service:**
|
||||
```bash
|
||||
kubectl get svc antigravity
|
||||
kubectl describe svc antigravity
|
||||
# Check all containers
|
||||
kubectl get pod -l app.kubernetes.io/instance=mydev -o jsonpath='{.items[0].spec.containers[*].name}'
|
||||
|
||||
# Check MCP container logs
|
||||
kubectl logs deployment/devcontainer-mydev -c kubernetes-mcp
|
||||
kubectl logs deployment/devcontainer-mydev -c flux-mcp
|
||||
kubectl logs deployment/devcontainer-mydev -c homeassistant-mcp
|
||||
|
||||
# Verify RBAC permissions (for Kubernetes/Flux MCP)
|
||||
kubectl auth can-i --list --as system:serviceaccount:default:devcontainer-mydev
|
||||
|
||||
# Check Home Assistant MCP credentials
|
||||
kubectl get secret devcontainer-mydev-secrets-env -o jsonpath='{.data.homeassistant-url}' | base64 -d
|
||||
# Verify the URL is accessible from the pod
|
||||
kubectl exec deployment/devcontainer-mydev -- curl -s http://homeassistant.local:8123/api/
|
||||
```
|
||||
|
||||
**Port forward test:**
|
||||
### Storage Issues
|
||||
|
||||
```bash
|
||||
kubectl port-forward antigravity-0 5800:5800
|
||||
# Try accessing http://localhost:5800
|
||||
# Check PVC
|
||||
kubectl get pvc userhome-mydev
|
||||
kubectl describe pvc userhome-mydev
|
||||
|
||||
# Check available storage classes
|
||||
kubectl get storageclass
|
||||
|
||||
# Verify ReadWriteMany support
|
||||
kubectl get storageclass <class-name> -o yaml | grep -i accessmodes
|
||||
```
|
||||
|
||||
## Quick Deploy Example
|
||||
## Best Practices
|
||||
|
||||
Complete deployment with all values filled in:
|
||||
### Production Deployment
|
||||
|
||||
1. **Use specific image tags** instead of `latest`:
|
||||
```bash
|
||||
helm install mydev ./chart --set image.tag=v1.0.0
|
||||
```
|
||||
|
||||
2. **Set resource limits** appropriately:
|
||||
```yaml
|
||||
resources:
|
||||
requests:
|
||||
memory: "4Gi"
|
||||
cpu: "2000m"
|
||||
limits:
|
||||
memory: "8Gi"
|
||||
cpu: "4000m"
|
||||
```
|
||||
|
||||
3. **Enable VNC password**:
|
||||
```bash
|
||||
kubectl create secret generic devcontainer-mydev-secrets-env \
|
||||
--from-literal=VNC_PASSWORD='strong-password-here'
|
||||
```
|
||||
|
||||
4. **Use dedicated namespace**:
|
||||
```bash
|
||||
kubectl create namespace dev-environments
|
||||
helm install mydev ./chart -n dev-environments
|
||||
```
|
||||
|
||||
5. **Configure appropriate cluster access**:
|
||||
- Use `readonlyns` or `readwritens` for namespace-scoped work
|
||||
- Avoid `readwrite` cluster-wide access unless necessary
|
||||
|
||||
### Multi-User Deployment
|
||||
|
||||
For teams, create separate deployments per user:
|
||||
|
||||
```bash
|
||||
# 1. Set your values
|
||||
STORAGE_CLASS="ceph-filesystem"
|
||||
GITHUB_REPO="https://github.com/myuser/myproject"
|
||||
GITHUB_TOKEN="ghp_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
|
||||
VNC_PASSWORD="my-secure-password-123"
|
||||
GATEWAY_NAME="cilium-gateway"
|
||||
GATEWAY_NAMESPACE="kube-system"
|
||||
DOMAIN="devcontainer.example.com"
|
||||
# User 1
|
||||
helm install alice-dev ./chart \
|
||||
--set name=alice-dev \
|
||||
--set githubRepo=https://github.com/alice/project
|
||||
|
||||
# 2. Update storage class
|
||||
sed -i "s/storageClassName: .*/storageClassName: \"$STORAGE_CLASS\"/" k8s/statefulset.yaml
|
||||
|
||||
# 3. Update GitHub repo
|
||||
sed -i "s|github-repo: .*|github-repo: \"$GITHUB_REPO\"|" k8s/configmap.yaml
|
||||
|
||||
# 4. Update Gateway
|
||||
sed -i "s/- name: gateway/- name: $GATEWAY_NAME/" k8s/httproute.yaml
|
||||
sed -i "s/namespace: gateway-system/namespace: $GATEWAY_NAMESPACE/" k8s/httproute.yaml
|
||||
sed -i "s/antigravity.example.com/$DOMAIN/" k8s/httproute.yaml
|
||||
|
||||
# 5. Create sealed secret
|
||||
kubectl create secret generic antigravity-secrets \
|
||||
--from-literal=github-token="$GITHUB_TOKEN" \
|
||||
--from-literal=vnc-password="$VNC_PASSWORD" \
|
||||
--dry-run=client -o yaml | \
|
||||
kubeseal --format=yaml > k8s/sealedsecrets.yaml
|
||||
|
||||
# 6. Deploy
|
||||
kubectl apply -k k8s/
|
||||
|
||||
# 7. Watch deployment
|
||||
kubectl get pods -l app=antigravity -w
|
||||
# User 2
|
||||
helm install bob-dev ./chart \
|
||||
--set name=bob-dev \
|
||||
--set githubRepo=https://github.com/bob/project
|
||||
```
|
||||
|
||||
## Updates and Maintenance
|
||||
### Backup and Recovery
|
||||
|
||||
### Updating the Image
|
||||
|
||||
The image is automatically built and pushed to ghcr.io on every commit to main.
|
||||
|
||||
**To use latest:**
|
||||
```bash
|
||||
kubectl set image statefulset/antigravity \
|
||||
antigravity=ghcr.io/cpfarhood/devcontainer:latest
|
||||
```
|
||||
|
||||
**To use specific version:**
|
||||
```bash
|
||||
kubectl set image statefulset/antigravity \
|
||||
antigravity=ghcr.io/cpfarhood/devcontainer:v1.0.0
|
||||
```
|
||||
|
||||
### Changing Repository
|
||||
|
||||
Edit the ConfigMap and restart:
|
||||
```bash
|
||||
kubectl edit configmap antigravity-config
|
||||
# Change github-repo value
|
||||
kubectl rollout restart statefulset/antigravity
|
||||
```
|
||||
|
||||
### Scaling
|
||||
The home directory persists on PVC. To backup:
|
||||
|
||||
```bash
|
||||
# Scale to multiple instances (each gets own home PVC)
|
||||
kubectl scale statefulset antigravity --replicas=3
|
||||
# Create backup pod
|
||||
kubectl run backup --image=busybox --restart=Never --rm -i --tty \
|
||||
-- tar czf - -C /home . | gzip > home-backup.tar.gz
|
||||
```
|
||||
|
||||
## Support
|
||||
|
||||
For issues or questions:
|
||||
- GitHub Issues: https://github.com/cpfarhood/devcontainer/issues
|
||||
- Documentation: https://github.com/cpfarhood/devcontainer
|
||||
- Documentation: https://github.com/cpfarhood/devcontainer
|
||||
+184
-16
@@ -1,7 +1,10 @@
|
||||
FROM jlesage/baseimage-gui:ubuntu-22.04-v4
|
||||
|
||||
# Bust cache for all layers below (base image pull is still cached)
|
||||
ARG CACHE_BUST
|
||||
|
||||
# Set environment variables
|
||||
ENV APP_NAME="Antigravity Dev Container" \
|
||||
ENV APP_NAME="Dev Container" \
|
||||
KEEP_APP_RUNNING=1 \
|
||||
DISPLAY_WIDTH=1920 \
|
||||
DISPLAY_HEIGHT=1080 \
|
||||
@@ -25,30 +28,191 @@ RUN apt-get update && apt-get install -y \
|
||||
sudo \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
# Install Chrome
|
||||
# Install Chrome and xdg-utils (needed for xdg-open to work in VNC)
|
||||
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 && \
|
||||
apt-get update && \
|
||||
apt-get install -y google-chrome-stable && \
|
||||
apt-get install -y google-chrome-stable xdg-utils && \
|
||||
rm -rf /var/lib/apt/lists/*
|
||||
|
||||
# Install Node.js (LTS version for Happy Coder)
|
||||
RUN curl -fsSL https://deb.nodesource.com/setup_lts.x | bash - && \
|
||||
# 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.
|
||||
# Cleans up crash lock files and suppresses the crash-restore bubble so that
|
||||
# sessions/cookies survive unclean pod shutdowns (SIGKILL).
|
||||
RUN printf '#!/bin/bash\n\
|
||||
CHROME_DIR="/config/userdata/.config/google-chrome"\n\
|
||||
mkdir -p "$CHROME_DIR"\n\
|
||||
# Remove stale lock files left by unclean container shutdown\n\
|
||||
rm -f "$CHROME_DIR/SingletonLock" "$CHROME_DIR/SingletonSocket" "$CHROME_DIR/SingletonCookie"\n\
|
||||
# Mark the previous session as clean so Chrome does not clear cookies\n\
|
||||
PREFS="$CHROME_DIR/Default/Preferences"\n\
|
||||
if [ -f "$PREFS" ]; then\n\
|
||||
sed -i '\''s/"exit_type":"Crashed"/"exit_type":"Normal"/g; s/"exited_cleanly":false/"exited_cleanly":true/g'\'' "$PREFS"\n\
|
||||
fi\n\
|
||||
exec /usr/bin/google-chrome-stable \\\n\
|
||||
--no-sandbox \\\n\
|
||||
--disable-dev-shm-usage \\\n\
|
||||
--disable-gpu \\\n\
|
||||
--disable-session-crashed-bubble \\\n\
|
||||
--user-data-dir="$CHROME_DIR" \\\n\
|
||||
"$@"\n' > /usr/local/bin/google-chrome && \
|
||||
chmod +x /usr/local/bin/google-chrome
|
||||
|
||||
# Install Node.js LTS via NodeSource
|
||||
ARG NODE_MAJOR=22
|
||||
RUN curl -fsSL https://deb.nodesource.com/setup_${NODE_MAJOR}.x | bash - && \
|
||||
apt-get install -y nodejs && \
|
||||
rm -rf /var/lib/apt/lists/* && \
|
||||
node --version && npm --version
|
||||
|
||||
# Install Claude Code native binary (npm wrapper breaks remote control)
|
||||
RUN curl -fsSL https://claude.ai/install.sh | bash && \
|
||||
cp /root/.local/bin/claude /usr/local/bin/claude && \
|
||||
rm -rf /root/.local/bin/claude && \
|
||||
claude --version
|
||||
|
||||
# Disable Claude Code auto-updater (doesn't work inside Docker)
|
||||
RUN mkdir -p /etc/skel/.claude && \
|
||||
echo '{"env":{"DISABLE_AUTOUPDATER":"1"}}' > /etc/skel/.claude/settings.json
|
||||
|
||||
# Install OpenCode AI coding agent
|
||||
RUN OPENCODE_VERSION=$(curl -sL https://api.github.com/repos/opencode-ai/opencode/releases/latest | jq -r '.tag_name') && \
|
||||
curl -fsSL "https://github.com/opencode-ai/opencode/releases/download/${OPENCODE_VERSION}/opencode-linux-x86_64.tar.gz" | \
|
||||
tar -xz -C /usr/local/bin opencode && \
|
||||
chmod +x /usr/local/bin/opencode
|
||||
|
||||
# Install Crush AI coding agent (OpenCode successor by Charm)
|
||||
RUN CRUSH_VERSION=$(curl -sL https://api.github.com/repos/charmbracelet/crush/releases/latest | jq -r '.tag_name' | sed 's/^v//') && \
|
||||
curl -fsSL "https://github.com/charmbracelet/crush/releases/download/v${CRUSH_VERSION}/crush_${CRUSH_VERSION}_Linux_x86_64.tar.gz" -o /tmp/crush.tar.gz && \
|
||||
tar -xzf /tmp/crush.tar.gz -C /tmp && \
|
||||
mv /tmp/crush_${CRUSH_VERSION}_Linux_x86_64/crush /usr/local/bin/crush && \
|
||||
chmod +x /usr/local/bin/crush && \
|
||||
rm -rf /tmp/crush*
|
||||
|
||||
# Install Helm CLI for Kubernetes chart management
|
||||
ARG HELM_VERSION=3.17.1
|
||||
RUN curl -fsSL "https://get.helm.sh/helm-v${HELM_VERSION}-linux-amd64.tar.gz" | \
|
||||
tar -xz --strip-components=1 -C /usr/local/bin linux-amd64/helm && \
|
||||
chmod +x /usr/local/bin/helm
|
||||
|
||||
# Install OpenTofu (open-source Terraform alternative)
|
||||
ARG OPENTOFU_VERSION=1.11.5
|
||||
RUN curl -fsSL "https://github.com/opentofu/opentofu/releases/download/v${OPENTOFU_VERSION}/tofu_${OPENTOFU_VERSION}_linux_amd64.zip" -o /tmp/tofu.zip && \
|
||||
unzip -o /tmp/tofu.zip -d /usr/local/bin tofu && \
|
||||
chmod +x /usr/local/bin/tofu && \
|
||||
rm /tmp/tofu.zip
|
||||
|
||||
# Install GitHub CLI (gh) via official APT repo
|
||||
RUN curl -fsSL https://cli.github.com/packages/githubcli-archive-keyring.gpg | dd of=/usr/share/keyrings/githubcli-archive-keyring.gpg && \
|
||||
chmod go+r /usr/share/keyrings/githubcli-archive-keyring.gpg && \
|
||||
echo "deb [arch=amd64 signed-by=/usr/share/keyrings/githubcli-archive-keyring.gpg] https://cli.github.com/packages stable main" > /etc/apt/sources.list.d/github-cli.list && \
|
||||
apt-get update && \
|
||||
apt-get install -y gh && \
|
||||
rm -rf /var/lib/apt/lists/*
|
||||
|
||||
# Install Happy Coder globally
|
||||
RUN npm install -g happy-coder
|
||||
# Install kubeseal CLI for Bitnami Sealed Secrets
|
||||
RUN KUBESEAL_VERSION=$(curl -sL https://api.github.com/repos/bitnami-labs/sealed-secrets/releases/latest | jq -r '.tag_name' | sed 's/^v//') && \
|
||||
curl -fsSL "https://github.com/bitnami-labs/sealed-secrets/releases/download/v${KUBESEAL_VERSION}/kubeseal-${KUBESEAL_VERSION}-linux-amd64.tar.gz" | \
|
||||
tar -xz -C /usr/local/bin kubeseal && \
|
||||
chmod +x /usr/local/bin/kubeseal
|
||||
|
||||
# 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 && \
|
||||
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 && \
|
||||
# ── LSP servers for Claude Code language intelligence ──
|
||||
|
||||
# npm-based LSP servers: Python (pyright), TypeScript/JavaScript, PHP
|
||||
RUN npm install -g pyright typescript-language-server typescript intelephense
|
||||
|
||||
# Install Go runtime and gopls LSP server
|
||||
ARG GO_VERSION=1.23.6
|
||||
RUN curl -fsSL "https://go.dev/dl/go${GO_VERSION}.linux-amd64.tar.gz" | tar -xz -C /usr/local && \
|
||||
/usr/local/go/bin/go install golang.org/x/tools/gopls@latest && \
|
||||
mv /root/go/bin/gopls /usr/local/bin/gopls && \
|
||||
rm -rf /root/go
|
||||
ENV PATH="/usr/local/go/bin:${PATH}"
|
||||
|
||||
# Install clangd LSP server (C/C++)
|
||||
RUN apt-get update && \
|
||||
apt-get install -y clangd && \
|
||||
rm -rf /var/lib/apt/lists/*
|
||||
|
||||
# Install rust-analyzer LSP server (Rust) — standalone binary, no full toolchain needed
|
||||
RUN RUST_ANALYZER_VERSION=$(curl -sL https://api.github.com/repos/rust-lang/rust-analyzer/releases/latest | jq -r '.tag_name') && \
|
||||
curl -fsSL "https://github.com/rust-lang/rust-analyzer/releases/download/${RUST_ANALYZER_VERSION}/rust-analyzer-x86_64-unknown-linux-gnu.gz" | \
|
||||
gunzip > /usr/local/bin/rust-analyzer && \
|
||||
chmod +x /usr/local/bin/rust-analyzer
|
||||
|
||||
# Install lua-language-server (Lua)
|
||||
RUN LUA_LS_VERSION=$(curl -sL https://api.github.com/repos/LuaLS/lua-language-server/releases/latest | jq -r '.tag_name') && \
|
||||
mkdir -p /opt/lua-language-server && \
|
||||
curl -fsSL "https://github.com/LuaLS/lua-language-server/releases/download/${LUA_LS_VERSION}/lua-language-server-${LUA_LS_VERSION}-linux-x64.tar.gz" | \
|
||||
tar -xz -C /opt/lua-language-server && \
|
||||
ln -s /opt/lua-language-server/bin/lua-language-server /usr/local/bin/lua-language-server
|
||||
|
||||
# Install JDK for Java/Kotlin LSP servers
|
||||
RUN apt-get update && \
|
||||
apt-get install -y openjdk-17-jdk-headless && \
|
||||
rm -rf /var/lib/apt/lists/*
|
||||
|
||||
# Install kotlin-language-server
|
||||
RUN KLS_VERSION=$(curl -sL https://api.github.com/repos/fwcd/kotlin-language-server/releases/latest | jq -r '.tag_name') && \
|
||||
curl -fsSL "https://github.com/fwcd/kotlin-language-server/releases/download/${KLS_VERSION}/server.zip" -o /tmp/kls.zip && \
|
||||
unzip -o /tmp/kls.zip -d /opt/kotlin-language-server && \
|
||||
ln -s /opt/kotlin-language-server/server/bin/kotlin-language-server /usr/local/bin/kotlin-language-server && \
|
||||
rm /tmp/kls.zip
|
||||
|
||||
# Install jdtls (Java LSP) — Eclipse JDT Language Server
|
||||
RUN JDTLS_TARBALL=$(curl -sL https://download.eclipse.org/jdtls/snapshots/latest.txt) && \
|
||||
mkdir -p /opt/jdtls && \
|
||||
curl -fsSL "https://download.eclipse.org/jdtls/snapshots/${JDTLS_TARBALL}" | tar -xz -C /opt/jdtls && \
|
||||
printf '#!/bin/bash\nexec java -Declipse.application=org.eclipse.jdt.ls.core.id1 -Dosgi.bundles.defaultStartLevel=4 -Declipse.product=org.eclipse.jdt.ls.core.product -jar /opt/jdtls/plugins/org.eclipse.equinox.launcher_*.jar -configuration /opt/jdtls/config_linux "$@"\n' > /usr/local/bin/jdtls && \
|
||||
chmod +x /usr/local/bin/jdtls
|
||||
|
||||
# Install VSCode (using Microsoft's current recommended setup)
|
||||
RUN wget -qO- https://packages.microsoft.com/keys/microsoft.asc | gpg --dearmor > /tmp/microsoft.gpg && \
|
||||
install -D -o root -g root -m 644 /tmp/microsoft.gpg /usr/share/keyrings/microsoft.gpg && \
|
||||
rm -f /tmp/microsoft.gpg && \
|
||||
printf 'Types: deb\nURIs: https://packages.microsoft.com/repos/code\nSuites: stable\nComponents: main\nArchitectures: amd64\nSigned-By: /usr/share/keyrings/microsoft.gpg\n' \
|
||||
> /etc/apt/sources.list.d/vscode.sources && \
|
||||
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 && \
|
||||
# Clear package cache to force fresh repository data
|
||||
rm -rf /var/lib/apt/lists/* && \
|
||||
apt-get update && \
|
||||
# Show available versions for debugging
|
||||
apt-cache policy antigravity && \
|
||||
# Install latest version
|
||||
apt-get install -y --no-install-recommends antigravity && \
|
||||
# Display installed version
|
||||
dpkg -l | grep antigravity && \
|
||||
rm -rf /var/lib/apt/lists/*
|
||||
|
||||
# Pre-configure Antigravity to skip onboarding/setup on first run
|
||||
RUN mkdir -p /etc/skel/.config/antigravity/User/globalStorage && \
|
||||
echo '{"antigravityUnifiedStateSync.seenNuxOneTimeMigration": true, "antigravityUnifiedStateSync.browserOnboarding.completed": true, "antigravityUnifiedStateSync.hasOnboardingCompleted": true, "browserOnboarding.hasSeenWelcome": true, "antigravityUnifiedStateSync.browserPreferences.hasAddedLocalhostToAllowlist": true, "antigravityUnifiedStateSync.oauthToken.hasLegacyMigrated": true, "antigravityUnifiedStateSync.auth.tokenSyncEnabled": true, "antigravityUnifiedStateSync.auth.cloudSyncEnabled": true, "theme": "vs-dark"}' \
|
||||
> /etc/skel/.config/antigravity/User/globalStorage/storage.json && \
|
||||
echo '{"workbench.startupEditor": "none", "workbench.welcomePage.walkthroughs.openOnInstall": false, "workbench.tips.enabled": false, "extensions.ignoreRecommendations": true, "telemetry.telemetryLevel": "off", "update.mode": "none", "extensions.autoUpdate": false, "extensions.autoCheckUpdates": false, "workbench.enableExperiments": true, "workbench.settings.enableNaturalLanguageSearch": true, "antigravity.onboarding.completed": true, "antigravity.browserOnboarding.completed": true, "antigravity.setup.completed": true, "antigravity.ai.enabled": true, "antigravity.ai.autoComplete.enabled": true, "antigravity.ai.chat.enabled": true, "antigravity.ai.codeActions.enabled": true, "antigravity.ai.explainCode.enabled": true, "antigravity.ai.generateCode.enabled": true, "antigravity.ai.optimizeCode.enabled": true, "antigravity.ai.autoSuggest.enabled": true, "antigravity.telemetry.crashReporter": "on", "antigravity.ai.acceptTerms": true, "antigravity.auth.syncState": true, "antigravity.auth.enableTokenSync": true, "antigravity.ai.enableCloudSync": true, "antigravity.settings.sync": true}' \
|
||||
> /etc/skel/.config/antigravity/User/settings.json && \
|
||||
# Validate Antigravity installation
|
||||
/usr/share/antigravity/antigravity --version || echo "WARNING: Antigravity version check failed"
|
||||
|
||||
# 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 && \
|
||||
@@ -58,19 +222,23 @@ RUN groupadd -g 1000 user && \
|
||||
RUN mkdir -p /workspace && \
|
||||
chown -R user:user /workspace
|
||||
|
||||
# Copy startup script
|
||||
# Copy startup scripts
|
||||
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
|
||||
|
||||
# Configure container to run as user user
|
||||
ENV HOME=/home/user \
|
||||
USER=user
|
||||
ENV HOME=/config/userdata \
|
||||
USER=user \
|
||||
BROWSER=/usr/local/bin/google-chrome
|
||||
|
||||
# Expose VNC port (baseimage-gui default)
|
||||
EXPOSE 5800
|
||||
|
||||
# Set app name for baseimage-gui
|
||||
RUN set-cont-env APP_NAME "Antigravity"
|
||||
RUN set-cont-env APP_NAME "Dev Container"
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
# Variables
|
||||
REGISTRY ?= ghcr.io/cpfarhood
|
||||
IMAGE_NAME ?= antigravity
|
||||
IMAGE_NAME ?= devcontainer
|
||||
IMAGE_TAG ?= latest
|
||||
FULL_IMAGE = $(REGISTRY)/$(IMAGE_NAME):$(IMAGE_TAG)
|
||||
|
||||
@@ -26,48 +26,61 @@ run:
|
||||
-e GITHUB_REPO="${GITHUB_REPO}" \
|
||||
-e GITHUB_TOKEN="${GITHUB_TOKEN}" \
|
||||
-e VNC_PASSWORD="${VNC_PASSWORD}" \
|
||||
-e HAPPY_EXPERIMENTAL="true" \
|
||||
-v $(PWD)/home:/home \
|
||||
-v $(PWD)/workspace:/workspace \
|
||||
--name antigravity \
|
||||
--name devcontainer \
|
||||
$(FULL_IMAGE)
|
||||
@echo "Access at http://localhost:5800"
|
||||
|
||||
# Stop the running container
|
||||
stop:
|
||||
@echo "Stopping antigravity container..."
|
||||
docker stop antigravity || true
|
||||
docker rm antigravity || true
|
||||
@echo "Stopping devcontainer..."
|
||||
docker stop devcontainer || true
|
||||
docker rm devcontainer || true
|
||||
|
||||
# Clean up local volumes
|
||||
clean: stop
|
||||
@echo "Cleaning up..."
|
||||
rm -rf ./home ./workspace
|
||||
|
||||
# Kubernetes deployment
|
||||
k8s-deploy:
|
||||
@echo "Deploying to Kubernetes..."
|
||||
kubectl apply -k k8s/
|
||||
# Helm deployment
|
||||
RELEASE_NAME ?= mydev
|
||||
NAMESPACE ?= default
|
||||
|
||||
k8s-delete:
|
||||
@echo "Deleting from Kubernetes..."
|
||||
kubectl delete -k k8s/
|
||||
helm-deploy:
|
||||
@echo "Deploying with Helm (release: $(RELEASE_NAME))..."
|
||||
@if [ -z "$(GITHUB_REPO)" ]; then \
|
||||
echo "ERROR: GITHUB_REPO environment variable is required"; \
|
||||
echo "Usage: GITHUB_REPO=https://github.com/user/repo make helm-deploy"; \
|
||||
exit 1; \
|
||||
fi
|
||||
helm upgrade --install $(RELEASE_NAME) ./chart \
|
||||
--namespace $(NAMESPACE) \
|
||||
--set name=$(RELEASE_NAME) \
|
||||
--set githubRepo="$(GITHUB_REPO)" \
|
||||
--set image.repository=$(REGISTRY)/$(IMAGE_NAME) \
|
||||
--set image.tag=$(IMAGE_TAG)
|
||||
|
||||
k8s-logs:
|
||||
@echo "Showing logs..."
|
||||
kubectl logs -f antigravity-0
|
||||
helm-delete:
|
||||
@echo "Deleting Helm release $(RELEASE_NAME)..."
|
||||
helm uninstall $(RELEASE_NAME) --namespace $(NAMESPACE)
|
||||
@echo "Note: PVC persists. To delete: kubectl delete pvc userhome-$(RELEASE_NAME) -n $(NAMESPACE)"
|
||||
|
||||
k8s-shell:
|
||||
@echo "Opening shell..."
|
||||
kubectl exec -it antigravity-0 -- bash
|
||||
helm-logs:
|
||||
@echo "Showing logs for $(RELEASE_NAME)..."
|
||||
kubectl logs -f deployment/devcontainer-$(RELEASE_NAME) -n $(NAMESPACE)
|
||||
|
||||
k8s-port-forward:
|
||||
@echo "Port forwarding to localhost:5800..."
|
||||
kubectl port-forward antigravity-0 5800:5800
|
||||
helm-shell:
|
||||
@echo "Opening shell in $(RELEASE_NAME)..."
|
||||
kubectl exec -it deployment/devcontainer-$(RELEASE_NAME) -n $(NAMESPACE) -- bash
|
||||
|
||||
helm-port-forward:
|
||||
@echo "Port forwarding $(RELEASE_NAME) to localhost:5800..."
|
||||
kubectl port-forward deployment/devcontainer-$(RELEASE_NAME) 5800:5800 -n $(NAMESPACE)
|
||||
|
||||
# Show help
|
||||
help:
|
||||
@echo "Antigravity Dev Container Makefile"
|
||||
@echo "Dev Container Makefile"
|
||||
@echo ""
|
||||
@echo "Usage: make [target]"
|
||||
@echo ""
|
||||
@@ -78,24 +91,29 @@ help:
|
||||
@echo " stop - Stop running container"
|
||||
@echo " clean - Clean up containers and volumes"
|
||||
@echo ""
|
||||
@echo "Kubernetes Targets:"
|
||||
@echo " k8s-deploy - Deploy to Kubernetes"
|
||||
@echo " k8s-delete - Delete from Kubernetes"
|
||||
@echo " k8s-logs - Show container logs"
|
||||
@echo " k8s-shell - Open shell in container"
|
||||
@echo " k8s-port-forward - Port forward to localhost"
|
||||
@echo "Helm/Kubernetes Targets:"
|
||||
@echo " helm-deploy - Deploy with Helm chart (requires GITHUB_REPO)"
|
||||
@echo " helm-delete - Delete Helm release"
|
||||
@echo " helm-logs - Show container logs"
|
||||
@echo " helm-shell - Open shell in container"
|
||||
@echo " helm-port-forward - Port forward to localhost"
|
||||
@echo ""
|
||||
@echo "Variables:"
|
||||
@echo " REGISTRY - Docker registry (default: ghcr.io/cpfarhood)"
|
||||
@echo " IMAGE_NAME - Image name (default: antigravity)"
|
||||
@echo " IMAGE_NAME - Image name (default: devcontainer)"
|
||||
@echo " IMAGE_TAG - Image tag (default: latest)"
|
||||
@echo " RELEASE_NAME - Helm release name (default: mydev)"
|
||||
@echo " NAMESPACE - Kubernetes namespace (default: default)"
|
||||
@echo " GITHUB_REPO - GitHub repository URL (required for helm-deploy)"
|
||||
@echo ""
|
||||
@echo "Environment Variables for 'make run':"
|
||||
@echo " GITHUB_REPO - GitHub repository URL"
|
||||
@echo " GITHUB_TOKEN - GitHub token (optional)"
|
||||
@echo " VNC_PASSWORD - VNC password (optional)"
|
||||
@echo ""
|
||||
@echo "Example:"
|
||||
@echo "Examples:"
|
||||
@echo " make build"
|
||||
@echo " make push REGISTRY=ghcr.io/myuser IMAGE_TAG=v1.0"
|
||||
@echo " GITHUB_REPO=https://github.com/user/repo make run"
|
||||
@echo " GITHUB_REPO=https://github.com/user/repo make helm-deploy"
|
||||
@echo " RELEASE_NAME=alice-dev GITHUB_REPO=https://github.com/alice/project make helm-deploy"
|
||||
|
||||
@@ -1,367 +1,440 @@
|
||||
# Antigravity Dev Container
|
||||
# Dev Container
|
||||
|
||||

|
||||
|
||||
A containerized development environment with GUI access, featuring:
|
||||
- **Antigravity** (VSCode/Cloud IDE) via web browser
|
||||
- **Happy Coder** - AI-powered development assistant
|
||||
- **Automatic GitHub repo cloning**
|
||||
- **Persistent user home directory**
|
||||
- **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)
|
||||
A containerized cloud development environment with web-based GUI access, featuring:
|
||||
- **VSCode or Google Antigravity** via browser-based VNC (port 5800)
|
||||
- **SSH access** option (OpenSSH on port 22, additive with any IDE)
|
||||
- **Claude Code**, **OpenCode**, and **Crush** AI coding agents (terminal-based)
|
||||
- **Built-in web file manager** for uploading/downloading files via the VNC web interface
|
||||
- **Helm CLI** included for Kubernetes chart development and deployment
|
||||
- **Automatic GitHub repo cloning** on startup
|
||||
- **Persistent home directory** via ReadWriteMany PVC
|
||||
- **Kubernetes-native** Helm chart deployment
|
||||
|
||||
## Quick Start
|
||||
|
||||
**👉 For detailed deployment instructions, see [DEPLOYMENT.md](DEPLOYMENT.md)**
|
||||
|
||||
### 1. Get the Image
|
||||
|
||||
The image is automatically built and published to GitHub Container Registry on every push to main.
|
||||
### Option A: Install from Helm Repo (Recommended)
|
||||
|
||||
```bash
|
||||
# Pull the latest image
|
||||
docker pull ghcr.io/cpfarhood/devcontainer:latest
|
||||
# Add the Helm repository
|
||||
helm repo add devcontainer https://cpfarhood.github.io/devcontainer
|
||||
helm repo update
|
||||
|
||||
# Or pull a specific version
|
||||
docker pull ghcr.io/cpfarhood/devcontainer:v1.0.0
|
||||
# Deploy with one command
|
||||
helm install mydev devcontainer/devcontainer \
|
||||
--set name=mydev \
|
||||
--set githubRepo=https://github.com/youruser/yourrepo
|
||||
```
|
||||
|
||||
**Building locally (optional):**
|
||||
### Option B: Install from Source
|
||||
|
||||
```bash
|
||||
# Clone and customize the quickstart template
|
||||
cp chart/values-quickstart.yaml my-values.yaml
|
||||
# Edit my-values.yaml to set your name and repository
|
||||
|
||||
helm install mydev ./chart -f my-values.yaml
|
||||
```
|
||||
|
||||
### Option C: One-Command from Source
|
||||
|
||||
```bash
|
||||
helm install mydev ./chart \
|
||||
--set name=mydev \
|
||||
--set githubRepo=https://github.com/youruser/yourrepo
|
||||
```
|
||||
|
||||
### Option C: Full Configuration
|
||||
|
||||
### 1. Create a secret
|
||||
|
||||
The secret is picked up automatically via `envFrom`. Keys recognised:
|
||||
|
||||
| Key | Purpose |
|
||||
|-----|---------|
|
||||
| `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`) |
|
||||
| `HOMEASSISTANT_URL` | Home Assistant URL (required when `mcp.sidecars.homeassistant.enabled: true`) |
|
||||
| `HOMEASSISTANT_TOKEN` | Home Assistant long-lived access token (required when `mcp.sidecars.homeassistant.enabled: true`) |
|
||||
| `DATABASE_URI` | PostgreSQL connection string (required when `mcp.sidecars.pgtuner.enabled: true`) |
|
||||
| `PGTUNER_EXCLUDE_USERIDS` | Comma-separated PostgreSQL user OIDs to exclude from monitoring (optional) |
|
||||
|
||||
```bash
|
||||
kubectl create secret generic devcontainer-mydev-secrets-env \
|
||||
--from-literal=GITHUB_TOKEN='ghp_...' \
|
||||
--from-literal=VNC_PASSWORD='changeme'
|
||||
```
|
||||
|
||||
Or use SealedSecrets:
|
||||
|
||||
```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
|
||||
|
||||
The Helm chart uses a logical organization with these main sections:
|
||||
- **Basic Configuration**: name, image, githubRepo
|
||||
- **Access & Interface**: IDE, SSH, display, user settings
|
||||
- **Infrastructure**: storage, resources, cluster access
|
||||
- **Integrations**: MCP sidecars
|
||||
- **Smart Defaults**: auto-detection and profiles
|
||||
|
||||
📖 **Documentation**:
|
||||
- [USAGE.md](chart/USAGE.md) - Comprehensive examples and scenarios
|
||||
- [values-quickstart.yaml](chart/values-quickstart.yaml) - Minimal configuration
|
||||
- [values.schema.json](chart/values.schema.json) - IDE validation support
|
||||
|
||||
### Core values
|
||||
|
||||
| Value | Default | Description |
|
||||
|-------|---------|-------------|
|
||||
| `name` | `""` | Instance name — used in all resource names (`devcontainer-{name}`) |
|
||||
| `githubRepo` | `""` | Repository to clone into `/workspace` on startup |
|
||||
| `ide.type` | `vscode` | IDE to launch — `vscode`, `antigravity`, or `none` (see below) |
|
||||
| `ssh.enabled` | `false` | Also start an OpenSSH server on port 22 (additive, any IDE) |
|
||||
| `fileManager.enabled` | `false` | Enable the built-in web file manager for upload/download |
|
||||
| `image.repository` | `ghcr.io/cpfarhood/devcontainer` | Container image |
|
||||
| `image.tag` | `latest` | Image tag |
|
||||
|
||||
### IDE choice
|
||||
|
||||
`ide.type` 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.enabled: true` starts OpenSSH on port 22 **in addition to** the IDE. It works with any `ide.type` value:
|
||||
|
||||
```bash
|
||||
# SSH-only (no VNC)
|
||||
helm install mydev ./chart --set name=mydev --set ide.type=none --set ssh.enabled=true
|
||||
|
||||
# VSCode in VNC + SSH access at the same time
|
||||
helm install mydev ./chart --set name=mydev --set ssh.enabled=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
|
||||
```
|
||||
|
||||
### Web file manager
|
||||
|
||||
The base image includes a built-in web file manager for uploading and downloading files through the VNC web interface (port 5800). No additional sidecar is needed.
|
||||
|
||||
| Value | Default | Description |
|
||||
|-------|---------|-------------|
|
||||
| `fileManager.enabled` | `false` | Enable the web file manager |
|
||||
| `fileManager.allowedPaths` | `/workspace,/config` | Paths accessible by the file manager (`AUTO`, `ALL`, or comma-separated) |
|
||||
| `fileManager.deniedPaths` | `""` | Paths to deny (takes precedence over allowed) |
|
||||
|
||||
```bash
|
||||
# Enable the file manager
|
||||
helm install mydev ./chart \
|
||||
--set name=mydev \
|
||||
--set githubRepo=https://github.com/youruser/yourrepo \
|
||||
--set fileManager.enabled=true
|
||||
```
|
||||
|
||||
### 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.
|
||||
|
||||
### MCP Sidecars
|
||||
|
||||
The devcontainer includes MCP (Model Context Protocol) servers as sidecar containers that enable AI assistants to interact with various services:
|
||||
|
||||
| Sidecar | Default | Purpose |
|
||||
|---------|---------|---------|
|
||||
| `mcp.sidecars.kubernetes.enabled` | `true` | Kubernetes API access via MCP |
|
||||
| `mcp.sidecars.flux.enabled` | `true` | Flux GitOps operations via MCP |
|
||||
| `mcp.sidecars.homeassistant.enabled` | `false` | Home Assistant smart home control via MCP |
|
||||
| `mcp.sidecars.pgtuner.enabled` | `false` | PostgreSQL performance tuning and analysis via MCP |
|
||||
| `mcp.sidecars.playwright.enabled` | `true` | Browser automation and web testing via MCP |
|
||||
|
||||
**Notes:**
|
||||
- GitHub MCP is accessed via the Copilot API (`https://api.githubcopilot.com/mcp/`), not as a sidecar
|
||||
- Kubernetes and Flux sidecars require `clusterAccess` != `none` to be deployed (automatically disabled when no cluster access)
|
||||
- Kubernetes and Flux sidecars inherit the pod's ServiceAccount RBAC permissions (controlled by `clusterAccess`)
|
||||
- Home Assistant sidecar requires `HOMEASSISTANT_URL` and `HOMEASSISTANT_TOKEN` in the env secret
|
||||
- PostgreSQL tuner sidecar requires `DATABASE_URI` in the env secret (PostgreSQL connection string)
|
||||
- Playwright sidecar provides browser automation and web testing capabilities
|
||||
|
||||
**Disable MCP sidecars:**
|
||||
```bash
|
||||
# Disable multiple sidecars
|
||||
helm install mydev ./chart \
|
||||
--set name=mydev \
|
||||
--set githubRepo=https://github.com/youruser/yourrepo \
|
||||
--set mcp.sidecars.kubernetes.enabled=false \
|
||||
--set mcp.sidecars.flux.enabled=false \
|
||||
--set mcp.sidecars.playwright.enabled=false
|
||||
|
||||
# Or selectively disable
|
||||
helm install mydev ./chart \
|
||||
--set name=mydev \
|
||||
--set githubRepo=https://github.com/youruser/yourrepo \
|
||||
--set mcp.sidecars.flux.enabled=false # Disable only Flux MCP
|
||||
```
|
||||
|
||||
**Enable Home Assistant MCP:**
|
||||
```bash
|
||||
# Create secret with Home Assistant credentials
|
||||
kubectl create secret generic devcontainer-mydev-secrets-env \
|
||||
--from-literal=GITHUB_TOKEN='ghp_...' \
|
||||
--from-literal=HOMEASSISTANT_URL='http://homeassistant.local:8123' \
|
||||
--from-literal=HOMEASSISTANT_TOKEN='your_long_lived_access_token'
|
||||
|
||||
# Deploy with Home Assistant MCP enabled
|
||||
helm install mydev ./chart \
|
||||
--set name=mydev \
|
||||
--set githubRepo=https://github.com/youruser/yourrepo \
|
||||
--set mcp.sidecars.homeassistant.enabled=true
|
||||
```
|
||||
|
||||
**Enable PostgreSQL Tuner MCP:**
|
||||
```bash
|
||||
# Create secret with PostgreSQL connection string
|
||||
kubectl create secret generic devcontainer-mydev-secrets-env \
|
||||
--from-literal=GITHUB_TOKEN='ghp_...' \
|
||||
--from-literal=DATABASE_URI='postgresql://user:password@postgres.example.com:5432/dbname'
|
||||
|
||||
# Deploy with PostgreSQL tuner MCP enabled
|
||||
helm install mydev ./chart \
|
||||
--set name=mydev \
|
||||
--set githubRepo=https://github.com/youruser/yourrepo \
|
||||
--set mcp.sidecars.pgtuner.enabled=true
|
||||
```
|
||||
|
||||
**Custom MCP configuration:**
|
||||
```yaml
|
||||
# values.yaml override
|
||||
mcp:
|
||||
sidecars:
|
||||
kubernetes:
|
||||
enabled: true
|
||||
image:
|
||||
repository: quay.io/containers/kubernetes_mcp_server
|
||||
tag: v0.0.57
|
||||
port: 8080
|
||||
resources:
|
||||
requests:
|
||||
memory: "64Mi"
|
||||
cpu: "50m"
|
||||
limits:
|
||||
memory: "256Mi"
|
||||
cpu: "500m"
|
||||
flux:
|
||||
enabled: false # Disabled in this example
|
||||
homeassistant:
|
||||
enabled: true
|
||||
image:
|
||||
repository: ghcr.io/homeassistant-ai/ha-mcp
|
||||
tag: stable
|
||||
port: 8087
|
||||
pgtuner:
|
||||
enabled: true
|
||||
image:
|
||||
repository: dog830228/pgtuner_mcp
|
||||
tag: latest
|
||||
port: 8085
|
||||
playwright:
|
||||
enabled: true
|
||||
image:
|
||||
repository: mcr.microsoft.com/playwright/mcp
|
||||
tag: latest
|
||||
port: 8086
|
||||
resources:
|
||||
requests:
|
||||
memory: "128Mi"
|
||||
cpu: "100m"
|
||||
limits:
|
||||
memory: "512Mi"
|
||||
cpu: "1000m"
|
||||
```
|
||||
|
||||
### Display and resources
|
||||
|
||||
| Value | Default | Description |
|
||||
|-------|---------|-------------|
|
||||
| `display.width` | `1920` | VNC width (px) |
|
||||
| `display.height` | `1080` | VNC height (px) |
|
||||
| `display.secureConnection` | `0` | Set to `1` if TLS is not terminated upstream |
|
||||
| `user.id` | `1000` | UID for the app user |
|
||||
| `user.groupId` | `1000` | GID for the app user |
|
||||
| `storage.size` | `32Gi` | Home PVC size |
|
||||
| `storage.className` | `ceph-filesystem` | StorageClass (must be ReadWriteMany) |
|
||||
| `shm.sizeLimit` | `2Gi` | `/dev/shm` size (memory-backed; used by Electron apps) |
|
||||
| `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}
|
||||
→ IDE=vscode: code --new-window --wait /workspace/{repo}
|
||||
IDE=antigravity: antigravity --no-sandbox --user-data-dir ~/.config/antigravity ... /workspace/{repo}
|
||||
IDE=none: sleep infinity
|
||||
(SSH=true: sshd also running as root on port 22; host keys persisted on PVC)
|
||||
```
|
||||
|
||||
### Storage
|
||||
|
||||
| Mount | Source | Persistence |
|
||||
|-------|--------|-------------|
|
||||
| `/config` | ReadWriteMany PVC (`userhome-{name}`) | Survives pod restarts — stores Claude credentials, dotfiles, git config |
|
||||
| `/workspace` | `emptyDir` | Ephemeral — repo is re-cloned on each pod start |
|
||||
|
||||
---
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### 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 app.kubernetes.io/instance=mydev
|
||||
```
|
||||
|
||||
### Pod not picking up new image after upgrade
|
||||
|
||||
The chart uses `image.tag: latest`. Kubernetes won't restart the pod on a Helm upgrade unless the Deployment spec changes. Force a restart manually:
|
||||
|
||||
```bash
|
||||
kubectl rollout restart deployment/devcontainer-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
|
||||
docker build -t ghcr.io/cpfarhood/devcontainer:latest .
|
||||
docker push ghcr.io/cpfarhood/devcontainer:latest
|
||||
```
|
||||
|
||||
### 2. Configure Secrets
|
||||
The image is also built and pushed automatically by CI on every push to `main` and on version tags (`v*`).
|
||||
|
||||
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
|
||||
|
||||
- Built on [jlesage/baseimage-gui](https://github.com/jlesage/docker-baseimage-gui)
|
||||
- Uses [Happy Coder](https://happy.engineering)
|
||||
- Inspired by Google's Project IDX
|
||||
- Base image: [jlesage/docker-baseimage-gui](https://github.com/jlesage/docker-baseimage-gui)
|
||||
- AI assistant: [Claude](https://claude.ai)
|
||||
|
||||
+384
-275
@@ -1,305 +1,414 @@
|
||||
# Configuration Variables Reference
|
||||
# Helm Chart Values Reference
|
||||
|
||||
Quick reference for all configurable variables in this project.
|
||||
Complete reference for all configurable values in the Antigravity Dev Container Helm chart.
|
||||
|
||||
## Required Variables
|
||||
## Core Configuration
|
||||
|
||||
These MUST be configured before deployment:
|
||||
|
||||
### Storage Class Name
|
||||
- **Variable:** `storageClassName`
|
||||
- **File:** `k8s/statefulset.yaml`
|
||||
- **Line:** ~117
|
||||
### name
|
||||
- **Type:** String
|
||||
- **Description:** ReadWriteMany storage class available in your cluster
|
||||
- **Example:** `ceph-filesystem`, `nfs-client`, `efs-sc`
|
||||
- **How to find:** `kubectl get storageclass`
|
||||
|
||||
### GitHub Repository URL
|
||||
- **Variable:** `github-repo`
|
||||
- **File:** `k8s/configmap.yaml`
|
||||
- **Line:** ~9
|
||||
- **Type:** String (URL)
|
||||
- **Description:** Repository to clone on container startup
|
||||
- **Format:** `https://github.com/username/repository`
|
||||
- **Example:** `https://github.com/cpfarhood/my-project`
|
||||
|
||||
### Gateway Name
|
||||
- **Variable:** `parentRefs[0].name`
|
||||
- **File:** `k8s/httproute.yaml`
|
||||
- **Line:** ~8
|
||||
- **Type:** String
|
||||
- **Description:** Name of your Gateway resource
|
||||
- **How to find:** `kubectl get gateway -A`
|
||||
|
||||
### Gateway Namespace
|
||||
- **Variable:** `parentRefs[0].namespace`
|
||||
- **File:** `k8s/httproute.yaml`
|
||||
- **Line:** ~9
|
||||
- **Type:** String
|
||||
- **Description:** Namespace where Gateway is deployed
|
||||
- **How to find:** `kubectl get gateway -A`
|
||||
|
||||
### Domain Hostname
|
||||
- **Variable:** `hostnames[0]`
|
||||
- **File:** `k8s/httproute.yaml`
|
||||
- **Line:** ~11
|
||||
- **Type:** String (FQDN)
|
||||
- **Description:** Domain name for accessing the container
|
||||
- **Example:** `devcontainer.example.com`
|
||||
|
||||
## Optional Variables
|
||||
|
||||
### GitHub Token
|
||||
- **Variable:** `github-token`
|
||||
- **File:** Sealed Secret
|
||||
- **Type:** String (GitHub PAT)
|
||||
- **Description:** Personal Access Token for private repos
|
||||
- **Required:** Only for private repositories
|
||||
- **Format:** `ghp_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx`
|
||||
- **Scopes:** `repo`
|
||||
|
||||
### VNC Password
|
||||
- **Variable:** `vnc-password`
|
||||
- **File:** Sealed Secret
|
||||
- **Type:** String
|
||||
- **Description:** Password for VNC web interface
|
||||
- **Required:** Recommended for security
|
||||
- **Format:** Any string (12+ characters recommended)
|
||||
|
||||
### Namespace
|
||||
- **Variable:** `namespace`
|
||||
- **File:** `k8s/kustomization.yaml`
|
||||
- **Line:** ~5
|
||||
- **Type:** String
|
||||
- **Description:** Kubernetes namespace for deployment
|
||||
- **Default:** `default`
|
||||
|
||||
### Container Image
|
||||
- **Variable:** `image`
|
||||
- **File:** `k8s/statefulset.yaml`
|
||||
- **Line:** ~32
|
||||
- **Type:** String (image reference)
|
||||
- **Description:** Docker image to deploy
|
||||
- **Default:** `ghcr.io/cpfarhood/devcontainer:latest`
|
||||
- **Format:** `registry/repository:tag`
|
||||
|
||||
### Memory Request
|
||||
- **Variable:** `resources.requests.memory`
|
||||
- **File:** `k8s/statefulset.yaml`
|
||||
- **Line:** ~99
|
||||
- **Type:** String (quantity)
|
||||
- **Description:** Minimum memory to reserve
|
||||
- **Default:** `2Gi`
|
||||
- **Format:** `<number>Gi` or `<number>Mi`
|
||||
|
||||
### Memory Limit
|
||||
- **Variable:** `resources.limits.memory`
|
||||
- **File:** `k8s/statefulset.yaml`
|
||||
- **Line:** ~102
|
||||
- **Type:** String (quantity)
|
||||
- **Description:** Maximum memory allowed
|
||||
- **Default:** `8Gi`
|
||||
- **Format:** `<number>Gi` or `<number>Mi`
|
||||
|
||||
### CPU Request
|
||||
- **Variable:** `resources.requests.cpu`
|
||||
- **File:** `k8s/statefulset.yaml`
|
||||
- **Line:** ~100
|
||||
- **Type:** String (quantity)
|
||||
- **Description:** Minimum CPU to reserve
|
||||
- **Default:** `1000m` (1 core)
|
||||
- **Format:** `<number>m` (millicores) or `<number>` (cores)
|
||||
|
||||
### CPU Limit
|
||||
- **Variable:** `resources.limits.cpu`
|
||||
- **File:** `k8s/statefulset.yaml`
|
||||
- **Line:** ~103
|
||||
- **Type:** String (quantity)
|
||||
- **Description:** Maximum CPU allowed
|
||||
- **Default:** `4000m` (4 cores)
|
||||
- **Format:** `<number>m` (millicores) or `<number>` (cores)
|
||||
|
||||
### Storage Size
|
||||
- **Variable:** `storage` (under volumeClaimTemplates)
|
||||
- **File:** `k8s/statefulset.yaml`
|
||||
- **Line:** ~120
|
||||
- **Type:** String (quantity)
|
||||
- **Description:** Size of home directory PVC
|
||||
- **Default:** `10Gi`
|
||||
- **Format:** `<number>Gi` or `<number>Ti`
|
||||
|
||||
### Happy Server URL
|
||||
- **Variable:** `happy-server-url`
|
||||
- **File:** `k8s/configmap.yaml`
|
||||
- **Line:** ~12 (commented)
|
||||
- **Type:** String (URL)
|
||||
- **Description:** Custom Happy Coder server
|
||||
- **Default:** `https://api.cluster-fluster.com`
|
||||
- **When to set:** Self-hosted Happy instance only
|
||||
|
||||
### Happy Webapp URL
|
||||
- **Variable:** `happy-webapp-url`
|
||||
- **File:** `k8s/configmap.yaml`
|
||||
- **Line:** ~13 (commented)
|
||||
- **Type:** String (URL)
|
||||
- **Description:** Custom Happy Coder webapp
|
||||
- **Default:** `https://app.happy.engineering`
|
||||
- **When to set:** Self-hosted Happy instance only
|
||||
|
||||
### Display Width
|
||||
- **Variable:** `DISPLAY_WIDTH`
|
||||
- **File:** `k8s/statefulset.yaml`
|
||||
- **Line:** ~56
|
||||
- **Type:** String (number)
|
||||
- **Description:** VNC display width in pixels
|
||||
- **Default:** `1920`
|
||||
|
||||
### Display Height
|
||||
- **Variable:** `DISPLAY_HEIGHT`
|
||||
- **File:** `k8s/statefulset.yaml`
|
||||
- **Line:** ~58
|
||||
- **Type:** String (number)
|
||||
- **Description:** VNC display height in pixels
|
||||
- **Default:** `1080`
|
||||
|
||||
### User ID
|
||||
- **Variable:** `USER_ID`
|
||||
- **File:** `k8s/statefulset.yaml`
|
||||
- **Line:** ~51
|
||||
- **Type:** String (number)
|
||||
- **Description:** UID for claude user
|
||||
- **Default:** `1000`
|
||||
|
||||
### Group ID
|
||||
- **Variable:** `GROUP_ID`
|
||||
- **File:** `k8s/statefulset.yaml`
|
||||
- **Line:** ~53
|
||||
- **Type:** String (number)
|
||||
- **Description:** GID for claude user
|
||||
- **Default:** `1000`
|
||||
|
||||
### StatefulSet Replicas
|
||||
- **Variable:** `replicas`
|
||||
- **File:** `k8s/statefulset.yaml`
|
||||
- **Line:** ~21
|
||||
- **Type:** Integer
|
||||
- **Description:** Number of container instances
|
||||
- **Default:** `1`
|
||||
- **Note:** Each replica gets own home PVC
|
||||
|
||||
## Environment Variables (Runtime)
|
||||
|
||||
These are set at runtime, not in configuration files:
|
||||
|
||||
### GITHUB_REPO
|
||||
- **Type:** String (URL)
|
||||
- **Description:** Repository URL (from ConfigMap)
|
||||
- **Default:** `""`
|
||||
- **Required:** Yes
|
||||
- **Source:** ConfigMap `antigravity.github-repo`
|
||||
- **Description:** Instance name used to generate resource names (`devcontainer-{name}`, `userhome-{name}`)
|
||||
- **Example:** `mydev`, `alice-dev`, `team-workspace`
|
||||
|
||||
### GITHUB_TOKEN
|
||||
### githubRepo
|
||||
- **Type:** String
|
||||
- **Description:** GitHub PAT (from Secret)
|
||||
- **Required:** No (only for private repos)
|
||||
- **Source:** Secret `antigravity.github-token`
|
||||
- **Default:** `""`
|
||||
- **Required:** Yes
|
||||
- **Description:** GitHub repository URL to clone into `/workspace`
|
||||
- **Example:** `https://github.com/username/repository`
|
||||
|
||||
### VNC_PASSWORD
|
||||
### ide
|
||||
- **Type:** String
|
||||
- **Description:** VNC password (from Secret)
|
||||
- **Required:** No
|
||||
- **Source:** Secret `antigravity.vnc-password`
|
||||
- **Default:** `vscode`
|
||||
- **Options:** `vscode`, `antigravity`, `none`
|
||||
- **Description:** IDE to launch inside the container
|
||||
- `vscode` — VSCode via VNC browser UI on port 5800
|
||||
- `antigravity` — Google Antigravity (VSCode fork) via VNC on port 5800
|
||||
- `none` — No IDE; useful when `ssh: true` is the sole access method
|
||||
|
||||
### HAPPY_SERVER_URL
|
||||
- **Type:** String (URL)
|
||||
- **Description:** Happy server URL (from ConfigMap)
|
||||
- **Required:** No
|
||||
- **Source:** ConfigMap `antigravity.happy-server-url`
|
||||
### ssh
|
||||
- **Type:** Boolean
|
||||
- **Default:** `false`
|
||||
- **Description:** Start an OpenSSH server on port 22 in addition to the IDE
|
||||
- **Note:** Requires `SSH_AUTHORIZED_KEYS` in env secret for key-based login
|
||||
|
||||
### HAPPY_WEBAPP_URL
|
||||
- **Type:** String (URL)
|
||||
- **Description:** Happy webapp URL (from ConfigMap)
|
||||
- **Required:** No
|
||||
- **Source:** ConfigMap `antigravity.happy-webapp-url`
|
||||
## Image Configuration
|
||||
|
||||
### HAPPY_HOME_DIR
|
||||
- **Type:** String (path)
|
||||
- **Description:** Happy data directory
|
||||
- **Required:** No
|
||||
- **Default:** `/home/claude/.happy`
|
||||
- **Source:** Hardcoded in StatefulSet
|
||||
### image.repository
|
||||
- **Type:** String
|
||||
- **Default:** `ghcr.io/cpfarhood/devcontainer`
|
||||
- **Description:** Container image repository
|
||||
|
||||
### HAPPY_EXPERIMENTAL
|
||||
- **Type:** String (boolean)
|
||||
- **Description:** Enable Happy experimental features
|
||||
- **Required:** No
|
||||
### image.tag
|
||||
- **Type:** String
|
||||
- **Default:** `latest`
|
||||
- **Description:** Container image tag
|
||||
- **Best Practice:** Use specific version tags for production
|
||||
|
||||
### image.pullPolicy
|
||||
- **Type:** String
|
||||
- **Default:** `Always`
|
||||
- **Options:** `Always`, `IfNotPresent`, `Never`
|
||||
- **Description:** Image pull policy
|
||||
|
||||
## Display Configuration
|
||||
|
||||
### display.width
|
||||
- **Type:** String
|
||||
- **Default:** `"1920"`
|
||||
- **Description:** VNC display width in pixels
|
||||
|
||||
### display.height
|
||||
- **Type:** String
|
||||
- **Default:** `"1080"`
|
||||
- **Description:** VNC display height in pixels
|
||||
|
||||
### secureConnection
|
||||
- **Type:** String
|
||||
- **Default:** `"0"`
|
||||
- **Options:** `"0"`, `"1"`
|
||||
- **Description:** Set to `"0"` when TLS is terminated at the gateway layer
|
||||
|
||||
## User Configuration
|
||||
|
||||
### userId
|
||||
- **Type:** String
|
||||
- **Default:** `"1000"`
|
||||
- **Description:** UID for the app user
|
||||
|
||||
### groupId
|
||||
- **Type:** String
|
||||
- **Default:** `"1000"`
|
||||
- **Description:** GID for the app user
|
||||
|
||||
## Storage Configuration
|
||||
|
||||
### storage.size
|
||||
- **Type:** String
|
||||
- **Default:** `32Gi`
|
||||
- **Description:** Size of the persistent home directory
|
||||
- **Format:** Kubernetes quantity (e.g., `10Gi`, `100Gi`, `1Ti`)
|
||||
|
||||
### storage.className
|
||||
- **Type:** String
|
||||
- **Default:** `ceph-filesystem`
|
||||
- **Description:** StorageClass name (must support ReadWriteMany)
|
||||
- **Examples:** `ceph-filesystem`, `nfs-client`, `efs-sc`, `azurefile`
|
||||
|
||||
### shm.sizeLimit
|
||||
- **Type:** String
|
||||
- **Default:** `2Gi`
|
||||
- **Description:** `/dev/shm` size (memory-backed emptyDir for Electron apps)
|
||||
|
||||
## Resource Limits
|
||||
|
||||
### resources.requests.memory
|
||||
- **Type:** String
|
||||
- **Default:** `2Gi`
|
||||
- **Description:** Minimum memory to reserve
|
||||
- **Format:** Kubernetes quantity
|
||||
|
||||
### resources.requests.cpu
|
||||
- **Type:** String
|
||||
- **Default:** `1000m`
|
||||
- **Description:** Minimum CPU to reserve
|
||||
- **Format:** Millicores (`1000m` = 1 CPU core)
|
||||
|
||||
### resources.limits.memory
|
||||
- **Type:** String
|
||||
- **Default:** `8Gi`
|
||||
- **Description:** Maximum memory allowed
|
||||
- **Format:** Kubernetes quantity
|
||||
|
||||
### resources.limits.cpu
|
||||
- **Type:** String
|
||||
- **Default:** `4000m`
|
||||
- **Description:** Maximum CPU allowed
|
||||
- **Format:** Millicores (`4000m` = 4 CPU cores)
|
||||
|
||||
## Kubernetes Access
|
||||
|
||||
### clusterAccess
|
||||
- **Type:** String
|
||||
- **Default:** `none`
|
||||
- **Options:**
|
||||
- `none` — No cluster access
|
||||
- `readonlyns` — Read-only access to release namespace
|
||||
- `readwritens` — Full access to release namespace
|
||||
- `readonly` — Read-only access cluster-wide
|
||||
- `readwrite` — Full access cluster-wide
|
||||
- **Description:** RBAC permissions for the pod's ServiceAccount
|
||||
|
||||
## Secrets
|
||||
|
||||
### envSecretName
|
||||
- **Type:** String
|
||||
- **Default:** `""` (auto-generates as `devcontainer-{name}-secrets-env`)
|
||||
- **Description:** Name of existing Secret containing environment variables
|
||||
- **Keys Recognized:**
|
||||
- `GITHUB_TOKEN` — PAT for private repo access
|
||||
- `VNC_PASSWORD` — Password for VNC web UI
|
||||
- `ANTHROPIC_API_KEY` — API key for Claude
|
||||
- `SSH_AUTHORIZED_KEYS` — Public keys for SSH access
|
||||
- `homeassistant-url` — Home Assistant base URL (e.g., http://homeassistant.local:8123)
|
||||
- `homeassistant-token` — Home Assistant long-lived access token
|
||||
|
||||
## MCP Sidecars
|
||||
|
||||
### mcpSidecars.kubernetes.enabled
|
||||
- **Type:** Boolean
|
||||
- **Default:** `true`
|
||||
- **Source:** Hardcoded in StatefulSet
|
||||
- **Description:** Enable Kubernetes MCP server sidecar
|
||||
|
||||
## Variable Groups by Use Case
|
||||
### mcpSidecars.kubernetes.image.repository
|
||||
- **Type:** String
|
||||
- **Default:** `quay.io/containers/kubernetes_mcp_server`
|
||||
- **Description:** Kubernetes MCP server image
|
||||
|
||||
### Minimal Deployment
|
||||
Only these variables are required for basic deployment:
|
||||
1. `storageClassName`
|
||||
2. `github-repo`
|
||||
3. `parentRefs.name`
|
||||
4. `parentRefs.namespace`
|
||||
5. `hostnames`
|
||||
### mcpSidecars.kubernetes.image.tag
|
||||
- **Type:** String
|
||||
- **Default:** `latest`
|
||||
- **Description:** Kubernetes MCP server image tag
|
||||
|
||||
### Private Repository Deployment
|
||||
Add these for private repos:
|
||||
1. All minimal deployment variables
|
||||
2. `github-token` (sealed secret)
|
||||
### mcpSidecars.kubernetes.port
|
||||
- **Type:** Integer
|
||||
- **Default:** `8080`
|
||||
- **Description:** Port for Kubernetes MCP server
|
||||
|
||||
### Production Deployment
|
||||
Recommended for production:
|
||||
1. All private repository variables
|
||||
2. `vnc-password` (sealed secret)
|
||||
3. `resources.requests.*` (adjusted for workload)
|
||||
4. `resources.limits.*` (adjusted for workload)
|
||||
5. `namespace` (dedicated namespace)
|
||||
### mcpSidecars.kubernetes.resources
|
||||
- **Type:** Object
|
||||
- **Default:**
|
||||
```yaml
|
||||
requests:
|
||||
memory: "64Mi"
|
||||
cpu: "50m"
|
||||
limits:
|
||||
memory: "256Mi"
|
||||
cpu: "500m"
|
||||
```
|
||||
- **Description:** Resource limits for Kubernetes MCP sidecar
|
||||
|
||||
### Multi-User Deployment
|
||||
For multiple users:
|
||||
1. All production deployment variables
|
||||
2. `replicas` (set to number of users)
|
||||
3. Larger `storage` size for home PVCs
|
||||
### mcpSidecars.flux.enabled
|
||||
- **Type:** Boolean
|
||||
- **Default:** `true`
|
||||
- **Description:** Enable Flux MCP server sidecar
|
||||
|
||||
## Quick Copy Templates
|
||||
### mcpSidecars.flux.image.repository
|
||||
- **Type:** String
|
||||
- **Default:** `ghcr.io/controlplaneio-fluxcd/flux-operator-mcp`
|
||||
- **Description:** Flux MCP server image
|
||||
|
||||
### mcpSidecars.flux.image.tag
|
||||
- **Type:** String
|
||||
- **Default:** `v0.41.1`
|
||||
- **Description:** Flux MCP server image tag
|
||||
|
||||
### mcpSidecars.flux.port
|
||||
- **Type:** Integer
|
||||
- **Default:** `8081`
|
||||
- **Description:** Port for Flux MCP server
|
||||
|
||||
### mcpSidecars.flux.resources
|
||||
- **Type:** Object
|
||||
- **Default:**
|
||||
```yaml
|
||||
requests:
|
||||
memory: "64Mi"
|
||||
cpu: "50m"
|
||||
limits:
|
||||
memory: "256Mi"
|
||||
cpu: "500m"
|
||||
```
|
||||
- **Description:** Resource limits for Flux MCP sidecar
|
||||
|
||||
### mcpSidecars.homeassistant.enabled
|
||||
- **Type:** Boolean
|
||||
- **Default:** `false`
|
||||
- **Description:** Enable Home Assistant MCP server sidecar
|
||||
- **Note:** Requires `homeassistant-url` and `homeassistant-token` in env secret
|
||||
|
||||
### mcpSidecars.homeassistant.image.repository
|
||||
- **Type:** String
|
||||
- **Default:** `ghcr.io/homeassistant-ai/ha-mcp`
|
||||
- **Description:** Home Assistant MCP server image
|
||||
|
||||
### mcpSidecars.homeassistant.image.tag
|
||||
- **Type:** String
|
||||
- **Default:** `stable`
|
||||
- **Description:** Home Assistant MCP server image tag
|
||||
- **Options:** `stable` (recommended), `latest` (dev builds), `v{version}` (specific version)
|
||||
|
||||
### mcpSidecars.homeassistant.port
|
||||
- **Type:** Integer
|
||||
- **Default:** `8087`
|
||||
- **Description:** Port for Home Assistant MCP server (SSE mode)
|
||||
|
||||
### mcpSidecars.homeassistant.resources
|
||||
- **Type:** Object
|
||||
- **Default:**
|
||||
```yaml
|
||||
requests:
|
||||
memory: "64Mi"
|
||||
cpu: "50m"
|
||||
limits:
|
||||
memory: "256Mi"
|
||||
cpu: "500m"
|
||||
```
|
||||
- **Description:** Resource limits for Home Assistant MCP sidecar
|
||||
|
||||
## Usage Examples
|
||||
|
||||
### Minimal Configuration
|
||||
|
||||
### Minimal Required Variables
|
||||
```yaml
|
||||
# k8s/statefulset.yaml
|
||||
storageClassName: "CHANGE_ME" # Line ~117
|
||||
|
||||
# k8s/configmap.yaml
|
||||
github-repo: "CHANGE_ME" # Line ~9
|
||||
|
||||
# k8s/httproute.yaml
|
||||
parentRefs:
|
||||
- name: CHANGE_ME # Line ~8
|
||||
namespace: CHANGE_ME # Line ~9
|
||||
hostnames:
|
||||
- "CHANGE_ME" # Line ~11
|
||||
name: mydev
|
||||
githubRepo: https://github.com/user/repo
|
||||
```
|
||||
|
||||
### With Secrets
|
||||
```bash
|
||||
kubectl create secret generic antigravity-secrets \
|
||||
--from-literal=github-token='CHANGE_ME' \
|
||||
--from-literal=vnc-password='CHANGE_ME' \
|
||||
--dry-run=client -o yaml | \
|
||||
kubeseal --format=yaml > k8s/sealedsecrets.yaml
|
||||
```
|
||||
### Production Configuration
|
||||
|
||||
### With Resource Adjustments
|
||||
```yaml
|
||||
# k8s/statefulset.yaml (lines ~98-103)
|
||||
name: prod-workspace
|
||||
githubRepo: https://github.com/company/application
|
||||
ide: vscode
|
||||
ssh: true
|
||||
|
||||
image:
|
||||
tag: v1.0.0
|
||||
|
||||
storage:
|
||||
size: 100Gi
|
||||
className: ceph-filesystem
|
||||
|
||||
resources:
|
||||
requests:
|
||||
memory: "CHANGE_ME" # e.g., 4Gi
|
||||
cpu: "CHANGE_ME" # e.g., 2000m
|
||||
memory: "4Gi"
|
||||
cpu: "2000m"
|
||||
limits:
|
||||
memory: "CHANGE_ME" # e.g., 16Gi
|
||||
cpu: "CHANGE_ME" # e.g., 8000m
|
||||
memory: "16Gi"
|
||||
cpu: "8000m"
|
||||
|
||||
clusterAccess: readwritens
|
||||
|
||||
mcpSidecars:
|
||||
kubernetes:
|
||||
enabled: true
|
||||
flux:
|
||||
enabled: false
|
||||
```
|
||||
|
||||
### Development Team Configuration
|
||||
|
||||
```yaml
|
||||
name: team-dev
|
||||
githubRepo: https://github.com/team/project
|
||||
ide: antigravity
|
||||
|
||||
display:
|
||||
width: "2560"
|
||||
height: "1440"
|
||||
|
||||
storage:
|
||||
size: 50Gi
|
||||
className: nfs-client
|
||||
|
||||
clusterAccess: readonly
|
||||
|
||||
```
|
||||
|
||||
### Smart Home Development Configuration
|
||||
|
||||
```yaml
|
||||
name: smarthome-dev
|
||||
githubRepo: https://github.com/user/home-automation
|
||||
ide: vscode
|
||||
|
||||
clusterAccess: readwritens
|
||||
|
||||
mcpSidecars:
|
||||
kubernetes:
|
||||
enabled: true
|
||||
flux:
|
||||
enabled: false
|
||||
homeassistant:
|
||||
enabled: true
|
||||
image:
|
||||
tag: stable
|
||||
|
||||
# Requires secrets:
|
||||
# homeassistant-url: http://homeassistant.local:8123
|
||||
# homeassistant-token: <long-lived-access-token>
|
||||
```
|
||||
|
||||
## Helm CLI Examples
|
||||
|
||||
### Using --set Flags
|
||||
|
||||
```bash
|
||||
# Basic deployment
|
||||
helm install mydev ./chart \
|
||||
--set name=mydev \
|
||||
--set githubRepo=https://github.com/user/repo
|
||||
|
||||
# With multiple values
|
||||
helm install mydev ./chart \
|
||||
--set name=mydev \
|
||||
--set githubRepo=https://github.com/user/repo \
|
||||
--set ide=antigravity \
|
||||
--set storage.size=50Gi \
|
||||
--set clusterAccess=readwritens \
|
||||
--set mcpSidecars.flux.enabled=false
|
||||
```
|
||||
|
||||
### Using Values File
|
||||
|
||||
Create `custom-values.yaml`:
|
||||
```yaml
|
||||
name: mydev
|
||||
githubRepo: https://github.com/user/repo
|
||||
storage:
|
||||
size: 50Gi
|
||||
clusterAccess: readwritens
|
||||
```
|
||||
|
||||
Deploy:
|
||||
```bash
|
||||
helm install mydev ./chart -f custom-values.yaml
|
||||
```
|
||||
|
||||
### Combining Methods
|
||||
|
||||
```bash
|
||||
helm install mydev ./chart \
|
||||
-f base-values.yaml \
|
||||
-f prod-values.yaml \
|
||||
--set githubRepo=https://github.com/user/repo \
|
||||
--set image.tag=v2.0.0
|
||||
```
|
||||
|
||||
## Value Precedence
|
||||
|
||||
Values are applied in order of precedence (highest to lowest):
|
||||
1. `--set` flags on command line
|
||||
2. `-f` values files (later files override earlier)
|
||||
3. `chart/values.yaml` defaults
|
||||
|
||||
## Environment Variables
|
||||
|
||||
These environment variables are set in the container based on chart values:
|
||||
|
||||
| Environment Variable | Source Value | Description |
|
||||
|---------------------|--------------|-------------|
|
||||
| `GITHUB_REPO` | `githubRepo` | Repository to clone |
|
||||
| `GITHUB_TOKEN` | Secret: `github-token` | PAT for private repos |
|
||||
| `VNC_PASSWORD` | Secret: `vnc-password` | VNC access password |
|
||||
| `ANTHROPIC_API_KEY` | Secret: `anthropic-api-key` | Claude API key |
|
||||
| `SSH_AUTHORIZED_KEYS` | Secret: `ssh-authorized-keys` | SSH public keys |
|
||||
| `DISPLAY_WIDTH` | `display.width` | VNC width |
|
||||
| `DISPLAY_HEIGHT` | `display.height` | VNC height |
|
||||
| `SECURE_CONNECTION` | `secureConnection` | TLS termination |
|
||||
| `USER_ID` | `userId` | App user UID |
|
||||
| `GROUP_ID` | `groupId` | App user GID |
|
||||
| `IDE` | `ide` | IDE to launch |
|
||||
| `SSH` | `ssh` | SSH server enabled |
|
||||
+8
-3
@@ -1,6 +1,11 @@
|
||||
apiVersion: v2
|
||||
name: antigravity
|
||||
description: Antigravity Dev Container with Happy Coder AI assistant
|
||||
name: devcontainer
|
||||
description: Dev Container with AI coding agents and MCP sidecars
|
||||
type: application
|
||||
version: 0.1.0
|
||||
version: 2.6.0
|
||||
appVersion: "latest"
|
||||
keywords:
|
||||
- development
|
||||
- devcontainer
|
||||
- vscode
|
||||
- ai
|
||||
|
||||
+381
@@ -0,0 +1,381 @@
|
||||
# Dev Container Helm Chart Usage Guide
|
||||
|
||||
This guide provides common usage patterns and examples for the Dev Container Helm chart.
|
||||
|
||||
## Quick Start
|
||||
|
||||
### 1. Minimal Installation (Recommended)
|
||||
|
||||
Use the quickstart values for the simplest setup:
|
||||
|
||||
```bash
|
||||
# Copy and customize quickstart values
|
||||
cp values-quickstart.yaml my-values.yaml
|
||||
|
||||
# Edit my-values.yaml to set your name and repo:
|
||||
# name: myproject
|
||||
# githubRepo: https://github.com/youruser/yourproject
|
||||
|
||||
# Install
|
||||
helm install myproject ./chart -f my-values.yaml
|
||||
```
|
||||
|
||||
### 2. One-Command Installation
|
||||
|
||||
```bash
|
||||
helm install mydev ./chart \
|
||||
--set name=mydev \
|
||||
--set githubRepo=https://github.com/youruser/yourrepo
|
||||
```
|
||||
|
||||
## Common Use Cases
|
||||
|
||||
### Development Environment
|
||||
|
||||
**Scenario**: Standard development with GitHub integration
|
||||
|
||||
```yaml
|
||||
name: dev-environment
|
||||
githubRepo: https://github.com/company/project
|
||||
|
||||
ide:
|
||||
type: vscode
|
||||
|
||||
mcp:
|
||||
sidecars:
|
||||
kubernetes:
|
||||
enabled: true
|
||||
playwright:
|
||||
enabled: true
|
||||
flux:
|
||||
enabled: false # Disable if not using Flux
|
||||
```
|
||||
|
||||
### Team Workspace
|
||||
|
||||
**Scenario**: Shared development environment with more resources
|
||||
|
||||
```yaml
|
||||
name: team-workspace
|
||||
githubRepo: https://github.com/company/project
|
||||
|
||||
resources:
|
||||
requests:
|
||||
memory: "4Gi"
|
||||
cpu: "2000m"
|
||||
limits:
|
||||
memory: "16Gi"
|
||||
cpu: "8000m"
|
||||
|
||||
storage:
|
||||
size: 64Gi
|
||||
|
||||
ssh:
|
||||
enabled: true # Enable SSH access for team
|
||||
|
||||
clusterAccess: readwrite # Full cluster access
|
||||
```
|
||||
|
||||
### Kubernetes Admin Environment
|
||||
|
||||
**Scenario**: Platform engineering with full cluster access
|
||||
|
||||
```yaml
|
||||
name: k8s-admin
|
||||
githubRepo: https://github.com/company/k8s-configs
|
||||
|
||||
clusterAccess: readwrite
|
||||
|
||||
mcp:
|
||||
sidecars:
|
||||
kubernetes:
|
||||
enabled: true
|
||||
flux:
|
||||
enabled: true
|
||||
pgtuner:
|
||||
enabled: true # Database administration
|
||||
playwright:
|
||||
enabled: false # Save resources
|
||||
```
|
||||
|
||||
### AI/ML Development
|
||||
|
||||
**Scenario**: AI development with browser automation
|
||||
|
||||
```yaml
|
||||
name: ai-playground
|
||||
githubRepo: https://github.com/company/ai-project
|
||||
|
||||
resources:
|
||||
requests:
|
||||
memory: "8Gi" # More memory for ML workloads
|
||||
cpu: "4000m"
|
||||
limits:
|
||||
memory: "32Gi"
|
||||
cpu: "16000m"
|
||||
|
||||
storage:
|
||||
size: 128Gi # Large datasets
|
||||
|
||||
mcp:
|
||||
sidecars:
|
||||
playwright:
|
||||
enabled: true # Web scraping, testing
|
||||
kubernetes:
|
||||
enabled: false # Save resources
|
||||
flux:
|
||||
enabled: false
|
||||
```
|
||||
|
||||
### Lightweight Environment
|
||||
|
||||
**Scenario**: Resource-constrained setup
|
||||
|
||||
```yaml
|
||||
name: lightweight
|
||||
githubRepo: https://github.com/youruser/small-project
|
||||
|
||||
resources:
|
||||
requests:
|
||||
memory: "1Gi"
|
||||
cpu: "500m"
|
||||
limits:
|
||||
memory: "2Gi"
|
||||
cpu: "1000m"
|
||||
|
||||
storage:
|
||||
size: 8Gi
|
||||
|
||||
mcp:
|
||||
sidecars:
|
||||
kubernetes:
|
||||
enabled: false
|
||||
flux:
|
||||
enabled: false
|
||||
playwright:
|
||||
enabled: false
|
||||
# Only keep essential sidecars enabled
|
||||
```
|
||||
|
||||
## Secret Configuration
|
||||
|
||||
### Basic Secrets
|
||||
|
||||
```bash
|
||||
# GitHub access only
|
||||
kubectl create secret generic devcontainer-mydev-secrets-env \
|
||||
--from-literal=GITHUB_TOKEN='ghp_...' \
|
||||
--from-literal=VNC_PASSWORD='changeme'
|
||||
```
|
||||
|
||||
### Extended Secrets
|
||||
|
||||
```bash
|
||||
# Full feature set
|
||||
kubectl create secret generic devcontainer-mydev-secrets-env \
|
||||
--from-literal=GITHUB_TOKEN='ghp_...' \
|
||||
--from-literal=VNC_PASSWORD='changeme' \
|
||||
--from-literal=SSH_AUTHORIZED_KEYS='ssh-ed25519 AAAA...' \
|
||||
--from-literal=HOMEASSISTANT_URL='http://homeassistant.local:8123' \
|
||||
--from-literal=HOMEASSISTANT_TOKEN='eyJ...' \
|
||||
--from-literal=DATABASE_URI='postgresql://user:pass@postgres:5432/db'
|
||||
```
|
||||
|
||||
## Storage Configuration
|
||||
|
||||
### Different Storage Classes
|
||||
|
||||
```yaml
|
||||
# For different Kubernetes distributions
|
||||
storage:
|
||||
className: "" # Auto-detect (recommended)
|
||||
# className: longhorn # Longhorn
|
||||
# className: nfs-client # NFS
|
||||
# className: fast-ssd # Custom fast storage
|
||||
```
|
||||
|
||||
### Storage Sizes by Use Case
|
||||
|
||||
```yaml
|
||||
# Small projects
|
||||
storage:
|
||||
size: 8Gi
|
||||
|
||||
# Standard development
|
||||
storage:
|
||||
size: 32Gi
|
||||
|
||||
# Large projects / datasets
|
||||
storage:
|
||||
size: 128Gi
|
||||
|
||||
# Team environments
|
||||
storage:
|
||||
size: 256Gi
|
||||
```
|
||||
|
||||
## Access Patterns
|
||||
|
||||
### VNC Only (Default)
|
||||
|
||||
```yaml
|
||||
ide:
|
||||
type: vscode
|
||||
|
||||
# Access via: kubectl port-forward deployment/devcontainer-mydev 5800:5800
|
||||
```
|
||||
|
||||
### SSH Only
|
||||
|
||||
```yaml
|
||||
ide:
|
||||
type: none
|
||||
|
||||
ssh:
|
||||
enabled: true
|
||||
|
||||
# Access via: kubectl port-forward deployment/devcontainer-mydev 2222:22
|
||||
# ssh -p 2222 user@localhost
|
||||
```
|
||||
|
||||
### Both VNC and SSH
|
||||
|
||||
```yaml
|
||||
ide:
|
||||
type: vscode
|
||||
|
||||
ssh:
|
||||
enabled: true
|
||||
|
||||
# VNC: kubectl port-forward deployment/devcontainer-mydev 5800:5800
|
||||
# SSH: kubectl port-forward deployment/devcontainer-mydev 2222:22
|
||||
```
|
||||
|
||||
## Resource Profiles
|
||||
|
||||
### Small (1-2 developers)
|
||||
```yaml
|
||||
resources:
|
||||
requests:
|
||||
memory: "1Gi"
|
||||
cpu: "500m"
|
||||
limits:
|
||||
memory: "4Gi"
|
||||
cpu: "2000m"
|
||||
```
|
||||
|
||||
### Medium (standard development)
|
||||
```yaml
|
||||
resources:
|
||||
requests:
|
||||
memory: "2Gi"
|
||||
cpu: "1000m"
|
||||
limits:
|
||||
memory: "8Gi"
|
||||
cpu: "4000m"
|
||||
```
|
||||
|
||||
### Large (intensive workloads)
|
||||
```yaml
|
||||
resources:
|
||||
requests:
|
||||
memory: "4Gi"
|
||||
cpu: "2000m"
|
||||
limits:
|
||||
memory: "16Gi"
|
||||
cpu: "8000m"
|
||||
```
|
||||
|
||||
### XLarge (AI/ML, data processing)
|
||||
```yaml
|
||||
resources:
|
||||
requests:
|
||||
memory: "8Gi"
|
||||
cpu: "4000m"
|
||||
limits:
|
||||
memory: "32Gi"
|
||||
cpu: "16000m"
|
||||
```
|
||||
|
||||
## MCP Sidecar Combinations
|
||||
|
||||
### Minimal (basic development)
|
||||
```yaml
|
||||
mcp:
|
||||
sidecars:
|
||||
kubernetes:
|
||||
enabled: false
|
||||
flux:
|
||||
enabled: false
|
||||
playwright:
|
||||
enabled: true # Keep for web testing
|
||||
```
|
||||
|
||||
### Standard (full-stack development)
|
||||
```yaml
|
||||
mcp:
|
||||
sidecars:
|
||||
kubernetes:
|
||||
enabled: true
|
||||
flux:
|
||||
enabled: false
|
||||
playwright:
|
||||
enabled: true
|
||||
```
|
||||
|
||||
### DevOps/Platform (infrastructure work)
|
||||
```yaml
|
||||
mcp:
|
||||
sidecars:
|
||||
kubernetes:
|
||||
enabled: true
|
||||
flux:
|
||||
enabled: true
|
||||
pgtuner:
|
||||
enabled: true
|
||||
playwright:
|
||||
enabled: false
|
||||
```
|
||||
|
||||
### All Features
|
||||
```yaml
|
||||
mcp:
|
||||
sidecars:
|
||||
kubernetes:
|
||||
enabled: true
|
||||
flux:
|
||||
enabled: true
|
||||
homeassistant:
|
||||
enabled: true
|
||||
pgtuner:
|
||||
enabled: true
|
||||
playwright:
|
||||
enabled: true
|
||||
```
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Values Validation
|
||||
|
||||
Your IDE should automatically validate values.yaml against the schema. If not:
|
||||
|
||||
```bash
|
||||
# Manual validation (if you have a JSON schema validator)
|
||||
helm template ./chart -f values.yaml > /dev/null
|
||||
```
|
||||
|
||||
### Common Issues
|
||||
|
||||
**Resource Limits**: Start with smaller resource requests and increase as needed.
|
||||
|
||||
**Storage Class**: Use `className: ""` for auto-detection.
|
||||
|
||||
**GitHub Access**: Ensure GITHUB_TOKEN has `repo` scope.
|
||||
|
||||
**MCP Sidecars**: Disable unused sidecars to save resources.
|
||||
|
||||
### Getting Help
|
||||
|
||||
1. Check the main [README.md](../README.md) for detailed documentation
|
||||
2. Review [values.yaml](values.yaml) for all available options
|
||||
3. Use [values-quickstart.yaml](values-quickstart.yaml) as a starting point
|
||||
@@ -0,0 +1,31 @@
|
||||
Dev Container "{{ .Values.name }}" has been deployed.
|
||||
|
||||
{{- if ne (.Values.ide.type | default "vscode") "none" }}
|
||||
|
||||
Access the IDE:
|
||||
kubectl port-forward deployment/{{ include "devcontainer.fullname" . }} 5800:5800 -n {{ .Release.Namespace }}
|
||||
Then open: http://localhost:5800
|
||||
{{- end }}
|
||||
|
||||
{{- if .Values.ssh.enabled }}
|
||||
|
||||
SSH access:
|
||||
kubectl port-forward deployment/{{ include "devcontainer.fullname" . }} 2222:22 -n {{ .Release.Namespace }}
|
||||
Then: ssh -p 2222 user@localhost
|
||||
{{- end }}
|
||||
|
||||
Useful commands:
|
||||
Logs: kubectl logs -f deployment/{{ include "devcontainer.fullname" . }} -n {{ .Release.Namespace }}
|
||||
Shell: kubectl exec -it deployment/{{ include "devcontainer.fullname" . }} -n {{ .Release.Namespace }} -- bash
|
||||
|
||||
{{- if not (lookup "v1" "Secret" .Release.Namespace (include "devcontainer.envSecretName" .)) }}
|
||||
|
||||
Optional: Create a secret for GITHUB_TOKEN, VNC_PASSWORD, etc:
|
||||
kubectl create secret generic {{ include "devcontainer.envSecretName" . }} \
|
||||
--from-literal=GITHUB_TOKEN=ghp_xxx \
|
||||
--from-literal=VNC_PASSWORD=changeme \
|
||||
-n {{ .Release.Namespace }}
|
||||
{{- end }}
|
||||
|
||||
Note: The PVC "{{ include "devcontainer.pvcName" . }}" is protected from deletion on helm uninstall.
|
||||
To remove it manually: kubectl delete pvc {{ include "devcontainer.pvcName" . }} -n {{ .Release.Namespace }}
|
||||
@@ -1,28 +1,111 @@
|
||||
{{/*
|
||||
Resource name prefix: devcontainer-{name}
|
||||
*/}}
|
||||
{{- define "antigravity.fullname" -}}
|
||||
{{- define "devcontainer.fullname" -}}
|
||||
{{- if not .Values.name }}
|
||||
{{- fail "values.name is required and must not be empty" }}
|
||||
{{- end }}
|
||||
{{- printf "devcontainer-%s" .Values.name }}
|
||||
{{- end }}
|
||||
|
||||
{{/*
|
||||
PVC name: userhome-{name}
|
||||
*/}}
|
||||
{{- define "antigravity.pvcName" -}}
|
||||
{{- define "devcontainer.pvcName" -}}
|
||||
{{- printf "userhome-%s" .Values.name }}
|
||||
{{- end }}
|
||||
|
||||
{{/*
|
||||
Secret name for env vars, default to devcontainer-{name}-secrets-env
|
||||
*/}}
|
||||
{{- define "antigravity.envSecretName" -}}
|
||||
{{- define "devcontainer.envSecretName" -}}
|
||||
{{- .Values.envSecretName | default (printf "devcontainer-%s-secrets-env" .Values.name) }}
|
||||
{{- end }}
|
||||
|
||||
{{/*
|
||||
Common labels
|
||||
*/}}
|
||||
{{- define "antigravity.labels" -}}
|
||||
{{- define "devcontainer.labels" -}}
|
||||
app: devcontainer
|
||||
instance: {{ .Values.name }}
|
||||
app.kubernetes.io/name: devcontainer
|
||||
app.kubernetes.io/instance: {{ .Values.name }}
|
||||
app.kubernetes.io/managed-by: {{ .Release.Service }}
|
||||
helm.sh/chart: {{ .Chart.Name }}-{{ .Chart.Version | replace "+" "_" }}
|
||||
{{- end }}
|
||||
|
||||
{{/*
|
||||
Selector labels — keep narrow since changing these requires recreating the Deployment
|
||||
*/}}
|
||||
{{- define "devcontainer.selectorLabels" -}}
|
||||
app: devcontainer
|
||||
instance: {{ .Values.name }}
|
||||
{{- end }}
|
||||
|
||||
{{/*
|
||||
Smart resource sizing based on enabled features
|
||||
*/}}
|
||||
{{- define "devcontainer.smartResources" -}}
|
||||
{{- $baseMemory := "2Gi" }}
|
||||
{{- $baseCpu := "1000m" }}
|
||||
{{- $limitMemory := "8Gi" }}
|
||||
{{- $limitCpu := "4000m" }}
|
||||
|
||||
{{/* Adjust for enabled MCP sidecars */}}
|
||||
{{- if .Values.mcp.sidecars.playwright.enabled }}
|
||||
{{- $baseMemory = "3Gi" }}
|
||||
{{- $limitMemory = "12Gi" }}
|
||||
{{- end }}
|
||||
|
||||
{{/* Adjust for IDE type */}}
|
||||
{{- if eq .Values.ide.type "antigravity" }}
|
||||
{{- $baseMemory = "4Gi" }}
|
||||
{{- $limitMemory = "16Gi" }}
|
||||
{{- end }}
|
||||
|
||||
requests:
|
||||
memory: {{ .Values.resources.requests.memory | default $baseMemory | quote }}
|
||||
cpu: {{ .Values.resources.requests.cpu | default $baseCpu | quote }}
|
||||
limits:
|
||||
memory: {{ .Values.resources.limits.memory | default $limitMemory | quote }}
|
||||
cpu: {{ .Values.resources.limits.cpu | default $limitCpu | quote }}
|
||||
{{- end }}
|
||||
|
||||
{{/*
|
||||
Auto-detect environment type and set smart defaults
|
||||
*/}}
|
||||
{{- define "devcontainer.smartDefaults" -}}
|
||||
{{- $isDev := or (contains "dev" .Values.name) (contains "test" .Values.name) (contains "local" .Values.name) }}
|
||||
{{- $isProd := or (contains "prod" .Values.name) (contains "production" .Values.name) }}
|
||||
{{- $isTeam := or (contains "team" .Values.name) (contains "shared" .Values.name) }}
|
||||
|
||||
{{/* Development environment - enable more sidecars, smaller resources */}}
|
||||
{{- if $isDev }}
|
||||
development: true
|
||||
{{/* Production environment - conservative defaults, fewer sidecars */}}
|
||||
{{- else if $isProd }}
|
||||
production: true
|
||||
{{/* Team environment - enable SSH, more resources */}}
|
||||
{{- else if $isTeam }}
|
||||
team: true
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
|
||||
{{/*
|
||||
Smart MCP sidecar selection based on cluster access
|
||||
*/}}
|
||||
{{- define "devcontainer.mcpDefaults" -}}
|
||||
{{- if eq .Values.clusterAccess "none" }}
|
||||
{{/* No cluster access - disable k8s/flux sidecars */}}
|
||||
kubernetes:
|
||||
enabled: false
|
||||
flux:
|
||||
enabled: false
|
||||
{{- else }}
|
||||
{{/* Has cluster access - enable k8s sidecars */}}
|
||||
kubernetes:
|
||||
enabled: true
|
||||
flux:
|
||||
enabled: {{ ne .Values.clusterAccess "readonly" }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
|
||||
+251
-18
@@ -1,62 +1,106 @@
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: {{ include "antigravity.fullname" . }}
|
||||
name: {{ include "devcontainer.fullname" . }}
|
||||
labels:
|
||||
{{- include "antigravity.labels" . | nindent 4 }}
|
||||
{{- include "devcontainer.labels" . | nindent 4 }}
|
||||
spec:
|
||||
replicas: 1
|
||||
selector:
|
||||
matchLabels:
|
||||
{{- include "antigravity.labels" . | nindent 6 }}
|
||||
{{- include "devcontainer.selectorLabels" . | nindent 6 }}
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
{{- include "antigravity.labels" . | nindent 8 }}
|
||||
{{- include "devcontainer.labels" . | nindent 8 }}
|
||||
spec:
|
||||
{{- if ne (.Values.clusterAccess | default "none") "none" }}
|
||||
serviceAccountName: {{ include "devcontainer.fullname" . }}
|
||||
{{- end }}
|
||||
securityContext:
|
||||
fsGroup: 1000
|
||||
fsGroupChangePolicy: "OnRootMismatch"
|
||||
{{- if and .Values.ide.type (eq .Values.ide.type "antigravity") }}
|
||||
initContainers:
|
||||
- name: setup-userdata
|
||||
image: busybox:1.37
|
||||
command: ['sh', '-c']
|
||||
args:
|
||||
- |
|
||||
echo "Setting up userdata directory..."
|
||||
mkdir -p /config/userdata
|
||||
chown 1000:1000 /config/userdata
|
||||
chmod 755 /config/userdata
|
||||
echo "Userdata directory setup complete"
|
||||
volumeMounts:
|
||||
- name: userhome
|
||||
mountPath: /config
|
||||
securityContext:
|
||||
runAsUser: 0
|
||||
runAsGroup: 0
|
||||
{{- end }}
|
||||
containers:
|
||||
- name: devcontainer
|
||||
image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}"
|
||||
imagePullPolicy: {{ .Values.image.pullPolicy }}
|
||||
ports:
|
||||
{{- if ne (.Values.ide.type | default "vscode") "none" }}
|
||||
- containerPort: 5800
|
||||
name: vnc-web
|
||||
protocol: TCP
|
||||
{{- end }}
|
||||
{{- if .Values.ssh.enabled }}
|
||||
- containerPort: 22
|
||||
name: ssh
|
||||
protocol: TCP
|
||||
{{- end }}
|
||||
env:
|
||||
- name: IDE
|
||||
value: {{ .Values.ide.type | default "vscode" | quote }}
|
||||
- name: SSH
|
||||
value: {{ .Values.ssh.enabled | toString | quote }}
|
||||
- name: USER_ID
|
||||
value: {{ .Values.userId | quote }}
|
||||
value: {{ .Values.user.id | quote }}
|
||||
- name: GROUP_ID
|
||||
value: {{ .Values.groupId | quote }}
|
||||
value: {{ .Values.user.groupId | quote }}
|
||||
- name: DISPLAY_WIDTH
|
||||
value: {{ .Values.display.width | quote }}
|
||||
- name: DISPLAY_HEIGHT
|
||||
value: {{ .Values.display.height | quote }}
|
||||
- name: SECURE_CONNECTION
|
||||
value: {{ .Values.secureConnection | quote }}
|
||||
- name: HAPPY_HOME_DIR
|
||||
value: {{ .Values.happyHomeDir | quote }}
|
||||
- name: HAPPY_EXPERIMENTAL
|
||||
value: {{ .Values.happyExperimental | quote }}
|
||||
- name: HAPPY_SERVER_URL
|
||||
value: {{ .Values.happyServerUrl | quote }}
|
||||
- name: HAPPY_WEBAPP_URL
|
||||
value: {{ .Values.happyWebappUrl | quote }}
|
||||
value: {{ .Values.display.secureConnection | quote }}
|
||||
{{- if .Values.fileManager.enabled }}
|
||||
- name: WEB_FILE_MANAGER
|
||||
value: "1"
|
||||
- name: WEB_FILE_MANAGER_ALLOWED_PATHS
|
||||
value: {{ .Values.fileManager.allowedPaths | quote }}
|
||||
{{- if .Values.fileManager.deniedPaths }}
|
||||
- name: WEB_FILE_MANAGER_DENIED_PATHS
|
||||
value: {{ .Values.fileManager.deniedPaths | quote }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
{{- if .Values.githubRepo }}
|
||||
- name: GITHUB_REPO
|
||||
value: {{ .Values.githubRepo | quote }}
|
||||
{{- end }}
|
||||
{{- if .Values.githubRepos }}
|
||||
- name: GITHUB_REPOS
|
||||
value: {{ join "," .Values.githubRepos | quote }}
|
||||
{{- end }}
|
||||
envFrom:
|
||||
- secretRef:
|
||||
name: {{ include "antigravity.envSecretName" . }}
|
||||
name: {{ include "devcontainer.envSecretName" . }}
|
||||
optional: true
|
||||
resources:
|
||||
{{- toYaml .Values.resources | nindent 12 }}
|
||||
volumeMounts:
|
||||
- name: userhome
|
||||
mountPath: /home
|
||||
mountPath: /config
|
||||
- name: workspace
|
||||
mountPath: /workspace
|
||||
- name: shm
|
||||
mountPath: /dev/shm
|
||||
{{- if ne (.Values.ide.type | default "vscode") "none" }}
|
||||
livenessProbe:
|
||||
httpGet:
|
||||
path: /
|
||||
@@ -69,9 +113,198 @@ spec:
|
||||
port: 5800
|
||||
initialDelaySeconds: 10
|
||||
periodSeconds: 5
|
||||
{{- else if .Values.ssh.enabled }}
|
||||
livenessProbe:
|
||||
tcpSocket:
|
||||
port: 22
|
||||
initialDelaySeconds: 15
|
||||
periodSeconds: 10
|
||||
readinessProbe:
|
||||
tcpSocket:
|
||||
port: 22
|
||||
initialDelaySeconds: 5
|
||||
periodSeconds: 5
|
||||
{{- end }}
|
||||
{{- if and .Values.mcp.sidecars.kubernetes.enabled (ne .Values.clusterAccess "none") }}
|
||||
- name: kubernetes-mcp
|
||||
image: "{{ .Values.mcp.sidecars.kubernetes.image.repository }}:{{ .Values.mcp.sidecars.kubernetes.image.tag }}"
|
||||
args:
|
||||
- --port
|
||||
- {{ .Values.mcp.sidecars.kubernetes.port | quote }}
|
||||
ports:
|
||||
- containerPort: {{ .Values.mcp.sidecars.kubernetes.port }}
|
||||
name: k8s-mcp
|
||||
protocol: TCP
|
||||
livenessProbe:
|
||||
httpGet:
|
||||
path: /healthz
|
||||
port: {{ .Values.mcp.sidecars.kubernetes.port }}
|
||||
initialDelaySeconds: 10
|
||||
periodSeconds: 10
|
||||
readinessProbe:
|
||||
httpGet:
|
||||
path: /healthz
|
||||
port: {{ .Values.mcp.sidecars.kubernetes.port }}
|
||||
initialDelaySeconds: 5
|
||||
periodSeconds: 5
|
||||
resources:
|
||||
{{- toYaml .Values.mcp.sidecars.kubernetes.resources | nindent 12 }}
|
||||
{{- end }}
|
||||
{{- if and .Values.mcp.sidecars.flux.enabled (ne .Values.clusterAccess "none") }}
|
||||
- name: flux-mcp
|
||||
image: "{{ .Values.mcp.sidecars.flux.image.repository }}:{{ .Values.mcp.sidecars.flux.image.tag }}"
|
||||
args:
|
||||
- serve
|
||||
- --transport=sse
|
||||
- --port={{ .Values.mcp.sidecars.flux.port }}
|
||||
ports:
|
||||
- containerPort: {{ .Values.mcp.sidecars.flux.port }}
|
||||
name: flux-mcp
|
||||
protocol: TCP
|
||||
livenessProbe:
|
||||
tcpSocket:
|
||||
port: {{ .Values.mcp.sidecars.flux.port }}
|
||||
initialDelaySeconds: 10
|
||||
periodSeconds: 10
|
||||
readinessProbe:
|
||||
tcpSocket:
|
||||
port: {{ .Values.mcp.sidecars.flux.port }}
|
||||
initialDelaySeconds: 5
|
||||
periodSeconds: 5
|
||||
resources:
|
||||
{{- toYaml .Values.mcp.sidecars.flux.resources | nindent 12 }}
|
||||
{{- end }}
|
||||
{{- if .Values.mcp.sidecars.helm.enabled }}
|
||||
- name: helm-mcp
|
||||
image: "{{ .Values.mcp.sidecars.helm.image.repository }}:{{ .Values.mcp.sidecars.helm.image.tag }}"
|
||||
args:
|
||||
- -mode=sse
|
||||
ports:
|
||||
- containerPort: {{ .Values.mcp.sidecars.helm.port }}
|
||||
name: helm-mcp
|
||||
protocol: TCP
|
||||
livenessProbe:
|
||||
tcpSocket:
|
||||
port: {{ .Values.mcp.sidecars.helm.port }}
|
||||
initialDelaySeconds: 10
|
||||
periodSeconds: 10
|
||||
readinessProbe:
|
||||
tcpSocket:
|
||||
port: {{ .Values.mcp.sidecars.helm.port }}
|
||||
initialDelaySeconds: 5
|
||||
periodSeconds: 5
|
||||
resources:
|
||||
{{- toYaml .Values.mcp.sidecars.helm.resources | nindent 12 }}
|
||||
{{- end }}
|
||||
{{- if .Values.mcp.sidecars.homeassistant.enabled }}
|
||||
- name: homeassistant-mcp
|
||||
image: "{{ .Values.mcp.sidecars.homeassistant.image.repository }}:{{ .Values.mcp.sidecars.homeassistant.image.tag }}"
|
||||
imagePullPolicy: IfNotPresent
|
||||
command: ["fastmcp", "run", "--transport", "sse", "--host", "0.0.0.0", "--port", "{{ .Values.mcp.sidecars.homeassistant.port }}"]
|
||||
ports:
|
||||
- name: homeassistant
|
||||
containerPort: {{ .Values.mcp.sidecars.homeassistant.port }}
|
||||
env:
|
||||
- name: HOMEASSISTANT_URL
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: {{ include "devcontainer.envSecretName" . }}
|
||||
key: HOMEASSISTANT_URL
|
||||
optional: true
|
||||
- name: HOMEASSISTANT_TOKEN
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: {{ include "devcontainer.envSecretName" . }}
|
||||
key: HOMEASSISTANT_TOKEN
|
||||
optional: true
|
||||
livenessProbe:
|
||||
tcpSocket:
|
||||
port: {{ .Values.mcp.sidecars.homeassistant.port }}
|
||||
initialDelaySeconds: 10
|
||||
periodSeconds: 10
|
||||
readinessProbe:
|
||||
tcpSocket:
|
||||
port: {{ .Values.mcp.sidecars.homeassistant.port }}
|
||||
initialDelaySeconds: 5
|
||||
periodSeconds: 5
|
||||
resources:
|
||||
{{- toYaml .Values.mcp.sidecars.homeassistant.resources | nindent 12 }}
|
||||
{{- end }}
|
||||
{{- if .Values.mcp.sidecars.pgtuner.enabled }}
|
||||
- name: pgtuner-mcp
|
||||
image: "{{ .Values.mcp.sidecars.pgtuner.image.repository }}:{{ .Values.mcp.sidecars.pgtuner.image.tag }}"
|
||||
imagePullPolicy: Always # pgtuner uses `latest` tag (no versioned releases available)
|
||||
command: ["python", "-m", "pgtuner_mcp", "--mode", "sse", "--host", "0.0.0.0", "--port", "{{ .Values.mcp.sidecars.pgtuner.port }}"]
|
||||
ports:
|
||||
- name: pgtuner
|
||||
containerPort: {{ .Values.mcp.sidecars.pgtuner.port }}
|
||||
env:
|
||||
- name: DATABASE_URI
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: {{ include "devcontainer.envSecretName" . }}
|
||||
key: DATABASE_URI
|
||||
optional: true
|
||||
- name: PGTUNER_EXCLUDE_USERIDS
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: {{ include "devcontainer.envSecretName" . }}
|
||||
key: PGTUNER_EXCLUDE_USERIDS
|
||||
optional: true
|
||||
livenessProbe:
|
||||
tcpSocket:
|
||||
port: {{ .Values.mcp.sidecars.pgtuner.port }}
|
||||
initialDelaySeconds: 10
|
||||
periodSeconds: 10
|
||||
readinessProbe:
|
||||
tcpSocket:
|
||||
port: {{ .Values.mcp.sidecars.pgtuner.port }}
|
||||
initialDelaySeconds: 5
|
||||
periodSeconds: 5
|
||||
resources:
|
||||
{{- toYaml .Values.mcp.sidecars.pgtuner.resources | nindent 12 }}
|
||||
{{- end }}
|
||||
{{- if .Values.mcp.sidecars.playwright.enabled }}
|
||||
- name: playwright-mcp
|
||||
image: "{{ .Values.mcp.sidecars.playwright.image.repository }}:{{ .Values.mcp.sidecars.playwright.image.tag }}"
|
||||
imagePullPolicy: IfNotPresent
|
||||
command: ["node"]
|
||||
args:
|
||||
- cli.js
|
||||
- --headless
|
||||
- --browser
|
||||
- chromium
|
||||
- --no-sandbox
|
||||
- --host
|
||||
- 0.0.0.0
|
||||
- --port
|
||||
- {{ .Values.mcp.sidecars.playwright.port | quote }}
|
||||
ports:
|
||||
- name: playwright
|
||||
containerPort: {{ .Values.mcp.sidecars.playwright.port }}
|
||||
livenessProbe:
|
||||
tcpSocket:
|
||||
port: {{ .Values.mcp.sidecars.playwright.port }}
|
||||
initialDelaySeconds: 15
|
||||
periodSeconds: 10
|
||||
readinessProbe:
|
||||
tcpSocket:
|
||||
port: {{ .Values.mcp.sidecars.playwright.port }}
|
||||
initialDelaySeconds: 10
|
||||
periodSeconds: 5
|
||||
resources:
|
||||
{{- toYaml .Values.mcp.sidecars.playwright.resources | nindent 12 }}
|
||||
securityContext:
|
||||
runAsUser: 1000
|
||||
runAsGroup: 1000
|
||||
{{- end }}
|
||||
volumes:
|
||||
- name: workspace
|
||||
emptyDir: {}
|
||||
- name: shm
|
||||
emptyDir:
|
||||
medium: Memory
|
||||
sizeLimit: {{ .Values.shm.sizeLimit }}
|
||||
- name: userhome
|
||||
persistentVolumeClaim:
|
||||
claimName: {{ include "antigravity.pvcName" . }}
|
||||
claimName: {{ include "devcontainer.pvcName" . }}
|
||||
|
||||
@@ -1,13 +1,17 @@
|
||||
apiVersion: v1
|
||||
kind: PersistentVolumeClaim
|
||||
metadata:
|
||||
name: {{ include "antigravity.pvcName" . }}
|
||||
name: {{ include "devcontainer.pvcName" . }}
|
||||
annotations:
|
||||
helm.sh/resource-policy: keep
|
||||
labels:
|
||||
{{- include "antigravity.labels" . | nindent 4 }}
|
||||
{{- include "devcontainer.labels" . | nindent 4 }}
|
||||
spec:
|
||||
accessModes:
|
||||
- ReadWriteMany
|
||||
{{- if .Values.storage.className }}
|
||||
storageClassName: {{ .Values.storage.className }}
|
||||
{{- end }}
|
||||
resources:
|
||||
requests:
|
||||
storage: {{ .Values.storage.size }}
|
||||
|
||||
@@ -0,0 +1,97 @@
|
||||
{{- $access := .Values.clusterAccess | default "none" }}
|
||||
{{- $name := include "devcontainer.fullname" . }}
|
||||
{{- $ns := .Release.Namespace }}
|
||||
{{- $labels := include "devcontainer.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 }}
|
||||
@@ -1,14 +1,22 @@
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: {{ include "antigravity.fullname" . }}
|
||||
name: {{ include "devcontainer.fullname" . }}
|
||||
labels:
|
||||
{{- include "antigravity.labels" . | nindent 4 }}
|
||||
{{- include "devcontainer.labels" . | nindent 4 }}
|
||||
spec:
|
||||
ports:
|
||||
{{- if ne (.Values.ide.type | default "vscode") "none" }}
|
||||
- port: 5800
|
||||
name: vnc-web
|
||||
protocol: TCP
|
||||
targetPort: vnc-web
|
||||
{{- end }}
|
||||
{{- if .Values.ssh.enabled }}
|
||||
- port: 22
|
||||
name: ssh
|
||||
protocol: TCP
|
||||
targetPort: ssh
|
||||
{{- end }}
|
||||
selector:
|
||||
{{- include "antigravity.labels" . | nindent 4 }}
|
||||
{{- include "devcontainer.labels" . | nindent 4 }}
|
||||
|
||||
@@ -0,0 +1,63 @@
|
||||
# =============================================================================
|
||||
# QUICKSTART VALUES - Just set these 3 essentials!
|
||||
# =============================================================================
|
||||
|
||||
# Instance name (required)
|
||||
name: mydev
|
||||
|
||||
# GitHub repository to clone (required)
|
||||
githubRepo: https://github.com/youruser/yourrepo
|
||||
|
||||
# Multiple repositories (optional, takes precedence over githubRepo)
|
||||
# githubRepos:
|
||||
# - https://github.com/youruser/repo1
|
||||
# - https://github.com/youruser/repo2
|
||||
|
||||
# IDE choice (optional - defaults to vscode)
|
||||
# Options: vscode | antigravity | none
|
||||
ide:
|
||||
type: vscode
|
||||
|
||||
# =============================================================================
|
||||
# COMMON CUSTOMIZATIONS (optional)
|
||||
# =============================================================================
|
||||
|
||||
# Enable SSH access
|
||||
# ssh:
|
||||
# enabled: true
|
||||
|
||||
# Adjust resources for smaller/larger workloads
|
||||
# resources:
|
||||
# requests:
|
||||
# memory: "1Gi" # Smaller
|
||||
# cpu: "500m"
|
||||
# limits:
|
||||
# memory: "4Gi" # Smaller
|
||||
# cpu: "2000m"
|
||||
|
||||
# Different storage size
|
||||
# storage:
|
||||
# size: 16Gi # Smaller
|
||||
|
||||
# Disable some MCP sidecars to save resources
|
||||
# mcp:
|
||||
# sidecars:
|
||||
# kubernetes:
|
||||
# enabled: false
|
||||
# flux:
|
||||
# enabled: false
|
||||
|
||||
# =============================================================================
|
||||
# USAGE INSTRUCTIONS
|
||||
# =============================================================================
|
||||
|
||||
# 1. Copy this file: cp values-quickstart.yaml my-values.yaml
|
||||
# 2. Edit the 'name' and 'githubRepo' fields above
|
||||
# 3. Deploy: helm install mydev ./chart -f my-values.yaml
|
||||
# 4. Access: kubectl port-forward deployment/devcontainer-mydev 5800:5800
|
||||
# 5. Open: http://localhost:5800
|
||||
|
||||
# For secrets (GitHub token, passwords):
|
||||
# kubectl create secret generic devcontainer-mydev-secrets-env \
|
||||
# --from-literal=GITHUB_TOKEN='ghp_...' \
|
||||
# --from-literal=VNC_PASSWORD='changeme'
|
||||
@@ -0,0 +1,262 @@
|
||||
{
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"$id": "https://github.com/cpfarhood/devcontainer/chart/values.schema.json",
|
||||
"title": "Dev Container Helm Chart Values Schema",
|
||||
"description": "Schema for validating values.yaml in the Dev Container Helm chart",
|
||||
"type": "object",
|
||||
"additionalProperties": true,
|
||||
"properties": {
|
||||
"name": {
|
||||
"type": "string",
|
||||
"description": "Instance name used to generate resource names",
|
||||
"pattern": "^[a-z0-9][a-z0-9-]*[a-z0-9]$",
|
||||
"minLength": 1,
|
||||
"maxLength": 63
|
||||
},
|
||||
"image": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"repository": {
|
||||
"type": "string",
|
||||
"description": "Container image repository"
|
||||
},
|
||||
"tag": {
|
||||
"type": "string",
|
||||
"description": "Container image tag"
|
||||
},
|
||||
"pullPolicy": {
|
||||
"type": "string",
|
||||
"enum": ["Always", "IfNotPresent", "Never"],
|
||||
"description": "Image pull policy"
|
||||
}
|
||||
},
|
||||
"required": ["repository", "tag"]
|
||||
},
|
||||
"githubRepo": {
|
||||
"type": "string",
|
||||
"description": "GitHub repository URL to clone into /workspace"
|
||||
},
|
||||
"githubRepos": {
|
||||
"type": "array",
|
||||
"items": { "type": "string" },
|
||||
"description": "Multiple GitHub repository URLs to clone (takes precedence over githubRepo)"
|
||||
},
|
||||
"fileManager": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"enabled": {
|
||||
"type": "boolean",
|
||||
"description": "Enable the built-in web file manager"
|
||||
},
|
||||
"allowedPaths": {
|
||||
"type": "string",
|
||||
"description": "Paths accessible by the file manager (AUTO, ALL, or comma-separated list)"
|
||||
},
|
||||
"deniedPaths": {
|
||||
"type": "string",
|
||||
"description": "Paths to deny access to (takes precedence over allowedPaths)"
|
||||
}
|
||||
},
|
||||
"required": ["enabled"]
|
||||
},
|
||||
"ide": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"type": {
|
||||
"type": "string",
|
||||
"enum": ["vscode", "antigravity", "none"],
|
||||
"description": "IDE to launch in the container"
|
||||
}
|
||||
},
|
||||
"required": ["type"]
|
||||
},
|
||||
"ssh": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"enabled": {
|
||||
"type": "boolean",
|
||||
"description": "Enable SSH server on port 22"
|
||||
}
|
||||
},
|
||||
"required": ["enabled"]
|
||||
},
|
||||
"display": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"width": {
|
||||
"type": "string",
|
||||
"pattern": "^[0-9]+$",
|
||||
"description": "VNC display width in pixels"
|
||||
},
|
||||
"height": {
|
||||
"type": "string",
|
||||
"pattern": "^[0-9]+$",
|
||||
"description": "VNC display height in pixels"
|
||||
},
|
||||
"secureConnection": {
|
||||
"type": "string",
|
||||
"enum": ["0", "1"],
|
||||
"description": "Enable secure VNC connection"
|
||||
}
|
||||
},
|
||||
"required": ["width", "height", "secureConnection"]
|
||||
},
|
||||
"user": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "string",
|
||||
"pattern": "^[0-9]+$",
|
||||
"description": "User ID (UID)"
|
||||
},
|
||||
"groupId": {
|
||||
"type": "string",
|
||||
"pattern": "^[0-9]+$",
|
||||
"description": "Group ID (GID)"
|
||||
}
|
||||
},
|
||||
"required": ["id", "groupId"]
|
||||
},
|
||||
"storage": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"size": {
|
||||
"type": "string",
|
||||
"pattern": "^[0-9]+[KMGT]i$",
|
||||
"description": "Storage size (e.g., 32Gi)"
|
||||
},
|
||||
"className": {
|
||||
"type": "string",
|
||||
"description": "Storage class name (must support ReadWriteMany)"
|
||||
}
|
||||
},
|
||||
"required": ["size"]
|
||||
},
|
||||
"resources": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"requests": {
|
||||
"$ref": "#/$defs/resourceSpec"
|
||||
},
|
||||
"limits": {
|
||||
"$ref": "#/$defs/resourceSpec"
|
||||
}
|
||||
},
|
||||
"required": ["requests", "limits"]
|
||||
},
|
||||
"shm": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"sizeLimit": {
|
||||
"type": "string",
|
||||
"pattern": "^[0-9]+[KMGT]i$",
|
||||
"description": "Shared memory size limit"
|
||||
}
|
||||
},
|
||||
"required": ["sizeLimit"]
|
||||
},
|
||||
"clusterAccess": {
|
||||
"type": "string",
|
||||
"enum": ["none", "readonlyns", "readwritens", "readonly", "readwrite"],
|
||||
"description": "Kubernetes cluster access level"
|
||||
},
|
||||
"mcp": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"sidecars": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"kubernetes": {
|
||||
"$ref": "#/$defs/mcpSidecar"
|
||||
},
|
||||
"flux": {
|
||||
"$ref": "#/$defs/mcpSidecar"
|
||||
},
|
||||
"homeassistant": {
|
||||
"$ref": "#/$defs/mcpSidecar"
|
||||
},
|
||||
"pgtuner": {
|
||||
"$ref": "#/$defs/mcpSidecar"
|
||||
},
|
||||
"helm": {
|
||||
"$ref": "#/$defs/mcpSidecar"
|
||||
},
|
||||
"playwright": {
|
||||
"$ref": "#/$defs/mcpSidecar"
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
}
|
||||
},
|
||||
"required": ["sidecars"]
|
||||
},
|
||||
"envSecretName": {
|
||||
"type": "string",
|
||||
"description": "Custom environment secret name"
|
||||
},
|
||||
"resourceProfile": {
|
||||
"type": "string",
|
||||
"enum": ["auto", "small", "medium", "large", "xlarge"],
|
||||
"description": "Resource profile preset"
|
||||
}
|
||||
},
|
||||
"required": ["name"],
|
||||
"$defs": {
|
||||
"resourceSpec": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"memory": {
|
||||
"type": "string",
|
||||
"pattern": "^[0-9]+[KMGT]i$",
|
||||
"description": "Memory resource specification"
|
||||
},
|
||||
"cpu": {
|
||||
"type": "string",
|
||||
"pattern": "^[0-9]+m?$",
|
||||
"description": "CPU resource specification"
|
||||
}
|
||||
},
|
||||
"required": ["memory", "cpu"]
|
||||
},
|
||||
"mcpSidecar": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"enabled": {
|
||||
"type": "boolean",
|
||||
"description": "Enable this MCP sidecar"
|
||||
},
|
||||
"image": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"repository": {
|
||||
"type": "string"
|
||||
},
|
||||
"tag": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": ["repository", "tag"]
|
||||
},
|
||||
"port": {
|
||||
"type": "integer",
|
||||
"minimum": 1,
|
||||
"maximum": 65535,
|
||||
"description": "Port for the MCP sidecar"
|
||||
},
|
||||
"resources": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"requests": {
|
||||
"$ref": "#/$defs/resourceSpec"
|
||||
},
|
||||
"limits": {
|
||||
"$ref": "#/$defs/resourceSpec"
|
||||
}
|
||||
},
|
||||
"required": ["requests", "limits"]
|
||||
}
|
||||
},
|
||||
"required": ["enabled", "image", "port", "resources"]
|
||||
}
|
||||
}
|
||||
}
|
||||
+173
-14
@@ -1,6 +1,11 @@
|
||||
# =============================================================================
|
||||
# BASIC CONFIGURATION
|
||||
# =============================================================================
|
||||
|
||||
# Instance name — used to generate resource names (devcontainer-{name}, userhome-{name})
|
||||
name: ""
|
||||
|
||||
# Container image configuration
|
||||
image:
|
||||
repository: ghcr.io/cpfarhood/devcontainer
|
||||
tag: latest
|
||||
@@ -9,27 +14,57 @@ image:
|
||||
# GitHub repository to clone into /workspace
|
||||
githubRepo: ""
|
||||
|
||||
# Happy Coder endpoints
|
||||
happyServerUrl: "https://happy.farh.net"
|
||||
happyWebappUrl: "https://happy-coder.farh.net"
|
||||
happyHomeDir: "/home/user/.happy"
|
||||
happyExperimental: "true"
|
||||
# Multiple GitHub repositories to clone into /workspace (takes precedence over githubRepo)
|
||||
# Example:
|
||||
# githubRepos:
|
||||
# - https://github.com/user/repo1
|
||||
# - https://github.com/user/repo2
|
||||
githubRepos: []
|
||||
|
||||
# VNC display
|
||||
# =============================================================================
|
||||
# ACCESS & INTERFACE
|
||||
# =============================================================================
|
||||
|
||||
# IDE configuration
|
||||
ide:
|
||||
# Options: vscode | antigravity | none
|
||||
type: vscode
|
||||
|
||||
# SSH access configuration
|
||||
ssh:
|
||||
enabled: false
|
||||
|
||||
# Web file manager — built-in upload/download via the VNC web interface (port 5800)
|
||||
# Uses the base image's WEB_FILE_MANAGER feature (no extra sidecar needed)
|
||||
fileManager:
|
||||
enabled: false
|
||||
# Paths the file manager can access (default: AUTO = mapped volumes)
|
||||
# Options: AUTO | ALL | comma-separated list of paths
|
||||
allowedPaths: "/workspace,/config"
|
||||
# Paths to deny (takes precedence over allowedPaths)
|
||||
deniedPaths: ""
|
||||
|
||||
# VNC display settings
|
||||
display:
|
||||
width: "1920"
|
||||
height: "1080"
|
||||
secureConnection: "0" # Set to "1" when TLS is not terminated upstream
|
||||
|
||||
# Set to "0" when TLS is terminated at the gateway layer
|
||||
secureConnection: "0"
|
||||
# User configuration
|
||||
user:
|
||||
id: "1000"
|
||||
groupId: "1000"
|
||||
|
||||
userId: "1000"
|
||||
groupId: "1000"
|
||||
# =============================================================================
|
||||
# INFRASTRUCTURE & RESOURCES
|
||||
# =============================================================================
|
||||
|
||||
# Storage configuration
|
||||
storage:
|
||||
size: 32Gi
|
||||
className: ceph-filesystem
|
||||
className: "" # Empty string uses the cluster's default StorageClass (must support ReadWriteMany)
|
||||
|
||||
# Resource allocation
|
||||
resources:
|
||||
requests:
|
||||
memory: "2Gi"
|
||||
@@ -38,6 +73,130 @@ resources:
|
||||
memory: "8Gi"
|
||||
cpu: "4000m"
|
||||
|
||||
# Name of existing Secret containing env vars (GITHUB_TOKEN, VNC_PASSWORD, etc.)
|
||||
# Defaults to: devcontainer-{name}-secrets-env
|
||||
envSecretName: ""
|
||||
# Shared memory for Electron apps (Chrome, Antigravity)
|
||||
shm:
|
||||
sizeLimit: 2Gi
|
||||
|
||||
# Kubernetes cluster access via RBAC
|
||||
# Options: none | readonlyns | readwritens | readonly | readwrite
|
||||
clusterAccess: none
|
||||
|
||||
# =============================================================================
|
||||
# INTEGRATIONS
|
||||
# =============================================================================
|
||||
|
||||
# MCP (Model Context Protocol) server sidecars
|
||||
mcp:
|
||||
sidecars:
|
||||
# Kubernetes API access
|
||||
kubernetes:
|
||||
enabled: true
|
||||
image:
|
||||
repository: quay.io/containers/kubernetes_mcp_server
|
||||
tag: v0.0.57
|
||||
port: 8080
|
||||
resources:
|
||||
requests:
|
||||
memory: "64Mi"
|
||||
cpu: "50m"
|
||||
limits:
|
||||
memory: "256Mi"
|
||||
cpu: "500m"
|
||||
|
||||
# Flux GitOps operations
|
||||
flux:
|
||||
enabled: true
|
||||
image:
|
||||
repository: ghcr.io/controlplaneio-fluxcd/flux-operator-mcp
|
||||
tag: v0.41.1
|
||||
port: 8081
|
||||
resources:
|
||||
requests:
|
||||
memory: "64Mi"
|
||||
cpu: "50m"
|
||||
limits:
|
||||
memory: "256Mi"
|
||||
cpu: "500m"
|
||||
|
||||
|
||||
# Helm chart browsing and management
|
||||
helm:
|
||||
enabled: false
|
||||
image:
|
||||
repository: ghcr.io/zekker6/mcp-helm
|
||||
tag: v1.3.1
|
||||
port: 8012
|
||||
resources:
|
||||
requests:
|
||||
memory: "64Mi"
|
||||
cpu: "50m"
|
||||
limits:
|
||||
memory: "256Mi"
|
||||
cpu: "500m"
|
||||
|
||||
# Home Assistant smart home control
|
||||
homeassistant:
|
||||
enabled: false # Requires HOMEASSISTANT_URL and HOMEASSISTANT_TOKEN
|
||||
image:
|
||||
repository: ghcr.io/homeassistant-ai/ha-mcp
|
||||
tag: "6.7.1"
|
||||
port: 8087
|
||||
resources:
|
||||
requests:
|
||||
memory: "64Mi"
|
||||
cpu: "50m"
|
||||
limits:
|
||||
memory: "256Mi"
|
||||
cpu: "500m"
|
||||
|
||||
# PostgreSQL performance tuning
|
||||
pgtuner:
|
||||
enabled: false # Requires DATABASE_URI in secrets
|
||||
image:
|
||||
repository: dog830228/pgtuner_mcp
|
||||
tag: latest
|
||||
port: 8085
|
||||
resources:
|
||||
requests:
|
||||
memory: "64Mi"
|
||||
cpu: "50m"
|
||||
limits:
|
||||
memory: "256Mi"
|
||||
cpu: "500m"
|
||||
|
||||
# Browser automation and web testing
|
||||
playwright:
|
||||
enabled: true
|
||||
image:
|
||||
repository: mcr.microsoft.com/playwright/mcp
|
||||
tag: v0.0.68
|
||||
port: 8086
|
||||
resources:
|
||||
requests:
|
||||
memory: "128Mi"
|
||||
cpu: "100m"
|
||||
limits:
|
||||
memory: "512Mi"
|
||||
cpu: "1000m"
|
||||
|
||||
# =============================================================================
|
||||
# SMART DEFAULTS & AUTO-DETECTION
|
||||
# =============================================================================
|
||||
|
||||
# Environment auto-detection based on name patterns
|
||||
# Automatically adjusts defaults for dev/test/prod/team environments
|
||||
autoDetect:
|
||||
environment: true # Auto-detect dev/prod/team from name
|
||||
storageClass: true # Auto-detect ReadWriteMany storage class
|
||||
resources: true # Auto-size resources based on enabled features
|
||||
|
||||
# Resource profiles (auto-selected based on environment and features)
|
||||
# Override specific values above to customize
|
||||
resourceProfile: auto # auto | small | medium | large | xlarge
|
||||
|
||||
# =============================================================================
|
||||
# ADVANCED CONFIGURATION
|
||||
# =============================================================================
|
||||
|
||||
# Custom env secret name (defaults to: devcontainer-{name}-secrets-env)
|
||||
envSecretName: ""
|
||||
+17
-7
@@ -2,19 +2,29 @@
|
||||
|
||||
## Key Architecture Facts
|
||||
- Image: `ghcr.io/cpfarhood/devcontainer:latest` (repo name is `devcontainer`, not `antigravity`)
|
||||
- `imagePullPolicy: Always` in statefulset (set during initial deployment debugging)
|
||||
- Deployed via Helm chart (`chart/`), not kustomize anymore
|
||||
- Service must NOT be headless (`clusterIP: None`) — Cilium gateway can't route to headless services
|
||||
- `SECURE_CONNECTION=0` — TLS is terminated at the gateway, not the app
|
||||
- Container user is `user` (UID 1000) — baseimage-gui runs startapp.sh as `app` user, sudo is not available
|
||||
- HTTPRoute is managed by Authentik outpost, not in kustomization
|
||||
|
||||
## Cluster Patterns
|
||||
- External gateway: `external` in `gateway-system`, handles `*.farh.net` on port 443 HTTPS only
|
||||
- Hostnames must be exactly `*.farh.net` (not `*.subdomain.farh.net`) to match gateway listener
|
||||
- Authentik outpost Terraform lives in `../kubernetes/terraform/authentik-*-proxy/`
|
||||
- Outpost config uses `external` gateway for public apps, `internal` for internal apps
|
||||
## Deployment Method
|
||||
- **Primary**: Helm chart in `chart/` directory
|
||||
- **Makefile targets**: `helm-deploy`, `helm-delete`, `helm-logs`, `helm-shell`, `helm-port-forward`
|
||||
- **Old kustomize** (`k8s/` directory) has been removed — all deployments use Helm now
|
||||
- Chart published as OCI artifact to GHCR, reconciled by Flux
|
||||
|
||||
## MCP Sidecars
|
||||
- **Kubernetes MCP** (v0.0.57, port 8080): Only deployed when enabled AND `clusterAccess` != `none`
|
||||
- **Flux MCP** (v0.41.1, port 8081): Only deployed when enabled AND `clusterAccess` != `none`
|
||||
- **Home Assistant MCP** (6.7.1, port 8087): Disabled by default, requires secrets:
|
||||
- `homeassistant-url`: Base URL like `http://homeassistant.local:8123`
|
||||
- `homeassistant-token`: Long-lived access token
|
||||
- **Playwright MCP**: External service, not a sidecar
|
||||
- Configure via `mcpSidecars.<name>.enabled` in values
|
||||
- **Version Strategy**: All MCP images use pinned versions for stability (no `latest` tags)
|
||||
|
||||
## Common Gotchas
|
||||
- `baseimage-gui` creates user dynamically — don't hardcode usernames in scripts, use numeric UID/GID
|
||||
- `chown /home` fails (PVC root not owned by container) — only chown subdirectories
|
||||
- `sudo` not available in startapp.sh — script already runs as correct user
|
||||
- MCP sidecars need appropriate secrets and RBAC permissions to function
|
||||
|
||||
@@ -0,0 +1,72 @@
|
||||
{
|
||||
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
|
||||
"extends": [
|
||||
"config:recommended",
|
||||
":gitSignOff"
|
||||
],
|
||||
"semanticCommits": "enabled",
|
||||
"dependencyDashboard": true,
|
||||
"suppressNotifications": [
|
||||
"prEditedNotification"
|
||||
],
|
||||
"rebaseWhen": "conflicted",
|
||||
"commitMessagePrefix": "chore(deps):",
|
||||
"commitMessageAction": "update",
|
||||
"commitMessageTopic": "{{depName}}",
|
||||
"prConcurrentLimit": 5,
|
||||
"prHourlyLimit": 2,
|
||||
"schedule": [
|
||||
"before 6am on monday"
|
||||
],
|
||||
"packageRules": [
|
||||
{
|
||||
"description": "GitHub Actions",
|
||||
"matchManagers": [
|
||||
"github-actions"
|
||||
],
|
||||
"groupName": "github-actions",
|
||||
"additionalBranchPrefix": "github-actions-",
|
||||
"semanticCommitScope": "github-actions",
|
||||
"pinDigests": true
|
||||
},
|
||||
{
|
||||
"description": "Docker base image",
|
||||
"matchManagers": [
|
||||
"dockerfile"
|
||||
],
|
||||
"groupName": "docker",
|
||||
"additionalBranchPrefix": "docker-",
|
||||
"semanticCommitScope": "docker"
|
||||
},
|
||||
{
|
||||
"description": "Automerge patch updates",
|
||||
"matchUpdateTypes": [
|
||||
"patch"
|
||||
],
|
||||
"automerge": true,
|
||||
"automergeType": "pr",
|
||||
"platformAutomerge": true
|
||||
},
|
||||
{
|
||||
"description": "Automerge minor updates for stable packages",
|
||||
"matchUpdateTypes": [
|
||||
"minor"
|
||||
],
|
||||
"matchCurrentVersion": "!/^0/",
|
||||
"automerge": true,
|
||||
"automergeType": "pr",
|
||||
"platformAutomerge": true
|
||||
},
|
||||
{
|
||||
"description": "Separate major updates - require manual review",
|
||||
"matchUpdateTypes": [
|
||||
"major"
|
||||
],
|
||||
"automerge": false,
|
||||
"additionalBranchPrefix": "major-"
|
||||
}
|
||||
],
|
||||
"ignorePaths": [
|
||||
"**/node_modules/**"
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
#!/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="/config/userdata"
|
||||
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 $!)"
|
||||
@@ -0,0 +1,10 @@
|
||||
#!/bin/sh
|
||||
# Fix the app user (UID 1000) created by baseimage-gui at runtime.
|
||||
# baseimage-gui sets shell=/sbin/nologin and home=/dev/null, which
|
||||
# prevents VSCode from opening terminals.
|
||||
if id app >/dev/null 2>&1; then
|
||||
usermod -s /bin/bash app
|
||||
usermod -d /config/userdata app
|
||||
else
|
||||
echo "WARNING: 'app' user not found, skipping usermod" >&2
|
||||
fi
|
||||
+138
-49
@@ -1,71 +1,160 @@
|
||||
#!/bin/bash
|
||||
# Initialize repository and start Happy Coder
|
||||
# Initialize repository
|
||||
set -e
|
||||
|
||||
echo "=== Repository Initialization ==="
|
||||
|
||||
# Check if GITHUB_REPO is set
|
||||
if [ -z "$GITHUB_REPO" ]; then
|
||||
echo "GITHUB_REPO not set, skipping repository clone"
|
||||
# Ensure home directory exists on the PVC before any git operations
|
||||
# (git config --global writes to $HOME/.gitconfig, which fails on a fresh volume)
|
||||
RUN_UID="${USER_ID:-1000}"
|
||||
RUN_GID="${GROUP_ID:-1000}"
|
||||
mkdir -p "$HOME"
|
||||
chown "$RUN_UID:$RUN_GID" "$HOME"
|
||||
|
||||
# Set up basic git configuration
|
||||
echo "Configuring git user settings..."
|
||||
# Use environment variables if provided, otherwise use defaults
|
||||
GIT_USER_NAME="${GIT_USER_NAME:-DevContainer User}"
|
||||
GIT_USER_EMAIL="${GIT_USER_EMAIL:-devcontainer@example.com}"
|
||||
|
||||
git config --global user.name "$GIT_USER_NAME"
|
||||
git config --global user.email "$GIT_USER_EMAIL"
|
||||
|
||||
# Set up git credentials early if GITHUB_TOKEN is provided
|
||||
# This ensures all git operations have proper authentication
|
||||
if [ -n "$GITHUB_TOKEN" ]; then
|
||||
echo "Setting up git credentials..."
|
||||
# Configure git to use credential store globally
|
||||
git config --global credential.helper store
|
||||
|
||||
# Create or update the credentials file
|
||||
CREDENTIALS_FILE="/config/userdata/.git-credentials"
|
||||
mkdir -p "$(dirname "$CREDENTIALS_FILE")"
|
||||
|
||||
# Support multiple git hosting providers
|
||||
# GitHub supports both oauth2 and token as username
|
||||
echo "https://oauth2:${GITHUB_TOKEN}@github.com" > "$CREDENTIALS_FILE"
|
||||
echo "https://${GITHUB_TOKEN}:x-oauth-basic@github.com" >> "$CREDENTIALS_FILE"
|
||||
echo "https://token:${GITHUB_TOKEN}@github.com" >> "$CREDENTIALS_FILE"
|
||||
|
||||
# GitLab format (if same token works)
|
||||
if [ -n "$GITLAB_HOST" ]; then
|
||||
echo "https://oauth2:${GITHUB_TOKEN}@${GITLAB_HOST}" >> "$CREDENTIALS_FILE"
|
||||
fi
|
||||
|
||||
chmod 600 "$CREDENTIALS_FILE"
|
||||
|
||||
# Also create a symlink in the home directory if it doesn't exist
|
||||
# This handles cases where git might look in different locations
|
||||
if [ ! -f "$HOME/.git-credentials" ] && [ "$HOME" != "/config/userdata" ]; then
|
||||
ln -sf "$CREDENTIALS_FILE" "$HOME/.git-credentials"
|
||||
fi
|
||||
|
||||
echo "Git credentials configured"
|
||||
else
|
||||
# Even without a token, ensure git has a proper credential helper configured
|
||||
# This prevents errors when credentials are added later
|
||||
echo "No GITHUB_TOKEN provided, configuring basic git settings..."
|
||||
git config --global credential.helper store
|
||||
|
||||
# Create an empty credentials file with proper permissions
|
||||
CREDENTIALS_FILE="/config/userdata/.git-credentials"
|
||||
mkdir -p "$(dirname "$CREDENTIALS_FILE")"
|
||||
touch "$CREDENTIALS_FILE"
|
||||
chmod 600 "$CREDENTIALS_FILE"
|
||||
|
||||
# Create symlink if needed
|
||||
if [ ! -f "$HOME/.git-credentials" ] && [ "$HOME" != "/config/userdata" ]; then
|
||||
ln -sf "$CREDENTIALS_FILE" "$HOME/.git-credentials"
|
||||
fi
|
||||
fi
|
||||
|
||||
# Build list of repositories to clone
|
||||
REPOS=()
|
||||
if [ -n "$GITHUB_REPOS" ]; then
|
||||
# GITHUB_REPOS is a comma-separated list (takes precedence over GITHUB_REPO)
|
||||
IFS=',' read -ra RAW_REPOS <<< "$GITHUB_REPOS"
|
||||
for repo in "${RAW_REPOS[@]}"; do
|
||||
repo="$(echo "$repo" | xargs)" # trim whitespace
|
||||
[ -n "$repo" ] && REPOS+=("$repo")
|
||||
done
|
||||
elif [ -n "$GITHUB_REPO" ]; then
|
||||
REPOS+=("$GITHUB_REPO")
|
||||
fi
|
||||
|
||||
if [ ${#REPOS[@]} -eq 0 ]; then
|
||||
echo "No repositories configured, skipping clone"
|
||||
WORKSPACE_DIR="/workspace/default"
|
||||
mkdir -p "$WORKSPACE_DIR"
|
||||
else
|
||||
# Parse repo name from URL
|
||||
REPO_NAME=$(basename "$GITHUB_REPO" .git)
|
||||
WORKSPACE_DIR="/workspace/$REPO_NAME"
|
||||
CLONED_DIRS=()
|
||||
for REPO_URL in "${REPOS[@]}"; do
|
||||
REPO_NAME=$(basename "$REPO_URL" .git)
|
||||
REPO_DIR="/workspace/$REPO_NAME"
|
||||
|
||||
echo "Repository: $GITHUB_REPO"
|
||||
echo "Target directory: $WORKSPACE_DIR"
|
||||
echo "Repository: $REPO_URL"
|
||||
echo "Target directory: $REPO_DIR"
|
||||
|
||||
# Check if repo already exists
|
||||
if [ -d "$WORKSPACE_DIR/.git" ]; then
|
||||
echo "Repository already exists, pulling latest changes..."
|
||||
cd "$WORKSPACE_DIR"
|
||||
|
||||
# Configure git to use token if provided
|
||||
if [ -n "$GITHUB_TOKEN" ]; then
|
||||
git config credential.helper store
|
||||
echo "https://oauth2:${GITHUB_TOKEN}@github.com" > /home/.git-credentials
|
||||
chmod 600 /home/.git-credentials
|
||||
fi
|
||||
|
||||
git pull || echo "Pull failed, continuing anyway..."
|
||||
else
|
||||
echo "Cloning repository..."
|
||||
mkdir -p "$(dirname "$WORKSPACE_DIR")"
|
||||
|
||||
# Clone with token if provided
|
||||
if [ -n "$GITHUB_TOKEN" ]; then
|
||||
# Replace https://github.com/ with https://oauth2:token@github.com/
|
||||
CLONE_URL=$(echo "$GITHUB_REPO" | sed "s|https://github.com/|https://oauth2:${GITHUB_TOKEN}@github.com/|")
|
||||
git clone "$CLONE_URL" "$WORKSPACE_DIR"
|
||||
|
||||
# Configure credentials for future use
|
||||
git config --global credential.helper store
|
||||
echo "https://oauth2:${GITHUB_TOKEN}@github.com" > /home/.git-credentials
|
||||
chmod 600 /home/.git-credentials
|
||||
if [ -d "$REPO_DIR/.git" ]; then
|
||||
echo "Repository already exists, pulling latest changes..."
|
||||
cd "$REPO_DIR"
|
||||
git pull || echo "Pull failed, continuing anyway..."
|
||||
else
|
||||
git clone "$GITHUB_REPO" "$WORKSPACE_DIR"
|
||||
echo "Cloning repository..."
|
||||
mkdir -p "$(dirname "$REPO_DIR")"
|
||||
|
||||
if [ -n "$GITHUB_TOKEN" ]; then
|
||||
CLONE_URL=$(echo "$REPO_URL" | sed "s|https://github.com/|https://oauth2:${GITHUB_TOKEN}@github.com/|")
|
||||
git clone "$CLONE_URL" "$REPO_DIR"
|
||||
else
|
||||
git clone "$REPO_URL" "$REPO_DIR"
|
||||
fi
|
||||
fi
|
||||
|
||||
CLONED_DIRS+=("$REPO_DIR")
|
||||
done
|
||||
|
||||
if [ ${#CLONED_DIRS[@]} -eq 1 ]; then
|
||||
# Single repo — open directory directly (same as legacy behavior)
|
||||
WORKSPACE_DIR="${CLONED_DIRS[0]}"
|
||||
else
|
||||
# Multiple repos — generate a multi-root workspace file
|
||||
WS_FILE="/workspace/workspace.code-workspace"
|
||||
printf '{\n "folders": [\n' > "$WS_FILE"
|
||||
for i in "${!CLONED_DIRS[@]}"; do
|
||||
printf ' {"path": "%s"}' "${CLONED_DIRS[$i]}" >> "$WS_FILE"
|
||||
if [ "$i" -lt $(( ${#CLONED_DIRS[@]} - 1 )) ]; then
|
||||
printf ',\n' >> "$WS_FILE"
|
||||
else
|
||||
printf '\n' >> "$WS_FILE"
|
||||
fi
|
||||
done
|
||||
printf ' ],\n "settings": {}\n}\n' >> "$WS_FILE"
|
||||
WORKSPACE_DIR="$WS_FILE"
|
||||
echo "Generated multi-root workspace: $WS_FILE"
|
||||
fi
|
||||
fi
|
||||
|
||||
# Set ownership using numeric IDs (username may not exist yet in baseimage-gui)
|
||||
RUN_UID="${USER_ID:-1000}"
|
||||
RUN_GID="${GROUP_ID:-1000}"
|
||||
chown -R "$RUN_UID:$RUN_GID" "$WORKSPACE_DIR"
|
||||
for dir in "${CLONED_DIRS[@]}"; do
|
||||
chown -R "$RUN_UID:$RUN_GID" "$dir"
|
||||
done
|
||||
if [ -n "$WS_FILE" ] && [ -f "$WS_FILE" ]; then
|
||||
chown "$RUN_UID:$RUN_GID" "$WS_FILE"
|
||||
fi
|
||||
# Ensure default workspace dir ownership if no repos were cloned
|
||||
if [ ${#REPOS[@]} -eq 0 ]; then
|
||||
chown -R "$RUN_UID:$RUN_GID" "$WORKSPACE_DIR"
|
||||
fi
|
||||
|
||||
# Ensure home directory exists on the PVC (may be absent on a fresh volume)
|
||||
mkdir -p "$HOME"
|
||||
chown "$RUN_UID:$RUN_GID" "$HOME"
|
||||
|
||||
# Start Happy Coder daemon
|
||||
echo "Starting Happy Coder..."
|
||||
cd "$WORKSPACE_DIR"
|
||||
|
||||
happy daemon start
|
||||
|
||||
echo "Happy Coder daemon started"
|
||||
# Seed Claude Code settings if missing (disable auto-updater in Docker)
|
||||
if [ ! -f "$HOME/.claude/settings.json" ]; then
|
||||
mkdir -p "$HOME/.claude"
|
||||
echo '{"env":{"DISABLE_AUTOUPDATER":"1"}}' > "$HOME/.claude/settings.json"
|
||||
chown -R "$RUN_UID:$RUN_GID" "$HOME/.claude"
|
||||
fi
|
||||
|
||||
# Export workspace directory for startapp.sh
|
||||
echo "$WORKSPACE_DIR" > /tmp/workspace-dir
|
||||
|
||||
+28
-6
@@ -2,9 +2,9 @@
|
||||
# Start application script for baseimage-gui
|
||||
set -e
|
||||
|
||||
echo "=== Starting Antigravity Dev Container ==="
|
||||
echo "=== Starting Dev Container ==="
|
||||
|
||||
# Initialize repository and Happy Coder
|
||||
# Initialize repository
|
||||
/usr/local/bin/init-repo
|
||||
|
||||
# Get workspace directory
|
||||
@@ -14,8 +14,30 @@ 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"
|
||||
# --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
|
||||
;;
|
||||
*)
|
||||
if [ "$IDE" != "vscode" ]; then
|
||||
echo "WARNING: Unknown IDE value '$IDE', defaulting to VSCode"
|
||||
fi
|
||||
echo "Opening VSCode in: $WORKSPACE_DIR"
|
||||
exec code --new-window --wait "$WORKSPACE_DIR"
|
||||
;;
|
||||
esac
|
||||
|
||||
Executable
+46
@@ -0,0 +1,46 @@
|
||||
#!/bin/bash
|
||||
# Test script to verify git credentials configuration
|
||||
|
||||
set -e
|
||||
|
||||
echo "=== Git Credentials Test ==="
|
||||
|
||||
# Check git configuration
|
||||
echo "1. Git user configuration:"
|
||||
git config --global user.name || echo " ❌ user.name not set"
|
||||
git config --global user.email || echo " ❌ user.email not set"
|
||||
|
||||
echo ""
|
||||
echo "2. Git credential helper:"
|
||||
git config --global credential.helper || echo " ❌ credential.helper not set"
|
||||
|
||||
echo ""
|
||||
echo "3. Credentials file locations:"
|
||||
CREDENTIALS_FILE="/config/userdata/.git-credentials"
|
||||
if [ -f "$CREDENTIALS_FILE" ]; then
|
||||
echo " ✓ $CREDENTIALS_FILE exists"
|
||||
echo " Permissions: $(stat -c %a $CREDENTIALS_FILE)"
|
||||
echo " Lines in file: $(wc -l < $CREDENTIALS_FILE)"
|
||||
else
|
||||
echo " ❌ $CREDENTIALS_FILE does not exist"
|
||||
fi
|
||||
|
||||
if [ -f "$HOME/.git-credentials" ]; then
|
||||
if [ -L "$HOME/.git-credentials" ]; then
|
||||
echo " ✓ $HOME/.git-credentials is a symlink to $(readlink -f $HOME/.git-credentials)"
|
||||
else
|
||||
echo " ✓ $HOME/.git-credentials exists (not a symlink)"
|
||||
fi
|
||||
else
|
||||
echo " ❌ $HOME/.git-credentials does not exist"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "4. Environment check:"
|
||||
echo " HOME=$HOME"
|
||||
echo " GITHUB_TOKEN=${GITHUB_TOKEN:+[SET]}"
|
||||
echo " GIT_USER_NAME=${GIT_USER_NAME:-[NOT SET]}"
|
||||
echo " GIT_USER_EMAIL=${GIT_USER_EMAIL:-[NOT SET]}"
|
||||
|
||||
echo ""
|
||||
echo "=== Test Complete ==="
|
||||
Reference in New Issue
Block a user