Compare commits

...

48 Commits

Author SHA1 Message Date
DevContainer User 5da23def5b fix: add automatic release workflow for tag-based releases
- Triggers on tag pushes (v*) to create GitHub releases
- Publishes Helm chart to OCI registry
- Generates release notes with commit history
- Complements existing build-and-push workflow

Now releases are fully automated:
1. Push tag → build-and-push.yaml builds Docker images
2. Push tag → release.yaml creates GitHub release + publishes Helm chart

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 21:17:37 +00:00
DevContainer User 5532eee8cd chore: release version 0.2.4
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 19:13:41 +00:00
DevContainer User d32e453f93 feat: add PostgreSQL tuner (pgtuner) MCP sidecar
- Add pgtuner MCP sidecar configuration (disabled by default)
- Supports PostgreSQL performance tuning and optimization
- Analyzes slow queries, recommends indexes, detects bloat
- Requires DATABASE_URI secret to be configured
- Runs in SSE mode on port 8085

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 19:11:50 +00:00
DevContainer User f95e8877e8 chore: release version 0.2.3
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 17:31:09 +00:00
DevContainer User 46267b6e26 fix: resolve antigravity IDE init and homeassistant MCP issues (fixes #27, #28)
- Fix homeassistant MCP sidecar command by removing incorrect module specification
- Add init container for antigravity IDE to create /config/userdata directory
- Bump chart version to 0.2.2

The homeassistant sidecar was failing with "File not found: /app/ha_mcp.main"
because fastmcp should run without explicit module specification.

The antigravity IDE was failing to initialize due to missing /config/userdata
directory. Added an init container to ensure the directory exists with proper
permissions before the main container starts.

Generated with Claude Code via Happy

Co-Authored-By: Claude <noreply@anthropic.com>
Co-Authored-By: Happy <yesreply@happy.engineering>
2026-02-21 17:29:38 +00:00
github-actions[bot] c4cbd67399 chore: release version 0.2.2 2026-02-21 16:27:25 +00:00
DevContainer User a7799dbb16 hotfix: disable GitHub MCP sidecar - image doesn't exist
The GitHub MCP server has been archived and moved to servers-archived repo.
There is no Docker image available at ghcr.io/modelcontextprotocol/servers/github.

Disabling by default to prevent ImagePullBackOff errors.

TODO: Either build custom image from archived source or find alternative.

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 16:25:45 +00:00
github-actions[bot] 45b8e5e95e chore: release version 0.2.1 2026-02-21 16:20:17 +00:00
DevContainer User a0b409239e feat: add GitHub MCP sidecar and fix Home Assistant MCP command (fixes #26)
- Fixed Home Assistant MCP command flags from --sse-server-host/port to --host/port
- Added GitHub MCP server as new sidecar (enabled by default)
- Uses existing GITHUB_TOKEN from environment
- Updated documentation and .mcp.json configuration

The GitHub MCP sidecar provides AI assistants with ability to interact with
GitHub repositories, issues, PRs, and more using the same token used for
repository cloning.

Fixes #26

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 16:19:11 +00:00
DevContainer User eacf41302c cleanup: remove redundant workflows and simplify CI/CD
Removed 4 redundant workflow files:
- helm-publish.yaml (integrated into release-unified)
- helm-release.yaml.old (the auto-version-bump nightmare)
- release.yaml (replaced by release-unified)
- tag-release.md (old docs, replaced by README.md)

Now down to just 3 clean workflows:
- release-unified.yaml: Complete release process
- quick-fix.yaml: Emergency Docker builds
- build-and-push.yaml: Automatic CI for PRs/tags

Also updated documentation to reflect the simplified pipeline.

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 16:12:35 +00:00
github-actions[bot] cbdee590bf chore: release version 0.2.0 2026-02-21 16:09:49 +00:00
DevContainer User 5570b2c617 fix: completely overhaul the CI/CD pipeline to not suck
BREAKING CHANGE: Removed auto-version-bump workflow that was causing [skip ci] hell

New features:
- Unified release workflow that handles everything in one go
- Quick fix workflow for emergency deployments
- No more [skip ci] preventing Docker builds
- No more manual tag juggling
- Single button release process

The old pipeline was a disaster with disconnected workflows, auto-version-bumps
with [skip ci] that prevented Docker builds, and required manual tag deletion/
re-pushing to trigger builds. This new pipeline is actually usable.

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 16:04:46 +00:00
DevContainer User c3f8421d60 cleanup: remove CI trigger file 2026-02-21 15:29:29 +00:00
DevContainer User 21d8fc73e6 ci: trigger build for v0.1.24
Force CI/CD pipeline to build the Docker image for v0.1.24 which includes:
- Git credentials automation fix
- Home Assistant MCP sidecar command fix

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:29:19 +00:00
DevContainer User 0a63894f6d chore: bump chart version to 0.1.22 [skip ci] 2026-02-21 15:10:36 +00:00
DevContainer User a50a1815e0 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:10:17 +00:00
github-actions[bot] 131dad8611 chore: bump chart version to 0.1.23 [skip ci] 2026-02-21 15:00:25 +00:00
Chris Farhood 581d0737e4 fix(chart): correct ha-mcp image tag from v6.7.1 to 6.7.1
The ghcr.io/homeassistant-ai/ha-mcp registry publishes tags without
the 'v' prefix, causing ErrImagePull when the sidecar is enabled.

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:00:13 +00:00
DevContainer User 68110d911f chore: bump chart version to 0.1.21 [skip ci] 2026-02-21 14:33:16 +00:00
DevContainer User 427f7a710c fix: automate git credentials setup during container initialization
- Configure git credentials at the beginning of init-repo.sh
- Set up git user name/email with defaults or from environment variables
- Create .git-credentials file with proper permissions (600)
- Support multiple GitHub credential formats for better compatibility
- Create symlinks to handle different credential file locations
- Add test script to verify credentials configuration
- Update documentation with new environment variables

This fixes issues where containers fail due to missing .git-credentials
by ensuring credentials are properly configured before any git operations.

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 14:28:58 +00:00
github-actions[bot] 745a0cdf59 chore: bump chart version to 0.1.20 [skip ci] 2026-02-21 13:54:03 +00:00
Developer 115907cdc8 fix: pin MCP sidecar versions for stability
- Pin kubernetes-mcp to v0.0.57 (Jan 27, 2025) with token exchange and field selector support
- Pin flux-mcp to v0.41.1 (already pinned)
- Pin homeassistant-mcp to v6.7.1 (Feb 20, 2026) - latest stable release
- Update documentation with version details
- Bump chart version to 0.1.19

This ensures reproducible deployments and prevents unexpected breaking changes
from floating tags (latest/stable).

Generated with Claude Code via Happy

Co-Authored-By: Claude <noreply@anthropic.com>
Co-Authored-By: Happy <yesreply@happy.engineering>
2026-02-21 13:53:49 +00:00
github-actions[bot] 0c4f93c077 chore: bump chart version to 0.1.19 [skip ci] 2026-02-21 13:27:34 +00:00
Developer a83d79bc10 feat: add Home Assistant MCP sidecar and fix K8s/Flux MCP deployment logic
Added features:
- Home Assistant MCP server as optional sidecar (mcpSidecars.homeassistant)
- Requires homeassistant-url and homeassistant-token secrets
- Runs on port 8087 using SSE transport mode
- Disabled by default due to credential requirements

Fixed deployment logic:
- Kubernetes and Flux MCP sidecars now only deploy when:
  1. They are enabled in values (mcpSidecars.<name>.enabled: true)
  2. AND clusterAccess is not "none" (they need RBAC to function)
- Prevents unnecessary container failures when no permissions exist

Documentation updates:
- Complete Helm values reference for all MCP sidecars
- Deployment examples and troubleshooting guides
- Updated memory notes with current architecture

Breaking change:
- K8s/Flux MCP sidecars won't deploy with clusterAccess=none
- This is intentional as they cannot function without RBAC

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 13:27:20 +00:00
Developer 2258df4ae3 docs: comprehensive documentation update for Helm chart and MCP sidecars
- Added MCP sidecar configuration documentation across all docs
- Migrated from kustomize to Helm-based deployment instructions
- Updated Makefile with new helm-* targets, removed outdated k8s-* targets
- Rewrote DEPLOYMENT.md to focus on Helm chart deployment
- Transformed VARIABLES.md into complete Helm values reference
- Added MCP sidecar section to README.md with configuration examples

Key improvements:
- Clear instructions for enabling/disabling MCP servers
- Consistent Helm-based deployment throughout documentation
- Comprehensive values reference with examples
- Better organization for different deployment scenarios

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 12:48:40 +00:00
github-actions[bot] d4b069cbdc chore: bump chart version to 0.1.17 [skip ci] 2026-02-21 12:11:46 +00:00
Chris Farhood db7e422b96 fix(mcp): use v0.41.1 for flux-operator-mcp instead of latest 2026-02-21 07:11:37 -05:00
Chris Farhood d5bbf21578 docs: update CLAUDE.md for Helm chart and MCP sidecars
Reflect current architecture: Helm chart instead of raw k8s/ manifests,
document MCP sidecar containers, fix storage mount path, and update
Kubernetes notes for Flux-based deployment.

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 02:01:40 +00:00
github-actions[bot] 1c3398b178 chore: bump chart version to 0.1.15 [skip ci] 2026-02-21 00:33:29 +00:00
Chris Farhood 4e67c48a4c Merge pull request #25 from cpfarhood/feat/mcp-sidecars
feat: add Kubernetes and Flux MCP servers as pod sidecars
2026-02-21 00:33:20 +00:00
Chris Farhood df3413f54e feat: add Kubernetes and Flux MCP servers as pod sidecars
Run MCP servers as sidecar containers so they inherit the pod's
ServiceAccount permissions instead of requiring separate deployments
with their own RBAC. Kubernetes MCP on :8080, Flux MCP on :8081.

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 00:29:55 +00:00
Chris Farhood 6a35f38a8c add mcp to repo 2026-02-20 23:33:40 +00:00
github-actions[bot] 431b9079ee chore: bump chart version to 0.1.14 [skip ci] 2026-02-20 22:27:56 +00:00
Chris Farhood 00d88b16b5 Merge pull request #24 from cpfarhood/fix/persist-config-to-pvc
fix: mount PVC at /config to persist Chrome and app state
2026-02-20 22:27:47 +00:00
Chris Farhood c10dd718e1 Merge pull request #23 from cpfarhood/docs/final-readme-pass
docs: final README pass
2026-02-20 22:27:05 +00:00
Antigravity Developer b6bf4b6640 fix: mount PVC at /config to persist Chrome and app state across restarts
The jlesage/baseimage-gui sets XDG_CONFIG_HOME=/config/xdg/config at
runtime, so Chrome was writing its profile to /config/xdg/config/google-chrome
which lived on ephemeral storage. This caused Chrome to open as a fresh
install on every pod restart.

Changes:
- Mount the PVC at /config instead of /home (aligns with baseimage-gui convention)
- Move user home directory to /config/userdata (on the PVC)
- Add explicit --user-data-dir for Chrome pointing to PVC path
- Clean up Chrome crash lock files and patch Preferences on startup
  to prevent session/cookie loss after unclean pod shutdown
- Update all scripts (sshd, init-repo, cont-init) to use new paths
- Remove unnecessary cont-init-home.sh

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-20 22:21:36 +00:00
Antigravity Developer c42b47bb56 fix: persist Chrome profile data and settings across container restarts
- Add explicit --user-data-dir flag to Chrome wrapper to ensure profile data
  is stored in the persistent home directory
- Add cont-init-home.sh script to properly initialize home directory structure
  on container startup with correct permissions
- Ensure Chrome config directory exists before Chrome starts
- Bump chart version to 0.1.13

This fixes the issue where Chrome loses authentication and settings after
pod restarts by explicitly managing where Chrome stores its profile data.

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-20 21:31:32 +00:00
Chris Farhood 288c1a4103 docs: final README pass
- Add Antigravity and SSH to feature list in intro
- Add shm.sizeLimit to Display/resources table
- Update startup flow to show accurate antigravity launch flags
- Fix kubectl describe label selector
- Add troubleshooting note for latest-tag pod restart

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-20 15:52:09 -05:00
github-actions[bot] 2caa8a790f chore: bump chart version to 0.1.12 [skip ci] 2026-02-20 20:46:18 +00:00
Chris Farhood 7a6a515b53 Merge pull request #22 from cpfarhood/fix/antigravity-userdata-persistence
fix: pin Antigravity user data to home PVC to survive pod restarts
2026-02-20 15:46:07 -05:00
Chris Farhood 4f126a938b fix: persist SSH host keys on home PVC to avoid known_hosts warnings
On first boot, generated host keys are saved to ~/.ssh/host_keys/ on
the persistent home PVC. On subsequent boots they are restored, so SSH
clients never see a "host key changed" warning after a pod restart.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-20 15:45:34 -05:00
Chris Farhood 4af38a5d2e fix: pin Antigravity user data to home PVC to survive pod restarts
Without explicit --user-data-dir and --extensions-dir, Antigravity may
default to a path outside /home and lose settings on restart, causing
the setup wizard to reappear. Pinning both to $HOME ensures they land
on the persistent home PVC.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-20 15:43:21 -05:00
github-actions[bot] 90350a2090 chore: bump chart version to 0.1.10 [skip ci] 2026-02-20 20:25:42 +00:00
Chris Farhood 5b8e6a290b Merge pull request #21 from cpfarhood/fix/antigravity-no-sandbox
fix: add --no-sandbox to antigravity launch command
2026-02-20 15:25:33 -05:00
Chris Farhood e860499757 fix: mount memory-backed emptyDir at /dev/shm for Electron apps
Instead of disabling shared memory usage, mount a proper tmpfs at
/dev/shm so Antigravity (and Chrome) have real shared memory available.
Removes --disable-dev-shm-usage; keeps --no-sandbox (separate issue).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-20 15:24:15 -05:00
Chris Farhood e90a2fe553 fix: add --no-sandbox to antigravity launch command
Electron apps crash in Docker without --no-sandbox and
--disable-dev-shm-usage, same as Chrome. VSCode handles this
internally; Antigravity does not.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-20 15:22:19 -05:00
github-actions[bot] 897f1409b5 chore: bump chart version to 0.1.7 [skip ci] 2026-02-20 19:21:23 +00:00
Chris Farhood 32d4fe4944 Merge pull request #20 from cpfarhood/feat/ide-choice
feat: add IDE choice (VSCode, Google Antigravity, SSH)
2026-02-20 14:21:11 -05:00
23 changed files with 1819 additions and 1069 deletions
+7
View File
@@ -0,0 +1,7 @@
{
"enabledMcpjsonServers": [
"kubernetes",
"flux",
"playwright"
]
}
+94
View File
@@ -0,0 +1,94 @@
# 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 GHCR
- ✅ Creates GitHub Release with changelog
- ✅ No more `[skip ci]` blocking builds!
### 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:
- Pull requests (builds but doesn't push)
- Tags starting with `v*` (builds and pushes)
- 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
- **No more `[skip ci]`** blocking builds
- **Clear separation** of concerns
-57
View File
@@ -1,57 +0,0 @@
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
+54
View File
@@ -0,0 +1,54 @@
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
permissions:
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
+159
View File
@@ -0,0 +1,159 @@
name: Unified Release
on:
workflow_dispatch:
inputs:
version:
description: 'Version to release (e.g., 0.1.25)'
required: true
type: string
release_type:
description: 'Release type'
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: |
if [ "${{ github.event.inputs.version }}" != "" ]; then
VERSION="${{ github.event.inputs.version }}"
else
# Auto-determine next version based on release type
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)
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 commit -m "chore: release version ${{ steps.version.outputs.version }}"
- 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
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
cache-from: type=gha
cache-to: type=gha,mode=max
platforms: linux/amd64
- name: Package Helm Chart
run: |
helm registry login ghcr.io \
--username ${{ github.actor }} \
--password ${{ secrets.GITHUB_TOKEN }}
helm package chart/
helm push devcontainer-${{ steps.version.outputs.version }}.tgz oci://ghcr.io/cpfarhood/charts
- name: Generate Release Notes
id: notes
run: |
# Get commits since last tag
PREV_TAG=$(git describe --tags --abbrev=0 HEAD^ 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 << EOF > release-notes.md
## 🚀 Release ${{ steps.version.outputs.version }}
### Changes
${COMMITS}
### Docker Image
\`\`\`bash
docker pull ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.version.outputs.tag }}
\`\`\`
### Helm Chart
\`\`\`bash
helm install devcontainer oci://ghcr.io/cpfarhood/charts/devcontainer --version ${{ steps.version.outputs.version }}
\`\`\`
EOF
echo "notes<<EOF" >> $GITHUB_OUTPUT
cat release-notes.md >> $GITHUB_OUTPUT
echo "EOF" >> $GITHUB_OUTPUT
- name: Create GitHub Release
uses: actions/create-release@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
tag_name: ${{ steps.version.outputs.tag }}
release_name: Release ${{ steps.version.outputs.tag }}
body: ${{ steps.notes.outputs.notes }}
draft: false
prerelease: false
+55 -20
View File
@@ -5,47 +5,82 @@ on:
tags: tags:
- 'v*' - 'v*'
permissions: env:
contents: write REGISTRY: ghcr.io
packages: write IMAGE_NAME: ${{ github.repository }}
jobs: jobs:
release: release:
runs-on: ubuntu-latest runs-on: ubuntu-latest
permissions:
contents: write
packages: write
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v6 uses: actions/checkout@v6
with: with:
fetch-depth: 0 fetch-depth: 0
- name: Set up Helm
uses: azure/setup-helm@v4
- name: Extract version from tag
id: version
run: |
TAG=${GITHUB_REF#refs/tags/}
VERSION=${TAG#v}
echo "tag=${TAG}" >> $GITHUB_OUTPUT
echo "version=${VERSION}" >> $GITHUB_OUTPUT
echo "🚀 Creating release for ${TAG}"
- name: Package and Push Helm Chart
run: |
helm registry login ghcr.io \
--username ${{ github.actor }} \
--password ${{ secrets.GITHUB_TOKEN }}
helm package chart/
helm push devcontainer-${{ steps.version.outputs.version }}.tgz oci://ghcr.io/cpfarhood/charts
- name: Generate Release Notes - name: Generate Release Notes
id: notes id: notes
run: | run: |
# Get the tag message or generate from commits # Get commits since last tag
TAG_MESSAGE=$(git tag -l --format='%(contents)' ${{ github.ref_name }}) PREV_TAG=$(git describe --tags --abbrev=0 ${{ steps.version.outputs.tag }}^ 2>/dev/null || echo "")
if [ -z "$TAG_MESSAGE" ]; then if [ -z "$PREV_TAG" ]; then
# Generate from commit messages since last tag COMMITS=$(git log --pretty=format:"- %s (%h)" ${{ steps.version.outputs.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 else
NOTES="${TAG_MESSAGE}\n\n## Docker Image\n\n\`\`\`bash\ndocker pull ghcr.io/${{ github.repository }}:${{ github.ref_name }}\n\`\`\`" COMMITS=$(git log --pretty=format:"- %s (%h)" ${PREV_TAG}..${{ steps.version.outputs.tag }})
fi fi
cat << EOF > release-notes.md
## 🚀 Release ${{ steps.version.outputs.version }}
### Changes
${COMMITS}
### Docker Image
\`\`\`bash
docker pull ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.version.outputs.tag }}
\`\`\`
### Helm Chart
\`\`\`bash
helm install devcontainer oci://ghcr.io/cpfarhood/charts/devcontainer --version ${{ steps.version.outputs.version }}
\`\`\`
EOF
echo "notes<<EOF" >> $GITHUB_OUTPUT echo "notes<<EOF" >> $GITHUB_OUTPUT
echo -e "$NOTES" >> $GITHUB_OUTPUT cat release-notes.md >> $GITHUB_OUTPUT
echo "EOF" >> $GITHUB_OUTPUT echo "EOF" >> $GITHUB_OUTPUT
- name: Create Release - name: Create GitHub Release
uses: actions/create-release@v1 uses: actions/create-release@v1
env: env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with: with:
tag_name: ${{ github.ref_name }} tag_name: ${{ steps.version.outputs.tag }}
release_name: Release ${{ github.ref_name }} release_name: Release ${{ steps.version.outputs.tag }}
body: ${{ steps.notes.outputs.notes }} body: ${{ steps.notes.outputs.notes }}
draft: false draft: false
prerelease: false prerelease: false
-259
View File
@@ -1,259 +0,0 @@
# Release Process
This document describes how to create releases for this project.
## Semantic Versioning
We follow [Semantic Versioning 2.0.0](https://semver.org/):
- **MAJOR** version (v2.0.0): Incompatible API/breaking changes
- **MINOR** version (v1.1.0): New features, backwards compatible
- **PATCH** version (v1.0.1): Bug fixes, backwards compatible
## Creating a Release
### Method 1: Using GitHub CLI (Recommended)
```bash
# Ensure you're on main branch and up to date
git checkout main
git pull
# Create and push a tag
VERSION="v1.0.0" # Change this
git tag -a "$VERSION" -m "Release $VERSION
## What's New
- Feature 1
- Feature 2
- Bug fix 1
## Docker Image
\`\`\`bash
docker pull ghcr.io/cpfarhood/devcontainer:$VERSION
\`\`\`
"
git push origin "$VERSION"
# The GitHub Actions workflow will automatically:
# 1. Build the Docker image
# 2. Push to ghcr.io with multiple tags
# 3. Create a GitHub release with notes
```
### Method 2: Using Git Tags Only
```bash
git checkout main
git pull
# Create annotated tag
git tag -a v1.0.0 -m "Release v1.0.0"
# Push tag
git push origin v1.0.0
```
### Method 3: Using GitHub Web UI
1. Go to https://github.com/cpfarhood/devcontainer/releases
2. Click "Draft a new release"
3. Click "Choose a tag"
4. Type the new version (e.g., `v1.0.0`)
5. Click "Create new tag on publish"
6. Fill in the release title and description
7. Click "Publish release"
## What Happens Automatically
When you push a version tag (`v*`), GitHub Actions will:
1. **Build Docker image** with multiple tags:
- `ghcr.io/cpfarhood/devcontainer:v1.2.3` (exact version)
- `ghcr.io/cpfarhood/devcontainer:1.2` (minor version)
- `ghcr.io/cpfarhood/devcontainer:1` (major version)
- `ghcr.io/cpfarhood/devcontainer:latest` (if on default branch)
2. **Create GitHub Release** with:
- Auto-generated release notes from commits
- Docker pull command in the description
## Version Bump Guidelines
### Patch Release (v1.0.X)
- Bug fixes
- Documentation updates
- Minor dependency updates
- No new features
- No breaking changes
**Example:** v1.0.1
```bash
git tag -a v1.0.1 -m "Release v1.0.1 - Bug fixes"
git push origin v1.0.1
```
### Minor Release (v1.X.0)
- New features
- New optional configuration variables
- Enhancements to existing features
- Backwards compatible
- No breaking changes
**Example:** v1.1.0
```bash
git tag -a v1.1.0 -m "Release v1.1.0 - New Happy Coder features"
git push origin v1.1.0
```
### Major Release (vX.0.0)
- Breaking changes
- Required configuration changes
- Removal of deprecated features
- Incompatible API changes
**Example:** v2.0.0
```bash
git tag -a v2.0.0 -m "Release v2.0.0 - Breaking: New storage architecture"
git push origin v2.0.0
```
## Pre-releases
For alpha, beta, or release candidates:
```bash
# Alpha
git tag -a v1.1.0-alpha.1 -m "Release v1.1.0-alpha.1"
git push origin v1.1.0-alpha.1
# Beta
git tag -a v1.1.0-beta.1 -m "Release v1.1.0-beta.1"
git push origin v1.1.0-beta.1
# Release Candidate
git tag -a v1.1.0-rc.1 -m "Release v1.1.0-rc.1"
git push origin v1.1.0-rc.1
```
## Release Checklist
Before creating a release:
- [ ] All tests pass
- [ ] Documentation is up to date
- [ ] CHANGELOG.md is updated (if you maintain one)
- [ ] Version number follows semver
- [ ] On main/master branch
- [ ] All changes are committed
- [ ] Tag message includes release notes
## Docker Image Tags
Each release creates multiple Docker tags for flexibility:
| Git Tag | Docker Tags Created |
|---------|---------------------|
| v1.2.3 | `:v1.2.3`, `:1.2`, `:1`, `:latest` |
| v2.0.0 | `:v2.0.0`, `:2.0`, `:2`, `:latest` |
| v1.2.4-beta.1 | `:v1.2.4-beta.1`, `:1.2-beta` |
**Usage examples:**
```bash
# Specific version (recommended for production)
docker pull ghcr.io/cpfarhood/devcontainer:v1.2.3
# Minor version (gets patches automatically)
docker pull ghcr.io/cpfarhood/devcontainer:1.2
# Major version (gets minor updates and patches)
docker pull ghcr.io/cpfarhood/devcontainer:1
# Latest (always gets newest stable release)
docker pull ghcr.io/cpfarhood/devcontainer:latest
```
## Viewing Releases
- **GitHub Releases:** https://github.com/cpfarhood/devcontainer/releases
- **Docker Images:** https://github.com/cpfarhood/devcontainer/pkgs/container/devcontainer
- **Git Tags:** `git tag -l`
## Deleting a Release
If you need to delete a bad release:
```bash
# Delete local tag
git tag -d v1.0.0
# Delete remote tag
git push origin :refs/tags/v1.0.0
# Delete GitHub release (use web UI or gh CLI)
gh release delete v1.0.0
```
**Note:** Docker images pushed to ghcr.io cannot be easily deleted. It's better to create a new patch version.
## First Release
For the initial v1.0.0 release:
```bash
git checkout main
git pull
git tag -a v1.0.0 -m "Release v1.0.0 - Initial Release
## Features
- Antigravity IDE with web-based VNC access
- Happy Coder AI assistant integration
- Automatic GitHub repository cloning
- Persistent home directory with ReadWriteMany PVC
- Secure non-root execution (claude user, UID 1000)
- Support for private repositories with GitHub token
- HTTPRoute (Gateway API) support
- Multi-platform Docker images
- Comprehensive deployment documentation
## Docker Image
\`\`\`bash
docker pull ghcr.io/cpfarhood/devcontainer:v1.0.0
\`\`\`
## Deployment
See DEPLOYMENT.md for complete deployment instructions.
"
git push origin v1.0.0
```
## Example Release Workflow
```bash
# 1. Finish your feature/fix on a branch
git checkout feature/new-feature
git commit -m "feat: Add new feature"
git push
# 2. Create PR and merge to main
gh pr create
# ... get approval and merge ...
# 3. Pull latest main
git checkout main
git pull
# 4. Create release tag
git tag -a v1.1.0 -m "Release v1.1.0 - New feature"
git push origin v1.1.0
# 5. Wait for GitHub Actions
# - Check: https://github.com/cpfarhood/devcontainer/actions
# 6. Verify release
# - GitHub: https://github.com/cpfarhood/devcontainer/releases
# - Docker: docker pull ghcr.io/cpfarhood/devcontainer:v1.1.0
```
+17 -18
View File
@@ -1,29 +1,28 @@
{ {
"mcpServers": { "mcpServers": {
"github": { "github": {
"command": "github-mcp-server", "type": "http",
"args": ["stdio"], "url": "https://api.githubcopilot.com/mcp/",
"env": { "headers": {
"GITHUB_PERSONAL_ACCESS_TOKEN": "${CLAUDE_GITHUB_TOKEN}" "Authorization": "Bearer ${GITHUB_TOKEN}"
} }
}, },
"kubernetes (local)": { "kubernetes": {
"command": "npx", "type": "sse",
"args": [ "url": "http://localhost:8080/sse"
"-y",
"kubernetes-mcp-server@latest"
]
}, },
"flux (local)":{ "flux": {
"command":"flux-operator-mcp", "type": "sse",
"args":["serve"], "url": "http://localhost:8081/sse"
"env":{
"KUBECONFIG":"/Users/cpfarhood/.kube/config"
}
}, },
"playwright": { "playwright": {
"command": "npx", "type": "sse",
"args": ["-y", "@playwright/mcp@latest"] "url": "http://playwright-mcp.playwright.svc.cluster.local:3000/sse"
},
"pgtuner": {
"type": "sse",
"url": "http://localhost:8085/sse"
} }
} }
} }
+86 -19
View File
@@ -33,12 +33,14 @@ make clean # Remove volumes
### Kubernetes Deployment ### Kubernetes Deployment
```bash ```bash
make k8s-deploy # Deploy via kustomize GITHUB_REPO="https://github.com/user/repo" make helm-deploy # Deploy with Helm
kubectl apply -k k8s/ # Direct kustomize apply make helm-delete # Tear down Helm release
make k8s-delete # Tear down make helm-port-forward # Forward port 5800 to localhost
make k8s-port-forward # Forward port 5800 to localhost make helm-logs # Stream container logs
make k8s-logs # Stream container logs make helm-shell # Open interactive shell in pod
make k8s-shell # Open interactive shell in pod
# Or use Helm directly
helm install mydev ./chart --set name=mydev --set githubRepo=https://github.com/user/repo
``` ```
### Other Useful Targets ### Other Useful Targets
@@ -55,26 +57,87 @@ make push # Push image to registry (build first)
``` ```
Container start Container start
→ scripts/startapp.sh → scripts/startapp.sh
→ scripts/init-repo.sh (clone GITHUB_REPO, start Happy Coder) → scripts/init-repo.sh
→ launch VSCode as user `claude` in /workspace → Configure git user & credentials
→ Clone GITHUB_REPO (if set)
→ Start Happy Coder
→ Launch VSCode as user `user` in /workspace
``` ```
### Key Files ### Key Files
| File | Purpose | | File | Purpose |
|------|---------| |------|---------|
| `Dockerfile` | Image definition — installs Chrome, Node.js, VSCode, Happy Coder; creates non-root user `claude` (UID 1000) | | `Dockerfile` | Image definition — installs Chrome, Node.js, VSCode, Happy Coder; creates non-root user (UID 1000) |
| `scripts/init-repo.sh` | Clones GitHub repo, authenticates with token, starts Happy Coder background service | | `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 |
| `k8s/statefulset.yaml` | StatefulSet + headless Service; mounts `/home` (PVC) and `/workspace` (emptyDir) | | `chart/` | Helm chart for Kubernetes deployment |
| `k8s/configmap.yaml` | `GITHUB_REPO`, `HAPPY_SERVER_URL`, `HAPPY_WEBAPP_URL` | | `chart/templates/deployment.yaml` | Deployment spec — main container + MCP sidecar containers |
| `k8s/httproute.yaml` | Gateway API HTTPRoute for external browser access | | `chart/templates/rbac.yaml` | ServiceAccount, Role/ClusterRole based on `clusterAccess` value |
| `k8s/secrets-example.yaml` | Template for SealedSecrets (GitHub token, VNC password) | | `chart/templates/pvc.yaml` | PersistentVolumeClaim for user home |
| `chart/templates/service.yaml` | ClusterIP Service (VNC + optional SSH) |
| `chart/values.yaml` | Default Helm values |
| `.mcp.json` | MCP server connection config (Kubernetes, Flux, GitHub, Home Assistant, Playwright) |
| `Makefile` | Build/deploy automation | | `Makefile` | Build/deploy automation |
### MCP Sidecars
MCP (Model Context Protocol) servers run as sidecar containers in the pod, enabling AI assistants to interact with various services:
| Sidecar | Image | Version | Port | Endpoint | Default |
|---------|-------|---------|------|----------|---------|
| `kubernetes-mcp` | `quay.io/containers/kubernetes_mcp_server` | v0.0.57 | 8080 | `http://localhost:8080/sse` | Enabled |
| `flux-mcp` | `ghcr.io/controlplaneio-fluxcd/flux-operator-mcp` | v0.41.1 | 8081 | `http://localhost:8081/sse` | Enabled |
| `github-mcp` | `ghcr.io/modelcontextprotocol/servers/github` | latest | 8088 | `http://localhost:8088/sse` | Enabled |
| `homeassistant-mcp` | `ghcr.io/homeassistant-ai/ha-mcp` | 6.7.1 | 8087 | `http://localhost:8087/sse` | Disabled |
**Note:**
- 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
- GitHub sidecar uses `GITHUB_TOKEN` from the env secret (same token used for repo cloning)
- Home Assistant sidecar requires `HOMEASSISTANT_URL` and `HOMEASSISTANT_TOKEN` in the env secret
- Playwright MCP remains an external service
#### Enabling/Disabling MCP Servers
To control MCP sidecars, set the `enabled` flag in your values override:
```yaml
# Disable all MCP sidecars
mcpSidecars:
kubernetes:
enabled: false
flux:
enabled: false
github:
enabled: false
homeassistant:
enabled: false
# Or selectively enable/disable
mcpSidecars:
kubernetes:
enabled: true # Keep Kubernetes MCP enabled
flux:
enabled: false # Disable Flux MCP
github:
enabled: true # Keep GitHub MCP enabled (uses GITHUB_TOKEN)
homeassistant:
enabled: true # Enable Home Assistant MCP (requires secrets)
```
When deploying via Helm:
```bash
# Using --set flag
helm install my-devcontainer ./chart --set mcpSidecars.kubernetes.enabled=false --set mcpSidecars.flux.enabled=false
# Or with a values file
helm install my-devcontainer ./chart -f custom-values.yaml
```
### Storage Model ### Storage Model
- `/home` — ReadWriteMany PVC (persists across pod restarts, holds user config/dotfiles) - `/config` — ReadWriteMany PVC (persists across pod restarts, holds user config/dotfiles)
- `/workspace` — emptyDir by default (ephemeral; can be changed to PVC) - `/workspace` — emptyDir by default (ephemeral; can be changed to PVC)
### Environment Variables ### Environment Variables
@@ -83,7 +146,10 @@ Container start
- `GITHUB_REPO` — URL of repository to clone into `/workspace` - `GITHUB_REPO` — URL of repository to clone into `/workspace`
**Optional:** **Optional:**
- `GITHUB_TOKEN` — PAT for private repo access - `GITHUB_TOKEN` — PAT for private repo access (automatically configures git credentials)
- `GIT_USER_NAME` — Git user name for commits (default: "DevContainer User")
- `GIT_USER_EMAIL` — Git user email for commits (default: "devcontainer@example.com")
- `GITLAB_HOST` — GitLab hostname if using GitLab with same token
- `VNC_PASSWORD` — VNC web interface password - `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)
@@ -100,8 +166,9 @@ Image registry: `ghcr.io/cpfarhood/devcontainer`
## Kubernetes Notes ## Kubernetes Notes
- Uses Kustomize (`kubectl apply -k k8s/`) - Deployed via Helm chart (`chart/`), published as OCI artifact to GHCR, reconciled by Flux
- Storage class is `ceph-filesystem` by default — change in `statefulset.yaml` for other clusters - 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
- Secrets managed via SealedSecrets (see `k8s/secrets-example.yaml`) - Secrets: optional env Secret (`devcontainer-{name}-secrets-env`) for `GITHUB_TOKEN`, `VNC_PASSWORD`, etc.
- RBAC: controlled by `clusterAccess` value (`none`, `readonlyns`, `readwritens`, `readonly`, `readwrite`)
+377 -364
View File
@@ -1,436 +1,449 @@
# Deployment Guide # Deployment Guide
This guide provides step-by-step instructions for deploying the Antigravity Dev Container to Kubernetes. This guide provides step-by-step instructions for deploying the Antigravity Dev Container using Helm.
## Prerequisites ## Prerequisites
- Kubernetes cluster with Gateway API support - Kubernetes cluster (1.19+)
- `kubectl` configured to access your cluster - `kubectl` configured to access your cluster
- `helm` CLI installed (3.0+)
- ReadWriteMany storage class available (e.g., `ceph-filesystem`, `nfs-client`, `efs-sc`) - ReadWriteMany storage class available (e.g., `ceph-filesystem`, `nfs-client`, `efs-sc`)
- Sealed Secrets controller installed (for secret encryption)
- GitHub Container Registry access (images are public) - GitHub Container Registry access (images are public)
## Required Configuration Variables ## Quick Start
Before deploying, you need to provide the following configuration: ### 1. Clone the Repository
### 1. Storage Configuration
**Variable:** `storageClassName`
**Location:** `k8s/statefulset.yaml` (line ~117)
**Description:** The ReadWriteMany storage class name in your cluster
**Example values:**
- `ceph-filesystem` (Rook-Ceph)
- `nfs-client` (NFS)
- `efs-sc` (AWS EFS)
- `azurefile` (Azure Files)
- `filestore` (GCP Filestore)
**How to find your storage class:**
```bash
kubectl get storageclass
```
Look for a storage class that supports `ReadWriteMany` access mode.
### 2. GitHub Repository (Required)
**Variable:** `github-repo`
**Location:** `k8s/configmap.yaml` (line ~9)
**Description:** The GitHub repository URL to clone on container startup
**Format:** `https://github.com/username/repository`
**Example:** `https://github.com/cpfarhood/my-project`
### 3. GitHub Token (Optional, for private repos)
**Variable:** `github-token`
**Location:** `k8s/secrets-example.yaml` (sealed secret)
**Description:** GitHub Personal Access Token for cloning private repositories
**Format:** `ghp_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx`
**Required:** Only if cloning a private repository
**How to create a GitHub token:**
1. Go to https://github.com/settings/tokens
2. Click "Generate new token (classic)"
3. Select scopes: `repo` (for private repos)
4. Generate and copy the token
### 4. VNC Password (Optional)
**Variable:** `vnc-password`
**Location:** `k8s/secrets-example.yaml` (sealed secret)
**Description:** Password for accessing the VNC web interface
**Format:** Any string (recommend 12+ characters)
**Required:** Optional, but recommended for security
### 5. Gateway Configuration (Required for external access)
**Variables:**
- `parentRefs.name` - Your Gateway resource name
- `parentRefs.namespace` - Namespace where Gateway is deployed
- `hostnames` - Domain name for accessing the container
**Location:** `k8s/httproute.yaml`
**Example:**
```yaml
parentRefs:
- name: cilium-gateway # Your Gateway name
namespace: kube-system # Your Gateway namespace
hostnames:
- "devcontainer.example.com" # Your domain
```
### 6. Namespace (Optional)
**Variable:** `namespace`
**Location:** `k8s/kustomization.yaml` (line ~5)
**Description:** Kubernetes namespace to deploy into
**Default:** `default`
**Example:** `devcontainer`, `development`, `team-workspaces`
### 7. Container Image (Optional)
**Variable:** `image`
**Location:** `k8s/statefulset.yaml` (line ~32)
**Description:** Docker image to use
**Default:** `ghcr.io/cpfarhood/devcontainer:latest`
**Format:** `registry/repository:tag`
### 8. Resource Limits (Optional)
**Variables:**
- `resources.requests.memory` (default: `2Gi`)
- `resources.requests.cpu` (default: `1000m`)
- `resources.limits.memory` (default: `8Gi`)
- `resources.limits.cpu` (default: `4000m`)
**Location:** `k8s/statefulset.yaml` (lines ~98-103)
### 9. Happy Coder Configuration (Optional)
**Variables:**
- `happy-server-url` - Custom Happy server URL
- `happy-webapp-url` - Custom Happy webapp URL
**Location:** `k8s/configmap.yaml` (lines ~12-13, commented out)
**Default:** Uses Happy's default servers
**When to set:** Only if using a self-hosted Happy instance
## Deployment Steps
### Step 1: Clone the Repository
```bash ```bash
git clone https://github.com/cpfarhood/devcontainer.git git clone https://github.com/cpfarhood/devcontainer.git
cd devcontainer cd devcontainer
``` ```
### Step 2: Configure Storage Class ### 2. Create Secret (Optional)
Edit `k8s/statefulset.yaml` and find the `volumeClaimTemplates` section (around line 117): For private repos or VNC password:
```bash ```bash
# Find your storage class kubectl create secret generic devcontainer-mydev-secrets-env \
kubectl get storageclass --from-literal=GITHUB_TOKEN='ghp_...' \
--from-literal=VNC_PASSWORD='changeme' \
# Edit the file --from-literal=ANTHROPIC_API_KEY='sk-ant-...'
vi k8s/statefulset.yaml
``` ```
Change `storageClassName` to match your cluster: ### 3. Deploy with Helm
```yaml
volumeClaimTemplates:
- metadata:
name: userhome
spec:
accessModes: [ "ReadWriteMany" ]
storageClassName: "ceph-filesystem" # ← Change this
resources:
requests:
storage: 10Gi
```
### Step 3: Configure GitHub Repository
Edit `k8s/configmap.yaml`:
```bash ```bash
vi k8s/configmap.yaml # Basic deployment
helm install mydev ./chart \
--set name=mydev \
--set githubRepo=https://github.com/youruser/yourrepo
# With custom storage class
helm install mydev ./chart \
--set name=mydev \
--set githubRepo=https://github.com/youruser/yourrepo \
--set storage.className=nfs-client
# With cluster access for kubectl
helm install mydev ./chart \
--set name=mydev \
--set githubRepo=https://github.com/youruser/yourrepo \
--set clusterAccess=readwritens
``` ```
Set your repository URL: ### 4. Access the Container
```yaml
data:
github-repo: "https://github.com/yourusername/yourrepo"
```
### Step 4: Configure Gateway (HTTPRoute)
Edit `k8s/httproute.yaml`:
```bash ```bash
# Find your Gateway # Port forward
kubectl get gateway -A kubectl port-forward deployment/devcontainer-mydev 5800:5800
# Edit the file
vi k8s/httproute.yaml
```
Update with your Gateway details:
```yaml
spec:
parentRefs:
- name: your-gateway-name # ← Change this
namespace: your-gateway-namespace # ← Change this
hostnames:
- "devcontainer.yourdomain.com" # ← Change this
```
### Step 5: Create Secrets
Create the secrets for GitHub token and VNC password:
```bash
# Create the secret
kubectl create secret generic antigravity-secrets \
--from-literal=github-token='ghp_your_token_here' \
--from-literal=vnc-password='your_vnc_password' \
--dry-run=client -o yaml | \
kubeseal --format=yaml > k8s/sealedsecrets.yaml
# Verify the sealed secret was created
cat k8s/sealedsecrets.yaml
```
**If you don't have Sealed Secrets controller:**
Option 1: Install Sealed Secrets
```bash
kubectl apply -f https://github.com/bitnami-labs/sealed-secrets/releases/download/v0.24.0/controller.yaml
```
Option 2: Use plain secrets (not recommended for production)
```bash
kubectl create secret generic antigravity-secrets \
--from-literal=github-token='ghp_your_token_here' \
--from-literal=vnc-password='your_vnc_password'
```
### Step 6: Review Configuration (Optional)
Review and adjust optional settings:
**Namespace:**
```bash
vi k8s/kustomization.yaml
# Change line 5: namespace: default
```
**Resource limits:**
```bash
vi k8s/statefulset.yaml
# Adjust lines 98-103 for your needs
```
### Step 7: Deploy to Kubernetes
```bash
# Deploy everything
kubectl apply -k k8s/
# Or if you changed the namespace
kubectl apply -k k8s/ -n your-namespace
```
### Step 8: Verify Deployment
```bash
# Check StatefulSet
kubectl get statefulset antigravity
# Check Pod
kubectl get pods -l app=antigravity
# Check PVC
kubectl get pvc -l app=antigravity
# Check HTTPRoute
kubectl get httproute antigravity
# View logs
kubectl logs antigravity-0
```
### Step 9: Access the Container
**Option A: Via HTTPRoute (external access)**
```bash
# Open in browser
open https://devcontainer.yourdomain.com
```
**Option B: Via Port Forward (local access)**
```bash
# Port forward to localhost
kubectl port-forward statefulset/antigravity 5800:5800
# Open in browser
open http://localhost:5800 open http://localhost:5800
``` ```
## Configuration Summary ## Deployment Options
Here's a quick checklist of all variables you need to set: ### Using Values File
### Required Variables Create a custom `values.yaml`:
| Variable | File | Line | Example Value | ```yaml
|----------|------|------|---------------| name: mydev
| `storageClassName` | `k8s/statefulset.yaml` | ~117 | `ceph-filesystem` | githubRepo: https://github.com/youruser/yourrepo
| `github-repo` | `k8s/configmap.yaml` | ~9 | `https://github.com/user/repo` | ide: vscode
| `parentRefs.name` | `k8s/httproute.yaml` | ~8 | `cilium-gateway` | ssh: false
| `parentRefs.namespace` | `k8s/httproute.yaml` | ~9 | `kube-system` |
| `hostnames` | `k8s/httproute.yaml` | ~10 | `devcontainer.example.com` |
### Optional Variables # Storage
storage:
size: 32Gi
className: ceph-filesystem
| Variable | File | Line | Default | When to Change | # Resources
|----------|------|------|---------|----------------| resources:
| `namespace` | `k8s/kustomization.yaml` | ~5 | `default` | If deploying to different namespace | requests:
| `github-token` | Sealed secret | N/A | None | For private repos | memory: "4Gi"
| `vnc-password` | Sealed secret | N/A | None | For VNC security | cpu: "2000m"
| `image` | `k8s/statefulset.yaml` | ~32 | `ghcr.io/cpfarhood/devcontainer:latest` | For specific version or custom build | limits:
| `resources.*` | `k8s/statefulset.yaml` | ~98-103 | 2Gi/8Gi RAM, 1/4 CPU | Based on workload needs | memory: "16Gi"
| `happy-server-url` | `k8s/configmap.yaml` | ~12 | Default Happy server | For self-hosted Happy | cpu: "8000m"
| `happy-webapp-url` | `k8s/configmap.yaml` | ~13 | Default Happy webapp | For self-hosted Happy |
# Kubernetes access
clusterAccess: readwritens
# MCP sidecars
mcpSidecars:
kubernetes:
enabled: true
flux:
enabled: false
```
Deploy:
```bash
helm install mydev ./chart -f values.yaml
```
### SSH Access Setup
Enable SSH and add your public key:
```bash
# Create secret with SSH key
kubectl create secret generic devcontainer-mydev-secrets-env \
--from-literal=SSH_AUTHORIZED_KEYS='ssh-ed25519 AAAA...'
# Deploy with SSH enabled
helm install mydev ./chart \
--set name=mydev \
--set githubRepo=https://github.com/youruser/yourrepo \
--set ssh=true
# Connect via SSH
kubectl port-forward deployment/devcontainer-mydev 2222:22
ssh -p 2222 user@localhost
```
### MCP Sidecar Configuration
Control MCP servers for AI-assisted operations.
**Important:** Kubernetes and Flux MCP sidecars are only deployed when:
1. They are enabled in values (`mcpSidecars.<name>.enabled: true`)
2. AND `clusterAccess` is not `none` (they need RBAC permissions to function)
```bash
# Disable all MCP sidecars
helm install mydev ./chart \
--set name=mydev \
--set githubRepo=https://github.com/youruser/yourrepo \
--set mcpSidecars.kubernetes.enabled=false \
--set mcpSidecars.flux.enabled=false \
--set mcpSidecars.homeassistant.enabled=false
# Enable only Kubernetes MCP
helm install mydev ./chart \
--set name=mydev \
--set githubRepo=https://github.com/youruser/yourrepo \
--set mcpSidecars.kubernetes.enabled=true \
--set mcpSidecars.flux.enabled=false
# Enable Home Assistant MCP (requires credentials)
kubectl create secret generic devcontainer-mydev-secrets-env \
--from-literal=homeassistant-url='http://homeassistant.local:8123' \
--from-literal=homeassistant-token='your_long_lived_token'
helm install mydev ./chart \
--set name=mydev \
--set githubRepo=https://github.com/youruser/yourrepo \
--set mcpSidecars.homeassistant.enabled=true
```
### Cluster Access Levels
Configure Kubernetes RBAC permissions:
| Value | Scope | Permissions | Use Case |
|-------|-------|-------------|----------|
| `none` | No access | None | Default, isolated development |
| `readonlyns` | Namespace | Read-only | View resources in namespace |
| `readwritens` | Namespace | Full access | Deploy apps in namespace |
| `readonly` | Cluster-wide | Read-only | View all cluster resources |
| `readwrite` | Cluster-wide | Full access | Cluster administration |
```bash
# Example: Full access within namespace
helm install mydev ./chart \
--set name=mydev \
--set githubRepo=https://github.com/youruser/yourrepo \
--set clusterAccess=readwritens
```
## Ingress Configuration
### Using Gateway API HTTPRoute
Create an HTTPRoute for external access:
```yaml
apiVersion: gateway.networking.k8s.io/v1beta1
kind: HTTPRoute
metadata:
name: devcontainer-mydev
spec:
parentRefs:
- name: your-gateway
namespace: your-gateway-namespace
hostnames:
- devcontainer.example.com
rules:
- backendRefs:
- name: devcontainer-mydev
port: 5800
```
### Using Traditional Ingress
Create an Ingress resource:
```yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: devcontainer-mydev
spec:
rules:
- host: devcontainer.example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: devcontainer-mydev
port:
number: 5800
```
## Advanced Configurations
### Custom 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
```bash
helm install mydev ./chart \
--set name=mydev \
--set githubRepo=https://github.com/youruser/yourrepo \
--set display.width=2560 \
--set display.height=1440
```
### Different IDE Options
```bash
# Use Google Antigravity
helm install mydev ./chart \
--set name=mydev \
--set githubRepo=https://github.com/youruser/yourrepo \
--set ide=antigravity
# SSH-only mode (no GUI)
helm install mydev ./chart \
--set name=mydev \
--set githubRepo=https://github.com/youruser/yourrepo \
--set ide=none \
--set ssh=true
```
## Helm Operations
### List Deployments
```bash
helm list
```
### Upgrade Deployment
```bash
# Change values
helm upgrade mydev ./chart \
--set name=mydev \
--set githubRepo=https://github.com/youruser/newrepo
# Upgrade with new chart version
git pull
helm upgrade mydev ./chart
```
### Uninstall
```bash
helm uninstall mydev
# Note: PVC persists by default
kubectl delete pvc userhome-mydev
```
### Rollback
```bash
# View history
helm history mydev
# Rollback to previous version
helm rollback mydev
# Rollback to specific revision
helm rollback mydev 3
```
## Troubleshooting ## Troubleshooting
### Pod not starting ### Pod Not Starting
**Check events:**
```bash ```bash
kubectl describe pod antigravity-0 # Check pod status
kubectl get pods -l app.kubernetes.io/instance=mydev
# Describe pod for events
kubectl describe pod -l app.kubernetes.io/instance=mydev
# Check logs
kubectl logs deployment/devcontainer-mydev
``` ```
**Common issues:** ### Repository Not Cloning
- Storage class doesn't support ReadWriteMany
- PVC not binding (check storage class exists)
- Image pull errors (check image name)
### Repository not cloning
**Check logs:**
```bash ```bash
kubectl logs antigravity-0 | grep -A 10 "Repository Initialization" # Check init logs
kubectl logs deployment/devcontainer-mydev | grep "Repository Initialization"
# Verify secret exists
kubectl get secret devcontainer-mydev-secrets-env
# Check environment
kubectl exec deployment/devcontainer-mydev -- env | grep GITHUB
``` ```
**Common issues:** ### VNC Not Accessible
- Invalid GitHub URL
- Private repo without token
- Token doesn't have correct permissions
### HTTPRoute not working
**Check HTTPRoute:**
```bash ```bash
kubectl describe httproute antigravity # Check service
kubectl get svc devcontainer-mydev
kubectl describe svc devcontainer-mydev
# Test with port-forward
kubectl port-forward deployment/devcontainer-mydev 5800:5800
``` ```
**Common issues:** ### MCP Sidecar Issues
- Gateway name/namespace incorrect
- Domain not pointing to Gateway
- TLS certificate not issued
### VNC not accessible
**Check service:**
```bash ```bash
kubectl get svc antigravity # Check all containers
kubectl describe svc antigravity kubectl get pod -l app.kubernetes.io/instance=mydev -o jsonpath='{.items[0].spec.containers[*].name}'
# Check MCP container logs
kubectl logs deployment/devcontainer-mydev -c kubernetes-mcp
kubectl logs deployment/devcontainer-mydev -c flux-mcp
kubectl logs deployment/devcontainer-mydev -c homeassistant-mcp
# Verify RBAC permissions (for Kubernetes/Flux MCP)
kubectl auth can-i --list --as system:serviceaccount:default:devcontainer-mydev
# Check Home Assistant MCP credentials
kubectl get secret devcontainer-mydev-secrets-env -o jsonpath='{.data.homeassistant-url}' | base64 -d
# Verify the URL is accessible from the pod
kubectl exec deployment/devcontainer-mydev -- curl -s http://homeassistant.local:8123/api/
``` ```
**Port forward test:** ### Storage Issues
```bash ```bash
kubectl port-forward antigravity-0 5800:5800 # Check PVC
# Try accessing http://localhost:5800 kubectl get pvc userhome-mydev
kubectl describe pvc userhome-mydev
# Check available storage classes
kubectl get storageclass
# Verify ReadWriteMany support
kubectl get storageclass <class-name> -o yaml | grep -i accessmodes
``` ```
## Quick Deploy Example ## Best Practices
Complete deployment with all values filled in: ### Production Deployment
1. **Use specific image tags** instead of `latest`:
```bash
helm install mydev ./chart --set image.tag=v1.0.0
```
2. **Set resource limits** appropriately:
```yaml
resources:
requests:
memory: "4Gi"
cpu: "2000m"
limits:
memory: "8Gi"
cpu: "4000m"
```
3. **Enable VNC password**:
```bash
kubectl create secret generic devcontainer-mydev-secrets-env \
--from-literal=VNC_PASSWORD='strong-password-here'
```
4. **Use dedicated namespace**:
```bash
kubectl create namespace dev-environments
helm install mydev ./chart -n dev-environments
```
5. **Configure appropriate cluster access**:
- Use `readonlyns` or `readwritens` for namespace-scoped work
- Avoid `readwrite` cluster-wide access unless necessary
### Multi-User Deployment
For teams, create separate deployments per user:
```bash ```bash
# 1. Set your values # User 1
STORAGE_CLASS="ceph-filesystem" helm install alice-dev ./chart \
GITHUB_REPO="https://github.com/myuser/myproject" --set name=alice-dev \
GITHUB_TOKEN="ghp_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" --set githubRepo=https://github.com/alice/project
VNC_PASSWORD="my-secure-password-123"
GATEWAY_NAME="cilium-gateway"
GATEWAY_NAMESPACE="kube-system"
DOMAIN="devcontainer.example.com"
# 2. Update storage class # User 2
sed -i "s/storageClassName: .*/storageClassName: \"$STORAGE_CLASS\"/" k8s/statefulset.yaml helm install bob-dev ./chart \
--set name=bob-dev \
# 3. Update GitHub repo --set githubRepo=https://github.com/bob/project
sed -i "s|github-repo: .*|github-repo: \"$GITHUB_REPO\"|" k8s/configmap.yaml
# 4. Update Gateway
sed -i "s/- name: gateway/- name: $GATEWAY_NAME/" k8s/httproute.yaml
sed -i "s/namespace: gateway-system/namespace: $GATEWAY_NAMESPACE/" k8s/httproute.yaml
sed -i "s/antigravity.example.com/$DOMAIN/" k8s/httproute.yaml
# 5. Create sealed secret
kubectl create secret generic antigravity-secrets \
--from-literal=github-token="$GITHUB_TOKEN" \
--from-literal=vnc-password="$VNC_PASSWORD" \
--dry-run=client -o yaml | \
kubeseal --format=yaml > k8s/sealedsecrets.yaml
# 6. Deploy
kubectl apply -k k8s/
# 7. Watch deployment
kubectl get pods -l app=antigravity -w
``` ```
## Updates and Maintenance ### Backup and Recovery
### Updating the Image The home directory persists on PVC. To backup:
The image is automatically built and pushed to ghcr.io on every commit to main.
**To use latest:**
```bash
kubectl set image statefulset/antigravity \
antigravity=ghcr.io/cpfarhood/devcontainer:latest
```
**To use specific version:**
```bash
kubectl set image statefulset/antigravity \
antigravity=ghcr.io/cpfarhood/devcontainer:v1.0.0
```
### Changing Repository
Edit the ConfigMap and restart:
```bash
kubectl edit configmap antigravity-config
# Change github-repo value
kubectl rollout restart statefulset/antigravity
```
### Scaling
```bash ```bash
# Scale to multiple instances (each gets own home PVC) # Create backup pod
kubectl scale statefulset antigravity --replicas=3 kubectl run backup --image=busybox --restart=Never --rm -i --tty \
-- tar czf - -C /home . | gzip > home-backup.tar.gz
``` ```
## Support ## Support
For issues or questions: For issues or questions:
- GitHub Issues: https://github.com/cpfarhood/devcontainer/issues - GitHub Issues: https://github.com/cpfarhood/devcontainer/issues
- Documentation: https://github.com/cpfarhood/devcontainer - Documentation: https://github.com/cpfarhood/devcontainer
+20 -2
View File
@@ -35,7 +35,25 @@ RUN wget -q -O - https://dl.google.com/linux/linux_signing_key.pub | gpg --dearm
# Chrome wrapper: adds flags required for running inside a Docker container. # Chrome wrapper: adds flags required for running inside a Docker container.
# xdg-open (used by Claude Code on Linux) respects $BROWSER, so pointing it # xdg-open (used by Claude Code on Linux) respects $BROWSER, so pointing it
# here ensures the OAuth popup works without manual --no-sandbox invocations. # here ensures the OAuth popup works without manual --no-sandbox invocations.
RUN printf '#!/bin/bash\nexec /usr/bin/google-chrome-stable \\\n --no-sandbox \\\n --disable-dev-shm-usage \\\n --disable-gpu \\\n "$@"\n' > /usr/local/bin/google-chrome && \ # Cleans up crash lock files and suppresses the crash-restore bubble so that
# sessions/cookies survive unclean pod shutdowns (SIGKILL).
RUN printf '#!/bin/bash\n\
CHROME_DIR="/config/userdata/.config/google-chrome"\n\
mkdir -p "$CHROME_DIR"\n\
# Remove stale lock files left by unclean container shutdown\n\
rm -f "$CHROME_DIR/SingletonLock" "$CHROME_DIR/SingletonSocket" "$CHROME_DIR/SingletonCookie"\n\
# Mark the previous session as clean so Chrome does not clear cookies\n\
PREFS="$CHROME_DIR/Default/Preferences"\n\
if [ -f "$PREFS" ]; then\n\
sed -i '\''s/"exit_type":"Crashed"/"exit_type":"Normal"/g; s/"exited_cleanly":false/"exited_cleanly":true/g'\'' "$PREFS"\n\
fi\n\
exec /usr/bin/google-chrome-stable \\\n\
--no-sandbox \\\n\
--disable-dev-shm-usage \\\n\
--disable-gpu \\\n\
--disable-session-crashed-bubble \\\n\
--user-data-dir="$CHROME_DIR" \\\n\
"$@"\n' > /usr/local/bin/google-chrome && \
chmod +x /usr/local/bin/google-chrome chmod +x /usr/local/bin/google-chrome
# Install Node.js (LTS version for Happy Coder) # Install Node.js (LTS version for Happy Coder)
@@ -93,7 +111,7 @@ COPY --chmod=755 scripts/cont-init-sshd.sh /etc/cont-init.d/25-start-sshd.sh
WORKDIR /workspace WORKDIR /workspace
# Configure container to run as user user # Configure container to run as user user
ENV HOME=/home/user \ ENV HOME=/config/userdata \
USER=user \ USER=user \
BROWSER=/usr/local/bin/google-chrome BROWSER=/usr/local/bin/google-chrome
+42 -23
View File
@@ -44,26 +44,40 @@ clean: stop
@echo "Cleaning up..." @echo "Cleaning up..."
rm -rf ./home ./workspace rm -rf ./home ./workspace
# Kubernetes deployment # Helm deployment
k8s-deploy: RELEASE_NAME ?= mydev
@echo "Deploying to Kubernetes..." NAMESPACE ?= default
kubectl apply -k k8s/
k8s-delete: helm-deploy:
@echo "Deleting from Kubernetes..." @echo "Deploying with Helm (release: $(RELEASE_NAME))..."
kubectl delete -k k8s/ @if [ -z "$(GITHUB_REPO)" ]; then \
echo "ERROR: GITHUB_REPO environment variable is required"; \
echo "Usage: GITHUB_REPO=https://github.com/user/repo make helm-deploy"; \
exit 1; \
fi
helm upgrade --install $(RELEASE_NAME) ./chart \
--namespace $(NAMESPACE) \
--set name=$(RELEASE_NAME) \
--set githubRepo="$(GITHUB_REPO)" \
--set image.repository=$(REGISTRY)/$(IMAGE_NAME) \
--set image.tag=$(IMAGE_TAG)
k8s-logs: helm-delete:
@echo "Showing logs..." @echo "Deleting Helm release $(RELEASE_NAME)..."
kubectl logs -f antigravity-0 helm uninstall $(RELEASE_NAME) --namespace $(NAMESPACE)
@echo "Note: PVC persists. To delete: kubectl delete pvc userhome-$(RELEASE_NAME) -n $(NAMESPACE)"
k8s-shell: helm-logs:
@echo "Opening shell..." @echo "Showing logs for $(RELEASE_NAME)..."
kubectl exec -it antigravity-0 -- bash kubectl logs -f deployment/devcontainer-$(RELEASE_NAME) -n $(NAMESPACE)
k8s-port-forward: helm-shell:
@echo "Port forwarding to localhost:5800..." @echo "Opening shell in $(RELEASE_NAME)..."
kubectl port-forward antigravity-0 5800:5800 kubectl exec -it deployment/devcontainer-$(RELEASE_NAME) -n $(NAMESPACE) -- bash
helm-port-forward:
@echo "Port forwarding $(RELEASE_NAME) to localhost:5800..."
kubectl port-forward deployment/devcontainer-$(RELEASE_NAME) 5800:5800 -n $(NAMESPACE)
# Show help # Show help
help: help:
@@ -78,24 +92,29 @@ help:
@echo " stop - Stop running container" @echo " stop - Stop running container"
@echo " clean - Clean up containers and volumes" @echo " clean - Clean up containers and volumes"
@echo "" @echo ""
@echo "Kubernetes Targets:" @echo "Helm/Kubernetes Targets:"
@echo " k8s-deploy - Deploy to Kubernetes" @echo " helm-deploy - Deploy with Helm chart (requires GITHUB_REPO)"
@echo " k8s-delete - Delete from Kubernetes" @echo " helm-delete - Delete Helm release"
@echo " k8s-logs - Show container logs" @echo " helm-logs - Show container logs"
@echo " k8s-shell - Open shell in container" @echo " helm-shell - Open shell in container"
@echo " k8s-port-forward - Port forward to localhost" @echo " helm-port-forward - Port forward to localhost"
@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: antigravity)" @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 " NAMESPACE - Kubernetes namespace (default: default)"
@echo " GITHUB_REPO - GitHub repository URL (required for helm-deploy)"
@echo "" @echo ""
@echo "Environment Variables for 'make run':" @echo "Environment Variables for 'make run':"
@echo " GITHUB_REPO - GitHub repository URL" @echo " GITHUB_REPO - GitHub repository URL"
@echo " GITHUB_TOKEN - GitHub token (optional)" @echo " GITHUB_TOKEN - GitHub token (optional)"
@echo " VNC_PASSWORD - VNC password (optional)" @echo " VNC_PASSWORD - VNC password (optional)"
@echo "" @echo ""
@echo "Example:" @echo "Examples:"
@echo " make build" @echo " make build"
@echo " make push REGISTRY=ghcr.io/myuser IMAGE_TAG=v1.0" @echo " make push REGISTRY=ghcr.io/myuser IMAGE_TAG=v1.0"
@echo " GITHUB_REPO=https://github.com/user/repo make run" @echo " GITHUB_REPO=https://github.com/user/repo make run"
@echo " GITHUB_REPO=https://github.com/user/repo make helm-deploy"
@echo " RELEASE_NAME=alice-dev GITHUB_REPO=https://github.com/alice/project make helm-deploy"
+96 -4
View File
@@ -3,7 +3,8 @@
![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** 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)
- **Happy Coder** AI assistant backed by Claude - **Happy Coder** AI assistant backed by Claude
- **Automatic GitHub repo cloning** on startup - **Automatic GitHub repo cloning** on startup
- **Persistent home directory** via ReadWriteMany PVC - **Persistent home directory** via ReadWriteMany PVC
@@ -21,6 +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 `mcpSidecars.homeassistant.enabled: true`) |
| `homeassistant-token` | Home Assistant long-lived access token (required when `mcpSidecars.homeassistant.enabled: true`) |
```bash ```bash
kubectl create secret generic devcontainer-mydev-secrets-env \ kubectl create secret generic devcontainer-mydev-secrets-env \
@@ -149,6 +152,86 @@ helm install mydev ./chart \
With any non-`none` value, a `ServiceAccount` named `devcontainer-{name}` is created and set as the pod's `serviceAccountName`, so `kubectl` and any in-cluster API calls use it automatically. With any non-`none` value, a `ServiceAccount` named `devcontainer-{name}` is created and set as the pod's `serviceAccountName`, so `kubectl` and any in-cluster API calls use it automatically.
### MCP Sidecars
The devcontainer includes MCP (Model Context Protocol) servers as sidecar containers that enable AI assistants to interact with various services:
| Sidecar | Default | Purpose |
|---------|---------|---------|
| `mcpSidecars.kubernetes.enabled` | `true` | Kubernetes API access via MCP |
| `mcpSidecars.flux.enabled` | `true` | Flux GitOps operations via MCP |
| `mcpSidecars.homeassistant.enabled` | `false` | Home Assistant smart home control via MCP |
**Notes:**
- Kubernetes and Flux sidecars require `clusterAccess` != `none` to be deployed (automatically disabled when no cluster access)
- Kubernetes and Flux sidecars inherit the pod's ServiceAccount RBAC permissions (controlled by `clusterAccess`)
- Home Assistant sidecar requires additional configuration (see below)
**Disable MCP sidecars:**
```bash
# Disable both sidecars
helm install mydev ./chart \
--set name=mydev \
--set githubRepo=https://github.com/youruser/yourrepo \
--set mcpSidecars.kubernetes.enabled=false \
--set mcpSidecars.flux.enabled=false
# Or selectively disable
helm install mydev ./chart \
--set name=mydev \
--set githubRepo=https://github.com/youruser/yourrepo \
--set mcpSidecars.flux.enabled=false # Disable only Flux MCP
```
**Enable Home Assistant MCP:**
```bash
# Create secret with Home Assistant credentials
kubectl create secret generic devcontainer-mydev-secrets-env \
--from-literal=GITHUB_TOKEN='ghp_...' \
--from-literal=homeassistant-url='http://homeassistant.local:8123' \
--from-literal=homeassistant-token='your_long_lived_access_token'
# Deploy with Home Assistant MCP enabled
helm install mydev ./chart \
--set name=mydev \
--set githubRepo=https://github.com/youruser/yourrepo \
--set mcpSidecars.homeassistant.enabled=true
```
**Custom MCP configuration:**
```yaml
# values.yaml override
mcpSidecars:
kubernetes:
enabled: true
image:
repository: quay.io/containers/kubernetes_mcp_server
tag: latest
port: 8080
resources:
requests:
memory: "64Mi"
cpu: "50m"
limits:
memory: "256Mi"
cpu: "500m"
flux:
enabled: false # Disabled in this example
homeassistant:
enabled: true
image:
repository: ghcr.io/homeassistant-ai/ha-mcp
tag: 6.7.1 # Override the pinned version if needed
port: 8087
resources:
requests:
memory: "128Mi"
cpu: "100m"
limits:
memory: "512Mi"
cpu: "500m"
```
### Display and resources ### Display and resources
| Value | Default | Description | | Value | Default | Description |
@@ -160,6 +243,7 @@ With any non-`none` value, a `ServiceAccount` named `devcontainer-{name}` is cre
| `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) |
| `resources.requests.memory` | `2Gi` | | | `resources.requests.memory` | `2Gi` | |
| `resources.requests.cpu` | `1000m` | | | `resources.requests.cpu` | `1000m` | |
| `resources.limits.memory` | `8Gi` | | | `resources.limits.memory` | `8Gi` | |
@@ -182,9 +266,9 @@ Container start
→ rm daemon.state.json.lock — clear stale Happy lock → rm daemon.state.json.lock — clear stale Happy lock
→ happy daemon start — starts Happy Coder background daemon → 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 --new-window --wait /workspace/{repo} IDE=antigravity: antigravity --no-sandbox --user-data-dir ~/.config/antigravity ... /workspace/{repo}
IDE=none: sleep infinity IDE=none: sleep infinity
(SSH=true: sshd also running as root on port 22) (SSH=true: sshd also running as root on port 22; host keys persisted on PVC)
``` ```
### Storage ### Storage
@@ -230,7 +314,15 @@ Then restart the pod to pick up the new env var.
```bash ```bash
kubectl port-forward deployment/devcontainer-mydev 5800:5800 kubectl port-forward deployment/devcontainer-mydev 5800:5800
kubectl logs deployment/devcontainer-mydev kubectl logs deployment/devcontainer-mydev
kubectl describe pod -l instance=mydev kubectl describe pod -l app.kubernetes.io/instance=mydev
```
### Pod not picking up new image after upgrade
The chart uses `image.tag: latest`. Kubernetes won't restart the pod on a Helm upgrade unless the Deployment spec changes. Force a restart manually:
```bash
kubectl rollout restart deployment/devcontainer-mydev
``` ```
### Repository not cloning ### Repository not cloning
+414 -275
View File
@@ -1,305 +1,444 @@
# Configuration Variables Reference # Helm Chart Values Reference
Quick reference for all configurable variables in this project. Complete reference for all configurable values in the Antigravity Dev Container Helm chart.
## Required Variables ## Core Configuration
These MUST be configured before deployment: ### name
### Storage Class Name
- **Variable:** `storageClassName`
- **File:** `k8s/statefulset.yaml`
- **Line:** ~117
- **Type:** String - **Type:** String
- **Description:** ReadWriteMany storage class available in your cluster - **Default:** `""`
- **Example:** `ceph-filesystem`, `nfs-client`, `efs-sc`
- **How to find:** `kubectl get storageclass`
### GitHub Repository URL
- **Variable:** `github-repo`
- **File:** `k8s/configmap.yaml`
- **Line:** ~9
- **Type:** String (URL)
- **Description:** Repository to clone on container startup
- **Format:** `https://github.com/username/repository`
- **Example:** `https://github.com/cpfarhood/my-project`
### Gateway Name
- **Variable:** `parentRefs[0].name`
- **File:** `k8s/httproute.yaml`
- **Line:** ~8
- **Type:** String
- **Description:** Name of your Gateway resource
- **How to find:** `kubectl get gateway -A`
### Gateway Namespace
- **Variable:** `parentRefs[0].namespace`
- **File:** `k8s/httproute.yaml`
- **Line:** ~9
- **Type:** String
- **Description:** Namespace where Gateway is deployed
- **How to find:** `kubectl get gateway -A`
### Domain Hostname
- **Variable:** `hostnames[0]`
- **File:** `k8s/httproute.yaml`
- **Line:** ~11
- **Type:** String (FQDN)
- **Description:** Domain name for accessing the container
- **Example:** `devcontainer.example.com`
## Optional Variables
### GitHub Token
- **Variable:** `github-token`
- **File:** Sealed Secret
- **Type:** String (GitHub PAT)
- **Description:** Personal Access Token for private repos
- **Required:** Only for private repositories
- **Format:** `ghp_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx`
- **Scopes:** `repo`
### VNC Password
- **Variable:** `vnc-password`
- **File:** Sealed Secret
- **Type:** String
- **Description:** Password for VNC web interface
- **Required:** Recommended for security
- **Format:** Any string (12+ characters recommended)
### Namespace
- **Variable:** `namespace`
- **File:** `k8s/kustomization.yaml`
- **Line:** ~5
- **Type:** String
- **Description:** Kubernetes namespace for deployment
- **Default:** `default`
### Container Image
- **Variable:** `image`
- **File:** `k8s/statefulset.yaml`
- **Line:** ~32
- **Type:** String (image reference)
- **Description:** Docker image to deploy
- **Default:** `ghcr.io/cpfarhood/devcontainer:latest`
- **Format:** `registry/repository:tag`
### Memory Request
- **Variable:** `resources.requests.memory`
- **File:** `k8s/statefulset.yaml`
- **Line:** ~99
- **Type:** String (quantity)
- **Description:** Minimum memory to reserve
- **Default:** `2Gi`
- **Format:** `<number>Gi` or `<number>Mi`
### Memory Limit
- **Variable:** `resources.limits.memory`
- **File:** `k8s/statefulset.yaml`
- **Line:** ~102
- **Type:** String (quantity)
- **Description:** Maximum memory allowed
- **Default:** `8Gi`
- **Format:** `<number>Gi` or `<number>Mi`
### CPU Request
- **Variable:** `resources.requests.cpu`
- **File:** `k8s/statefulset.yaml`
- **Line:** ~100
- **Type:** String (quantity)
- **Description:** Minimum CPU to reserve
- **Default:** `1000m` (1 core)
- **Format:** `<number>m` (millicores) or `<number>` (cores)
### CPU Limit
- **Variable:** `resources.limits.cpu`
- **File:** `k8s/statefulset.yaml`
- **Line:** ~103
- **Type:** String (quantity)
- **Description:** Maximum CPU allowed
- **Default:** `4000m` (4 cores)
- **Format:** `<number>m` (millicores) or `<number>` (cores)
### Storage Size
- **Variable:** `storage` (under volumeClaimTemplates)
- **File:** `k8s/statefulset.yaml`
- **Line:** ~120
- **Type:** String (quantity)
- **Description:** Size of home directory PVC
- **Default:** `10Gi`
- **Format:** `<number>Gi` or `<number>Ti`
### Happy Server URL
- **Variable:** `happy-server-url`
- **File:** `k8s/configmap.yaml`
- **Line:** ~12 (commented)
- **Type:** String (URL)
- **Description:** Custom Happy Coder server
- **Default:** `https://api.cluster-fluster.com`
- **When to set:** Self-hosted Happy instance only
### Happy Webapp URL
- **Variable:** `happy-webapp-url`
- **File:** `k8s/configmap.yaml`
- **Line:** ~13 (commented)
- **Type:** String (URL)
- **Description:** Custom Happy Coder webapp
- **Default:** `https://app.happy.engineering`
- **When to set:** Self-hosted Happy instance only
### Display Width
- **Variable:** `DISPLAY_WIDTH`
- **File:** `k8s/statefulset.yaml`
- **Line:** ~56
- **Type:** String (number)
- **Description:** VNC display width in pixels
- **Default:** `1920`
### Display Height
- **Variable:** `DISPLAY_HEIGHT`
- **File:** `k8s/statefulset.yaml`
- **Line:** ~58
- **Type:** String (number)
- **Description:** VNC display height in pixels
- **Default:** `1080`
### User ID
- **Variable:** `USER_ID`
- **File:** `k8s/statefulset.yaml`
- **Line:** ~51
- **Type:** String (number)
- **Description:** UID for claude user
- **Default:** `1000`
### Group ID
- **Variable:** `GROUP_ID`
- **File:** `k8s/statefulset.yaml`
- **Line:** ~53
- **Type:** String (number)
- **Description:** GID for claude user
- **Default:** `1000`
### StatefulSet Replicas
- **Variable:** `replicas`
- **File:** `k8s/statefulset.yaml`
- **Line:** ~21
- **Type:** Integer
- **Description:** Number of container instances
- **Default:** `1`
- **Note:** Each replica gets own home PVC
## Environment Variables (Runtime)
These are set at runtime, not in configuration files:
### GITHUB_REPO
- **Type:** String (URL)
- **Description:** Repository URL (from ConfigMap)
- **Required:** Yes - **Required:** Yes
- **Source:** ConfigMap `antigravity.github-repo` - **Description:** Instance name used to generate resource names (`devcontainer-{name}`, `userhome-{name}`)
- **Example:** `mydev`, `alice-dev`, `team-workspace`
### GITHUB_TOKEN ### githubRepo
- **Type:** String - **Type:** String
- **Description:** GitHub PAT (from Secret) - **Default:** `""`
- **Required:** No (only for private repos) - **Required:** Yes
- **Source:** Secret `antigravity.github-token` - **Description:** GitHub repository URL to clone into `/workspace`
- **Example:** `https://github.com/username/repository`
### VNC_PASSWORD ### ide
- **Type:** String - **Type:** String
- **Description:** VNC password (from Secret) - **Default:** `vscode`
- **Required:** No - **Options:** `vscode`, `antigravity`, `none`
- **Source:** Secret `antigravity.vnc-password` - **Description:** IDE to launch inside the container
- `vscode` — VSCode via VNC browser UI on port 5800
- `antigravity` — Google Antigravity (VSCode fork) via VNC on port 5800
- `none` — No IDE; useful when `ssh: true` is the sole access method
### HAPPY_SERVER_URL ### ssh
- **Type:** String (URL) - **Type:** Boolean
- **Description:** Happy server URL (from ConfigMap) - **Default:** `false`
- **Required:** No - **Description:** Start an OpenSSH server on port 22 in addition to the IDE
- **Source:** ConfigMap `antigravity.happy-server-url` - **Note:** Requires `SSH_AUTHORIZED_KEYS` in env secret for key-based login
### HAPPY_WEBAPP_URL ## Image Configuration
- **Type:** String (URL)
- **Description:** Happy webapp URL (from ConfigMap)
- **Required:** No
- **Source:** ConfigMap `antigravity.happy-webapp-url`
### HAPPY_HOME_DIR ### image.repository
- **Type:** String (path) - **Type:** String
- **Description:** Happy data directory - **Default:** `ghcr.io/cpfarhood/devcontainer`
- **Required:** No - **Description:** Container image repository
- **Default:** `/home/claude/.happy`
- **Source:** Hardcoded in StatefulSet
### HAPPY_EXPERIMENTAL ### image.tag
- **Type:** String (boolean) - **Type:** String
- **Description:** Enable Happy experimental features - **Default:** `latest`
- **Required:** No - **Description:** Container image tag
- **Best Practice:** Use specific version tags for production
### image.pullPolicy
- **Type:** String
- **Default:** `Always`
- **Options:** `Always`, `IfNotPresent`, `Never`
- **Description:** Image pull policy
## 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.width
- **Type:** String
- **Default:** `"1920"`
- **Description:** VNC display width in pixels
### display.height
- **Type:** String
- **Default:** `"1080"`
- **Description:** VNC display height in pixels
### secureConnection
- **Type:** String
- **Default:** `"0"`
- **Options:** `"0"`, `"1"`
- **Description:** Set to `"0"` when TLS is terminated at the gateway layer
## User Configuration
### userId
- **Type:** String
- **Default:** `"1000"`
- **Description:** UID for the app user
### groupId
- **Type:** String
- **Default:** `"1000"`
- **Description:** GID for the app user
## Storage Configuration
### storage.size
- **Type:** String
- **Default:** `32Gi`
- **Description:** Size of the persistent home directory
- **Format:** Kubernetes quantity (e.g., `10Gi`, `100Gi`, `1Ti`)
### storage.className
- **Type:** String
- **Default:** `ceph-filesystem`
- **Description:** StorageClass name (must support ReadWriteMany)
- **Examples:** `ceph-filesystem`, `nfs-client`, `efs-sc`, `azurefile`
### shm.sizeLimit
- **Type:** String
- **Default:** `2Gi`
- **Description:** `/dev/shm` size (memory-backed emptyDir for Electron apps)
## Resource Limits
### resources.requests.memory
- **Type:** String
- **Default:** `2Gi`
- **Description:** Minimum memory to reserve
- **Format:** Kubernetes quantity
### resources.requests.cpu
- **Type:** String
- **Default:** `1000m`
- **Description:** Minimum CPU to reserve
- **Format:** Millicores (`1000m` = 1 CPU core)
### resources.limits.memory
- **Type:** String
- **Default:** `8Gi`
- **Description:** Maximum memory allowed
- **Format:** Kubernetes quantity
### resources.limits.cpu
- **Type:** String
- **Default:** `4000m`
- **Description:** Maximum CPU allowed
- **Format:** Millicores (`4000m` = 4 CPU cores)
## Kubernetes Access
### clusterAccess
- **Type:** String
- **Default:** `none`
- **Options:**
- `none` — No cluster access
- `readonlyns` — Read-only access to release namespace
- `readwritens` — Full access to release namespace
- `readonly` — Read-only access cluster-wide
- `readwrite` — Full access cluster-wide
- **Description:** RBAC permissions for the pod's ServiceAccount
## Secrets
### envSecretName
- **Type:** String
- **Default:** `""` (auto-generates as `devcontainer-{name}-secrets-env`)
- **Description:** Name of existing Secret containing environment variables
- **Keys Recognized:**
- `GITHUB_TOKEN` — PAT for private repo access
- `VNC_PASSWORD` — Password for VNC web UI
- `ANTHROPIC_API_KEY` — API key for Claude
- `SSH_AUTHORIZED_KEYS` — Public keys for SSH access
- `homeassistant-url` — Home Assistant base URL (e.g., http://homeassistant.local:8123)
- `homeassistant-token` — Home Assistant long-lived access token
## MCP Sidecars
### mcpSidecars.kubernetes.enabled
- **Type:** Boolean
- **Default:** `true` - **Default:** `true`
- **Source:** Hardcoded in StatefulSet - **Description:** Enable Kubernetes MCP server sidecar
## Variable Groups by Use Case ### mcpSidecars.kubernetes.image.repository
- **Type:** String
- **Default:** `quay.io/containers/kubernetes_mcp_server`
- **Description:** Kubernetes MCP server image
### Minimal Deployment ### mcpSidecars.kubernetes.image.tag
Only these variables are required for basic deployment: - **Type:** String
1. `storageClassName` - **Default:** `latest`
2. `github-repo` - **Description:** Kubernetes MCP server image tag
3. `parentRefs.name`
4. `parentRefs.namespace`
5. `hostnames`
### Private Repository Deployment ### mcpSidecars.kubernetes.port
Add these for private repos: - **Type:** Integer
1. All minimal deployment variables - **Default:** `8080`
2. `github-token` (sealed secret) - **Description:** Port for Kubernetes MCP server
### Production Deployment ### mcpSidecars.kubernetes.resources
Recommended for production: - **Type:** Object
1. All private repository variables - **Default:**
2. `vnc-password` (sealed secret) ```yaml
3. `resources.requests.*` (adjusted for workload) requests:
4. `resources.limits.*` (adjusted for workload) memory: "64Mi"
5. `namespace` (dedicated namespace) cpu: "50m"
limits:
memory: "256Mi"
cpu: "500m"
```
- **Description:** Resource limits for Kubernetes MCP sidecar
### Multi-User Deployment ### mcpSidecars.flux.enabled
For multiple users: - **Type:** Boolean
1. All production deployment variables - **Default:** `true`
2. `replicas` (set to number of users) - **Description:** Enable Flux MCP server sidecar
3. Larger `storage` size for home PVCs
## Quick Copy Templates ### mcpSidecars.flux.image.repository
- **Type:** String
- **Default:** `ghcr.io/controlplaneio-fluxcd/flux-operator-mcp`
- **Description:** Flux MCP server image
### mcpSidecars.flux.image.tag
- **Type:** String
- **Default:** `v0.41.1`
- **Description:** Flux MCP server image tag
### mcpSidecars.flux.port
- **Type:** Integer
- **Default:** `8081`
- **Description:** Port for Flux MCP server
### mcpSidecars.flux.resources
- **Type:** Object
- **Default:**
```yaml
requests:
memory: "64Mi"
cpu: "50m"
limits:
memory: "256Mi"
cpu: "500m"
```
- **Description:** Resource limits for Flux MCP sidecar
### mcpSidecars.homeassistant.enabled
- **Type:** Boolean
- **Default:** `false`
- **Description:** Enable Home Assistant MCP server sidecar
- **Note:** Requires `homeassistant-url` and `homeassistant-token` in env secret
### mcpSidecars.homeassistant.image.repository
- **Type:** String
- **Default:** `ghcr.io/homeassistant-ai/ha-mcp`
- **Description:** Home Assistant MCP server image
### mcpSidecars.homeassistant.image.tag
- **Type:** String
- **Default:** `stable`
- **Description:** Home Assistant MCP server image tag
- **Options:** `stable` (recommended), `latest` (dev builds), `v{version}` (specific version)
### mcpSidecars.homeassistant.port
- **Type:** Integer
- **Default:** `8087`
- **Description:** Port for Home Assistant MCP server (SSE mode)
### mcpSidecars.homeassistant.resources
- **Type:** Object
- **Default:**
```yaml
requests:
memory: "64Mi"
cpu: "50m"
limits:
memory: "256Mi"
cpu: "500m"
```
- **Description:** Resource limits for Home Assistant MCP sidecar
## Usage Examples
### Minimal Configuration
### Minimal Required Variables
```yaml ```yaml
# k8s/statefulset.yaml name: mydev
storageClassName: "CHANGE_ME" # Line ~117 githubRepo: https://github.com/user/repo
# k8s/configmap.yaml
github-repo: "CHANGE_ME" # Line ~9
# k8s/httproute.yaml
parentRefs:
- name: CHANGE_ME # Line ~8
namespace: CHANGE_ME # Line ~9
hostnames:
- "CHANGE_ME" # Line ~11
``` ```
### With Secrets ### Production Configuration
```bash
kubectl create secret generic antigravity-secrets \
--from-literal=github-token='CHANGE_ME' \
--from-literal=vnc-password='CHANGE_ME' \
--dry-run=client -o yaml | \
kubeseal --format=yaml > k8s/sealedsecrets.yaml
```
### With Resource Adjustments
```yaml ```yaml
# k8s/statefulset.yaml (lines ~98-103) name: prod-workspace
githubRepo: https://github.com/company/application
ide: vscode
ssh: true
image:
tag: v1.0.0
storage:
size: 100Gi
className: ceph-filesystem
resources: resources:
requests: requests:
memory: "CHANGE_ME" # e.g., 4Gi memory: "4Gi"
cpu: "CHANGE_ME" # e.g., 2000m cpu: "2000m"
limits: limits:
memory: "CHANGE_ME" # e.g., 16Gi memory: "16Gi"
cpu: "CHANGE_ME" # e.g., 8000m cpu: "8000m"
clusterAccess: readwritens
mcpSidecars:
kubernetes:
enabled: true
flux:
enabled: false
``` ```
### Development Team Configuration
```yaml
name: team-dev
githubRepo: https://github.com/team/project
ide: antigravity
display:
width: "2560"
height: "1440"
storage:
size: 50Gi
className: nfs-client
clusterAccess: readonly
happyServerUrl: https://happy.internal.company.com
happyWebappUrl: https://happy-app.internal.company.com
```
### Smart Home Development Configuration
```yaml
name: smarthome-dev
githubRepo: https://github.com/user/home-automation
ide: vscode
clusterAccess: readwritens
mcpSidecars:
kubernetes:
enabled: true
flux:
enabled: false
homeassistant:
enabled: true
image:
tag: stable
# Requires secrets:
# homeassistant-url: http://homeassistant.local:8123
# homeassistant-token: <long-lived-access-token>
```
## Helm CLI Examples
### Using --set Flags
```bash
# Basic deployment
helm install mydev ./chart \
--set name=mydev \
--set githubRepo=https://github.com/user/repo
# With multiple values
helm install mydev ./chart \
--set name=mydev \
--set githubRepo=https://github.com/user/repo \
--set ide=antigravity \
--set storage.size=50Gi \
--set clusterAccess=readwritens \
--set mcpSidecars.flux.enabled=false
```
### Using Values File
Create `custom-values.yaml`:
```yaml
name: mydev
githubRepo: https://github.com/user/repo
storage:
size: 50Gi
clusterAccess: readwritens
```
Deploy:
```bash
helm install mydev ./chart -f custom-values.yaml
```
### Combining Methods
```bash
helm install mydev ./chart \
-f base-values.yaml \
-f prod-values.yaml \
--set githubRepo=https://github.com/user/repo \
--set image.tag=v2.0.0
```
## Value Precedence
Values are applied in order of precedence (highest to lowest):
1. `--set` flags on command line
2. `-f` values files (later files override earlier)
3. `chart/values.yaml` defaults
## Environment Variables
These environment variables are set in the container based on chart values:
| Environment Variable | Source Value | Description |
|---------------------|--------------|-------------|
| `GITHUB_REPO` | `githubRepo` | Repository to clone |
| `GITHUB_TOKEN` | Secret: `github-token` | PAT for private repos |
| `VNC_PASSWORD` | Secret: `vnc-password` | VNC access password |
| `ANTHROPIC_API_KEY` | Secret: `anthropic-api-key` | Claude API key |
| `SSH_AUTHORIZED_KEYS` | Secret: `ssh-authorized-keys` | SSH public keys |
| `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_HEIGHT` | `display.height` | VNC height |
| `SECURE_CONNECTION` | `secureConnection` | TLS termination |
| `USER_ID` | `userId` | App user UID |
| `GROUP_ID` | `groupId` | App user GID |
| `IDE` | `ide` | IDE to launch |
| `SSH` | `ssh` | SSH server enabled |
+1 -1
View File
@@ -2,5 +2,5 @@ apiVersion: v2
name: devcontainer name: devcontainer
description: Antigravity Dev Container with Happy Coder AI assistant description: Antigravity Dev Container with Happy Coder AI assistant
type: application type: application
version: 0.1.6 version: 0.2.4
appVersion: "latest" appVersion: "latest"
+175 -1
View File
@@ -20,6 +20,25 @@ spec:
securityContext: securityContext:
fsGroup: 1000 fsGroup: 1000
fsGroupChangePolicy: "OnRootMismatch" fsGroupChangePolicy: "OnRootMismatch"
{{- if and .Values.ide (eq .Values.ide "antigravity") }}
initContainers:
- name: setup-userdata
image: busybox:latest
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 }}"
@@ -68,9 +87,11 @@ spec:
{{- toYaml .Values.resources | nindent 12 }} {{- toYaml .Values.resources | nindent 12 }}
volumeMounts: volumeMounts:
- name: userhome - name: userhome
mountPath: /home mountPath: /config
- name: workspace - name: workspace
mountPath: /workspace mountPath: /workspace
- name: shm
mountPath: /dev/shm
{{- if ne (.Values.ide | default "vscode") "none" }} {{- if ne (.Values.ide | default "vscode") "none" }}
livenessProbe: livenessProbe:
httpGet: httpGet:
@@ -96,9 +117,162 @@ spec:
initialDelaySeconds: 5 initialDelaySeconds: 5
periodSeconds: 5 periodSeconds: 5
{{- end }} {{- end }}
{{- if and .Values.mcpSidecars.kubernetes.enabled (ne .Values.clusterAccess "none") }}
- name: kubernetes-mcp
image: "{{ .Values.mcpSidecars.kubernetes.image.repository }}:{{ .Values.mcpSidecars.kubernetes.image.tag }}"
args:
- --port
- {{ .Values.mcpSidecars.kubernetes.port | quote }}
ports:
- containerPort: {{ .Values.mcpSidecars.kubernetes.port }}
name: k8s-mcp
protocol: TCP
livenessProbe:
httpGet:
path: /healthz
port: {{ .Values.mcpSidecars.kubernetes.port }}
initialDelaySeconds: 10
periodSeconds: 10
readinessProbe:
httpGet:
path: /healthz
port: {{ .Values.mcpSidecars.kubernetes.port }}
initialDelaySeconds: 5
periodSeconds: 5
resources:
{{- toYaml .Values.mcpSidecars.kubernetes.resources | nindent 12 }}
{{- end }}
{{- if and .Values.mcpSidecars.flux.enabled (ne .Values.clusterAccess "none") }}
- name: flux-mcp
image: "{{ .Values.mcpSidecars.flux.image.repository }}:{{ .Values.mcpSidecars.flux.image.tag }}"
args:
- serve
- --transport=sse
- --port={{ .Values.mcpSidecars.flux.port }}
ports:
- containerPort: {{ .Values.mcpSidecars.flux.port }}
name: flux-mcp
protocol: TCP
livenessProbe:
tcpSocket:
port: {{ .Values.mcpSidecars.flux.port }}
initialDelaySeconds: 10
periodSeconds: 10
readinessProbe:
tcpSocket:
port: {{ .Values.mcpSidecars.flux.port }}
initialDelaySeconds: 5
periodSeconds: 5
resources:
{{- toYaml .Values.mcpSidecars.flux.resources | nindent 12 }}
{{- end }}
{{- if .Values.mcpSidecars.homeassistant.enabled }}
- name: homeassistant-mcp
image: "{{ .Values.mcpSidecars.homeassistant.image.repository }}:{{ .Values.mcpSidecars.homeassistant.image.tag }}"
imagePullPolicy: Always
command: ["fastmcp", "run", "--transport", "sse", "--host", "0.0.0.0", "--port", "{{ .Values.mcpSidecars.homeassistant.port }}"]
ports:
- name: homeassistant
containerPort: {{ .Values.mcpSidecars.homeassistant.port }}
env:
- name: HOMEASSISTANT_URL
valueFrom:
secretKeyRef:
name: {{ include "antigravity.envSecretName" . }}
key: homeassistant-url
optional: true
- name: HOMEASSISTANT_TOKEN
valueFrom:
secretKeyRef:
name: {{ include "antigravity.envSecretName" . }}
key: homeassistant-token
optional: true
livenessProbe:
tcpSocket:
port: {{ .Values.mcpSidecars.homeassistant.port }}
initialDelaySeconds: 10
periodSeconds: 10
readinessProbe:
tcpSocket:
port: {{ .Values.mcpSidecars.homeassistant.port }}
initialDelaySeconds: 5
periodSeconds: 5
resources:
{{- toYaml .Values.mcpSidecars.homeassistant.resources | nindent 12 }}
{{- end }}
{{- if .Values.mcpSidecars.github.enabled }}
- name: github-mcp
image: "{{ .Values.mcpSidecars.github.image.repository }}:{{ .Values.mcpSidecars.github.image.tag }}"
imagePullPolicy: Always
args:
- --sse
- --port={{ .Values.mcpSidecars.github.port }}
ports:
- name: github
containerPort: {{ .Values.mcpSidecars.github.port }}
env:
- name: GITHUB_PERSONAL_ACCESS_TOKEN
valueFrom:
secretKeyRef:
name: {{ include "antigravity.envSecretName" . }}
key: github-token
optional: true
livenessProbe:
httpGet:
path: /health
port: {{ .Values.mcpSidecars.github.port }}
initialDelaySeconds: 10
periodSeconds: 10
readinessProbe:
httpGet:
path: /health
port: {{ .Values.mcpSidecars.github.port }}
initialDelaySeconds: 5
periodSeconds: 5
resources:
{{- toYaml .Values.mcpSidecars.github.resources | nindent 12 }}
{{- end }}
{{- if .Values.mcpSidecars.pgtuner.enabled }}
- name: pgtuner-mcp
image: "{{ .Values.mcpSidecars.pgtuner.image.repository }}:{{ .Values.mcpSidecars.pgtuner.image.tag }}"
imagePullPolicy: Always
command: ["python", "-m", "pgtuner_mcp", "--transport", "sse", "--port", "{{ .Values.mcpSidecars.pgtuner.port }}"]
ports:
- name: pgtuner
containerPort: {{ .Values.mcpSidecars.pgtuner.port }}
env:
- name: DATABASE_URI
valueFrom:
secretKeyRef:
name: {{ include "antigravity.envSecretName" . }}
key: database-uri
optional: true
- name: PGTUNER_EXCLUDE_USERIDS
valueFrom:
secretKeyRef:
name: {{ include "antigravity.envSecretName" . }}
key: pgtuner-exclude-userids
optional: true
livenessProbe:
tcpSocket:
port: {{ .Values.mcpSidecars.pgtuner.port }}
initialDelaySeconds: 10
periodSeconds: 10
readinessProbe:
tcpSocket:
port: {{ .Values.mcpSidecars.pgtuner.port }}
initialDelaySeconds: 5
periodSeconds: 5
resources:
{{- toYaml .Values.mcpSidecars.pgtuner.resources | nindent 12 }}
{{- end }}
volumes: volumes:
- name: workspace - name: workspace
emptyDir: {} emptyDir: {}
- name: shm
emptyDir:
medium: Memory
sizeLimit: {{ .Values.shm.sizeLimit }}
- name: userhome - name: userhome
persistentVolumeClaim: persistentVolumeClaim:
claimName: {{ include "antigravity.pvcName" . }} claimName: {{ include "antigravity.pvcName" . }}
+74 -1
View File
@@ -23,7 +23,7 @@ ssh: false
# Happy Coder endpoints # Happy Coder endpoints
happyServerUrl: "https://happy.farh.net" happyServerUrl: "https://happy.farh.net"
happyWebappUrl: "https://happy-coder.farh.net" happyWebappUrl: "https://happy-coder.farh.net"
happyHomeDir: "/home/user/.happy" happyHomeDir: "/config/userdata/.happy"
happyExperimental: "true" happyExperimental: "true"
# VNC display # VNC display
@@ -41,6 +41,11 @@ storage:
size: 32Gi size: 32Gi
className: ceph-filesystem className: ceph-filesystem
# Shared memory size — mounted at /dev/shm as a memory-backed emptyDir.
# Electron apps (Antigravity, Chrome) use /dev/shm for GPU/IPC buffers.
shm:
sizeLimit: 2Gi
resources: resources:
requests: requests:
memory: "2Gi" memory: "2Gi"
@@ -61,3 +66,71 @@ clusterAccess: none
# Name of existing Secret containing env vars (GITHUB_TOKEN, VNC_PASSWORD, etc.) # Name of existing Secret containing env vars (GITHUB_TOKEN, VNC_PASSWORD, etc.)
# Defaults to: devcontainer-{name}-secrets-env # Defaults to: devcontainer-{name}-secrets-env
envSecretName: "" envSecretName: ""
# MCP server sidecars — run alongside the devcontainer to inherit pod RBAC.
mcpSidecars:
kubernetes:
enabled: true
image:
repository: quay.io/containers/kubernetes_mcp_server
tag: v0.0.57 # Pinned version (Jan 27, 2025) with token exchange and field selector support
port: 8080
resources:
requests:
memory: "64Mi"
cpu: "50m"
limits:
memory: "256Mi"
cpu: "500m"
flux:
enabled: true
image:
repository: ghcr.io/controlplaneio-fluxcd/flux-operator-mcp
tag: v0.41.1
port: 8081
resources:
requests:
memory: "64Mi"
cpu: "50m"
limits:
memory: "256Mi"
cpu: "500m"
homeassistant:
enabled: false # Disabled by default, requires HOMEASSISTANT_URL and HOMEASSISTANT_TOKEN
image:
repository: ghcr.io/homeassistant-ai/ha-mcp
tag: 6.7.1 # Pinned version (Feb 20, 2026) - latest stable release
port: 8087
resources:
requests:
memory: "64Mi"
cpu: "50m"
limits:
memory: "256Mi"
cpu: "500m"
github:
enabled: false # DISABLED: GitHub MCP server has been archived, image doesn't exist
image:
repository: ghcr.io/modelcontextprotocol/servers/github
tag: latest # Update to specific version once available
port: 8088
resources:
requests:
memory: "64Mi"
cpu: "50m"
limits:
memory: "256Mi"
cpu: "500m"
pgtuner:
enabled: false # Disabled by default, requires DATABASE_URI in secrets
image:
repository: dog830228/pgtuner_mcp
tag: latest # TODO: pin to specific version once stable release available
port: 8085
resources:
requests:
memory: "64Mi"
cpu: "50m"
limits:
memory: "256Mi"
cpu: "500m"
+17 -7
View File
@@ -2,19 +2,29 @@
## Key Architecture Facts ## Key Architecture Facts
- Image: `ghcr.io/cpfarhood/devcontainer:latest` (repo name is `devcontainer`, not `antigravity`) - Image: `ghcr.io/cpfarhood/devcontainer:latest` (repo name is `devcontainer`, not `antigravity`)
- `imagePullPolicy: Always` in statefulset (set during initial deployment debugging) - Deployed via Helm chart (`chart/`), not kustomize anymore
- Service must NOT be headless (`clusterIP: None`) — Cilium gateway can't route to headless services - Service must NOT be headless (`clusterIP: None`) — Cilium gateway can't route to headless services
- `SECURE_CONNECTION=0` — TLS is terminated at the gateway, not the app - `SECURE_CONNECTION=0` — TLS is terminated at the gateway, not the app
- Container user is `user` (UID 1000) — baseimage-gui runs startapp.sh as `app` user, sudo is not available - Container user is `user` (UID 1000) — baseimage-gui runs startapp.sh as `app` user, sudo is not available
- HTTPRoute is managed by Authentik outpost, not in kustomization
## Cluster Patterns ## Deployment Method
- External gateway: `external` in `gateway-system`, handles `*.farh.net` on port 443 HTTPS only - **Primary**: Helm chart in `chart/` directory
- Hostnames must be exactly `*.farh.net` (not `*.subdomain.farh.net`) to match gateway listener - **Makefile targets**: `helm-deploy`, `helm-delete`, `helm-logs`, `helm-shell`, `helm-port-forward`
- Authentik outpost Terraform lives in `../kubernetes/terraform/authentik-*-proxy/` - **Old kustomize** (`k8s/` directory) has been removed — all deployments use Helm now
- Outpost config uses `external` gateway for public apps, `internal` for internal apps - Chart published as OCI artifact to GHCR, reconciled by Flux
## MCP Sidecars
- **Kubernetes MCP** (v0.0.57, port 8080): Only deployed when enabled AND `clusterAccess` != `none`
- **Flux MCP** (v0.41.1, port 8081): Only deployed when enabled AND `clusterAccess` != `none`
- **Home Assistant MCP** (6.7.1, port 8087): Disabled by default, requires secrets:
- `homeassistant-url`: Base URL like `http://homeassistant.local:8123`
- `homeassistant-token`: Long-lived access token
- **Playwright MCP**: External service, not a sidecar
- Configure via `mcpSidecars.<name>.enabled` in values
- **Version Strategy**: All MCP images use pinned versions for stability (no `latest` tags)
## Common Gotchas ## Common Gotchas
- `baseimage-gui` creates user dynamically — don't hardcode usernames in scripts, use numeric UID/GID - `baseimage-gui` creates user dynamically — don't hardcode usernames in scripts, use numeric UID/GID
- `chown /home` fails (PVC root not owned by container) — only chown subdirectories - `chown /home` fails (PVC root not owned by container) — only chown subdirectories
- `sudo` not available in startapp.sh — script already runs as correct user - `sudo` not available in startapp.sh — script already runs as correct user
- MCP sidecars need appropriate secrets and RBAC permissions to function
+21 -3
View File
@@ -5,12 +5,30 @@
echo "=== SSH enabled: starting sshd ===" echo "=== SSH enabled: starting sshd ==="
# Generate host keys if missing (first boot or ephemeral /etc/ssh) HOME_DIR="/config/userdata"
ssh-keygen -A 2>/dev/null || true HOST_KEY_STORE="$HOME_DIR/.ssh/host_keys"
# Persist host keys on the home PVC so clients don't see a "host key
# changed" warning after pod restarts.
if [ -d "$HOST_KEY_STORE" ] && [ -n "$(ls "$HOST_KEY_STORE"/ssh_host_* 2>/dev/null)" ]; then
# Restore previously generated host keys
echo "Restoring SSH host keys from PVC..."
cp "$HOST_KEY_STORE"/ssh_host_* /etc/ssh/
chmod 600 /etc/ssh/ssh_host_*_key
chmod 644 /etc/ssh/ssh_host_*_key.pub
else
# First boot: generate and save host keys to PVC
echo "Generating SSH host keys (first boot)..."
ssh-keygen -A 2>/dev/null || true
mkdir -p "$HOST_KEY_STORE"
cp /etc/ssh/ssh_host_* "$HOST_KEY_STORE/"
chmod 700 "$HOST_KEY_STORE"
chown -R 1000:1000 "$HOST_KEY_STORE"
echo "SSH host keys saved to PVC."
fi
# Populate authorized_keys from env var (injected via Kubernetes secret) # Populate authorized_keys from env var (injected via Kubernetes secret)
if [ -n "$SSH_AUTHORIZED_KEYS" ]; then if [ -n "$SSH_AUTHORIZED_KEYS" ]; then
HOME_DIR="/home/user"
mkdir -p "$HOME_DIR/.ssh" mkdir -p "$HOME_DIR/.ssh"
chmod 700 "$HOME_DIR/.ssh" chmod 700 "$HOME_DIR/.ssh"
printf '%s\n' "$SSH_AUTHORIZED_KEYS" > "$HOME_DIR/.ssh/authorized_keys" printf '%s\n' "$SSH_AUTHORIZED_KEYS" > "$HOME_DIR/.ssh/authorized_keys"
+1 -1
View File
@@ -3,4 +3,4 @@
# 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.
usermod -s /bin/bash app usermod -s /bin/bash app
usermod -d /home/user app usermod -d /config/userdata app
+56 -13
View File
@@ -4,6 +4,62 @@ set -e
echo "=== Repository Initialization ===" echo "=== Repository Initialization ==="
# Set up basic git configuration
echo "Configuring git user settings..."
# Use environment variables if provided, otherwise use defaults
GIT_USER_NAME="${GIT_USER_NAME:-DevContainer User}"
GIT_USER_EMAIL="${GIT_USER_EMAIL:-devcontainer@example.com}"
git config --global user.name "$GIT_USER_NAME"
git config --global user.email "$GIT_USER_EMAIL"
# Set up git credentials early if GITHUB_TOKEN is provided
# This ensures all git operations have proper authentication
if [ -n "$GITHUB_TOKEN" ]; then
echo "Setting up git credentials..."
# Configure git to use credential store globally
git config --global credential.helper store
# Create or update the credentials file
CREDENTIALS_FILE="/config/userdata/.git-credentials"
# Support multiple git hosting providers
# GitHub supports both oauth2 and token as username
echo "https://oauth2:${GITHUB_TOKEN}@github.com" > "$CREDENTIALS_FILE"
echo "https://${GITHUB_TOKEN}:x-oauth-basic@github.com" >> "$CREDENTIALS_FILE"
echo "https://token:${GITHUB_TOKEN}@github.com" >> "$CREDENTIALS_FILE"
# GitLab format (if same token works)
if [ -n "$GITLAB_HOST" ]; then
echo "https://oauth2:${GITHUB_TOKEN}@${GITLAB_HOST}" >> "$CREDENTIALS_FILE"
fi
chmod 600 "$CREDENTIALS_FILE"
# Also create a symlink in the home directory if it doesn't exist
# This handles cases where git might look in different locations
if [ ! -f "$HOME/.git-credentials" ] && [ "$HOME" != "/config/userdata" ]; then
ln -sf "$CREDENTIALS_FILE" "$HOME/.git-credentials"
fi
echo "Git credentials configured"
else
# Even without a token, ensure git has a proper credential helper configured
# This prevents errors when credentials are added later
echo "No GITHUB_TOKEN provided, configuring basic git settings..."
git config --global credential.helper store
# Create an empty credentials file with proper permissions
CREDENTIALS_FILE="/config/userdata/.git-credentials"
touch "$CREDENTIALS_FILE"
chmod 600 "$CREDENTIALS_FILE"
# Create symlink if needed
if [ ! -f "$HOME/.git-credentials" ] && [ "$HOME" != "/config/userdata" ]; then
ln -sf "$CREDENTIALS_FILE" "$HOME/.git-credentials"
fi
fi
# Check if GITHUB_REPO is set # Check if GITHUB_REPO is set
if [ -z "$GITHUB_REPO" ]; then if [ -z "$GITHUB_REPO" ]; then
echo "GITHUB_REPO not set, skipping repository clone" echo "GITHUB_REPO not set, skipping repository clone"
@@ -21,14 +77,6 @@ else
if [ -d "$WORKSPACE_DIR/.git" ]; then if [ -d "$WORKSPACE_DIR/.git" ]; then
echo "Repository already exists, pulling latest changes..." echo "Repository already exists, pulling latest changes..."
cd "$WORKSPACE_DIR" cd "$WORKSPACE_DIR"
# Configure git to use token if provided
if [ -n "$GITHUB_TOKEN" ]; then
git config credential.helper store
echo "https://oauth2:${GITHUB_TOKEN}@github.com" > /home/.git-credentials
chmod 600 /home/.git-credentials
fi
git pull || echo "Pull failed, continuing anyway..." git pull || echo "Pull failed, continuing anyway..."
else else
echo "Cloning repository..." echo "Cloning repository..."
@@ -39,11 +87,6 @@ else
# Replace https://github.com/ with https://oauth2:token@github.com/ # Replace https://github.com/ with https://oauth2:token@github.com/
CLONE_URL=$(echo "$GITHUB_REPO" | sed "s|https://github.com/|https://oauth2:${GITHUB_TOKEN}@github.com/|") CLONE_URL=$(echo "$GITHUB_REPO" | sed "s|https://github.com/|https://oauth2:${GITHUB_TOKEN}@github.com/|")
git clone "$CLONE_URL" "$WORKSPACE_DIR" git clone "$CLONE_URL" "$WORKSPACE_DIR"
# Configure credentials for future use
git config --global credential.helper store
echo "https://oauth2:${GITHUB_TOKEN}@github.com" > /home/.git-credentials
chmod 600 /home/.git-credentials
else else
git clone "$GITHUB_REPO" "$WORKSPACE_DIR" git clone "$GITHUB_REPO" "$WORKSPACE_DIR"
fi fi
+7 -1
View File
@@ -21,7 +21,13 @@ echo "Workspace: $WORKSPACE_DIR"
case "$IDE" in case "$IDE" in
antigravity) antigravity)
echo "Opening Google Antigravity in: $WORKSPACE_DIR" echo "Opening Google Antigravity in: $WORKSPACE_DIR"
exec antigravity --new-window --wait "$WORKSPACE_DIR" # --no-sandbox is required for Electron apps in Docker (no kernel sandbox available).
# Explicit --user-data-dir and --extensions-dir pin config to the home PVC so
# settings and the setup wizard state survive pod restarts.
exec antigravity --no-sandbox \
--user-data-dir "$HOME/.config/antigravity" \
--extensions-dir "$HOME/.antigravity/extensions" \
--new-window --wait "$WORKSPACE_DIR"
;; ;;
none) none)
echo "IDE=none: no IDE launched, keeping container alive." echo "IDE=none: no IDE launched, keeping container alive."
+46
View File
@@ -0,0 +1,46 @@
#!/bin/bash
# Test script to verify git credentials configuration
set -e
echo "=== Git Credentials Test ==="
# Check git configuration
echo "1. Git user configuration:"
git config --global user.name || echo " ❌ user.name not set"
git config --global user.email || echo " ❌ user.email not set"
echo ""
echo "2. Git credential helper:"
git config --global credential.helper || echo " ❌ credential.helper not set"
echo ""
echo "3. Credentials file locations:"
CREDENTIALS_FILE="/config/userdata/.git-credentials"
if [ -f "$CREDENTIALS_FILE" ]; then
echo "$CREDENTIALS_FILE exists"
echo " Permissions: $(stat -c %a $CREDENTIALS_FILE)"
echo " Lines in file: $(wc -l < $CREDENTIALS_FILE)"
else
echo "$CREDENTIALS_FILE does not exist"
fi
if [ -f "$HOME/.git-credentials" ]; then
if [ -L "$HOME/.git-credentials" ]; then
echo "$HOME/.git-credentials is a symlink to $(readlink -f $HOME/.git-credentials)"
else
echo "$HOME/.git-credentials exists (not a symlink)"
fi
else
echo "$HOME/.git-credentials does not exist"
fi
echo ""
echo "4. Environment check:"
echo " HOME=$HOME"
echo " GITHUB_TOKEN=${GITHUB_TOKEN:+[SET]}"
echo " GIT_USER_NAME=${GIT_USER_NAME:-[NOT SET]}"
echo " GIT_USER_EMAIL=${GIT_USER_EMAIL:-[NOT SET]}"
echo ""
echo "=== Test Complete ==="