Compare commits
50 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| ac1e5074b1 | |||
| 2a63f227f1 | |||
| 5da23def5b | |||
| 5532eee8cd | |||
| d32e453f93 | |||
| f95e8877e8 | |||
| 46267b6e26 | |||
| c4cbd67399 | |||
| a7799dbb16 | |||
| 45b8e5e95e | |||
| a0b409239e | |||
| eacf41302c | |||
| cbdee590bf | |||
| 5570b2c617 | |||
| c3f8421d60 | |||
| 21d8fc73e6 | |||
| 0a63894f6d | |||
| a50a1815e0 | |||
| 131dad8611 | |||
| 581d0737e4 | |||
| 68110d911f | |||
| 427f7a710c | |||
| 745a0cdf59 | |||
| 115907cdc8 | |||
| 0c4f93c077 | |||
| a83d79bc10 | |||
| 2258df4ae3 | |||
| d4b069cbdc | |||
| db7e422b96 | |||
| d5bbf21578 | |||
| 1c3398b178 | |||
| 4e67c48a4c | |||
| df3413f54e | |||
| 6a35f38a8c | |||
| 431b9079ee | |||
| 00d88b16b5 | |||
| c10dd718e1 | |||
| b6bf4b6640 | |||
| c42b47bb56 | |||
| 288c1a4103 | |||
| 2caa8a790f | |||
| 7a6a515b53 | |||
| 4f126a938b | |||
| 4af38a5d2e | |||
| 90350a2090 | |||
| 5b8e6a290b | |||
| e860499757 | |||
| e90a2fe553 | |||
| 897f1409b5 | |||
| 32d4fe4944 |
@@ -0,0 +1,7 @@
|
|||||||
|
{
|
||||||
|
"enabledMcpjsonServers": [
|
||||||
|
"kubernetes",
|
||||||
|
"flux",
|
||||||
|
"playwright"
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -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
|
||||||
@@ -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
|
|
||||||
@@ -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
|
||||||
@@ -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
|
||||||
@@ -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
|
||||||
@@ -1,259 +0,0 @@
|
|||||||
# Release Process
|
|
||||||
|
|
||||||
This document describes how to create releases for this project.
|
|
||||||
|
|
||||||
## Semantic Versioning
|
|
||||||
|
|
||||||
We follow [Semantic Versioning 2.0.0](https://semver.org/):
|
|
||||||
|
|
||||||
- **MAJOR** version (v2.0.0): Incompatible API/breaking changes
|
|
||||||
- **MINOR** version (v1.1.0): New features, backwards compatible
|
|
||||||
- **PATCH** version (v1.0.1): Bug fixes, backwards compatible
|
|
||||||
|
|
||||||
## Creating a Release
|
|
||||||
|
|
||||||
### Method 1: Using GitHub CLI (Recommended)
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Ensure you're on main branch and up to date
|
|
||||||
git checkout main
|
|
||||||
git pull
|
|
||||||
|
|
||||||
# Create and push a tag
|
|
||||||
VERSION="v1.0.0" # Change this
|
|
||||||
git tag -a "$VERSION" -m "Release $VERSION
|
|
||||||
|
|
||||||
## What's New
|
|
||||||
- Feature 1
|
|
||||||
- Feature 2
|
|
||||||
- Bug fix 1
|
|
||||||
|
|
||||||
## Docker Image
|
|
||||||
\`\`\`bash
|
|
||||||
docker pull ghcr.io/cpfarhood/devcontainer:$VERSION
|
|
||||||
\`\`\`
|
|
||||||
"
|
|
||||||
|
|
||||||
git push origin "$VERSION"
|
|
||||||
|
|
||||||
# The GitHub Actions workflow will automatically:
|
|
||||||
# 1. Build the Docker image
|
|
||||||
# 2. Push to ghcr.io with multiple tags
|
|
||||||
# 3. Create a GitHub release with notes
|
|
||||||
```
|
|
||||||
|
|
||||||
### Method 2: Using Git Tags Only
|
|
||||||
|
|
||||||
```bash
|
|
||||||
git checkout main
|
|
||||||
git pull
|
|
||||||
|
|
||||||
# Create annotated tag
|
|
||||||
git tag -a v1.0.0 -m "Release v1.0.0"
|
|
||||||
|
|
||||||
# Push tag
|
|
||||||
git push origin v1.0.0
|
|
||||||
```
|
|
||||||
|
|
||||||
### Method 3: Using GitHub Web UI
|
|
||||||
|
|
||||||
1. Go to https://github.com/cpfarhood/devcontainer/releases
|
|
||||||
2. Click "Draft a new release"
|
|
||||||
3. Click "Choose a tag"
|
|
||||||
4. Type the new version (e.g., `v1.0.0`)
|
|
||||||
5. Click "Create new tag on publish"
|
|
||||||
6. Fill in the release title and description
|
|
||||||
7. Click "Publish release"
|
|
||||||
|
|
||||||
## What Happens Automatically
|
|
||||||
|
|
||||||
When you push a version tag (`v*`), GitHub Actions will:
|
|
||||||
|
|
||||||
1. **Build Docker image** with multiple tags:
|
|
||||||
- `ghcr.io/cpfarhood/devcontainer:v1.2.3` (exact version)
|
|
||||||
- `ghcr.io/cpfarhood/devcontainer:1.2` (minor version)
|
|
||||||
- `ghcr.io/cpfarhood/devcontainer:1` (major version)
|
|
||||||
- `ghcr.io/cpfarhood/devcontainer:latest` (if on default branch)
|
|
||||||
|
|
||||||
2. **Create GitHub Release** with:
|
|
||||||
- Auto-generated release notes from commits
|
|
||||||
- Docker pull command in the description
|
|
||||||
|
|
||||||
## Version Bump Guidelines
|
|
||||||
|
|
||||||
### Patch Release (v1.0.X)
|
|
||||||
- Bug fixes
|
|
||||||
- Documentation updates
|
|
||||||
- Minor dependency updates
|
|
||||||
- No new features
|
|
||||||
- No breaking changes
|
|
||||||
|
|
||||||
**Example:** v1.0.1
|
|
||||||
```bash
|
|
||||||
git tag -a v1.0.1 -m "Release v1.0.1 - Bug fixes"
|
|
||||||
git push origin v1.0.1
|
|
||||||
```
|
|
||||||
|
|
||||||
### Minor Release (v1.X.0)
|
|
||||||
- New features
|
|
||||||
- New optional configuration variables
|
|
||||||
- Enhancements to existing features
|
|
||||||
- Backwards compatible
|
|
||||||
- No breaking changes
|
|
||||||
|
|
||||||
**Example:** v1.1.0
|
|
||||||
```bash
|
|
||||||
git tag -a v1.1.0 -m "Release v1.1.0 - New Happy Coder features"
|
|
||||||
git push origin v1.1.0
|
|
||||||
```
|
|
||||||
|
|
||||||
### Major Release (vX.0.0)
|
|
||||||
- Breaking changes
|
|
||||||
- Required configuration changes
|
|
||||||
- Removal of deprecated features
|
|
||||||
- Incompatible API changes
|
|
||||||
|
|
||||||
**Example:** v2.0.0
|
|
||||||
```bash
|
|
||||||
git tag -a v2.0.0 -m "Release v2.0.0 - Breaking: New storage architecture"
|
|
||||||
git push origin v2.0.0
|
|
||||||
```
|
|
||||||
|
|
||||||
## Pre-releases
|
|
||||||
|
|
||||||
For alpha, beta, or release candidates:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Alpha
|
|
||||||
git tag -a v1.1.0-alpha.1 -m "Release v1.1.0-alpha.1"
|
|
||||||
git push origin v1.1.0-alpha.1
|
|
||||||
|
|
||||||
# Beta
|
|
||||||
git tag -a v1.1.0-beta.1 -m "Release v1.1.0-beta.1"
|
|
||||||
git push origin v1.1.0-beta.1
|
|
||||||
|
|
||||||
# Release Candidate
|
|
||||||
git tag -a v1.1.0-rc.1 -m "Release v1.1.0-rc.1"
|
|
||||||
git push origin v1.1.0-rc.1
|
|
||||||
```
|
|
||||||
|
|
||||||
## Release Checklist
|
|
||||||
|
|
||||||
Before creating a release:
|
|
||||||
|
|
||||||
- [ ] All tests pass
|
|
||||||
- [ ] Documentation is up to date
|
|
||||||
- [ ] CHANGELOG.md is updated (if you maintain one)
|
|
||||||
- [ ] Version number follows semver
|
|
||||||
- [ ] On main/master branch
|
|
||||||
- [ ] All changes are committed
|
|
||||||
- [ ] Tag message includes release notes
|
|
||||||
|
|
||||||
## Docker Image Tags
|
|
||||||
|
|
||||||
Each release creates multiple Docker tags for flexibility:
|
|
||||||
|
|
||||||
| Git Tag | Docker Tags Created |
|
|
||||||
|---------|---------------------|
|
|
||||||
| v1.2.3 | `:v1.2.3`, `:1.2`, `:1`, `:latest` |
|
|
||||||
| v2.0.0 | `:v2.0.0`, `:2.0`, `:2`, `:latest` |
|
|
||||||
| v1.2.4-beta.1 | `:v1.2.4-beta.1`, `:1.2-beta` |
|
|
||||||
|
|
||||||
**Usage examples:**
|
|
||||||
```bash
|
|
||||||
# Specific version (recommended for production)
|
|
||||||
docker pull ghcr.io/cpfarhood/devcontainer:v1.2.3
|
|
||||||
|
|
||||||
# Minor version (gets patches automatically)
|
|
||||||
docker pull ghcr.io/cpfarhood/devcontainer:1.2
|
|
||||||
|
|
||||||
# Major version (gets minor updates and patches)
|
|
||||||
docker pull ghcr.io/cpfarhood/devcontainer:1
|
|
||||||
|
|
||||||
# Latest (always gets newest stable release)
|
|
||||||
docker pull ghcr.io/cpfarhood/devcontainer:latest
|
|
||||||
```
|
|
||||||
|
|
||||||
## Viewing Releases
|
|
||||||
|
|
||||||
- **GitHub Releases:** https://github.com/cpfarhood/devcontainer/releases
|
|
||||||
- **Docker Images:** https://github.com/cpfarhood/devcontainer/pkgs/container/devcontainer
|
|
||||||
- **Git Tags:** `git tag -l`
|
|
||||||
|
|
||||||
## Deleting a Release
|
|
||||||
|
|
||||||
If you need to delete a bad release:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Delete local tag
|
|
||||||
git tag -d v1.0.0
|
|
||||||
|
|
||||||
# Delete remote tag
|
|
||||||
git push origin :refs/tags/v1.0.0
|
|
||||||
|
|
||||||
# Delete GitHub release (use web UI or gh CLI)
|
|
||||||
gh release delete v1.0.0
|
|
||||||
```
|
|
||||||
|
|
||||||
**Note:** Docker images pushed to ghcr.io cannot be easily deleted. It's better to create a new patch version.
|
|
||||||
|
|
||||||
## First Release
|
|
||||||
|
|
||||||
For the initial v1.0.0 release:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
git checkout main
|
|
||||||
git pull
|
|
||||||
|
|
||||||
git tag -a v1.0.0 -m "Release v1.0.0 - Initial Release
|
|
||||||
|
|
||||||
## Features
|
|
||||||
- Antigravity IDE with web-based VNC access
|
|
||||||
- Happy Coder AI assistant integration
|
|
||||||
- Automatic GitHub repository cloning
|
|
||||||
- Persistent home directory with ReadWriteMany PVC
|
|
||||||
- Secure non-root execution (claude user, UID 1000)
|
|
||||||
- Support for private repositories with GitHub token
|
|
||||||
- HTTPRoute (Gateway API) support
|
|
||||||
- Multi-platform Docker images
|
|
||||||
- Comprehensive deployment documentation
|
|
||||||
|
|
||||||
## Docker Image
|
|
||||||
\`\`\`bash
|
|
||||||
docker pull ghcr.io/cpfarhood/devcontainer:v1.0.0
|
|
||||||
\`\`\`
|
|
||||||
|
|
||||||
## Deployment
|
|
||||||
See DEPLOYMENT.md for complete deployment instructions.
|
|
||||||
"
|
|
||||||
|
|
||||||
git push origin v1.0.0
|
|
||||||
```
|
|
||||||
|
|
||||||
## Example Release Workflow
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# 1. Finish your feature/fix on a branch
|
|
||||||
git checkout feature/new-feature
|
|
||||||
git commit -m "feat: Add new feature"
|
|
||||||
git push
|
|
||||||
|
|
||||||
# 2. Create PR and merge to main
|
|
||||||
gh pr create
|
|
||||||
# ... get approval and merge ...
|
|
||||||
|
|
||||||
# 3. Pull latest main
|
|
||||||
git checkout main
|
|
||||||
git pull
|
|
||||||
|
|
||||||
# 4. Create release tag
|
|
||||||
git tag -a v1.1.0 -m "Release v1.1.0 - New feature"
|
|
||||||
git push origin v1.1.0
|
|
||||||
|
|
||||||
# 5. Wait for GitHub Actions
|
|
||||||
# - Check: https://github.com/cpfarhood/devcontainer/actions
|
|
||||||
|
|
||||||
# 6. Verify release
|
|
||||||
# - GitHub: https://github.com/cpfarhood/devcontainer/releases
|
|
||||||
# - Docker: docker pull ghcr.io/cpfarhood/devcontainer:v1.1.0
|
|
||||||
```
|
|
||||||
@@ -1,29 +1,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"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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: 1–4 CPU, 2–8Gi memory
|
- Resource limits: 1–4 CPU, 2–8Gi 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
@@ -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
@@ -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
|
||||||
|
|
||||||
|
|||||||
@@ -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"
|
||||||
|
|||||||
@@ -3,7 +3,8 @@
|
|||||||

|

|
||||||
|
|
||||||
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
@@ -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
@@ -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.5
|
||||||
appVersion: "latest"
|
appVersion: "latest"
|
||||||
|
|||||||
@@ -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
@@ -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: stable
|
||||||
|
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
@@ -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
|
||||||
|
|||||||
@@ -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"
|
||||||
|
|||||||
@@ -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
@@ -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
@@ -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."
|
||||||
|
|||||||
Executable
+46
@@ -0,0 +1,46 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
# Test script to verify git credentials configuration
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
echo "=== Git Credentials Test ==="
|
||||||
|
|
||||||
|
# Check git configuration
|
||||||
|
echo "1. Git user configuration:"
|
||||||
|
git config --global user.name || echo " ❌ user.name not set"
|
||||||
|
git config --global user.email || echo " ❌ user.email not set"
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "2. Git credential helper:"
|
||||||
|
git config --global credential.helper || echo " ❌ credential.helper not set"
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "3. Credentials file locations:"
|
||||||
|
CREDENTIALS_FILE="/config/userdata/.git-credentials"
|
||||||
|
if [ -f "$CREDENTIALS_FILE" ]; then
|
||||||
|
echo " ✓ $CREDENTIALS_FILE exists"
|
||||||
|
echo " Permissions: $(stat -c %a $CREDENTIALS_FILE)"
|
||||||
|
echo " Lines in file: $(wc -l < $CREDENTIALS_FILE)"
|
||||||
|
else
|
||||||
|
echo " ❌ $CREDENTIALS_FILE does not exist"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ -f "$HOME/.git-credentials" ]; then
|
||||||
|
if [ -L "$HOME/.git-credentials" ]; then
|
||||||
|
echo " ✓ $HOME/.git-credentials is a symlink to $(readlink -f $HOME/.git-credentials)"
|
||||||
|
else
|
||||||
|
echo " ✓ $HOME/.git-credentials exists (not a symlink)"
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
echo " ❌ $HOME/.git-credentials does not exist"
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "4. Environment check:"
|
||||||
|
echo " HOME=$HOME"
|
||||||
|
echo " GITHUB_TOKEN=${GITHUB_TOKEN:+[SET]}"
|
||||||
|
echo " GIT_USER_NAME=${GIT_USER_NAME:-[NOT SET]}"
|
||||||
|
echo " GIT_USER_EMAIL=${GIT_USER_EMAIL:-[NOT SET]}"
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "=== Test Complete ==="
|
||||||
Reference in New Issue
Block a user