Compare commits

..

2 Commits

Author SHA1 Message Date
DevContainer User ac110f3edf chore: bump chart version to 0.1.22 [skip ci] 2026-02-21 15:09:38 +00:00
DevContainer User c311312a87 fix: correct Home Assistant MCP sidecar command for SSE mode
The Home Assistant MCP was failing with 'fastmcp-sse.json not found' error.
Updated command to use proper fastmcp arguments for SSE transport mode
without requiring a configuration file.

Generated with [Claude Code](https://claude.ai/code)
via [Happy](https://happy.engineering)

Co-Authored-By: Claude <noreply@anthropic.com>
Co-Authored-By: Happy <yesreply@happy.engineering>
2026-02-21 15:09:26 +00:00
32 changed files with 738 additions and 2010 deletions
-6
View File
@@ -1,6 +0,0 @@
{
"enabledPlugins": {
"voltagent-dev-exp@voltagent-subagents": true,
"voltagent-lang@voltagent-subagents": true
}
}
+2 -11
View File
@@ -2,15 +2,6 @@
"enabledMcpjsonServers": [ "enabledMcpjsonServers": [
"kubernetes", "kubernetes",
"flux", "flux",
"playwright", "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)"
]
}
} }
+1
View File
@@ -19,6 +19,7 @@
- [ ] Built Docker image locally - [ ] Built Docker image locally
- [ ] Tested container startup - [ ] Tested container startup
- [ ] Tested repository cloning - [ ] Tested repository cloning
- [ ] Tested Happy Coder integration
- [ ] Tested VNC web interface - [ ] Tested VNC web interface
## Checklist ## Checklist
-93
View File
@@ -1,93 +0,0 @@
# 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
+6 -4
View File
@@ -4,6 +4,8 @@ on:
push: push:
branches: branches:
- main - main
tags:
- 'v*'
pull_request: pull_request:
branches: branches:
- main - main
@@ -16,12 +18,10 @@ env:
jobs: jobs:
build-and-push: build-and-push:
runs-on: ubuntu-latest runs-on: ubuntu-latest
if: >-
github.event_name != 'push'
|| !contains(github.event.head_commit.message, '[skip ci]')
permissions: permissions:
contents: read contents: read
packages: write packages: write
id-token: write
steps: steps:
- name: Checkout repository - name: Checkout repository
@@ -46,6 +46,9 @@ jobs:
tags: | tags: |
type=ref,event=branch type=ref,event=branch
type=ref,event=pr type=ref,event=pr
type=semver,pattern={{version}}
type=semver,pattern={{major}}.{{minor}}
type=semver,pattern={{major}}
type=sha,prefix=sha- type=sha,prefix=sha-
type=raw,value=latest,enable={{is_default_branch}} type=raw,value=latest,enable={{is_default_branch}}
@@ -56,7 +59,6 @@ jobs:
push: ${{ github.event_name != 'pull_request' }} push: ${{ github.event_name != 'pull_request' }}
tags: ${{ steps.meta.outputs.tags }} tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }} labels: ${{ steps.meta.outputs.labels }}
build-args: CACHE_BUST=${{ github.sha }}
cache-from: type=gha cache-from: type=gha
cache-to: type=gha,mode=max cache-to: type=gha,mode=max
platforms: linux/amd64 platforms: linux/amd64
+57
View File
@@ -0,0 +1,57 @@
name: Publish Helm Chart
on:
push:
branches:
- main
paths:
- 'chart/**'
workflow_dispatch:
permissions:
contents: write
packages: write
jobs:
publish:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
with:
token: ${{ secrets.GITHUB_TOKEN }}
- name: Set up Helm
uses: azure/setup-helm@v4
- name: Bump patch version
id: bump
run: |
CURRENT=$(grep '^version:' chart/Chart.yaml | awk '{print $2}')
MAJOR=$(echo $CURRENT | cut -d. -f1)
MINOR=$(echo $CURRENT | cut -d. -f2)
PATCH=$(echo $CURRENT | cut -d. -f3)
NEW_VERSION="${MAJOR}.${MINOR}.$((PATCH + 1))"
sed -i "s/^version: .*/version: ${NEW_VERSION}/" chart/Chart.yaml
echo "version=${NEW_VERSION}" >> $GITHUB_OUTPUT
- name: Commit version bump
run: |
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"
git add chart/Chart.yaml
git commit -m "chore: bump chart version to ${{ steps.bump.outputs.version }} [skip ci]"
git push
- name: Log in to GHCR
run: |
helm registry login ghcr.io \
--username ${{ github.actor }} \
--password ${{ secrets.GITHUB_TOKEN }}
- name: Package chart
run: helm package chart/
- name: Push chart to GHCR
run: |
helm push devcontainer-${{ steps.bump.outputs.version }}.tgz oci://ghcr.io/cpfarhood/charts
-56
View File
@@ -1,56 +0,0 @@
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
-193
View File
@@ -1,193 +0,0 @@
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
+51
View File
@@ -0,0 +1,51 @@
name: Release
on:
push:
tags:
- 'v*'
permissions:
contents: write
packages: write
jobs:
release:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v6
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
+259
View File
@@ -0,0 +1,259 @@
# 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
```
+8 -10
View File
@@ -1,12 +1,5 @@
{ {
"mcpServers": { "mcpServers": {
"github": {
"type": "http",
"url": "https://api.githubcopilot.com/mcp/",
"headers": {
"Authorization": "Bearer ${GITHUB_TOKEN}"
}
},
"kubernetes": { "kubernetes": {
"type": "sse", "type": "sse",
"url": "http://localhost:8080/sse" "url": "http://localhost:8080/sse"
@@ -15,9 +8,14 @@
"type": "sse", "type": "sse",
"url": "http://localhost:8081/sse" "url": "http://localhost:8081/sse"
}, },
"homeassistant": {
"type": "sse",
"url": "http://localhost:8087/sse"
},
"playwright": { "playwright": {
"type": "sse", "type": "sse",
"url": "http://localhost:8086/sse" "url": "http://playwright-mcp.playwright.svc.cluster.local:3000/sse"
} }
} }
} }
+33 -78
View File
@@ -4,13 +4,11 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co
## Project Overview ## Project Overview
The Dev Container is a Docker-based cloud development environment that provides: Antigravity is a Docker-based cloud development environment that provides:
- Web-based GUI IDE (VSCode/Antigravity) via VNC on port 5800 - Web-based GUI IDE (VSCode/Antigravity) via VNC on port 5800
- Claude Code, OpenCode, and Crush AI coding agents (terminal-based) - Happy Coder AI assistant integration
- Built-in web file manager for uploading/downloading files (optional, via `fileManager.enabled`)
- Automatic GitHub repository cloning on startup - Automatic GitHub repository cloning on startup
- Kubernetes-native deployment with persistent home storage - 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. The stack is primarily **Bash scripts + YAML** — there is no Node.js package, compiled language, or test framework.
@@ -21,7 +19,7 @@ The stack is primarily **Bash scripts + YAML** — there is no Node.js package,
```bash ```bash
make build # Build Docker image make build # Build Docker image
make build REGISTRY=ghcr.io/myuser IMAGE_TAG=v1.0 # Custom registry/tag make build REGISTRY=ghcr.io/myuser IMAGE_TAG=v1.0 # Custom registry/tag
docker build -t ghcr.io/cpfarhood/devcontainer:latest . # Direct build docker build -t ghcr.io/cpfarhood/antigravity:latest . # Direct build
``` ```
### Running Locally ### Running Locally
@@ -61,8 +59,8 @@ Container start
→ scripts/startapp.sh → scripts/startapp.sh
→ scripts/init-repo.sh → scripts/init-repo.sh
→ Configure git user & credentials → Configure git user & credentials
→ Clone GITHUB_REPOS or GITHUB_REPO (if set) → Clone GITHUB_REPO (if set)
Generate workspace.code-workspace for multi-repo setups Start Happy Coder
→ Launch VSCode as user `user` in /workspace → Launch VSCode as user `user` in /workspace
``` ```
@@ -70,8 +68,8 @@ Container start
| File | Purpose | | File | Purpose |
|------|---------| |------|---------|
| `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) | | `Dockerfile` | Image definition — installs Chrome, Node.js, VSCode, Happy Coder; creates non-root user (UID 1000) |
| `scripts/init-repo.sh` | Configures git credentials, clones GitHub repo(s), generates multi-root workspace file | | `scripts/init-repo.sh` | Configures git credentials, clones GitHub repo, starts Happy Coder background service |
| `scripts/startapp.sh` | Calls init-repo.sh then opens VSCode in the workspace | | `scripts/startapp.sh` | Calls init-repo.sh then opens VSCode in the workspace |
| `chart/` | Helm chart for Kubernetes deployment | | `chart/` | Helm chart for Kubernetes deployment |
| `chart/templates/deployment.yaml` | Deployment spec — main container + MCP sidecar containers | | `chart/templates/deployment.yaml` | Deployment spec — main container + MCP sidecar containers |
@@ -79,7 +77,7 @@ Container start
| `chart/templates/pvc.yaml` | PersistentVolumeClaim for user home | | `chart/templates/pvc.yaml` | PersistentVolumeClaim for user home |
| `chart/templates/service.yaml` | ClusterIP Service (VNC + optional SSH) | | `chart/templates/service.yaml` | ClusterIP Service (VNC + optional SSH) |
| `chart/values.yaml` | Default Helm values | | `chart/values.yaml` | Default Helm values |
| `.mcp.json` | MCP server connection config (GitHub Copilot, Kubernetes, Flux, Helm, Fetch, Sequential Thinking, Playwright, pgtuner) | | `.mcp.json` | MCP server connection config (Kubernetes, Flux, Playwright) |
| `Makefile` | Build/deploy automation | | `Makefile` | Build/deploy automation |
### MCP Sidecars ### MCP Sidecars
@@ -90,23 +88,13 @@ MCP (Model Context Protocol) servers run as sidecar containers in the pod, enabl
|---------|-------|---------|------|----------|---------| |---------|-------|---------|------|----------|---------|
| `kubernetes-mcp` | `quay.io/containers/kubernetes_mcp_server` | v0.0.57 | 8080 | `http://localhost:8080/sse` | Enabled | | `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 | | `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 | | `homeassistant-mcp` | `ghcr.io/homeassistant-ai/ha-mcp` | v6.7.1 | 8087 | `http://localhost:8087/sse` | Disabled |
| `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:** **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 require `clusterAccess` != `none` to be deployed (they need RBAC permissions)
- Kubernetes and Flux sidecars inherit the pod's ServiceAccount 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 - 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 MCP remains an external service
- Playwright sidecar provides browser automation and web testing capabilities
#### Enabling/Disabling MCP Servers #### Enabling/Disabling MCP Servers
@@ -114,60 +102,30 @@ To control MCP sidecars, set the `enabled` flag in your values override:
```yaml ```yaml
# Disable all MCP sidecars # Disable all MCP sidecars
mcp: mcpSidecars:
sidecars: kubernetes:
kubernetes: enabled: false
enabled: false flux:
flux: enabled: false
enabled: false homeassistant:
helm: enabled: false
enabled: false
fetch:
enabled: false
sequentialthinking:
enabled: false
homeassistant:
enabled: false
pgtuner:
enabled: false
playwright:
enabled: false
# Or selectively enable/disable # Or selectively enable/disable
mcp: mcpSidecars:
sidecars: kubernetes:
kubernetes: enabled: true # Keep Kubernetes MCP enabled
enabled: true # Keep Kubernetes MCP enabled flux:
flux: enabled: false # Disable Flux MCP
enabled: false # Disable Flux MCP homeassistant:
helm: enabled: true # Enable Home Assistant MCP (requires secrets)
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: When deploying via Helm:
```bash ```bash
# Quick start (recommended) # Using --set flag
cp chart/values-quickstart.yaml my-values.yaml helm install my-devcontainer ./chart --set mcpSidecars.kubernetes.enabled=false --set mcpSidecars.flux.enabled=false
# Edit name and githubRepo in my-values.yaml
helm install my-devcontainer ./chart -f my-values.yaml
# Using --set flags # Or with a values file
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 helm install my-devcontainer ./chart -f custom-values.yaml
``` ```
@@ -178,9 +136,8 @@ helm install my-devcontainer ./chart -f custom-values.yaml
### Environment Variables ### Environment Variables
**Required (at least one):** **Required:**
- `GITHUB_REPO` — URL of a single repository to clone into `/workspace` - `GITHUB_REPO` — URL of 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:** **Optional:**
- `GITHUB_TOKEN` — PAT for private repo access (automatically configures git credentials) - `GITHUB_TOKEN` — PAT for private repo access (automatically configures git credentials)
@@ -190,22 +147,20 @@ helm install my-devcontainer ./chart -f custom-values.yaml
- `VNC_PASSWORD` — VNC web interface password - `VNC_PASSWORD` — VNC web interface password
- `DISPLAY_WIDTH` / `DISPLAY_HEIGHT` — VNC resolution - `DISPLAY_WIDTH` / `DISPLAY_HEIGHT` — VNC resolution
- `USER_ID` / `GROUP_ID` — Override UID/GID (default 1000) - `USER_ID` / `GROUP_ID` — Override UID/GID (default 1000)
- `WEB_FILE_MANAGER` — Set to `1` to enable the built-in web file manager (controlled via `fileManager.enabled` in Helm values) - `HAPPY_SERVER_URL` / `HAPPY_WEBAPP_URL` — Custom Happy Coder endpoints
- `WEB_FILE_MANAGER_ALLOWED_PATHS` — Paths accessible by the file manager (default: `/workspace,/config`) - `HAPPY_HOME_DIR` / `HAPPY_EXPERIMENTAL`
- `WEB_FILE_MANAGER_DENIED_PATHS` — Paths to deny access to (takes precedence over allowed)
### CI/CD ### 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. - **`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-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. - **`release.yaml`** — Creates a GitHub Release with docker pull instructions when a version tag is pushed.
- **`dependabot.yml`** — Weekly updates for GitHub Actions and Docker base image. - **`dependabot.yml`** — Weekly updates for GitHub Actions and Docker base image.
Image registry: `ghcr.io/cpfarhood/devcontainer` Image registry: `ghcr.io/cpfarhood/devcontainer`
Helm repo: `https://cpfarhood.github.io/devcontainer`
## Kubernetes Notes ## Kubernetes Notes
- Deployed via Helm chart (`chart/`), published to GitHub Pages Helm repo, reconciled by Flux - Deployed via Helm chart (`chart/`), published as OCI artifact to GHCR, reconciled by Flux
- Storage class is `ceph-filesystem` by default — change via `storage.className` in values - Storage class is `ceph-filesystem` by default — change via `storage.className` in values
- Resource limits: 14 CPU, 28Gi memory - Resource limits: 14 CPU, 28Gi memory
- Health checks (liveness/readiness probes) on port 5800 - Health checks (liveness/readiness probes) on port 5800
+12
View File
@@ -225,6 +225,18 @@ spec:
## Advanced Configurations ## Advanced Configurations
### Custom Happy Coder Endpoints
For self-hosted Happy instances:
```bash
helm install mydev ./chart \
--set name=mydev \
--set githubRepo=https://github.com/youruser/yourrepo \
--set happyServerUrl=https://your-happy-server.com \
--set happyWebappUrl=https://your-happy-webapp.com
```
### Custom Display Resolution ### Custom Display Resolution
```bash ```bash
+10 -133
View File
@@ -1,10 +1,7 @@
FROM jlesage/baseimage-gui:ubuntu-22.04-v4 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 # Set environment variables
ENV APP_NAME="Dev Container" \ ENV APP_NAME="Antigravity Dev Container" \
KEEP_APP_RUNNING=1 \ KEEP_APP_RUNNING=1 \
DISPLAY_WIDTH=1920 \ DISPLAY_WIDTH=1920 \
DISPLAY_HEIGHT=1080 \ DISPLAY_HEIGHT=1080 \
@@ -59,121 +56,17 @@ exec /usr/bin/google-chrome-stable \\\n\
"$@"\n' > /usr/local/bin/google-chrome && \ "$@"\n' > /usr/local/bin/google-chrome && \
chmod +x /usr/local/bin/google-chrome chmod +x /usr/local/bin/google-chrome
# Install Node.js LTS via NodeSource # Install Node.js (LTS version for Happy Coder)
ARG NODE_MAJOR=22 RUN curl -fsSL https://deb.nodesource.com/setup_lts.x | bash - && \
RUN curl -fsSL https://deb.nodesource.com/setup_${NODE_MAJOR}.x | bash - && \
apt-get install -y nodejs && \ 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/* rm -rf /var/lib/apt/lists/*
# Install kubeseal CLI for Bitnami Sealed Secrets # Install Happy Coder and Claude Code globally
RUN KUBESEAL_VERSION=$(curl -sL https://api.github.com/repos/bitnami-labs/sealed-secrets/releases/latest | jq -r '.tag_name' | sed 's/^v//') && \ RUN npm install -g happy-coder @anthropic-ai/claude-code
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
# ── LSP servers for Claude Code language intelligence ── # Install VSCode
RUN wget -qO- https://packages.microsoft.com/keys/microsoft.asc | gpg --dearmor -o /usr/share/keyrings/packages.microsoft.gpg && \
# npm-based LSP servers: Python (pyright), TypeScript/JavaScript, PHP 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 && \
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_VERSION=$(curl -sL https://api.github.com/repos/eclipse-jdtls/eclipse.jdt.ls/releases/latest | jq -r '.tag_name' | sed 's/^v//') && \
JDTLS_URL=$(curl -sL https://api.github.com/repos/eclipse-jdtls/eclipse.jdt.ls/releases/latest | jq -r '.assets[] | select(.name | endswith(".tar.gz")) | .browser_download_url' | head -1) && \
mkdir -p /opt/jdtls && \
curl -fsSL "$JDTLS_URL" | 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 update && \
apt-get install -y code && \ apt-get install -y code && \
rm -rf /var/lib/apt/lists/* rm -rf /var/lib/apt/lists/*
@@ -184,26 +77,10 @@ RUN mkdir -p /etc/apt/keyrings && \
gpg --dearmor --yes -o /etc/apt/keyrings/antigravity-repo-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" \ 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 && \ > /etc/apt/sources.list.d/antigravity.list && \
# Clear package cache to force fresh repository data
rm -rf /var/lib/apt/lists/* && \
apt-get update && \ apt-get update && \
# Show available versions for debugging apt-get install -y antigravity && \
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/* 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) # Install OpenSSH server (for SSH IDE mode)
RUN apt-get update && \ RUN apt-get update && \
apt-get install -y openssh-server && \ apt-get install -y openssh-server && \
@@ -242,4 +119,4 @@ ENV HOME=/config/userdata \
EXPOSE 5800 EXPOSE 5800
# Set app name for baseimage-gui # Set app name for baseimage-gui
RUN set-cont-env APP_NAME "Dev Container" RUN set-cont-env APP_NAME "Antigravity"
+8 -7
View File
@@ -2,7 +2,7 @@
# Variables # Variables
REGISTRY ?= ghcr.io/cpfarhood REGISTRY ?= ghcr.io/cpfarhood
IMAGE_NAME ?= devcontainer IMAGE_NAME ?= antigravity
IMAGE_TAG ?= latest IMAGE_TAG ?= latest
FULL_IMAGE = $(REGISTRY)/$(IMAGE_NAME):$(IMAGE_TAG) FULL_IMAGE = $(REGISTRY)/$(IMAGE_NAME):$(IMAGE_TAG)
@@ -26,17 +26,18 @@ run:
-e GITHUB_REPO="${GITHUB_REPO}" \ -e GITHUB_REPO="${GITHUB_REPO}" \
-e GITHUB_TOKEN="${GITHUB_TOKEN}" \ -e GITHUB_TOKEN="${GITHUB_TOKEN}" \
-e VNC_PASSWORD="${VNC_PASSWORD}" \ -e VNC_PASSWORD="${VNC_PASSWORD}" \
-e HAPPY_EXPERIMENTAL="true" \
-v $(PWD)/home:/home \ -v $(PWD)/home:/home \
-v $(PWD)/workspace:/workspace \ -v $(PWD)/workspace:/workspace \
--name devcontainer \ --name antigravity \
$(FULL_IMAGE) $(FULL_IMAGE)
@echo "Access at http://localhost:5800" @echo "Access at http://localhost:5800"
# Stop the running container # Stop the running container
stop: stop:
@echo "Stopping devcontainer..." @echo "Stopping antigravity container..."
docker stop devcontainer || true docker stop antigravity || true
docker rm devcontainer || true docker rm antigravity || true
# Clean up local volumes # Clean up local volumes
clean: stop clean: stop
@@ -80,7 +81,7 @@ helm-port-forward:
# Show help # Show help
help: help:
@echo "Dev Container Makefile" @echo "Antigravity Dev Container Makefile"
@echo "" @echo ""
@echo "Usage: make [target]" @echo "Usage: make [target]"
@echo "" @echo ""
@@ -100,7 +101,7 @@ help:
@echo "" @echo ""
@echo "Variables:" @echo "Variables:"
@echo " REGISTRY - Docker registry (default: ghcr.io/cpfarhood)" @echo " REGISTRY - Docker registry (default: ghcr.io/cpfarhood)"
@echo " IMAGE_NAME - Image name (default: devcontainer)" @echo " IMAGE_NAME - Image name (default: antigravity)"
@echo " IMAGE_TAG - Image tag (default: latest)" @echo " IMAGE_TAG - Image tag (default: latest)"
@echo " RELEASE_NAME - Helm release name (default: mydev)" @echo " RELEASE_NAME - Helm release name (default: mydev)"
@echo " NAMESPACE - Kubernetes namespace (default: default)" @echo " NAMESPACE - Kubernetes namespace (default: default)"
+77 -152
View File
@@ -1,52 +1,17 @@
# Dev Container # Antigravity Dev Container
![Build and Push](https://github.com/cpfarhood/devcontainer/actions/workflows/build-and-push.yaml/badge.svg) ![Build and Push](https://github.com/cpfarhood/devcontainer/actions/workflows/build-and-push.yaml/badge.svg)
A containerized cloud development environment with web-based GUI access, featuring: A containerized cloud development environment with web-based GUI access, featuring:
- **VSCode or Google Antigravity** via browser-based VNC (port 5800) - **VSCode or Google Antigravity** via browser-based VNC (port 5800)
- **SSH access** option (OpenSSH on port 22, additive with any IDE) - **SSH access** option (OpenSSH on port 22, additive with any IDE)
- **Claude Code**, **OpenCode**, and **Crush** AI coding agents (terminal-based) - **Happy Coder** AI assistant backed by Claude
- **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 - **Automatic GitHub repo cloning** on startup
- **Persistent home directory** via ReadWriteMany PVC - **Persistent home directory** via ReadWriteMany PVC
- **Kubernetes-native** Helm chart deployment - **Kubernetes-native** Helm chart deployment
## Quick Start ## Quick Start
### Option A: Install from Helm Repo (Recommended)
```bash
# Add the Helm repository
helm repo add devcontainer https://cpfarhood.github.io/devcontainer
helm repo update
# Deploy with one command
helm install mydev devcontainer/devcontainer \
--set name=mydev \
--set githubRepo=https://github.com/youruser/yourrepo
```
### 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 ### 1. Create a secret
The secret is picked up automatically via `envFrom`. Keys recognised: The secret is picked up automatically via `envFrom`. Keys recognised:
@@ -57,10 +22,8 @@ The secret is picked up automatically via `envFrom`. Keys recognised:
| `VNC_PASSWORD` | Password for the VNC web UI | | `VNC_PASSWORD` | Password for the VNC web UI |
| `ANTHROPIC_API_KEY` | API key — alternative to browser-based Claude login | | `ANTHROPIC_API_KEY` | API key — alternative to browser-based Claude login |
| `SSH_AUTHORIZED_KEYS` | Public key(s) for SSH access (required when `ssh: true`) | | `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-url` | Home Assistant URL (required when `mcpSidecars.homeassistant.enabled: true`) |
| `HOMEASSISTANT_TOKEN` | Home Assistant long-lived access token (required when `mcp.sidecars.homeassistant.enabled: true`) | | `homeassistant-token` | Home Assistant long-lived access token (required when `mcpSidecars.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 ```bash
kubectl create secret generic devcontainer-mydev-secrets-env \ kubectl create secret generic devcontainer-mydev-secrets-env \
@@ -110,33 +73,20 @@ A Chrome browser window will open inside VNC for the Claude Max OAuth login. Cre
## Helm Chart Reference ## 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 ### Core values
| Value | Default | Description | | Value | Default | Description |
|-------|---------|-------------| |-------|---------|-------------|
| `name` | `""` | Instance name — used in all resource names (`devcontainer-{name}`) | | `name` | `""` | Instance name — used in all resource names (`devcontainer-{name}`) |
| `githubRepo` | `""` | Repository to clone into `/workspace` on startup | | `githubRepo` | `""` | Repository to clone into `/workspace` on startup |
| `ide.type` | `vscode` | IDE to launch — `vscode`, `antigravity`, or `none` (see below) | | `ide` | `vscode` | IDE to launch — `vscode`, `antigravity`, or `none` (see below) |
| `ssh.enabled` | `false` | Also start an OpenSSH server on port 22 (additive, any IDE) | | `ssh` | `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.repository` | `ghcr.io/cpfarhood/devcontainer` | Container image |
| `image.tag` | `latest` | Image tag | | `image.tag` | `latest` | Image tag |
### IDE choice ### IDE choice
`ide.type` controls what GUI is launched in the VNC session: `ide` controls what GUI is launched in the VNC session:
| Value | Port | Description | | Value | Port | Description |
|-------|------|-------------| |-------|------|-------------|
@@ -146,14 +96,14 @@ The Helm chart uses a logical organization with these main sections:
### SSH access ### SSH access
`ssh.enabled: true` starts OpenSSH on port 22 **in addition to** the IDE. It works with any `ide.type` value: `ssh: true` starts OpenSSH on port 22 **in addition to** the IDE. It works with any `ide` value:
```bash ```bash
# SSH-only (no VNC) # SSH-only (no VNC)
helm install mydev ./chart --set name=mydev --set ide.type=none --set ssh.enabled=true helm install mydev ./chart --set name=mydev --set ide=none --set ssh=true
# VSCode in VNC + SSH access at the same time # VSCode in VNC + SSH access at the same time
helm install mydev ./chart --set name=mydev --set ssh.enabled=true helm install mydev ./chart --set name=mydev --set ssh=true
``` ```
Add your public key to the env secret: Add your public key to the env secret:
@@ -171,23 +121,14 @@ kubectl port-forward deployment/devcontainer-mydev 2222:22
ssh -p 2222 user@localhost ssh -p 2222 user@localhost
``` ```
### Web file manager ### Happy Coder
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 | | Value | Default | Description |
|-------|---------|-------------| |-------|---------|-------------|
| `fileManager.enabled` | `false` | Enable the web file manager | | `happyServerUrl` | `https://happy.farh.net` | Happy Coder server endpoint |
| `fileManager.allowedPaths` | `/workspace,/config` | Paths accessible by the file manager (`AUTO`, `ALL`, or comma-separated) | | `happyWebappUrl` | `https://happy-coder.farh.net` | Happy Coder webapp URL |
| `fileManager.deniedPaths` | `""` | Paths to deny (takes precedence over allowed) | | `happyHomeDir` | `/home/user/.happy` | Happy runtime state directory (persists on the home PVC) |
| `happyExperimental` | `true` | Enable experimental Happy features |
```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 ### Kubernetes cluster access
@@ -217,35 +158,29 @@ The devcontainer includes MCP (Model Context Protocol) servers as sidecar contai
| Sidecar | Default | Purpose | | Sidecar | Default | Purpose |
|---------|---------|---------| |---------|---------|---------|
| `mcp.sidecars.kubernetes.enabled` | `true` | Kubernetes API access via MCP | | `mcpSidecars.kubernetes.enabled` | `true` | Kubernetes API access via MCP |
| `mcp.sidecars.flux.enabled` | `true` | Flux GitOps operations via MCP | | `mcpSidecars.flux.enabled` | `true` | Flux GitOps operations via MCP |
| `mcp.sidecars.homeassistant.enabled` | `false` | Home Assistant smart home control via MCP | | `mcpSidecars.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:** **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 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`) - 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 - Home Assistant sidecar requires additional configuration (see below)
- 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:** **Disable MCP sidecars:**
```bash ```bash
# Disable multiple sidecars # Disable both sidecars
helm install mydev ./chart \ helm install mydev ./chart \
--set name=mydev \ --set name=mydev \
--set githubRepo=https://github.com/youruser/yourrepo \ --set githubRepo=https://github.com/youruser/yourrepo \
--set mcp.sidecars.kubernetes.enabled=false \ --set mcpSidecars.kubernetes.enabled=false \
--set mcp.sidecars.flux.enabled=false \ --set mcpSidecars.flux.enabled=false
--set mcp.sidecars.playwright.enabled=false
# Or selectively disable # Or selectively disable
helm install mydev ./chart \ helm install mydev ./chart \
--set name=mydev \ --set name=mydev \
--set githubRepo=https://github.com/youruser/yourrepo \ --set githubRepo=https://github.com/youruser/yourrepo \
--set mcp.sidecars.flux.enabled=false # Disable only Flux MCP --set mcpSidecars.flux.enabled=false # Disable only Flux MCP
``` ```
**Enable Home Assistant MCP:** **Enable Home Assistant MCP:**
@@ -253,75 +188,48 @@ helm install mydev ./chart \
# Create secret with Home Assistant credentials # Create secret with Home Assistant credentials
kubectl create secret generic devcontainer-mydev-secrets-env \ kubectl create secret generic devcontainer-mydev-secrets-env \
--from-literal=GITHUB_TOKEN='ghp_...' \ --from-literal=GITHUB_TOKEN='ghp_...' \
--from-literal=HOMEASSISTANT_URL='http://homeassistant.local:8123' \ --from-literal=homeassistant-url='http://homeassistant.local:8123' \
--from-literal=HOMEASSISTANT_TOKEN='your_long_lived_access_token' --from-literal=homeassistant-token='your_long_lived_access_token'
# Deploy with Home Assistant MCP enabled # Deploy with Home Assistant MCP enabled
helm install mydev ./chart \ helm install mydev ./chart \
--set name=mydev \ --set name=mydev \
--set githubRepo=https://github.com/youruser/yourrepo \ --set githubRepo=https://github.com/youruser/yourrepo \
--set mcp.sidecars.homeassistant.enabled=true --set mcpSidecars.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:** **Custom MCP configuration:**
```yaml ```yaml
# values.yaml override # values.yaml override
mcp: mcpSidecars:
sidecars: kubernetes:
kubernetes: enabled: true
enabled: true image:
image: repository: quay.io/containers/kubernetes_mcp_server
repository: quay.io/containers/kubernetes_mcp_server tag: latest
tag: v0.0.57 port: 8080
port: 8080 resources:
resources: requests:
requests: memory: "64Mi"
memory: "64Mi" cpu: "50m"
cpu: "50m" limits:
limits: memory: "256Mi"
memory: "256Mi" cpu: "500m"
cpu: "500m" flux:
flux: enabled: false # Disabled in this example
enabled: false # Disabled in this example homeassistant:
homeassistant: enabled: true
enabled: true image:
image: repository: ghcr.io/homeassistant-ai/ha-mcp
repository: ghcr.io/homeassistant-ai/ha-mcp tag: v6.7.1 # Override the pinned version if needed
tag: stable port: 8087
port: 8087 resources:
pgtuner: requests:
enabled: true memory: "128Mi"
image: cpu: "100m"
repository: dog830228/pgtuner_mcp limits:
tag: latest memory: "512Mi"
port: 8085 cpu: "500m"
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 ### Display and resources
@@ -330,9 +238,9 @@ mcp:
|-------|---------|-------------| |-------|---------|-------------|
| `display.width` | `1920` | VNC width (px) | | `display.width` | `1920` | VNC width (px) |
| `display.height` | `1080` | VNC height (px) | | `display.height` | `1080` | VNC height (px) |
| `display.secureConnection` | `0` | Set to `1` if TLS is not terminated upstream | | `secureConnection` | `0` | Set to `1` if TLS is not terminated upstream |
| `user.id` | `1000` | UID for the app user | | `userId` | `1000` | UID for the app user |
| `user.groupId` | `1000` | GID for the app user | | `groupId` | `1000` | GID for the app user |
| `storage.size` | `32Gi` | Home PVC size | | `storage.size` | `32Gi` | Home PVC size |
| `storage.className` | `ceph-filesystem` | StorageClass (must be ReadWriteMany) | | `storage.className` | `ceph-filesystem` | StorageClass (must be ReadWriteMany) |
| `shm.sizeLimit` | `2Gi` | `/dev/shm` size (memory-backed; used by Electron apps) | | `shm.sizeLimit` | `2Gi` | `/dev/shm` size (memory-backed; used by Electron apps) |
@@ -355,6 +263,8 @@ Container start
→ /startapp.sh (runs as app user, UID 1000) → /startapp.sh (runs as app user, UID 1000)
→ init-repo.sh → init-repo.sh
→ clone / pull GITHUB_REPO into /workspace/{repo} → clone / pull GITHUB_REPO into /workspace/{repo}
→ rm daemon.state.json.lock — clear stale Happy lock
→ happy daemon start — starts Happy Coder background daemon
→ IDE=vscode: code --new-window --wait /workspace/{repo} → IDE=vscode: code --new-window --wait /workspace/{repo}
IDE=antigravity: antigravity --no-sandbox --user-data-dir ~/.config/antigravity ... /workspace/{repo} IDE=antigravity: antigravity --no-sandbox --user-data-dir ~/.config/antigravity ... /workspace/{repo}
IDE=none: sleep infinity IDE=none: sleep infinity
@@ -365,13 +275,28 @@ Container start
| Mount | Source | Persistence | | Mount | Source | Persistence |
|-------|--------|-------------| |-------|--------|-------------|
| `/config` | ReadWriteMany PVC (`userhome-{name}`) | Survives pod restarts — stores Claude credentials, dotfiles, git config | | `/home` | ReadWriteMany PVC (`userhome-{name}`) | Survives pod restarts — stores Claude credentials, dotfiles, git config |
| `/workspace` | `emptyDir` | Ephemeral — repo is re-cloned on each pod start | | `/workspace` | `emptyDir` | Ephemeral — repo is re-cloned on each pod start |
Happy Coder's runtime state (`HAPPY_HOME_DIR`) is kept in `/home/user/.happy` on the persistent home PVC, so auth credentials and settings survive pod restarts. A stale lock file (`daemon.state.json.lock`) is removed automatically on each startup.
--- ---
## Troubleshooting ## Troubleshooting
### Happy Coder daemon not starting
```bash
# Check daemon status
happy daemon status
# Start manually (also clears any stale lock)
happy daemon start
# View daemon logs
ls ~/.happy/logs/
```
### Claude not authenticated ### Claude not authenticated
Browser-based OAuth login is the primary method (works inside VNC via the Chrome wrapper). If you prefer API key auth: Browser-based OAuth login is the primary method (works inside VNC via the Chrome wrapper). If you prefer API key auth:
@@ -437,4 +362,4 @@ The image is also built and pushed automatically by CI on every push to `main` a
## Credits ## Credits
- Base image: [jlesage/docker-baseimage-gui](https://github.com/jlesage/docker-baseimage-gui) - Base image: [jlesage/docker-baseimage-gui](https://github.com/jlesage/docker-baseimage-gui)
- AI assistant: [Claude](https://claude.ai) - AI assistant: [Happy Coder](https://happy.engineering) + [Claude](https://claude.ai)
+30
View File
@@ -52,6 +52,30 @@ Complete reference for all configurable values in the Antigravity Dev Container
- **Options:** `Always`, `IfNotPresent`, `Never` - **Options:** `Always`, `IfNotPresent`, `Never`
- **Description:** Image pull policy - **Description:** Image pull policy
## Happy Coder Configuration
### happyServerUrl
- **Type:** String
- **Default:** `https://happy.farh.net`
- **Description:** Happy Coder server endpoint
- **When to Change:** Self-hosted Happy instance
### happyWebappUrl
- **Type:** String
- **Default:** `https://happy-coder.farh.net`
- **Description:** Happy Coder webapp URL
- **When to Change:** Self-hosted Happy instance
### happyHomeDir
- **Type:** String
- **Default:** `/config/userdata/.happy`
- **Description:** Happy runtime state directory (persists on PVC)
### happyExperimental
- **Type:** String
- **Default:** `"true"`
- **Description:** Enable experimental Happy features
## Display Configuration ## Display Configuration
### display.width ### display.width
@@ -315,6 +339,8 @@ storage:
clusterAccess: readonly clusterAccess: readonly
happyServerUrl: https://happy.internal.company.com
happyWebappUrl: https://happy-app.internal.company.com
``` ```
### Smart Home Development Configuration ### Smart Home Development Configuration
@@ -405,6 +431,10 @@ These environment variables are set in the container based on chart values:
| `VNC_PASSWORD` | Secret: `vnc-password` | VNC access password | | `VNC_PASSWORD` | Secret: `vnc-password` | VNC access password |
| `ANTHROPIC_API_KEY` | Secret: `anthropic-api-key` | Claude API key | | `ANTHROPIC_API_KEY` | Secret: `anthropic-api-key` | Claude API key |
| `SSH_AUTHORIZED_KEYS` | Secret: `ssh-authorized-keys` | SSH public keys | | `SSH_AUTHORIZED_KEYS` | Secret: `ssh-authorized-keys` | SSH public keys |
| `HAPPY_SERVER_URL` | `happyServerUrl` | Happy server endpoint |
| `HAPPY_WEBAPP_URL` | `happyWebappUrl` | Happy webapp URL |
| `HAPPY_HOME_DIR` | `happyHomeDir` | Happy data directory |
| `HAPPY_EXPERIMENTAL` | `happyExperimental` | Experimental features |
| `DISPLAY_WIDTH` | `display.width` | VNC width | | `DISPLAY_WIDTH` | `display.width` | VNC width |
| `DISPLAY_HEIGHT` | `display.height` | VNC height | | `DISPLAY_HEIGHT` | `display.height` | VNC height |
| `SECURE_CONNECTION` | `secureConnection` | TLS termination | | `SECURE_CONNECTION` | `secureConnection` | TLS termination |
+2 -7
View File
@@ -1,11 +1,6 @@
apiVersion: v2 apiVersion: v2
name: devcontainer name: devcontainer
description: Dev Container with AI coding agents and MCP sidecars description: Antigravity Dev Container with Happy Coder AI assistant
type: application type: application
version: 2.5.0 version: 0.1.22
appVersion: "latest" appVersion: "latest"
keywords:
- development
- devcontainer
- vscode
- ai
-381
View File
@@ -1,381 +0,0 @@
# 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
-31
View File
@@ -1,31 +0,0 @@
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 }}
+4 -87
View File
@@ -1,111 +1,28 @@
{{/* {{/*
Resource name prefix: devcontainer-{name} Resource name prefix: devcontainer-{name}
*/}} */}}
{{- define "devcontainer.fullname" -}} {{- define "antigravity.fullname" -}}
{{- if not .Values.name }}
{{- fail "values.name is required and must not be empty" }}
{{- end }}
{{- printf "devcontainer-%s" .Values.name }} {{- printf "devcontainer-%s" .Values.name }}
{{- end }} {{- end }}
{{/* {{/*
PVC name: userhome-{name} PVC name: userhome-{name}
*/}} */}}
{{- define "devcontainer.pvcName" -}} {{- define "antigravity.pvcName" -}}
{{- printf "userhome-%s" .Values.name }} {{- printf "userhome-%s" .Values.name }}
{{- end }} {{- end }}
{{/* {{/*
Secret name for env vars, default to devcontainer-{name}-secrets-env Secret name for env vars, default to devcontainer-{name}-secrets-env
*/}} */}}
{{- define "devcontainer.envSecretName" -}} {{- define "antigravity.envSecretName" -}}
{{- .Values.envSecretName | default (printf "devcontainer-%s-secrets-env" .Values.name) }} {{- .Values.envSecretName | default (printf "devcontainer-%s-secrets-env" .Values.name) }}
{{- end }} {{- end }}
{{/* {{/*
Common labels Common labels
*/}} */}}
{{- define "devcontainer.labels" -}} {{- define "antigravity.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 app: devcontainer
instance: {{ .Values.name }} instance: {{ .Values.name }}
{{- end }} {{- 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 }}
+50 -167
View File
@@ -1,95 +1,68 @@
apiVersion: apps/v1 apiVersion: apps/v1
kind: Deployment kind: Deployment
metadata: metadata:
name: {{ include "devcontainer.fullname" . }} name: {{ include "antigravity.fullname" . }}
labels: labels:
{{- include "devcontainer.labels" . | nindent 4 }} {{- include "antigravity.labels" . | nindent 4 }}
spec: spec:
replicas: 1 replicas: 1
selector: selector:
matchLabels: matchLabels:
{{- include "devcontainer.selectorLabels" . | nindent 6 }} {{- include "antigravity.labels" . | nindent 6 }}
template: template:
metadata: metadata:
labels: labels:
{{- include "devcontainer.labels" . | nindent 8 }} {{- include "antigravity.labels" . | nindent 8 }}
spec: spec:
{{- if ne (.Values.clusterAccess | default "none") "none" }} {{- if ne (.Values.clusterAccess | default "none") "none" }}
serviceAccountName: {{ include "devcontainer.fullname" . }} serviceAccountName: {{ include "antigravity.fullname" . }}
{{- end }} {{- end }}
securityContext: securityContext:
fsGroup: 1000 fsGroup: 1000
fsGroupChangePolicy: "OnRootMismatch" 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: containers:
- name: devcontainer - name: devcontainer
image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}" image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}"
imagePullPolicy: {{ .Values.image.pullPolicy }} imagePullPolicy: {{ .Values.image.pullPolicy }}
ports: ports:
{{- if ne (.Values.ide.type | default "vscode") "none" }} {{- if ne (.Values.ide | default "vscode") "none" }}
- containerPort: 5800 - containerPort: 5800
name: vnc-web name: vnc-web
protocol: TCP protocol: TCP
{{- end }} {{- end }}
{{- if .Values.ssh.enabled }} {{- if .Values.ssh }}
- containerPort: 22 - containerPort: 22
name: ssh name: ssh
protocol: TCP protocol: TCP
{{- end }} {{- end }}
env: env:
- name: IDE - name: IDE
value: {{ .Values.ide.type | default "vscode" | quote }} value: {{ .Values.ide | default "vscode" | quote }}
- name: SSH - name: SSH
value: {{ .Values.ssh.enabled | toString | quote }} value: {{ .Values.ssh | toString | quote }}
- name: USER_ID - name: USER_ID
value: {{ .Values.user.id | quote }} value: {{ .Values.userId | quote }}
- name: GROUP_ID - name: GROUP_ID
value: {{ .Values.user.groupId | quote }} value: {{ .Values.groupId | quote }}
- name: DISPLAY_WIDTH - name: DISPLAY_WIDTH
value: {{ .Values.display.width | quote }} value: {{ .Values.display.width | quote }}
- name: DISPLAY_HEIGHT - name: DISPLAY_HEIGHT
value: {{ .Values.display.height | quote }} value: {{ .Values.display.height | quote }}
- name: SECURE_CONNECTION - name: SECURE_CONNECTION
value: {{ .Values.display.secureConnection | quote }} value: {{ .Values.secureConnection | quote }}
{{- if .Values.fileManager.enabled }} - name: HAPPY_HOME_DIR
- name: WEB_FILE_MANAGER value: {{ .Values.happyHomeDir | quote }}
value: "1" - name: HAPPY_EXPERIMENTAL
- name: WEB_FILE_MANAGER_ALLOWED_PATHS value: {{ .Values.happyExperimental | quote }}
value: {{ .Values.fileManager.allowedPaths | quote }} - name: HAPPY_SERVER_URL
{{- if .Values.fileManager.deniedPaths }} value: {{ .Values.happyServerUrl | quote }}
- name: WEB_FILE_MANAGER_DENIED_PATHS - name: HAPPY_WEBAPP_URL
value: {{ .Values.fileManager.deniedPaths | quote }} value: {{ .Values.happyWebappUrl | quote }}
{{- end }}
{{- end }}
{{- if .Values.githubRepo }}
- name: GITHUB_REPO - name: GITHUB_REPO
value: {{ .Values.githubRepo | quote }} value: {{ .Values.githubRepo | quote }}
{{- end }}
{{- if .Values.githubRepos }}
- name: GITHUB_REPOS
value: {{ join "," .Values.githubRepos | quote }}
{{- end }}
envFrom: envFrom:
- secretRef: - secretRef:
name: {{ include "devcontainer.envSecretName" . }} name: {{ include "antigravity.envSecretName" . }}
optional: true optional: true
resources: resources:
{{- toYaml .Values.resources | nindent 12 }} {{- toYaml .Values.resources | nindent 12 }}
@@ -100,7 +73,7 @@ spec:
mountPath: /workspace mountPath: /workspace
- name: shm - name: shm
mountPath: /dev/shm mountPath: /dev/shm
{{- if ne (.Values.ide.type | default "vscode") "none" }} {{- if ne (.Values.ide | default "vscode") "none" }}
livenessProbe: livenessProbe:
httpGet: httpGet:
path: / path: /
@@ -113,7 +86,7 @@ spec:
port: 5800 port: 5800
initialDelaySeconds: 10 initialDelaySeconds: 10
periodSeconds: 5 periodSeconds: 5
{{- else if .Values.ssh.enabled }} {{- else if .Values.ssh }}
livenessProbe: livenessProbe:
tcpSocket: tcpSocket:
port: 22 port: 22
@@ -125,178 +98,88 @@ spec:
initialDelaySeconds: 5 initialDelaySeconds: 5
periodSeconds: 5 periodSeconds: 5
{{- end }} {{- end }}
{{- if and .Values.mcp.sidecars.kubernetes.enabled (ne .Values.clusterAccess "none") }} {{- if and .Values.mcpSidecars.kubernetes.enabled (ne .Values.clusterAccess "none") }}
- name: kubernetes-mcp - name: kubernetes-mcp
image: "{{ .Values.mcp.sidecars.kubernetes.image.repository }}:{{ .Values.mcp.sidecars.kubernetes.image.tag }}" image: "{{ .Values.mcpSidecars.kubernetes.image.repository }}:{{ .Values.mcpSidecars.kubernetes.image.tag }}"
args: args:
- --port - --port
- {{ .Values.mcp.sidecars.kubernetes.port | quote }} - {{ .Values.mcpSidecars.kubernetes.port | quote }}
ports: ports:
- containerPort: {{ .Values.mcp.sidecars.kubernetes.port }} - containerPort: {{ .Values.mcpSidecars.kubernetes.port }}
name: k8s-mcp name: k8s-mcp
protocol: TCP protocol: TCP
livenessProbe: livenessProbe:
httpGet: httpGet:
path: /healthz path: /healthz
port: {{ .Values.mcp.sidecars.kubernetes.port }} port: {{ .Values.mcpSidecars.kubernetes.port }}
initialDelaySeconds: 10 initialDelaySeconds: 10
periodSeconds: 10 periodSeconds: 10
readinessProbe: readinessProbe:
httpGet: httpGet:
path: /healthz path: /healthz
port: {{ .Values.mcp.sidecars.kubernetes.port }} port: {{ .Values.mcpSidecars.kubernetes.port }}
initialDelaySeconds: 5 initialDelaySeconds: 5
periodSeconds: 5 periodSeconds: 5
resources: resources:
{{- toYaml .Values.mcp.sidecars.kubernetes.resources | nindent 12 }} {{- toYaml .Values.mcpSidecars.kubernetes.resources | nindent 12 }}
{{- end }} {{- end }}
{{- if and .Values.mcp.sidecars.flux.enabled (ne .Values.clusterAccess "none") }} {{- if and .Values.mcpSidecars.flux.enabled (ne .Values.clusterAccess "none") }}
- name: flux-mcp - name: flux-mcp
image: "{{ .Values.mcp.sidecars.flux.image.repository }}:{{ .Values.mcp.sidecars.flux.image.tag }}" image: "{{ .Values.mcpSidecars.flux.image.repository }}:{{ .Values.mcpSidecars.flux.image.tag }}"
args: args:
- serve - serve
- --transport=sse - --transport=sse
- --port={{ .Values.mcp.sidecars.flux.port }} - --port={{ .Values.mcpSidecars.flux.port }}
ports: ports:
- containerPort: {{ .Values.mcp.sidecars.flux.port }} - containerPort: {{ .Values.mcpSidecars.flux.port }}
name: flux-mcp name: flux-mcp
protocol: TCP protocol: TCP
livenessProbe: livenessProbe:
tcpSocket: tcpSocket:
port: {{ .Values.mcp.sidecars.flux.port }} port: {{ .Values.mcpSidecars.flux.port }}
initialDelaySeconds: 10 initialDelaySeconds: 10
periodSeconds: 10 periodSeconds: 10
readinessProbe: readinessProbe:
tcpSocket: tcpSocket:
port: {{ .Values.mcp.sidecars.flux.port }} port: {{ .Values.mcpSidecars.flux.port }}
initialDelaySeconds: 5 initialDelaySeconds: 5
periodSeconds: 5 periodSeconds: 5
resources: resources:
{{- toYaml .Values.mcp.sidecars.flux.resources | nindent 12 }} {{- toYaml .Values.mcpSidecars.flux.resources | nindent 12 }}
{{- end }} {{- end }}
{{- if .Values.mcp.sidecars.helm.enabled }} {{- if .Values.mcpSidecars.homeassistant.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 - name: homeassistant-mcp
image: "{{ .Values.mcp.sidecars.homeassistant.image.repository }}:{{ .Values.mcp.sidecars.homeassistant.image.tag }}" image: "{{ .Values.mcpSidecars.homeassistant.image.repository }}:{{ .Values.mcpSidecars.homeassistant.image.tag }}"
imagePullPolicy: IfNotPresent imagePullPolicy: Always
command: ["fastmcp", "run", "--transport", "sse", "--host", "0.0.0.0", "--port", "{{ .Values.mcp.sidecars.homeassistant.port }}"] command: ["fastmcp", "run", "ha_mcp.main:app", "--transport", "sse", "--sse-server-host", "0.0.0.0", "--sse-server-port", "{{ .Values.mcpSidecars.homeassistant.port }}"]
ports: ports:
- name: homeassistant - name: homeassistant
containerPort: {{ .Values.mcp.sidecars.homeassistant.port }} containerPort: {{ .Values.mcpSidecars.homeassistant.port }}
env: env:
- name: HOMEASSISTANT_URL - name: HOMEASSISTANT_URL
valueFrom: valueFrom:
secretKeyRef: secretKeyRef:
name: {{ include "devcontainer.envSecretName" . }} name: {{ include "antigravity.envSecretName" . }}
key: HOMEASSISTANT_URL key: homeassistant-url
optional: true optional: true
- name: HOMEASSISTANT_TOKEN - name: HOMEASSISTANT_TOKEN
valueFrom: valueFrom:
secretKeyRef: secretKeyRef:
name: {{ include "devcontainer.envSecretName" . }} name: {{ include "antigravity.envSecretName" . }}
key: HOMEASSISTANT_TOKEN key: homeassistant-token
optional: true optional: true
livenessProbe: livenessProbe:
tcpSocket: tcpSocket:
port: {{ .Values.mcp.sidecars.homeassistant.port }} port: {{ .Values.mcpSidecars.homeassistant.port }}
initialDelaySeconds: 10 initialDelaySeconds: 10
periodSeconds: 10 periodSeconds: 10
readinessProbe: readinessProbe:
tcpSocket: tcpSocket:
port: {{ .Values.mcp.sidecars.homeassistant.port }} port: {{ .Values.mcpSidecars.homeassistant.port }}
initialDelaySeconds: 5 initialDelaySeconds: 5
periodSeconds: 5 periodSeconds: 5
resources: resources:
{{- toYaml .Values.mcp.sidecars.homeassistant.resources | nindent 12 }} {{- toYaml .Values.mcpSidecars.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 }} {{- end }}
volumes: volumes:
- name: workspace - name: workspace
@@ -307,4 +190,4 @@ spec:
sizeLimit: {{ .Values.shm.sizeLimit }} sizeLimit: {{ .Values.shm.sizeLimit }}
- name: userhome - name: userhome
persistentVolumeClaim: persistentVolumeClaim:
claimName: {{ include "devcontainer.pvcName" . }} claimName: {{ include "antigravity.pvcName" . }}
+2 -6
View File
@@ -1,17 +1,13 @@
apiVersion: v1 apiVersion: v1
kind: PersistentVolumeClaim kind: PersistentVolumeClaim
metadata: metadata:
name: {{ include "devcontainer.pvcName" . }} name: {{ include "antigravity.pvcName" . }}
annotations:
helm.sh/resource-policy: keep
labels: labels:
{{- include "devcontainer.labels" . | nindent 4 }} {{- include "antigravity.labels" . | nindent 4 }}
spec: spec:
accessModes: accessModes:
- ReadWriteMany - ReadWriteMany
{{- if .Values.storage.className }}
storageClassName: {{ .Values.storage.className }} storageClassName: {{ .Values.storage.className }}
{{- end }}
resources: resources:
requests: requests:
storage: {{ .Values.storage.size }} storage: {{ .Values.storage.size }}
+2 -2
View File
@@ -1,7 +1,7 @@
{{- $access := .Values.clusterAccess | default "none" }} {{- $access := .Values.clusterAccess | default "none" }}
{{- $name := include "devcontainer.fullname" . }} {{- $name := include "antigravity.fullname" . }}
{{- $ns := .Release.Namespace }} {{- $ns := .Release.Namespace }}
{{- $labels := include "devcontainer.labels" . }} {{- $labels := include "antigravity.labels" . }}
{{- if ne $access "none" }} {{- if ne $access "none" }}
--- ---
+5 -5
View File
@@ -1,22 +1,22 @@
apiVersion: v1 apiVersion: v1
kind: Service kind: Service
metadata: metadata:
name: {{ include "devcontainer.fullname" . }} name: {{ include "antigravity.fullname" . }}
labels: labels:
{{- include "devcontainer.labels" . | nindent 4 }} {{- include "antigravity.labels" . | nindent 4 }}
spec: spec:
ports: ports:
{{- if ne (.Values.ide.type | default "vscode") "none" }} {{- if ne (.Values.ide | default "vscode") "none" }}
- port: 5800 - port: 5800
name: vnc-web name: vnc-web
protocol: TCP protocol: TCP
targetPort: vnc-web targetPort: vnc-web
{{- end }} {{- end }}
{{- if .Values.ssh.enabled }} {{- if .Values.ssh }}
- port: 22 - port: 22
name: ssh name: ssh
protocol: TCP protocol: TCP
targetPort: ssh targetPort: ssh
{{- end }} {{- end }}
selector: selector:
{{- include "devcontainer.labels" . | nindent 4 }} {{- include "antigravity.labels" . | nindent 4 }}
-63
View File
@@ -1,63 +0,0 @@
# =============================================================================
# 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'
-262
View File
@@ -1,262 +0,0 @@
{
"$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"]
}
}
}
+76 -168
View File
@@ -1,11 +1,6 @@
# =============================================================================
# BASIC CONFIGURATION
# =============================================================================
# Instance name — used to generate resource names (devcontainer-{name}, userhome-{name}) # Instance name — used to generate resource names (devcontainer-{name}, userhome-{name})
name: "" name: ""
# Container image configuration
image: image:
repository: ghcr.io/cpfarhood/devcontainer repository: ghcr.io/cpfarhood/devcontainer
tag: latest tag: latest
@@ -14,57 +9,43 @@ image:
# GitHub repository to clone into /workspace # GitHub repository to clone into /workspace
githubRepo: "" githubRepo: ""
# Multiple GitHub repositories to clone into /workspace (takes precedence over githubRepo) # IDE to launch inside the container.
# Example: # Options:
# githubRepos: # vscode — VSCode via VNC browser UI on port 5800 (default)
# - https://github.com/user/repo1 # antigravity — Google Antigravity (VSCode fork) via VNC on port 5800
# - https://github.com/user/repo2 # none — no IDE; useful when ssh: true is the sole access method
githubRepos: [] ide: vscode
# ============================================================================= # Start an OpenSSH server on port 22 in addition to the IDE.
# ACCESS & INTERFACE # Set SSH_AUTHORIZED_KEYS in the env secret to allow key-based login.
# ============================================================================= ssh: false
# IDE configuration # Happy Coder endpoints
ide: happyServerUrl: "https://happy.farh.net"
# Options: vscode | antigravity | none happyWebappUrl: "https://happy-coder.farh.net"
type: vscode happyHomeDir: "/config/userdata/.happy"
happyExperimental: "true"
# SSH access configuration # VNC display
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: display:
width: "1920" width: "1920"
height: "1080" height: "1080"
secureConnection: "0" # Set to "1" when TLS is not terminated upstream
# User configuration # Set to "0" when TLS is terminated at the gateway layer
user: secureConnection: "0"
id: "1000"
groupId: "1000"
# ============================================================================= userId: "1000"
# INFRASTRUCTURE & RESOURCES groupId: "1000"
# =============================================================================
# Storage configuration
storage: storage:
size: 32Gi size: 32Gi
className: "" # Empty string uses the cluster's default StorageClass (must support ReadWriteMany) className: ceph-filesystem
# Shared memory size — mounted at /dev/shm as a memory-backed emptyDir.
# Electron apps (Antigravity, Chrome) use /dev/shm for GPU/IPC buffers.
shm:
sizeLimit: 2Gi
# Resource allocation
resources: resources:
requests: requests:
memory: "2Gi" memory: "2Gi"
@@ -73,130 +54,57 @@ resources:
memory: "8Gi" memory: "8Gi"
cpu: "4000m" cpu: "4000m"
# Shared memory for Electron apps (Chrome, Antigravity) # Kubernetes cluster access granted to the devcontainer pod via RBAC.
shm: # Options:
sizeLimit: 2Gi # none — no cluster access (default)
# readonlyns — get/list/watch all resources in the release namespace
# Kubernetes cluster access via RBAC # readwritens — full access to all resources in the release namespace
# Options: none | readonlyns | readwritens | readonly | readwrite # readonly — get/list/watch all resources cluster-wide
# readwrite — full access to all resources cluster-wide
clusterAccess: none clusterAccess: none
# ============================================================================= # Name of existing Secret containing env vars (GITHUB_TOKEN, VNC_PASSWORD, etc.)
# INTEGRATIONS # Defaults to: devcontainer-{name}-secrets-env
# ============================================================================= envSecretName: ""
# MCP (Model Context Protocol) server sidecars # MCP server sidecars — run alongside the devcontainer to inherit pod RBAC.
mcp: mcpSidecars:
sidecars: kubernetes:
# Kubernetes API access enabled: true
kubernetes: image:
enabled: true repository: quay.io/containers/kubernetes_mcp_server
image: tag: v0.0.57 # Pinned version (Jan 27, 2025) with token exchange and field selector support
repository: quay.io/containers/kubernetes_mcp_server port: 8080
tag: v0.0.57 resources:
port: 8080 requests:
resources: memory: "64Mi"
requests: cpu: "50m"
memory: "64Mi" limits:
cpu: "50m" memory: "256Mi"
limits: cpu: "500m"
memory: "256Mi" flux:
cpu: "500m" enabled: true
image:
# Flux GitOps operations repository: ghcr.io/controlplaneio-fluxcd/flux-operator-mcp
flux: tag: v0.41.1
enabled: true port: 8081
image: resources:
repository: ghcr.io/controlplaneio-fluxcd/flux-operator-mcp requests:
tag: v0.41.1 memory: "64Mi"
port: 8081 cpu: "50m"
resources: limits:
requests: memory: "256Mi"
memory: "64Mi" cpu: "500m"
cpu: "50m" homeassistant:
limits: enabled: false # Disabled by default, requires HOMEASSISTANT_URL and HOMEASSISTANT_TOKEN
memory: "256Mi" image:
cpu: "500m" repository: ghcr.io/homeassistant-ai/ha-mcp
tag: v6.7.1 # Pinned version (Feb 20, 2026) - latest stable release
port: 8087
# Helm chart browsing and management resources:
helm: requests:
enabled: false memory: "64Mi"
image: cpu: "50m"
repository: ghcr.io/zekker6/mcp-helm limits:
tag: v1.3.1 memory: "256Mi"
port: 8012 cpu: "500m"
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: ""
+1 -1
View File
@@ -16,7 +16,7 @@
## MCP Sidecars ## MCP Sidecars
- **Kubernetes MCP** (v0.0.57, port 8080): Only deployed when enabled AND `clusterAccess` != `none` - **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` - **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: - **Home Assistant MCP** (v6.7.1, port 8087): Disabled by default, requires secrets:
- `homeassistant-url`: Base URL like `http://homeassistant.local:8123` - `homeassistant-url`: Base URL like `http://homeassistant.local:8123`
- `homeassistant-token`: Long-lived access token - `homeassistant-token`: Long-lived access token
- **Playwright MCP**: External service, not a sidecar - **Playwright MCP**: External service, not a sidecar
+2 -6
View File
@@ -2,9 +2,5 @@
# Fix the app user (UID 1000) created by baseimage-gui at runtime. # Fix the app user (UID 1000) created by baseimage-gui at runtime.
# baseimage-gui sets shell=/sbin/nologin and home=/dev/null, which # baseimage-gui sets shell=/sbin/nologin and home=/dev/null, which
# prevents VSCode from opening terminals. # prevents VSCode from opening terminals.
if id app >/dev/null 2>&1; then usermod -s /bin/bash app
usermod -s /bin/bash app usermod -d /config/userdata app
usermod -d /config/userdata app
else
echo "WARNING: 'app' user not found, skipping usermod" >&2
fi
+38 -76
View File
@@ -1,5 +1,5 @@
#!/bin/bash #!/bin/bash
# Initialize repository # Initialize repository and start Happy Coder
set -e set -e
echo "=== Repository Initialization ===" echo "=== Repository Initialization ==="
@@ -22,7 +22,6 @@ if [ -n "$GITHUB_TOKEN" ]; then
# Create or update the credentials file # Create or update the credentials file
CREDENTIALS_FILE="/config/userdata/.git-credentials" CREDENTIALS_FILE="/config/userdata/.git-credentials"
mkdir -p "$(dirname "$CREDENTIALS_FILE")"
# Support multiple git hosting providers # Support multiple git hosting providers
# GitHub supports both oauth2 and token as username # GitHub supports both oauth2 and token as username
@@ -52,7 +51,6 @@ else
# Create an empty credentials file with proper permissions # Create an empty credentials file with proper permissions
CREDENTIALS_FILE="/config/userdata/.git-credentials" CREDENTIALS_FILE="/config/userdata/.git-credentials"
mkdir -p "$(dirname "$CREDENTIALS_FILE")"
touch "$CREDENTIALS_FILE" touch "$CREDENTIALS_FILE"
chmod 600 "$CREDENTIALS_FILE" chmod 600 "$CREDENTIALS_FILE"
@@ -62,96 +60,60 @@ else
fi fi
fi fi
# Build list of repositories to clone # Check if GITHUB_REPO is set
REPOS=() if [ -z "$GITHUB_REPO" ]; then
if [ -n "$GITHUB_REPOS" ]; then echo "GITHUB_REPO not set, skipping repository clone"
# 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" WORKSPACE_DIR="/workspace/default"
mkdir -p "$WORKSPACE_DIR" mkdir -p "$WORKSPACE_DIR"
else else
CLONED_DIRS=() # Parse repo name from URL
for REPO_URL in "${REPOS[@]}"; do REPO_NAME=$(basename "$GITHUB_REPO" .git)
REPO_NAME=$(basename "$REPO_URL" .git) WORKSPACE_DIR="/workspace/$REPO_NAME"
REPO_DIR="/workspace/$REPO_NAME"
echo "Repository: $REPO_URL" echo "Repository: $GITHUB_REPO"
echo "Target directory: $REPO_DIR" echo "Target directory: $WORKSPACE_DIR"
if [ -d "$REPO_DIR/.git" ]; then # Check if repo already exists
echo "Repository already exists, pulling latest changes..." if [ -d "$WORKSPACE_DIR/.git" ]; then
cd "$REPO_DIR" echo "Repository already exists, pulling latest changes..."
git pull || echo "Pull failed, continuing anyway..." cd "$WORKSPACE_DIR"
else git pull || echo "Pull failed, continuing anyway..."
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 else
# Multiple repos — generate a multi-root workspace file echo "Cloning repository..."
WS_FILE="/workspace/workspace.code-workspace" mkdir -p "$(dirname "$WORKSPACE_DIR")"
printf '{\n "folders": [\n' > "$WS_FILE"
for i in "${!CLONED_DIRS[@]}"; do # Clone with token if provided
printf ' {"path": "%s"}' "${CLONED_DIRS[$i]}" >> "$WS_FILE" if [ -n "$GITHUB_TOKEN" ]; then
if [ "$i" -lt $(( ${#CLONED_DIRS[@]} - 1 )) ]; then # Replace https://github.com/ with https://oauth2:token@github.com/
printf ',\n' >> "$WS_FILE" CLONE_URL=$(echo "$GITHUB_REPO" | sed "s|https://github.com/|https://oauth2:${GITHUB_TOKEN}@github.com/|")
else git clone "$CLONE_URL" "$WORKSPACE_DIR"
printf '\n' >> "$WS_FILE" else
fi git clone "$GITHUB_REPO" "$WORKSPACE_DIR"
done fi
printf ' ],\n "settings": {}\n}\n' >> "$WS_FILE"
WORKSPACE_DIR="$WS_FILE"
echo "Generated multi-root workspace: $WS_FILE"
fi fi
fi fi
# Set ownership using numeric IDs (username may not exist yet in baseimage-gui) # Set ownership using numeric IDs (username may not exist yet in baseimage-gui)
RUN_UID="${USER_ID:-1000}" RUN_UID="${USER_ID:-1000}"
RUN_GID="${GROUP_ID:-1000}" RUN_GID="${GROUP_ID:-1000}"
for dir in "${CLONED_DIRS[@]}"; do chown -R "$RUN_UID:$RUN_GID" "$WORKSPACE_DIR"
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) # Ensure home directory exists on the PVC (may be absent on a fresh volume)
mkdir -p "$HOME" mkdir -p "$HOME"
chown "$RUN_UID:$RUN_GID" "$HOME" chown "$RUN_UID:$RUN_GID" "$HOME"
# Seed Claude Code settings if missing (disable auto-updater in Docker) # Start Happy Coder daemon. startapp.sh already runs as the app user (UID 1000),
if [ ! -f "$HOME/.claude/settings.json" ]; then # so no sudo needed — Happy/Claude Code will find credentials in the correct home dir.
mkdir -p "$HOME/.claude" echo "Starting Happy Coder..."
echo '{"env":{"DISABLE_AUTOUPDATER":"1"}}' > "$HOME/.claude/settings.json"
chown -R "$RUN_UID:$RUN_GID" "$HOME/.claude" # Remove stale lock file. HAPPY_HOME_DIR lives on the home PVC so it survives
fi # pod restarts — without this cleanup the daemon refuses to start after a crash.
rm -f "${HAPPY_HOME_DIR:-$HOME/.happy}/daemon.state.json.lock"
cd "$WORKSPACE_DIR"
happy daemon start || echo "Happy Coder daemon failed to start, continuing anyway..."
echo "Happy Coder daemon started"
# Export workspace directory for startapp.sh # Export workspace directory for startapp.sh
echo "$WORKSPACE_DIR" > /tmp/workspace-dir echo "$WORKSPACE_DIR" > /tmp/workspace-dir
+2 -5
View File
@@ -2,9 +2,9 @@
# Start application script for baseimage-gui # Start application script for baseimage-gui
set -e set -e
echo "=== Starting Dev Container ===" echo "=== Starting Antigravity Dev Container ==="
# Initialize repository # Initialize repository and Happy Coder
/usr/local/bin/init-repo /usr/local/bin/init-repo
# Get workspace directory # Get workspace directory
@@ -34,9 +34,6 @@ case "$IDE" in
exec sleep infinity exec sleep infinity
;; ;;
*) *)
if [ "$IDE" != "vscode" ]; then
echo "WARNING: Unknown IDE value '$IDE', defaulting to VSCode"
fi
echo "Opening VSCode in: $WORKSPACE_DIR" echo "Opening VSCode in: $WORKSPACE_DIR"
exec code --new-window --wait "$WORKSPACE_DIR" exec code --new-window --wait "$WORKSPACE_DIR"
;; ;;