Compare commits
66 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 2582c1d824 | |||
| 6dd560f2ad | |||
| 19d47da079 | |||
| 12d3444cc5 | |||
| eeb995e1fc | |||
| d26b69c587 | |||
| da40d57e07 | |||
| e99ec65cd9 | |||
| 38e481484e | |||
| cc38a07168 | |||
| 5c3600a424 | |||
| 5565354127 | |||
| b69cd80cae | |||
| 3e46bf5ec1 | |||
| c8a7bbcd6e | |||
| adb2ee4817 | |||
| 3637a0a6fc | |||
| f67066823b | |||
| 50560652cb | |||
| 0fc4ff503b | |||
| 04203e4efb | |||
| b710daac05 | |||
| 52a29da38d | |||
| ea71f71c74 | |||
| f6eceb4d94 | |||
| 84bf7841c3 | |||
| c823a30c2a | |||
| 27af9dc9c4 | |||
| 0944dcec1c | |||
| 60a2689658 | |||
| 53bc4b68a6 | |||
| d526a445fd | |||
| f56b3efb66 | |||
| a778d32b3b | |||
| b48fce97d5 | |||
| 47af7acc5e | |||
| da45415cfe | |||
| 897555b1dc | |||
| df1f4d9b50 | |||
| 2f5a8d65d5 | |||
| 0d8fe1ec64 | |||
| 00638d372c | |||
| 31ec139a8a | |||
| 71c6ca70cc | |||
| b9c30b8e4d | |||
| 794de6d0e5 | |||
| fbcd9c1f72 | |||
| 3be59e56eb | |||
| be9479ef75 | |||
| 065a6534e3 | |||
| d8d83ffa47 | |||
| 9535886945 | |||
| 44f30ec03f | |||
| 76391a8ed0 | |||
| ac1e5074b1 | |||
| 2a63f227f1 | |||
| 5da23def5b | |||
| 5532eee8cd | |||
| d32e453f93 | |||
| f95e8877e8 | |||
| 46267b6e26 | |||
| c4cbd67399 | |||
| a7799dbb16 | |||
| 45b8e5e95e | |||
| a0b409239e | |||
| eacf41302c |
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"enabledPlugins": {
|
||||
"voltagent-dev-exp@voltagent-subagents": true,
|
||||
"voltagent-lang@voltagent-subagents": true
|
||||
}
|
||||
}
|
||||
@@ -2,6 +2,15 @@
|
||||
"enabledMcpjsonServers": [
|
||||
"kubernetes",
|
||||
"flux",
|
||||
"playwright"
|
||||
]
|
||||
"playwright",
|
||||
"github",
|
||||
"pgtuner",
|
||||
"fetch",
|
||||
"sequentialthinking"
|
||||
],
|
||||
"permissions": {
|
||||
"allow": [
|
||||
"Bash(git add .claude/settings.local.json .claude/settings.json && git commit -m \"$\\(cat <<'EOF'\nchore: update Claude Code settings and enable voltagent plugins\n\nAdd fetch and sequentialthinking MCP servers to allowed list, and enable\nvoltagent dev-exp and lang subagent plugins.\n\nCo-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>\nEOF\n\\)\" && git status)"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
+59
-40
@@ -1,74 +1,93 @@
|
||||
# CI/CD Pipeline Guide
|
||||
|
||||
## 🚀 New Simplified Pipeline
|
||||
## 🚀 Simplified Pipeline - Only 3 Workflows!
|
||||
|
||||
### For Releases (Recommended)
|
||||
Use the **Unified Release** workflow from GitHub Actions tab:
|
||||
1. Go to Actions → Unified Release → Run workflow
|
||||
2. Enter version number (e.g., 0.1.25) or choose release type
|
||||
3. Click "Run workflow"
|
||||
### 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:
|
||||
**This single workflow does EVERYTHING:**
|
||||
- ✅ Updates chart version
|
||||
- ✅ Creates git tag
|
||||
- ✅ Builds and pushes Docker image with proper tags
|
||||
- ✅ Publishes Helm chart
|
||||
- ✅ Creates GitHub Release with notes
|
||||
- ✅ **NO MORE `[skip ci]` NONSENSE!**
|
||||
- ✅ Builds Docker image with all proper tags
|
||||
- ✅ Publishes Helm chart to GitHub Pages (`https://cpfarhood.github.io/devcontainer`)
|
||||
- ✅ Creates GitHub Release with changelog
|
||||
|
||||
### For Quick Fixes
|
||||
Use the **Quick Fix Build** workflow when you need to push a fix without ceremony:
|
||||
1. Go to Actions → Quick Fix Build → Run workflow
|
||||
2. Optionally specify a tag (defaults to 'latest')
|
||||
3. Click "Run workflow"
|
||||
### 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"
|
||||
|
||||
This builds and pushes the Docker image immediately without version bumps.
|
||||
**Just builds and pushes Docker image** - no version bumps, no releases.
|
||||
|
||||
### 3️⃣ Automatic CI → **Build and Push**
|
||||
Runs automatically on:
|
||||
- Pushes to `main` (builds and pushes; skipped for release commits via `[skip ci]`)
|
||||
- Pull requests (builds but doesn't push)
|
||||
- Manual trigger available
|
||||
|
||||
## Workflow Files
|
||||
|
||||
| Workflow | Purpose | Trigger | What it does |
|
||||
|----------|---------|---------|--------------|
|
||||
| `release-unified.yaml` | **Main release workflow** | Manual dispatch | Complete release process |
|
||||
| `quick-fix.yaml` | Emergency fixes | Manual dispatch | Just build & push Docker |
|
||||
| `build-and-push.yaml` | CI builds | Tags & PRs | Auto-build on tags/PRs |
|
||||
| `release.yaml` | GitHub releases | Tag push | Create GitHub release |
|
||||
| `helm-publish.yaml` | Helm chart only | Tags | Publish Helm chart |
|
||||
| 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 |
|
||||
|
||||
## Common Tasks
|
||||
## Examples
|
||||
|
||||
### Release a new version
|
||||
```bash
|
||||
# Option 1: Use GitHub UI
|
||||
# Via GitHub UI (Recommended):
|
||||
# Go to Actions → Unified Release → Run workflow
|
||||
|
||||
# Option 2: Use GitHub CLI
|
||||
gh workflow run release-unified.yaml -f version=0.1.25 -f release_type=patch
|
||||
# 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
|
||||
# Use GitHub UI: Actions → Quick Fix Build → Run workflow
|
||||
# Or:
|
||||
# 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 build status
|
||||
### Check workflow status
|
||||
```bash
|
||||
gh run list --workflow=release-unified.yaml
|
||||
# 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.1.25): Bug fixes
|
||||
- **Patch** (0.2.1): Bug fixes
|
||||
|
||||
## Old Pipeline Issues (Now Fixed!)
|
||||
## What We Fixed
|
||||
|
||||
❌ **REMOVED**: Auto-version-bump with `[skip ci]` that prevented Docker builds
|
||||
❌ **REMOVED**: Disconnected workflows requiring manual tag juggling
|
||||
❌ **REMOVED**: Complex multi-step process for releases
|
||||
### Before (Nightmare 😱)
|
||||
- Auto-version-bump with `[skip ci]` prevented Docker builds
|
||||
- 6+ disconnected workflows
|
||||
- Manual tag deletion and re-pushing
|
||||
- Version conflicts everywhere
|
||||
|
||||
✅ **NEW**: Single unified workflow that does everything
|
||||
✅ **NEW**: Manual control over versions
|
||||
✅ **NEW**: Quick fix workflow for emergencies
|
||||
### After (Simple! 🎉)
|
||||
- **3 total workflows** (down from 6+)
|
||||
- **1 button** for complete releases
|
||||
- Release builds its own Docker image — `[skip ci]` on the version commit prevents duplicate CI builds
|
||||
- **Clear separation** of concerns
|
||||
@@ -4,8 +4,7 @@ on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
tags:
|
||||
- 'v*'
|
||||
- 'feature/serverless-*' # Build development images for serverless features
|
||||
pull_request:
|
||||
branches:
|
||||
- main
|
||||
@@ -46,11 +45,10 @@ jobs:
|
||||
tags: |
|
||||
type=ref,event=branch
|
||||
type=ref,event=pr
|
||||
type=semver,pattern={{version}}
|
||||
type=semver,pattern={{major}}.{{minor}}
|
||||
type=semver,pattern={{major}}
|
||||
type=sha,prefix=sha-
|
||||
type=raw,value=latest,enable={{is_default_branch}}
|
||||
# Development tags for serverless features
|
||||
type=raw,value=2.0.0-dev,enable=${{ github.ref == 'refs/heads/feature/serverless-2.0.0' }}
|
||||
|
||||
- name: Build and push Docker image
|
||||
uses: docker/build-push-action@v6
|
||||
@@ -59,6 +57,53 @@ jobs:
|
||||
push: ${{ github.event_name != 'pull_request' }}
|
||||
tags: ${{ steps.meta.outputs.tags }}
|
||||
labels: ${{ steps.meta.outputs.labels }}
|
||||
build-args: |
|
||||
TOOLS_CACHEBUST=${{ github.run_id }}
|
||||
cache-from: type=gha
|
||||
cache-to: type=gha,mode=max
|
||||
platforms: linux/amd64
|
||||
|
||||
# Build routing proxy image for serverless features
|
||||
build-routing-proxy:
|
||||
runs-on: ubuntu-latest
|
||||
# Only build routing proxy for serverless feature branches
|
||||
if: github.ref == 'refs/heads/feature/serverless-2.0.0' && github.event_name != 'pull_request'
|
||||
permissions:
|
||||
contents: read
|
||||
packages: write
|
||||
id-token: write
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
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: Extract metadata for routing proxy
|
||||
id: meta-proxy
|
||||
uses: docker/metadata-action@v5
|
||||
with:
|
||||
images: ${{ env.REGISTRY }}/cpfarhood/devcontainer-routing-proxy
|
||||
tags: |
|
||||
type=raw,value=latest
|
||||
type=raw,value=2.0.0-dev
|
||||
type=sha,prefix=sha-
|
||||
|
||||
- name: Build and push routing proxy image
|
||||
uses: docker/build-push-action@v6
|
||||
with:
|
||||
context: ./serverless/routing-proxy
|
||||
push: true
|
||||
tags: ${{ steps.meta-proxy.outputs.tags }}
|
||||
labels: ${{ steps.meta-proxy.outputs.labels }}
|
||||
cache-from: type=gha
|
||||
cache-to: type=gha,mode=max
|
||||
platforms: linux/amd64
|
||||
|
||||
@@ -1,38 +0,0 @@
|
||||
name: Publish Helm Chart
|
||||
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- 'v*'
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
packages: write
|
||||
|
||||
jobs:
|
||||
publish:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: Set up Helm
|
||||
uses: azure/setup-helm@v4
|
||||
|
||||
- name: Get Chart Version
|
||||
id: version
|
||||
run: |
|
||||
VERSION=$(grep '^version:' chart/Chart.yaml | awk '{print $2}')
|
||||
echo "version=${VERSION}" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Log in to GHCR
|
||||
run: |
|
||||
helm registry login ghcr.io \
|
||||
--username ${{ github.actor }} \
|
||||
--password ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Package and Push Chart
|
||||
run: |
|
||||
helm package chart/
|
||||
helm push devcontainer-${{ steps.version.outputs.version }}.tgz oci://ghcr.io/cpfarhood/charts
|
||||
echo "✅ Helm chart published: devcontainer-${{ steps.version.outputs.version }}"
|
||||
@@ -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
|
||||
@@ -4,11 +4,11 @@ on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
version:
|
||||
description: 'Version to release (e.g., 0.1.25)'
|
||||
required: true
|
||||
description: 'Explicit version (e.g., 1.2.3). Leave blank to auto-increment.'
|
||||
required: false
|
||||
type: string
|
||||
release_type:
|
||||
description: 'Release type'
|
||||
description: 'Release type (used when version is blank)'
|
||||
required: true
|
||||
default: 'patch'
|
||||
type: choice
|
||||
@@ -49,37 +49,34 @@ jobs:
|
||||
- name: Determine Version
|
||||
id: version
|
||||
run: |
|
||||
if [ "${{ github.event.inputs.version }}" != "" ]; then
|
||||
VERSION="${{ github.event.inputs.version }}"
|
||||
INPUT_VERSION="${{ github.event.inputs.version }}"
|
||||
if [ -n "$INPUT_VERSION" ]; then
|
||||
VERSION="$INPUT_VERSION"
|
||||
else
|
||||
# Auto-determine next version based on release type
|
||||
# Auto-increment 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)
|
||||
# Strip any pre-release suffix (e.g., 2.0.0-dev -> 2.0.0)
|
||||
CURRENT=$(echo "$CURRENT" | sed 's/-.*//')
|
||||
MAJOR=$(echo "$CURRENT" | cut -d. -f1)
|
||||
MINOR=$(echo "$CURRENT" | cut -d. -f2)
|
||||
PATCH=$(echo "$CURRENT" | cut -d. -f3)
|
||||
|
||||
case "${{ github.event.inputs.release_type }}" in
|
||||
major)
|
||||
VERSION="$((MAJOR + 1)).0.0"
|
||||
;;
|
||||
minor)
|
||||
VERSION="${MAJOR}.$((MINOR + 1)).0"
|
||||
;;
|
||||
patch)
|
||||
VERSION="${MAJOR}.${MINOR}.$((PATCH + 1))"
|
||||
;;
|
||||
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}"
|
||||
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 }}"
|
||||
git diff --quiet --staged || git commit -m "chore(release): ${{ steps.version.outputs.version }} [skip ci]"
|
||||
|
||||
- name: Create and Push Tag
|
||||
run: |
|
||||
@@ -103,31 +100,75 @@ jobs:
|
||||
${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.version.outputs.tag }}
|
||||
${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.version.outputs.version }}
|
||||
${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest
|
||||
build-args: |
|
||||
TOOLS_CACHEBUST=${{ github.run_id }}
|
||||
cache-from: type=gha
|
||||
cache-to: type=gha,mode=max
|
||||
platforms: linux/amd64
|
||||
|
||||
- name: Package Helm Chart
|
||||
- name: Publish Helm Chart to GitHub Pages
|
||||
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
|
||||
CHART_TGZ="devcontainer-${{ steps.version.outputs.version }}.tgz"
|
||||
|
||||
- name: Generate Release Notes
|
||||
id: notes
|
||||
# Set up gh-pages in a temporary directory
|
||||
PAGES_DIR=$(mktemp -d)
|
||||
if git ls-remote --heads origin gh-pages | grep -q gh-pages; then
|
||||
# gh-pages exists — shallow clone just that branch
|
||||
git clone --single-branch --branch gh-pages \
|
||||
"https://x-access-token:${{ secrets.GITHUB_TOKEN }}@github.com/${{ github.repository }}.git" \
|
||||
"$PAGES_DIR"
|
||||
else
|
||||
# First time — initialize gh-pages
|
||||
git init "$PAGES_DIR"
|
||||
git -C "$PAGES_DIR" checkout --orphan gh-pages
|
||||
git -C "$PAGES_DIR" remote add origin \
|
||||
"https://x-access-token:${{ secrets.GITHUB_TOKEN }}@github.com/${{ github.repository }}.git"
|
||||
cat > "$PAGES_DIR/index.html" <<'HTMLEOF'
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head><title>Dev Container Helm Chart Repository</title></head>
|
||||
<body>
|
||||
<h1>Dev Container Helm Chart Repository</h1>
|
||||
<p>Add this repository to Helm:</p>
|
||||
<pre>helm repo add devcontainer https://cpfarhood.github.io/devcontainer</pre>
|
||||
<p>Install the chart:</p>
|
||||
<pre>helm install mydev devcontainer/devcontainer --set name=mydev</pre>
|
||||
</body>
|
||||
</html>
|
||||
HTMLEOF
|
||||
fi
|
||||
|
||||
git -C "$PAGES_DIR" config user.name "github-actions[bot]"
|
||||
git -C "$PAGES_DIR" config user.email "github-actions[bot]@users.noreply.github.com"
|
||||
|
||||
# Copy chart package and rebuild index
|
||||
cp "$CHART_TGZ" "$PAGES_DIR/"
|
||||
if [ -f "$PAGES_DIR/index.yaml" ]; then
|
||||
helm repo index "$PAGES_DIR" --url https://cpfarhood.github.io/devcontainer --merge "$PAGES_DIR/index.yaml"
|
||||
else
|
||||
helm repo index "$PAGES_DIR" --url https://cpfarhood.github.io/devcontainer
|
||||
fi
|
||||
|
||||
# Commit and push
|
||||
git -C "$PAGES_DIR" add .
|
||||
git -C "$PAGES_DIR" commit -m "Publish chart ${{ steps.version.outputs.version }}"
|
||||
git -C "$PAGES_DIR" push origin gh-pages
|
||||
|
||||
- name: Create GitHub Release
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
run: |
|
||||
# Get commits since last tag
|
||||
# Build release notes
|
||||
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)
|
||||
COMMITS=$(git log --pretty=format:"- %s (%h)" "${PREV_TAG}..HEAD")
|
||||
fi
|
||||
|
||||
cat << EOF > release-notes.md
|
||||
## 🚀 Release ${{ steps.version.outputs.version }}
|
||||
cat > release-notes.md <<EOF
|
||||
## Release ${{ steps.version.outputs.version }}
|
||||
|
||||
### Changes
|
||||
${COMMITS}
|
||||
@@ -139,21 +180,12 @@ jobs:
|
||||
|
||||
### Helm Chart
|
||||
\`\`\`bash
|
||||
helm install devcontainer oci://ghcr.io/cpfarhood/charts/devcontainer --version ${{ steps.version.outputs.version }}
|
||||
helm repo add devcontainer https://cpfarhood.github.io/devcontainer
|
||||
helm repo update
|
||||
helm install mydev devcontainer/devcontainer --version ${{ steps.version.outputs.version }} --set name=mydev
|
||||
\`\`\`
|
||||
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
|
||||
gh release create "${{ steps.version.outputs.tag }}" \
|
||||
--title "Release ${{ steps.version.outputs.tag }}" \
|
||||
--notes-file release-notes.md
|
||||
|
||||
@@ -1,51 +0,0 @@
|
||||
name: Release
|
||||
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- 'v*'
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
packages: write
|
||||
|
||||
jobs:
|
||||
release:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Generate Release Notes
|
||||
id: notes
|
||||
run: |
|
||||
# Get the tag message or generate from commits
|
||||
TAG_MESSAGE=$(git tag -l --format='%(contents)' ${{ github.ref_name }})
|
||||
if [ -z "$TAG_MESSAGE" ]; then
|
||||
# Generate from commit messages since last tag
|
||||
PREV_TAG=$(git describe --tags --abbrev=0 ${{ github.ref_name }}^ 2>/dev/null || echo "")
|
||||
if [ -z "$PREV_TAG" ]; then
|
||||
COMMITS=$(git log --pretty=format:"- %s (%h)" ${{ github.ref_name }})
|
||||
else
|
||||
COMMITS=$(git log --pretty=format:"- %s (%h)" ${PREV_TAG}..${{ github.ref_name }})
|
||||
fi
|
||||
NOTES="## Changes\n\n${COMMITS}\n\n## Docker Image\n\n\`\`\`bash\ndocker pull ghcr.io/${{ github.repository }}:${{ github.ref_name }}\n\`\`\`"
|
||||
else
|
||||
NOTES="${TAG_MESSAGE}\n\n## Docker Image\n\n\`\`\`bash\ndocker pull ghcr.io/${{ github.repository }}:${{ github.ref_name }}\n\`\`\`"
|
||||
fi
|
||||
echo "notes<<EOF" >> $GITHUB_OUTPUT
|
||||
echo -e "$NOTES" >> $GITHUB_OUTPUT
|
||||
echo "EOF" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Create Release
|
||||
uses: actions/create-release@v1
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
tag_name: ${{ github.ref_name }}
|
||||
release_name: Release ${{ github.ref_name }}
|
||||
body: ${{ steps.notes.outputs.notes }}
|
||||
draft: false
|
||||
prerelease: false
|
||||
@@ -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,5 +1,12 @@
|
||||
{
|
||||
"mcpServers": {
|
||||
"github": {
|
||||
"type": "http",
|
||||
"url": "https://api.githubcopilot.com/mcp/",
|
||||
"headers": {
|
||||
"Authorization": "Bearer ${GITHUB_TOKEN}"
|
||||
}
|
||||
},
|
||||
"kubernetes": {
|
||||
"type": "sse",
|
||||
"url": "http://localhost:8080/sse"
|
||||
@@ -8,14 +15,14 @@
|
||||
"type": "sse",
|
||||
"url": "http://localhost:8081/sse"
|
||||
},
|
||||
"homeassistant": {
|
||||
"type": "sse",
|
||||
"url": "http://localhost:8087/sse"
|
||||
},
|
||||
"playwright": {
|
||||
"type": "sse",
|
||||
"url": "http://playwright-mcp.playwright.svc.cluster.local:3000/sse"
|
||||
}
|
||||
"url": "http://localhost:8086/sse"
|
||||
},
|
||||
"pgtuner": {
|
||||
"type": "sse",
|
||||
"url": "http://localhost:8085/sse"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -4,11 +4,13 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co
|
||||
|
||||
## Project Overview
|
||||
|
||||
Antigravity is a Docker-based cloud development environment that provides:
|
||||
The Dev Container is a Docker-based cloud development environment that provides:
|
||||
- Web-based GUI IDE (VSCode/Antigravity) via VNC on port 5800
|
||||
- Happy Coder AI assistant integration
|
||||
- Claude Code, Happy Coder, OpenCode, and Crush AI coding agents (terminal-based)
|
||||
- Built-in web file manager for uploading/downloading files (optional, via `fileManager.enabled`)
|
||||
- Automatic GitHub repository cloning on startup
|
||||
- Kubernetes-native deployment with persistent home storage
|
||||
- MCP (Model Context Protocol) sidecars for AI assistant integrations
|
||||
|
||||
The stack is primarily **Bash scripts + YAML** — there is no Node.js package, compiled language, or test framework.
|
||||
|
||||
@@ -19,7 +21,7 @@ The stack is primarily **Bash scripts + YAML** — there is no Node.js package,
|
||||
```bash
|
||||
make build # Build Docker image
|
||||
make build REGISTRY=ghcr.io/myuser IMAGE_TAG=v1.0 # Custom registry/tag
|
||||
docker build -t ghcr.io/cpfarhood/antigravity:latest . # Direct build
|
||||
docker build -t ghcr.io/cpfarhood/devcontainer:latest . # Direct build
|
||||
```
|
||||
|
||||
### Running Locally
|
||||
@@ -60,7 +62,6 @@ Container start
|
||||
→ scripts/init-repo.sh
|
||||
→ Configure git user & credentials
|
||||
→ Clone GITHUB_REPO (if set)
|
||||
→ Start Happy Coder
|
||||
→ Launch VSCode as user `user` in /workspace
|
||||
```
|
||||
|
||||
@@ -68,8 +69,8 @@ Container start
|
||||
|
||||
| File | Purpose |
|
||||
|------|---------|
|
||||
| `Dockerfile` | Image definition — installs Chrome, Node.js, VSCode, Happy Coder; creates non-root user (UID 1000) |
|
||||
| `scripts/init-repo.sh` | Configures git credentials, clones GitHub repo, starts Happy Coder background service |
|
||||
| `Dockerfile` | Image definition — installs Chrome, Node.js, VSCode, Helm, Claude Code, Happy Coder, OpenCode, Crush; creates non-root user (UID 1000) |
|
||||
| `scripts/init-repo.sh` | Configures git credentials, clones GitHub repo |
|
||||
| `scripts/startapp.sh` | Calls init-repo.sh then opens VSCode in the workspace |
|
||||
| `chart/` | Helm chart for Kubernetes deployment |
|
||||
| `chart/templates/deployment.yaml` | Deployment spec — main container + MCP sidecar containers |
|
||||
@@ -77,7 +78,7 @@ Container start
|
||||
| `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, Playwright) |
|
||||
| `.mcp.json` | MCP server connection config (GitHub Copilot, Kubernetes, Flux, Fetch, Sequential Thinking, Playwright, pgtuner) |
|
||||
| `Makefile` | Build/deploy automation |
|
||||
|
||||
### MCP Sidecars
|
||||
@@ -88,13 +89,21 @@ MCP (Model Context Protocol) servers run as sidecar containers in the pod, enabl
|
||||
|---------|-------|---------|------|----------|---------|
|
||||
| `kubernetes-mcp` | `quay.io/containers/kubernetes_mcp_server` | v0.0.57 | 8080 | `http://localhost:8080/sse` | Enabled |
|
||||
| `flux-mcp` | `ghcr.io/controlplaneio-fluxcd/flux-operator-mcp` | v0.41.1 | 8081 | `http://localhost:8081/sse` | Enabled |
|
||||
| `homeassistant-mcp` | `ghcr.io/homeassistant-ai/ha-mcp` | 6.7.1 | 8087 | `http://localhost:8087/sse` | Disabled |
|
||||
| `fetch-mcp` | `mcp/fetch` | latest | 8082 | `http://localhost:8082/sse` | Enabled |
|
||||
| `sequentialthinking-mcp` | `mcp/sequentialthinking` | latest | 8083 | `http://localhost:8083/sse` | Enabled |
|
||||
| `homeassistant-mcp` | `ghcr.io/homeassistant-ai/ha-mcp` | stable | 8087 | `http://localhost:8087/sse` | Disabled |
|
||||
| `pgtuner-mcp` | `dog830228/pgtuner_mcp` | latest | 8085 | `http://localhost:8085/sse` | Disabled |
|
||||
| `playwright-mcp` | `mcr.microsoft.com/playwright/mcp` | latest | 8086 | `http://localhost:8086/sse` | Enabled |
|
||||
|
||||
**Note:**
|
||||
- GitHub MCP is accessed via the Copilot API (`https://api.githubcopilot.com/mcp/`), not as a sidecar
|
||||
- Kubernetes and Flux sidecars require `clusterAccess` != `none` to be deployed (they need RBAC permissions)
|
||||
- Kubernetes and Flux sidecars inherit the pod's ServiceAccount RBAC permissions
|
||||
- Fetch sidecar provides web content fetching capabilities and HTML to markdown conversion
|
||||
- Sequential thinking sidecar enables structured thinking and problem-solving processes
|
||||
- Home Assistant sidecar requires `HOMEASSISTANT_URL` and `HOMEASSISTANT_TOKEN` in the env secret
|
||||
- Playwright MCP remains an external service
|
||||
- PostgreSQL tuner sidecar requires `DATABASE_URI` in the env secret (PostgreSQL connection string)
|
||||
- Playwright sidecar provides browser automation and web testing capabilities
|
||||
|
||||
#### Enabling/Disabling MCP Servers
|
||||
|
||||
@@ -102,30 +111,56 @@ To control MCP sidecars, set the `enabled` flag in your values override:
|
||||
|
||||
```yaml
|
||||
# Disable all MCP sidecars
|
||||
mcpSidecars:
|
||||
kubernetes:
|
||||
enabled: false
|
||||
flux:
|
||||
enabled: false
|
||||
homeassistant:
|
||||
enabled: false
|
||||
mcp:
|
||||
sidecars:
|
||||
kubernetes:
|
||||
enabled: false
|
||||
flux:
|
||||
enabled: false
|
||||
fetch:
|
||||
enabled: false
|
||||
sequentialthinking:
|
||||
enabled: false
|
||||
homeassistant:
|
||||
enabled: false
|
||||
pgtuner:
|
||||
enabled: false
|
||||
playwright:
|
||||
enabled: false
|
||||
|
||||
# Or selectively enable/disable
|
||||
mcpSidecars:
|
||||
kubernetes:
|
||||
enabled: true # Keep Kubernetes MCP enabled
|
||||
flux:
|
||||
enabled: false # Disable Flux MCP
|
||||
homeassistant:
|
||||
enabled: true # Enable Home Assistant MCP (requires secrets)
|
||||
mcp:
|
||||
sidecars:
|
||||
kubernetes:
|
||||
enabled: true # Keep Kubernetes MCP enabled
|
||||
flux:
|
||||
enabled: false # Disable Flux MCP
|
||||
fetch:
|
||||
enabled: true # Enable Fetch MCP for web content fetching
|
||||
sequentialthinking:
|
||||
enabled: true # Enable Sequential Thinking MCP for problem-solving
|
||||
homeassistant:
|
||||
enabled: true # Enable Home Assistant MCP (requires secrets)
|
||||
pgtuner:
|
||||
enabled: true # Enable PostgreSQL tuner MCP (requires DATABASE_URI)
|
||||
playwright:
|
||||
enabled: true # Enable Playwright MCP for browser automation
|
||||
```
|
||||
|
||||
When deploying via Helm:
|
||||
```bash
|
||||
# Using --set flag
|
||||
helm install my-devcontainer ./chart --set mcpSidecars.kubernetes.enabled=false --set mcpSidecars.flux.enabled=false
|
||||
# Quick start (recommended)
|
||||
cp chart/values-quickstart.yaml my-values.yaml
|
||||
# Edit name and githubRepo in my-values.yaml
|
||||
helm install my-devcontainer ./chart -f my-values.yaml
|
||||
|
||||
# Or with a values file
|
||||
# Using --set flags
|
||||
helm install my-devcontainer ./chart \
|
||||
--set name=mydev \
|
||||
--set githubRepo=https://github.com/user/repo \
|
||||
--set mcp.sidecars.kubernetes.enabled=false
|
||||
|
||||
# Full customization
|
||||
helm install my-devcontainer ./chart -f custom-values.yaml
|
||||
```
|
||||
|
||||
@@ -149,18 +184,22 @@ helm install my-devcontainer ./chart -f custom-values.yaml
|
||||
- `USER_ID` / `GROUP_ID` — Override UID/GID (default 1000)
|
||||
- `HAPPY_SERVER_URL` / `HAPPY_WEBAPP_URL` — Custom Happy Coder endpoints
|
||||
- `HAPPY_HOME_DIR` / `HAPPY_EXPERIMENTAL`
|
||||
- `WEB_FILE_MANAGER` — Set to `1` to enable the built-in web file manager (controlled via `fileManager.enabled` in Helm values)
|
||||
- `WEB_FILE_MANAGER_ALLOWED_PATHS` — Paths accessible by the file manager (default: `/workspace,/config`)
|
||||
- `WEB_FILE_MANAGER_DENIED_PATHS` — Paths to deny access to (takes precedence over allowed)
|
||||
|
||||
### CI/CD
|
||||
|
||||
- **`build-and-push.yaml`** — Builds and pushes to GHCR on every push to `main`, version tags (`v*`), and PRs. Tags: `latest` (main), semver, branch name, commit SHA.
|
||||
- **`release.yaml`** — Creates a GitHub Release with docker pull instructions when a version tag is pushed.
|
||||
- **`release-unified.yaml`** — Manual release workflow: bumps chart version, builds Docker image, publishes Helm chart to GitHub Pages (`https://cpfarhood.github.io/devcontainer`), and creates GitHub Release.
|
||||
- **`dependabot.yml`** — Weekly updates for GitHub Actions and Docker base image.
|
||||
|
||||
Image registry: `ghcr.io/cpfarhood/devcontainer`
|
||||
Helm repo: `https://cpfarhood.github.io/devcontainer`
|
||||
|
||||
## Kubernetes Notes
|
||||
|
||||
- Deployed via Helm chart (`chart/`), published as OCI artifact to GHCR, reconciled by Flux
|
||||
- Deployed via Helm chart (`chart/`), published to GitHub Pages Helm repo, reconciled by Flux
|
||||
- Storage class is `ceph-filesystem` by default — change via `storage.className` in values
|
||||
- Resource limits: 1–4 CPU, 2–8Gi memory
|
||||
- Health checks (liveness/readiness probes) on port 5800
|
||||
|
||||
+57
-6
@@ -1,7 +1,7 @@
|
||||
FROM jlesage/baseimage-gui:ubuntu-22.04-v4
|
||||
|
||||
# Set environment variables
|
||||
ENV APP_NAME="Antigravity Dev Container" \
|
||||
ENV APP_NAME="Dev Container" \
|
||||
KEEP_APP_RUNNING=1 \
|
||||
DISPLAY_WIDTH=1920 \
|
||||
DISPLAY_HEIGHT=1080 \
|
||||
@@ -56,13 +56,45 @@ exec /usr/bin/google-chrome-stable \\\n\
|
||||
"$@"\n' > /usr/local/bin/google-chrome && \
|
||||
chmod +x /usr/local/bin/google-chrome
|
||||
|
||||
# Install Node.js (LTS version for Happy Coder)
|
||||
# Install Node.js LTS (required by Happy Coder)
|
||||
RUN curl -fsSL https://deb.nodesource.com/setup_lts.x | bash - && \
|
||||
apt-get install -y nodejs && \
|
||||
rm -rf /var/lib/apt/lists/*
|
||||
|
||||
# Install Happy Coder and Claude Code globally
|
||||
RUN npm install -g happy-coder @anthropic-ai/claude-code
|
||||
# Install Happy Coder globally via npm
|
||||
RUN npm install -g happy-coder
|
||||
|
||||
# Cache-bust: tools below fetch "latest" at build time — a changing ARG
|
||||
# forces Docker to re-run these layers instead of serving stale cache.
|
||||
ARG TOOLS_CACHEBUST=0
|
||||
|
||||
# Install Claude Code via native installer (no Node.js dependency)
|
||||
RUN CLAUDE_VERSION=$(curl -fsSL https://storage.googleapis.com/claude-code-dist-86c565f3-f756-42ad-8dfa-d59b1c096819/claude-code-releases/latest) && \
|
||||
echo "Installing Claude Code ${CLAUDE_VERSION}" && \
|
||||
curl -fsSL "https://storage.googleapis.com/claude-code-dist-86c565f3-f756-42ad-8dfa-d59b1c096819/claude-code-releases/${CLAUDE_VERSION}/linux-x64/claude" \
|
||||
-o /usr/local/bin/claude && \
|
||||
chmod +x /usr/local/bin/claude && \
|
||||
claude --version
|
||||
|
||||
# Install OpenCode AI coding agent
|
||||
RUN OPENCODE_VERSION=$(curl -sL https://api.github.com/repos/opencode-ai/opencode/releases/latest | jq -r '.tag_name') && \
|
||||
curl -fsSL "https://github.com/opencode-ai/opencode/releases/download/${OPENCODE_VERSION}/opencode-linux-x86_64.tar.gz" | \
|
||||
tar -xz -C /usr/local/bin opencode && \
|
||||
chmod +x /usr/local/bin/opencode
|
||||
|
||||
# Install Crush AI coding agent (OpenCode successor by Charm)
|
||||
RUN CRUSH_VERSION=$(curl -sL https://api.github.com/repos/charmbracelet/crush/releases/latest | jq -r '.tag_name' | sed 's/^v//') && \
|
||||
curl -fsSL "https://github.com/charmbracelet/crush/releases/download/v${CRUSH_VERSION}/crush_${CRUSH_VERSION}_Linux_x86_64.tar.gz" -o /tmp/crush.tar.gz && \
|
||||
tar -xzf /tmp/crush.tar.gz -C /tmp && \
|
||||
mv /tmp/crush_${CRUSH_VERSION}_Linux_x86_64/crush /usr/local/bin/crush && \
|
||||
chmod +x /usr/local/bin/crush && \
|
||||
rm -rf /tmp/crush*
|
||||
|
||||
# Install Helm CLI for Kubernetes chart management
|
||||
ARG HELM_VERSION=3.17.1
|
||||
RUN curl -fsSL "https://get.helm.sh/helm-v${HELM_VERSION}-linux-amd64.tar.gz" | \
|
||||
tar -xz --strip-components=1 -C /usr/local/bin linux-amd64/helm && \
|
||||
chmod +x /usr/local/bin/helm
|
||||
|
||||
# Install VSCode
|
||||
RUN wget -qO- https://packages.microsoft.com/keys/microsoft.asc | gpg --dearmor -o /usr/share/keyrings/packages.microsoft.gpg && \
|
||||
@@ -77,10 +109,26 @@ RUN mkdir -p /etc/apt/keyrings && \
|
||||
gpg --dearmor --yes -o /etc/apt/keyrings/antigravity-repo-key.gpg && \
|
||||
echo "deb [signed-by=/etc/apt/keyrings/antigravity-repo-key.gpg] https://us-central1-apt.pkg.dev/projects/antigravity-auto-updater-dev/ antigravity-debian main" \
|
||||
> /etc/apt/sources.list.d/antigravity.list && \
|
||||
# Clear package cache to force fresh repository data
|
||||
rm -rf /var/lib/apt/lists/* && \
|
||||
apt-get update && \
|
||||
apt-get install -y antigravity && \
|
||||
# Show available versions for debugging
|
||||
apt-cache policy antigravity && \
|
||||
# Install latest version
|
||||
apt-get install -y --no-install-recommends antigravity && \
|
||||
# Display installed version
|
||||
dpkg -l | grep antigravity && \
|
||||
rm -rf /var/lib/apt/lists/*
|
||||
|
||||
# Pre-configure Antigravity to skip onboarding/setup on first run
|
||||
RUN mkdir -p /etc/skel/.config/antigravity/User/globalStorage && \
|
||||
echo '{"antigravityUnifiedStateSync.seenNuxOneTimeMigration": true, "antigravityUnifiedStateSync.browserOnboarding.completed": true, "antigravityUnifiedStateSync.hasOnboardingCompleted": true, "browserOnboarding.hasSeenWelcome": true, "antigravityUnifiedStateSync.browserPreferences.hasAddedLocalhostToAllowlist": true, "antigravityUnifiedStateSync.oauthToken.hasLegacyMigrated": true, "antigravityUnifiedStateSync.auth.tokenSyncEnabled": true, "antigravityUnifiedStateSync.auth.cloudSyncEnabled": true, "theme": "vs-dark"}' \
|
||||
> /etc/skel/.config/antigravity/User/globalStorage/storage.json && \
|
||||
echo '{"workbench.startupEditor": "none", "workbench.welcomePage.walkthroughs.openOnInstall": false, "workbench.tips.enabled": false, "extensions.ignoreRecommendations": true, "telemetry.telemetryLevel": "off", "update.mode": "none", "extensions.autoUpdate": false, "extensions.autoCheckUpdates": false, "workbench.enableExperiments": true, "workbench.settings.enableNaturalLanguageSearch": true, "antigravity.onboarding.completed": true, "antigravity.browserOnboarding.completed": true, "antigravity.setup.completed": true, "antigravity.ai.enabled": true, "antigravity.ai.autoComplete.enabled": true, "antigravity.ai.chat.enabled": true, "antigravity.ai.codeActions.enabled": true, "antigravity.ai.explainCode.enabled": true, "antigravity.ai.generateCode.enabled": true, "antigravity.ai.optimizeCode.enabled": true, "antigravity.ai.autoSuggest.enabled": true, "antigravity.telemetry.crashReporter": "on", "antigravity.ai.acceptTerms": true, "antigravity.auth.syncState": true, "antigravity.auth.enableTokenSync": true, "antigravity.ai.enableCloudSync": true, "antigravity.settings.sync": true}' \
|
||||
> /etc/skel/.config/antigravity/User/settings.json && \
|
||||
# Validate Antigravity installation
|
||||
/usr/share/antigravity/antigravity --version || echo "WARNING: Antigravity version check failed"
|
||||
|
||||
# Install OpenSSH server (for SSH IDE mode)
|
||||
RUN apt-get update && \
|
||||
apt-get install -y openssh-server && \
|
||||
@@ -103,6 +151,9 @@ RUN mkdir -p /workspace && \
|
||||
# Copy startup scripts
|
||||
COPY --chmod=755 scripts/startapp.sh /startapp.sh
|
||||
COPY --chmod=755 scripts/init-repo.sh /usr/local/bin/init-repo
|
||||
# Copy serverless scripts (conditional execution)
|
||||
COPY --chmod=755 serverless/scripts/dynamic-init-repo.sh /usr/local/bin/dynamic-init-repo
|
||||
COPY --chmod=755 serverless/scripts/serverless-startapp.sh /usr/local/bin/serverless-startapp
|
||||
# Fix app user shell after baseimage-gui creates it at runtime
|
||||
COPY --chmod=755 scripts/cont-init-user.sh /etc/cont-init.d/20-fix-user-shell.sh
|
||||
COPY --chmod=755 scripts/cont-init-sshd.sh /etc/cont-init.d/25-start-sshd.sh
|
||||
@@ -119,4 +170,4 @@ ENV HOME=/config/userdata \
|
||||
EXPOSE 5800
|
||||
|
||||
# Set app name for baseimage-gui
|
||||
RUN set-cont-env APP_NAME "Antigravity"
|
||||
RUN set-cont-env APP_NAME "Dev Container"
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
# Variables
|
||||
REGISTRY ?= ghcr.io/cpfarhood
|
||||
IMAGE_NAME ?= antigravity
|
||||
IMAGE_NAME ?= devcontainer
|
||||
IMAGE_TAG ?= latest
|
||||
FULL_IMAGE = $(REGISTRY)/$(IMAGE_NAME):$(IMAGE_TAG)
|
||||
|
||||
@@ -29,15 +29,15 @@ run:
|
||||
-e HAPPY_EXPERIMENTAL="true" \
|
||||
-v $(PWD)/home:/home \
|
||||
-v $(PWD)/workspace:/workspace \
|
||||
--name antigravity \
|
||||
--name devcontainer \
|
||||
$(FULL_IMAGE)
|
||||
@echo "Access at http://localhost:5800"
|
||||
|
||||
# Stop the running container
|
||||
stop:
|
||||
@echo "Stopping antigravity container..."
|
||||
docker stop antigravity || true
|
||||
docker rm antigravity || true
|
||||
@echo "Stopping devcontainer..."
|
||||
docker stop devcontainer || true
|
||||
docker rm devcontainer || true
|
||||
|
||||
# Clean up local volumes
|
||||
clean: stop
|
||||
@@ -81,7 +81,7 @@ helm-port-forward:
|
||||
|
||||
# Show help
|
||||
help:
|
||||
@echo "Antigravity Dev Container Makefile"
|
||||
@echo "Dev Container Makefile"
|
||||
@echo ""
|
||||
@echo "Usage: make [target]"
|
||||
@echo ""
|
||||
@@ -101,7 +101,7 @@ help:
|
||||
@echo ""
|
||||
@echo "Variables:"
|
||||
@echo " REGISTRY - Docker registry (default: ghcr.io/cpfarhood)"
|
||||
@echo " IMAGE_NAME - Image name (default: antigravity)"
|
||||
@echo " IMAGE_NAME - Image name (default: devcontainer)"
|
||||
@echo " IMAGE_TAG - Image tag (default: latest)"
|
||||
@echo " RELEASE_NAME - Helm release name (default: mydev)"
|
||||
@echo " NAMESPACE - Kubernetes namespace (default: default)"
|
||||
|
||||
@@ -1,17 +1,52 @@
|
||||
# Antigravity Dev Container
|
||||
# Dev Container
|
||||
|
||||

|
||||
|
||||
A containerized cloud development environment with web-based GUI access, featuring:
|
||||
- **VSCode or Google Antigravity** via browser-based VNC (port 5800)
|
||||
- **SSH access** option (OpenSSH on port 22, additive with any IDE)
|
||||
- **Happy Coder** AI assistant backed by Claude
|
||||
- **Claude Code**, **Happy Coder**, **OpenCode**, and **Crush** AI coding agents (terminal-based)
|
||||
- **Built-in web file manager** for uploading/downloading files via the VNC web interface
|
||||
- **Helm CLI** included for Kubernetes chart development and deployment
|
||||
- **Automatic GitHub repo cloning** on startup
|
||||
- **Persistent home directory** via ReadWriteMany PVC
|
||||
- **Kubernetes-native** Helm chart deployment
|
||||
|
||||
## Quick Start
|
||||
|
||||
### Option A: Install from Helm Repo (Recommended)
|
||||
|
||||
```bash
|
||||
# Add the Helm repository
|
||||
helm repo add devcontainer https://cpfarhood.github.io/devcontainer
|
||||
helm repo update
|
||||
|
||||
# Deploy with one command
|
||||
helm install mydev devcontainer/devcontainer \
|
||||
--set name=mydev \
|
||||
--set githubRepo=https://github.com/youruser/yourrepo
|
||||
```
|
||||
|
||||
### Option B: Install from Source
|
||||
|
||||
```bash
|
||||
# Clone and customize the quickstart template
|
||||
cp chart/values-quickstart.yaml my-values.yaml
|
||||
# Edit my-values.yaml to set your name and repository
|
||||
|
||||
helm install mydev ./chart -f my-values.yaml
|
||||
```
|
||||
|
||||
### Option C: One-Command from Source
|
||||
|
||||
```bash
|
||||
helm install mydev ./chart \
|
||||
--set name=mydev \
|
||||
--set githubRepo=https://github.com/youruser/yourrepo
|
||||
```
|
||||
|
||||
### Option C: Full Configuration
|
||||
|
||||
### 1. Create a secret
|
||||
|
||||
The secret is picked up automatically via `envFrom`. Keys recognised:
|
||||
@@ -22,8 +57,10 @@ The secret is picked up automatically via `envFrom`. Keys recognised:
|
||||
| `VNC_PASSWORD` | Password for the VNC web UI |
|
||||
| `ANTHROPIC_API_KEY` | API key — alternative to browser-based Claude login |
|
||||
| `SSH_AUTHORIZED_KEYS` | Public key(s) for SSH access (required when `ssh: true`) |
|
||||
| `homeassistant-url` | Home Assistant URL (required when `mcpSidecars.homeassistant.enabled: true`) |
|
||||
| `homeassistant-token` | Home Assistant long-lived access token (required when `mcpSidecars.homeassistant.enabled: true`) |
|
||||
| `HOMEASSISTANT_URL` | Home Assistant URL (required when `mcp.sidecars.homeassistant.enabled: true`) |
|
||||
| `HOMEASSISTANT_TOKEN` | Home Assistant long-lived access token (required when `mcp.sidecars.homeassistant.enabled: true`) |
|
||||
| `DATABASE_URI` | PostgreSQL connection string (required when `mcp.sidecars.pgtuner.enabled: true`) |
|
||||
| `PGTUNER_EXCLUDE_USERIDS` | Comma-separated PostgreSQL user OIDs to exclude from monitoring (optional) |
|
||||
|
||||
```bash
|
||||
kubectl create secret generic devcontainer-mydev-secrets-env \
|
||||
@@ -73,20 +110,33 @@ A Chrome browser window will open inside VNC for the Claude Max OAuth login. Cre
|
||||
|
||||
## Helm Chart Reference
|
||||
|
||||
The Helm chart uses a logical organization with these main sections:
|
||||
- **Basic Configuration**: name, image, githubRepo
|
||||
- **Access & Interface**: IDE, SSH, display, user settings
|
||||
- **Infrastructure**: storage, resources, cluster access
|
||||
- **Integrations**: Happy Coder, MCP sidecars
|
||||
- **Smart Defaults**: auto-detection and profiles
|
||||
|
||||
📖 **Documentation**:
|
||||
- [USAGE.md](chart/USAGE.md) - Comprehensive examples and scenarios
|
||||
- [values-quickstart.yaml](chart/values-quickstart.yaml) - Minimal configuration
|
||||
- [values.schema.json](chart/values.schema.json) - IDE validation support
|
||||
|
||||
### Core values
|
||||
|
||||
| Value | Default | Description |
|
||||
|-------|---------|-------------|
|
||||
| `name` | `""` | Instance name — used in all resource names (`devcontainer-{name}`) |
|
||||
| `githubRepo` | `""` | Repository to clone into `/workspace` on startup |
|
||||
| `ide` | `vscode` | IDE to launch — `vscode`, `antigravity`, or `none` (see below) |
|
||||
| `ssh` | `false` | Also start an OpenSSH server on port 22 (additive, any `ide`) |
|
||||
| `ide.type` | `vscode` | IDE to launch — `vscode`, `antigravity`, or `none` (see below) |
|
||||
| `ssh.enabled` | `false` | Also start an OpenSSH server on port 22 (additive, any IDE) |
|
||||
| `fileManager.enabled` | `false` | Enable the built-in web file manager for upload/download |
|
||||
| `image.repository` | `ghcr.io/cpfarhood/devcontainer` | Container image |
|
||||
| `image.tag` | `latest` | Image tag |
|
||||
|
||||
### IDE choice
|
||||
|
||||
`ide` controls what GUI is launched in the VNC session:
|
||||
`ide.type` controls what GUI is launched in the VNC session:
|
||||
|
||||
| Value | Port | Description |
|
||||
|-------|------|-------------|
|
||||
@@ -96,14 +146,14 @@ A Chrome browser window will open inside VNC for the Claude Max OAuth login. Cre
|
||||
|
||||
### SSH access
|
||||
|
||||
`ssh: true` starts OpenSSH on port 22 **in addition to** the IDE. It works with any `ide` value:
|
||||
`ssh.enabled: true` starts OpenSSH on port 22 **in addition to** the IDE. It works with any `ide.type` value:
|
||||
|
||||
```bash
|
||||
# SSH-only (no VNC)
|
||||
helm install mydev ./chart --set name=mydev --set ide=none --set ssh=true
|
||||
helm install mydev ./chart --set name=mydev --set ide.type=none --set ssh.enabled=true
|
||||
|
||||
# VSCode in VNC + SSH access at the same time
|
||||
helm install mydev ./chart --set name=mydev --set ssh=true
|
||||
helm install mydev ./chart --set name=mydev --set ssh.enabled=true
|
||||
```
|
||||
|
||||
Add your public key to the env secret:
|
||||
@@ -121,14 +171,32 @@ kubectl port-forward deployment/devcontainer-mydev 2222:22
|
||||
ssh -p 2222 user@localhost
|
||||
```
|
||||
|
||||
### Web file manager
|
||||
|
||||
The base image includes a built-in web file manager for uploading and downloading files through the VNC web interface (port 5800). No additional sidecar is needed.
|
||||
|
||||
| Value | Default | Description |
|
||||
|-------|---------|-------------|
|
||||
| `fileManager.enabled` | `false` | Enable the web file manager |
|
||||
| `fileManager.allowedPaths` | `/workspace,/config` | Paths accessible by the file manager (`AUTO`, `ALL`, or comma-separated) |
|
||||
| `fileManager.deniedPaths` | `""` | Paths to deny (takes precedence over allowed) |
|
||||
|
||||
```bash
|
||||
# Enable the file manager
|
||||
helm install mydev ./chart \
|
||||
--set name=mydev \
|
||||
--set githubRepo=https://github.com/youruser/yourrepo \
|
||||
--set fileManager.enabled=true
|
||||
```
|
||||
|
||||
### Happy Coder
|
||||
|
||||
| Value | Default | Description |
|
||||
|-------|---------|-------------|
|
||||
| `happyServerUrl` | `https://happy.farh.net` | Happy Coder server endpoint |
|
||||
| `happyWebappUrl` | `https://happy-coder.farh.net` | Happy Coder webapp URL |
|
||||
| `happyHomeDir` | `/home/user/.happy` | Happy runtime state directory (persists on the home PVC) |
|
||||
| `happyExperimental` | `true` | Enable experimental Happy features |
|
||||
| `happy.serverUrl` | `https://happy.farh.net` | Happy Coder server endpoint |
|
||||
| `happy.webappUrl` | `https://happy-coder.farh.net` | Happy Coder webapp URL |
|
||||
| `happy.homeDir` | `/config/userdata/.happy` | Happy runtime state directory (persists on the home PVC) |
|
||||
| `happy.experimental` | `true` | Enable experimental Happy features |
|
||||
|
||||
### Kubernetes cluster access
|
||||
|
||||
@@ -158,29 +226,35 @@ The devcontainer includes MCP (Model Context Protocol) servers as sidecar contai
|
||||
|
||||
| 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 |
|
||||
| `mcp.sidecars.kubernetes.enabled` | `true` | Kubernetes API access via MCP |
|
||||
| `mcp.sidecars.flux.enabled` | `true` | Flux GitOps operations via MCP |
|
||||
| `mcp.sidecars.homeassistant.enabled` | `false` | Home Assistant smart home control via MCP |
|
||||
| `mcp.sidecars.pgtuner.enabled` | `false` | PostgreSQL performance tuning and analysis via MCP |
|
||||
| `mcp.sidecars.playwright.enabled` | `true` | Browser automation and web testing via MCP |
|
||||
|
||||
**Notes:**
|
||||
- GitHub MCP is accessed via the Copilot API (`https://api.githubcopilot.com/mcp/`), not as a sidecar
|
||||
- Kubernetes and Flux sidecars require `clusterAccess` != `none` to be deployed (automatically disabled when no cluster access)
|
||||
- Kubernetes and Flux sidecars inherit the pod's ServiceAccount RBAC permissions (controlled by `clusterAccess`)
|
||||
- Home Assistant sidecar requires additional configuration (see below)
|
||||
- Home Assistant sidecar requires `HOMEASSISTANT_URL` and `HOMEASSISTANT_TOKEN` in the env secret
|
||||
- PostgreSQL tuner sidecar requires `DATABASE_URI` in the env secret (PostgreSQL connection string)
|
||||
- Playwright sidecar provides browser automation and web testing capabilities
|
||||
|
||||
**Disable MCP sidecars:**
|
||||
```bash
|
||||
# Disable both sidecars
|
||||
# Disable multiple 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 mcp.sidecars.kubernetes.enabled=false \
|
||||
--set mcp.sidecars.flux.enabled=false \
|
||||
--set mcp.sidecars.playwright.enabled=false
|
||||
|
||||
# Or selectively disable
|
||||
helm install mydev ./chart \
|
||||
--set name=mydev \
|
||||
--set githubRepo=https://github.com/youruser/yourrepo \
|
||||
--set mcpSidecars.flux.enabled=false # Disable only Flux MCP
|
||||
--set mcp.sidecars.flux.enabled=false # Disable only Flux MCP
|
||||
```
|
||||
|
||||
**Enable Home Assistant MCP:**
|
||||
@@ -188,48 +262,75 @@ helm install mydev ./chart \
|
||||
# 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'
|
||||
--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
|
||||
--set mcp.sidecars.homeassistant.enabled=true
|
||||
```
|
||||
|
||||
**Enable PostgreSQL Tuner MCP:**
|
||||
```bash
|
||||
# Create secret with PostgreSQL connection string
|
||||
kubectl create secret generic devcontainer-mydev-secrets-env \
|
||||
--from-literal=GITHUB_TOKEN='ghp_...' \
|
||||
--from-literal=DATABASE_URI='postgresql://user:password@postgres.example.com:5432/dbname'
|
||||
|
||||
# Deploy with PostgreSQL tuner MCP enabled
|
||||
helm install mydev ./chart \
|
||||
--set name=mydev \
|
||||
--set githubRepo=https://github.com/youruser/yourrepo \
|
||||
--set mcp.sidecars.pgtuner.enabled=true
|
||||
```
|
||||
|
||||
**Custom MCP configuration:**
|
||||
```yaml
|
||||
# values.yaml override
|
||||
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"
|
||||
mcp:
|
||||
sidecars:
|
||||
kubernetes:
|
||||
enabled: true
|
||||
image:
|
||||
repository: quay.io/containers/kubernetes_mcp_server
|
||||
tag: v0.0.57
|
||||
port: 8080
|
||||
resources:
|
||||
requests:
|
||||
memory: "64Mi"
|
||||
cpu: "50m"
|
||||
limits:
|
||||
memory: "256Mi"
|
||||
cpu: "500m"
|
||||
flux:
|
||||
enabled: false # Disabled in this example
|
||||
homeassistant:
|
||||
enabled: true
|
||||
image:
|
||||
repository: ghcr.io/homeassistant-ai/ha-mcp
|
||||
tag: stable
|
||||
port: 8087
|
||||
pgtuner:
|
||||
enabled: true
|
||||
image:
|
||||
repository: dog830228/pgtuner_mcp
|
||||
tag: latest
|
||||
port: 8085
|
||||
playwright:
|
||||
enabled: true
|
||||
image:
|
||||
repository: mcr.microsoft.com/playwright/mcp
|
||||
tag: latest
|
||||
port: 8086
|
||||
resources:
|
||||
requests:
|
||||
memory: "128Mi"
|
||||
cpu: "100m"
|
||||
limits:
|
||||
memory: "512Mi"
|
||||
cpu: "1000m"
|
||||
```
|
||||
|
||||
### Display and resources
|
||||
@@ -238,9 +339,9 @@ mcpSidecars:
|
||||
|-------|---------|-------------|
|
||||
| `display.width` | `1920` | VNC width (px) |
|
||||
| `display.height` | `1080` | VNC height (px) |
|
||||
| `secureConnection` | `0` | Set to `1` if TLS is not terminated upstream |
|
||||
| `userId` | `1000` | UID for the app user |
|
||||
| `groupId` | `1000` | GID for the app user |
|
||||
| `display.secureConnection` | `0` | Set to `1` if TLS is not terminated upstream |
|
||||
| `user.id` | `1000` | UID for the app user |
|
||||
| `user.groupId` | `1000` | GID for the app user |
|
||||
| `storage.size` | `32Gi` | Home PVC size |
|
||||
| `storage.className` | `ceph-filesystem` | StorageClass (must be ReadWriteMany) |
|
||||
| `shm.sizeLimit` | `2Gi` | `/dev/shm` size (memory-backed; used by Electron apps) |
|
||||
@@ -263,8 +364,6 @@ Container start
|
||||
→ /startapp.sh (runs as app user, UID 1000)
|
||||
→ init-repo.sh
|
||||
→ clone / pull GITHUB_REPO into /workspace/{repo}
|
||||
→ rm daemon.state.json.lock — clear stale Happy lock
|
||||
→ happy daemon start — starts Happy Coder background daemon
|
||||
→ IDE=vscode: code --new-window --wait /workspace/{repo}
|
||||
IDE=antigravity: antigravity --no-sandbox --user-data-dir ~/.config/antigravity ... /workspace/{repo}
|
||||
IDE=none: sleep infinity
|
||||
@@ -275,26 +374,31 @@ Container start
|
||||
|
||||
| Mount | Source | Persistence |
|
||||
|-------|--------|-------------|
|
||||
| `/home` | ReadWriteMany PVC (`userhome-{name}`) | Survives pod restarts — stores Claude credentials, dotfiles, git config |
|
||||
| `/config` | ReadWriteMany PVC (`userhome-{name}`) | Survives pod restarts — stores Claude credentials, dotfiles, git config |
|
||||
| `/workspace` | `emptyDir` | Ephemeral — repo is re-cloned on each pod start |
|
||||
|
||||
Happy Coder's runtime state (`HAPPY_HOME_DIR`) is kept in `/home/user/.happy` on the persistent home PVC, so auth credentials and settings survive pod restarts. A stale lock file (`daemon.state.json.lock`) is removed automatically on each startup.
|
||||
Happy Coder's runtime state (`HAPPY_HOME_DIR`) is kept in `/config/userdata/.happy` on the persistent home PVC, so auth credentials and settings survive pod restarts when manually started.
|
||||
|
||||
---
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Happy Coder daemon not starting
|
||||
### Happy Coder (manual startup)
|
||||
|
||||
Happy daemon is not started automatically. Launch it manually when needed:
|
||||
|
||||
```bash
|
||||
# Start Happy Coder daemon manually
|
||||
happy daemon start
|
||||
|
||||
# Check daemon status
|
||||
happy daemon status
|
||||
|
||||
# Start manually (also clears any stale lock)
|
||||
happy daemon start
|
||||
|
||||
# View daemon logs
|
||||
ls ~/.happy/logs/
|
||||
|
||||
# Stop daemon if needed
|
||||
happy daemon stop
|
||||
```
|
||||
|
||||
### Claude not authenticated
|
||||
|
||||
+9
-2
@@ -1,6 +1,13 @@
|
||||
apiVersion: v2
|
||||
name: devcontainer
|
||||
description: Antigravity Dev Container with Happy Coder AI assistant
|
||||
description: Dev Container with AI coding agents and MCP sidecars - supports persistent and dynamic deployment modes
|
||||
type: application
|
||||
version: 0.2.0
|
||||
version: 2.0.2
|
||||
appVersion: "latest"
|
||||
keywords:
|
||||
- development
|
||||
- devcontainer
|
||||
- vscode
|
||||
- ai
|
||||
- knative
|
||||
- serverless
|
||||
|
||||
+381
@@ -0,0 +1,381 @@
|
||||
# Dev Container Helm Chart Usage Guide
|
||||
|
||||
This guide provides common usage patterns and examples for the Dev Container Helm chart.
|
||||
|
||||
## Quick Start
|
||||
|
||||
### 1. Minimal Installation (Recommended)
|
||||
|
||||
Use the quickstart values for the simplest setup:
|
||||
|
||||
```bash
|
||||
# Copy and customize quickstart values
|
||||
cp values-quickstart.yaml my-values.yaml
|
||||
|
||||
# Edit my-values.yaml to set your name and repo:
|
||||
# name: myproject
|
||||
# githubRepo: https://github.com/youruser/yourproject
|
||||
|
||||
# Install
|
||||
helm install myproject ./chart -f my-values.yaml
|
||||
```
|
||||
|
||||
### 2. One-Command Installation
|
||||
|
||||
```bash
|
||||
helm install mydev ./chart \
|
||||
--set name=mydev \
|
||||
--set githubRepo=https://github.com/youruser/yourrepo
|
||||
```
|
||||
|
||||
## Common Use Cases
|
||||
|
||||
### Development Environment
|
||||
|
||||
**Scenario**: Standard development with GitHub integration
|
||||
|
||||
```yaml
|
||||
name: dev-environment
|
||||
githubRepo: https://github.com/company/project
|
||||
|
||||
ide:
|
||||
type: vscode
|
||||
|
||||
mcp:
|
||||
sidecars:
|
||||
kubernetes:
|
||||
enabled: true
|
||||
playwright:
|
||||
enabled: true
|
||||
flux:
|
||||
enabled: false # Disable if not using Flux
|
||||
```
|
||||
|
||||
### Team Workspace
|
||||
|
||||
**Scenario**: Shared development environment with more resources
|
||||
|
||||
```yaml
|
||||
name: team-workspace
|
||||
githubRepo: https://github.com/company/project
|
||||
|
||||
resources:
|
||||
requests:
|
||||
memory: "4Gi"
|
||||
cpu: "2000m"
|
||||
limits:
|
||||
memory: "16Gi"
|
||||
cpu: "8000m"
|
||||
|
||||
storage:
|
||||
size: 64Gi
|
||||
|
||||
ssh:
|
||||
enabled: true # Enable SSH access for team
|
||||
|
||||
clusterAccess: readwrite # Full cluster access
|
||||
```
|
||||
|
||||
### Kubernetes Admin Environment
|
||||
|
||||
**Scenario**: Platform engineering with full cluster access
|
||||
|
||||
```yaml
|
||||
name: k8s-admin
|
||||
githubRepo: https://github.com/company/k8s-configs
|
||||
|
||||
clusterAccess: readwrite
|
||||
|
||||
mcp:
|
||||
sidecars:
|
||||
kubernetes:
|
||||
enabled: true
|
||||
flux:
|
||||
enabled: true
|
||||
pgtuner:
|
||||
enabled: true # Database administration
|
||||
playwright:
|
||||
enabled: false # Save resources
|
||||
```
|
||||
|
||||
### AI/ML Development
|
||||
|
||||
**Scenario**: AI development with browser automation
|
||||
|
||||
```yaml
|
||||
name: ai-playground
|
||||
githubRepo: https://github.com/company/ai-project
|
||||
|
||||
resources:
|
||||
requests:
|
||||
memory: "8Gi" # More memory for ML workloads
|
||||
cpu: "4000m"
|
||||
limits:
|
||||
memory: "32Gi"
|
||||
cpu: "16000m"
|
||||
|
||||
storage:
|
||||
size: 128Gi # Large datasets
|
||||
|
||||
mcp:
|
||||
sidecars:
|
||||
playwright:
|
||||
enabled: true # Web scraping, testing
|
||||
kubernetes:
|
||||
enabled: false # Save resources
|
||||
flux:
|
||||
enabled: false
|
||||
```
|
||||
|
||||
### Lightweight Environment
|
||||
|
||||
**Scenario**: Resource-constrained setup
|
||||
|
||||
```yaml
|
||||
name: lightweight
|
||||
githubRepo: https://github.com/youruser/small-project
|
||||
|
||||
resources:
|
||||
requests:
|
||||
memory: "1Gi"
|
||||
cpu: "500m"
|
||||
limits:
|
||||
memory: "2Gi"
|
||||
cpu: "1000m"
|
||||
|
||||
storage:
|
||||
size: 8Gi
|
||||
|
||||
mcp:
|
||||
sidecars:
|
||||
kubernetes:
|
||||
enabled: false
|
||||
flux:
|
||||
enabled: false
|
||||
playwright:
|
||||
enabled: false
|
||||
# Only keep essential sidecars enabled
|
||||
```
|
||||
|
||||
## Secret Configuration
|
||||
|
||||
### Basic Secrets
|
||||
|
||||
```bash
|
||||
# GitHub access only
|
||||
kubectl create secret generic devcontainer-mydev-secrets-env \
|
||||
--from-literal=GITHUB_TOKEN='ghp_...' \
|
||||
--from-literal=VNC_PASSWORD='changeme'
|
||||
```
|
||||
|
||||
### Extended Secrets
|
||||
|
||||
```bash
|
||||
# Full feature set
|
||||
kubectl create secret generic devcontainer-mydev-secrets-env \
|
||||
--from-literal=GITHUB_TOKEN='ghp_...' \
|
||||
--from-literal=VNC_PASSWORD='changeme' \
|
||||
--from-literal=SSH_AUTHORIZED_KEYS='ssh-ed25519 AAAA...' \
|
||||
--from-literal=HOMEASSISTANT_URL='http://homeassistant.local:8123' \
|
||||
--from-literal=HOMEASSISTANT_TOKEN='eyJ...' \
|
||||
--from-literal=DATABASE_URI='postgresql://user:pass@postgres:5432/db'
|
||||
```
|
||||
|
||||
## Storage Configuration
|
||||
|
||||
### Different Storage Classes
|
||||
|
||||
```yaml
|
||||
# For different Kubernetes distributions
|
||||
storage:
|
||||
className: "" # Auto-detect (recommended)
|
||||
# className: longhorn # Longhorn
|
||||
# className: nfs-client # NFS
|
||||
# className: fast-ssd # Custom fast storage
|
||||
```
|
||||
|
||||
### Storage Sizes by Use Case
|
||||
|
||||
```yaml
|
||||
# Small projects
|
||||
storage:
|
||||
size: 8Gi
|
||||
|
||||
# Standard development
|
||||
storage:
|
||||
size: 32Gi
|
||||
|
||||
# Large projects / datasets
|
||||
storage:
|
||||
size: 128Gi
|
||||
|
||||
# Team environments
|
||||
storage:
|
||||
size: 256Gi
|
||||
```
|
||||
|
||||
## Access Patterns
|
||||
|
||||
### VNC Only (Default)
|
||||
|
||||
```yaml
|
||||
ide:
|
||||
type: vscode
|
||||
|
||||
# Access via: kubectl port-forward deployment/devcontainer-mydev 5800:5800
|
||||
```
|
||||
|
||||
### SSH Only
|
||||
|
||||
```yaml
|
||||
ide:
|
||||
type: none
|
||||
|
||||
ssh:
|
||||
enabled: true
|
||||
|
||||
# Access via: kubectl port-forward deployment/devcontainer-mydev 2222:22
|
||||
# ssh -p 2222 user@localhost
|
||||
```
|
||||
|
||||
### Both VNC and SSH
|
||||
|
||||
```yaml
|
||||
ide:
|
||||
type: vscode
|
||||
|
||||
ssh:
|
||||
enabled: true
|
||||
|
||||
# VNC: kubectl port-forward deployment/devcontainer-mydev 5800:5800
|
||||
# SSH: kubectl port-forward deployment/devcontainer-mydev 2222:22
|
||||
```
|
||||
|
||||
## Resource Profiles
|
||||
|
||||
### Small (1-2 developers)
|
||||
```yaml
|
||||
resources:
|
||||
requests:
|
||||
memory: "1Gi"
|
||||
cpu: "500m"
|
||||
limits:
|
||||
memory: "4Gi"
|
||||
cpu: "2000m"
|
||||
```
|
||||
|
||||
### Medium (standard development)
|
||||
```yaml
|
||||
resources:
|
||||
requests:
|
||||
memory: "2Gi"
|
||||
cpu: "1000m"
|
||||
limits:
|
||||
memory: "8Gi"
|
||||
cpu: "4000m"
|
||||
```
|
||||
|
||||
### Large (intensive workloads)
|
||||
```yaml
|
||||
resources:
|
||||
requests:
|
||||
memory: "4Gi"
|
||||
cpu: "2000m"
|
||||
limits:
|
||||
memory: "16Gi"
|
||||
cpu: "8000m"
|
||||
```
|
||||
|
||||
### XLarge (AI/ML, data processing)
|
||||
```yaml
|
||||
resources:
|
||||
requests:
|
||||
memory: "8Gi"
|
||||
cpu: "4000m"
|
||||
limits:
|
||||
memory: "32Gi"
|
||||
cpu: "16000m"
|
||||
```
|
||||
|
||||
## MCP Sidecar Combinations
|
||||
|
||||
### Minimal (basic development)
|
||||
```yaml
|
||||
mcp:
|
||||
sidecars:
|
||||
kubernetes:
|
||||
enabled: false
|
||||
flux:
|
||||
enabled: false
|
||||
playwright:
|
||||
enabled: true # Keep for web testing
|
||||
```
|
||||
|
||||
### Standard (full-stack development)
|
||||
```yaml
|
||||
mcp:
|
||||
sidecars:
|
||||
kubernetes:
|
||||
enabled: true
|
||||
flux:
|
||||
enabled: false
|
||||
playwright:
|
||||
enabled: true
|
||||
```
|
||||
|
||||
### DevOps/Platform (infrastructure work)
|
||||
```yaml
|
||||
mcp:
|
||||
sidecars:
|
||||
kubernetes:
|
||||
enabled: true
|
||||
flux:
|
||||
enabled: true
|
||||
pgtuner:
|
||||
enabled: true
|
||||
playwright:
|
||||
enabled: false
|
||||
```
|
||||
|
||||
### All Features
|
||||
```yaml
|
||||
mcp:
|
||||
sidecars:
|
||||
kubernetes:
|
||||
enabled: true
|
||||
flux:
|
||||
enabled: true
|
||||
homeassistant:
|
||||
enabled: true
|
||||
pgtuner:
|
||||
enabled: true
|
||||
playwright:
|
||||
enabled: true
|
||||
```
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Values Validation
|
||||
|
||||
Your IDE should automatically validate values.yaml against the schema. If not:
|
||||
|
||||
```bash
|
||||
# Manual validation (if you have a JSON schema validator)
|
||||
helm template ./chart -f values.yaml > /dev/null
|
||||
```
|
||||
|
||||
### Common Issues
|
||||
|
||||
**Resource Limits**: Start with smaller resource requests and increase as needed.
|
||||
|
||||
**Storage Class**: Use `className: ""` for auto-detection.
|
||||
|
||||
**GitHub Access**: Ensure GITHUB_TOKEN has `repo` scope.
|
||||
|
||||
**MCP Sidecars**: Disable unused sidecars to save resources.
|
||||
|
||||
### Getting Help
|
||||
|
||||
1. Check the main [README.md](../README.md) for detailed documentation
|
||||
2. Review [values.yaml](values.yaml) for all available options
|
||||
3. Use [values-quickstart.yaml](values-quickstart.yaml) as a starting point
|
||||
@@ -0,0 +1,31 @@
|
||||
Dev Container "{{ .Values.name }}" has been deployed.
|
||||
|
||||
{{- if ne (.Values.ide.type | default "vscode") "none" }}
|
||||
|
||||
Access the IDE:
|
||||
kubectl port-forward deployment/{{ include "devcontainer.fullname" . }} 5800:5800 -n {{ .Release.Namespace }}
|
||||
Then open: http://localhost:5800
|
||||
{{- end }}
|
||||
|
||||
{{- if .Values.ssh.enabled }}
|
||||
|
||||
SSH access:
|
||||
kubectl port-forward deployment/{{ include "devcontainer.fullname" . }} 2222:22 -n {{ .Release.Namespace }}
|
||||
Then: ssh -p 2222 user@localhost
|
||||
{{- end }}
|
||||
|
||||
Useful commands:
|
||||
Logs: kubectl logs -f deployment/{{ include "devcontainer.fullname" . }} -n {{ .Release.Namespace }}
|
||||
Shell: kubectl exec -it deployment/{{ include "devcontainer.fullname" . }} -n {{ .Release.Namespace }} -- bash
|
||||
|
||||
{{- if not (lookup "v1" "Secret" .Release.Namespace (include "devcontainer.envSecretName" .)) }}
|
||||
|
||||
Optional: Create a secret for GITHUB_TOKEN, VNC_PASSWORD, etc:
|
||||
kubectl create secret generic {{ include "devcontainer.envSecretName" . }} \
|
||||
--from-literal=GITHUB_TOKEN=ghp_xxx \
|
||||
--from-literal=VNC_PASSWORD=changeme \
|
||||
-n {{ .Release.Namespace }}
|
||||
{{- end }}
|
||||
|
||||
Note: The PVC "{{ include "devcontainer.pvcName" . }}" is protected from deletion on helm uninstall.
|
||||
To remove it manually: kubectl delete pvc {{ include "devcontainer.pvcName" . }} -n {{ .Release.Namespace }}
|
||||
@@ -1,28 +1,111 @@
|
||||
{{/*
|
||||
Resource name prefix: devcontainer-{name}
|
||||
*/}}
|
||||
{{- define "antigravity.fullname" -}}
|
||||
{{- define "devcontainer.fullname" -}}
|
||||
{{- if not .Values.name }}
|
||||
{{- fail "values.name is required and must not be empty" }}
|
||||
{{- end }}
|
||||
{{- printf "devcontainer-%s" .Values.name }}
|
||||
{{- end }}
|
||||
|
||||
{{/*
|
||||
PVC name: userhome-{name}
|
||||
*/}}
|
||||
{{- define "antigravity.pvcName" -}}
|
||||
{{- define "devcontainer.pvcName" -}}
|
||||
{{- printf "userhome-%s" .Values.name }}
|
||||
{{- end }}
|
||||
|
||||
{{/*
|
||||
Secret name for env vars, default to devcontainer-{name}-secrets-env
|
||||
*/}}
|
||||
{{- define "antigravity.envSecretName" -}}
|
||||
{{- define "devcontainer.envSecretName" -}}
|
||||
{{- .Values.envSecretName | default (printf "devcontainer-%s-secrets-env" .Values.name) }}
|
||||
{{- end }}
|
||||
|
||||
{{/*
|
||||
Common labels
|
||||
*/}}
|
||||
{{- define "antigravity.labels" -}}
|
||||
{{- define "devcontainer.labels" -}}
|
||||
app: devcontainer
|
||||
instance: {{ .Values.name }}
|
||||
app.kubernetes.io/name: devcontainer
|
||||
app.kubernetes.io/instance: {{ .Values.name }}
|
||||
app.kubernetes.io/managed-by: {{ .Release.Service }}
|
||||
helm.sh/chart: {{ .Chart.Name }}-{{ .Chart.Version | replace "+" "_" }}
|
||||
{{- end }}
|
||||
|
||||
{{/*
|
||||
Selector labels — keep narrow since changing these requires recreating the Deployment
|
||||
*/}}
|
||||
{{- define "devcontainer.selectorLabels" -}}
|
||||
app: devcontainer
|
||||
instance: {{ .Values.name }}
|
||||
{{- end }}
|
||||
|
||||
{{/*
|
||||
Smart resource sizing based on enabled features
|
||||
*/}}
|
||||
{{- define "devcontainer.smartResources" -}}
|
||||
{{- $baseMemory := "2Gi" }}
|
||||
{{- $baseCpu := "1000m" }}
|
||||
{{- $limitMemory := "8Gi" }}
|
||||
{{- $limitCpu := "4000m" }}
|
||||
|
||||
{{/* Adjust for enabled MCP sidecars */}}
|
||||
{{- if .Values.mcp.sidecars.playwright.enabled }}
|
||||
{{- $baseMemory = "3Gi" }}
|
||||
{{- $limitMemory = "12Gi" }}
|
||||
{{- end }}
|
||||
|
||||
{{/* Adjust for IDE type */}}
|
||||
{{- if eq .Values.ide.type "antigravity" }}
|
||||
{{- $baseMemory = "4Gi" }}
|
||||
{{- $limitMemory = "16Gi" }}
|
||||
{{- end }}
|
||||
|
||||
requests:
|
||||
memory: {{ .Values.resources.requests.memory | default $baseMemory | quote }}
|
||||
cpu: {{ .Values.resources.requests.cpu | default $baseCpu | quote }}
|
||||
limits:
|
||||
memory: {{ .Values.resources.limits.memory | default $limitMemory | quote }}
|
||||
cpu: {{ .Values.resources.limits.cpu | default $limitCpu | quote }}
|
||||
{{- end }}
|
||||
|
||||
{{/*
|
||||
Auto-detect environment type and set smart defaults
|
||||
*/}}
|
||||
{{- define "devcontainer.smartDefaults" -}}
|
||||
{{- $isDev := or (contains "dev" .Values.name) (contains "test" .Values.name) (contains "local" .Values.name) }}
|
||||
{{- $isProd := or (contains "prod" .Values.name) (contains "production" .Values.name) }}
|
||||
{{- $isTeam := or (contains "team" .Values.name) (contains "shared" .Values.name) }}
|
||||
|
||||
{{/* Development environment - enable more sidecars, smaller resources */}}
|
||||
{{- if $isDev }}
|
||||
development: true
|
||||
{{/* Production environment - conservative defaults, fewer sidecars */}}
|
||||
{{- else if $isProd }}
|
||||
production: true
|
||||
{{/* Team environment - enable SSH, more resources */}}
|
||||
{{- else if $isTeam }}
|
||||
team: true
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
|
||||
{{/*
|
||||
Smart MCP sidecar selection based on cluster access
|
||||
*/}}
|
||||
{{- define "devcontainer.mcpDefaults" -}}
|
||||
{{- if eq .Values.clusterAccess "none" }}
|
||||
{{/* No cluster access - disable k8s/flux sidecars */}}
|
||||
kubernetes:
|
||||
enabled: false
|
||||
flux:
|
||||
enabled: false
|
||||
{{- else }}
|
||||
{{/* Has cluster access - enable k8s sidecars */}}
|
||||
kubernetes:
|
||||
enabled: true
|
||||
flux:
|
||||
enabled: {{ ne .Values.clusterAccess "readonly" }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
|
||||
+145
-46
@@ -1,68 +1,98 @@
|
||||
{{- if eq .Values.deploymentMode "persistent" }}
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: {{ include "antigravity.fullname" . }}
|
||||
name: {{ include "devcontainer.fullname" . }}
|
||||
labels:
|
||||
{{- include "antigravity.labels" . | nindent 4 }}
|
||||
{{- include "devcontainer.labels" . | nindent 4 }}
|
||||
spec:
|
||||
replicas: 1
|
||||
selector:
|
||||
matchLabels:
|
||||
{{- include "antigravity.labels" . | nindent 6 }}
|
||||
{{- include "devcontainer.selectorLabels" . | nindent 6 }}
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
{{- include "antigravity.labels" . | nindent 8 }}
|
||||
{{- include "devcontainer.labels" . | nindent 8 }}
|
||||
spec:
|
||||
{{- if ne (.Values.clusterAccess | default "none") "none" }}
|
||||
serviceAccountName: {{ include "antigravity.fullname" . }}
|
||||
serviceAccountName: {{ include "devcontainer.fullname" . }}
|
||||
{{- end }}
|
||||
securityContext:
|
||||
fsGroup: 1000
|
||||
fsGroupChangePolicy: "OnRootMismatch"
|
||||
{{- if and .Values.ide.type (eq .Values.ide.type "antigravity") }}
|
||||
initContainers:
|
||||
- name: setup-userdata
|
||||
image: busybox:1.37
|
||||
command: ['sh', '-c']
|
||||
args:
|
||||
- |
|
||||
echo "Setting up userdata directory..."
|
||||
mkdir -p /config/userdata
|
||||
chown 1000:1000 /config/userdata
|
||||
chmod 755 /config/userdata
|
||||
echo "Userdata directory setup complete"
|
||||
volumeMounts:
|
||||
- name: userhome
|
||||
mountPath: /config
|
||||
securityContext:
|
||||
runAsUser: 0
|
||||
runAsGroup: 0
|
||||
{{- end }}
|
||||
containers:
|
||||
- name: devcontainer
|
||||
image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}"
|
||||
imagePullPolicy: {{ .Values.image.pullPolicy }}
|
||||
ports:
|
||||
{{- if ne (.Values.ide | default "vscode") "none" }}
|
||||
{{- if ne (.Values.ide.type | default "vscode") "none" }}
|
||||
- containerPort: 5800
|
||||
name: vnc-web
|
||||
protocol: TCP
|
||||
{{- end }}
|
||||
{{- if .Values.ssh }}
|
||||
{{- if .Values.ssh.enabled }}
|
||||
- containerPort: 22
|
||||
name: ssh
|
||||
protocol: TCP
|
||||
{{- end }}
|
||||
env:
|
||||
- name: IDE
|
||||
value: {{ .Values.ide | default "vscode" | quote }}
|
||||
value: {{ .Values.ide.type | default "vscode" | quote }}
|
||||
- name: SSH
|
||||
value: {{ .Values.ssh | toString | quote }}
|
||||
value: {{ .Values.ssh.enabled | toString | quote }}
|
||||
- name: USER_ID
|
||||
value: {{ .Values.userId | quote }}
|
||||
value: {{ .Values.user.id | quote }}
|
||||
- name: GROUP_ID
|
||||
value: {{ .Values.groupId | quote }}
|
||||
value: {{ .Values.user.groupId | quote }}
|
||||
- name: DISPLAY_WIDTH
|
||||
value: {{ .Values.display.width | quote }}
|
||||
- name: DISPLAY_HEIGHT
|
||||
value: {{ .Values.display.height | quote }}
|
||||
- name: SECURE_CONNECTION
|
||||
value: {{ .Values.secureConnection | quote }}
|
||||
value: {{ .Values.display.secureConnection | quote }}
|
||||
{{- if .Values.fileManager.enabled }}
|
||||
- name: WEB_FILE_MANAGER
|
||||
value: "1"
|
||||
- name: WEB_FILE_MANAGER_ALLOWED_PATHS
|
||||
value: {{ .Values.fileManager.allowedPaths | quote }}
|
||||
{{- if .Values.fileManager.deniedPaths }}
|
||||
- name: WEB_FILE_MANAGER_DENIED_PATHS
|
||||
value: {{ .Values.fileManager.deniedPaths | quote }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
- name: HAPPY_HOME_DIR
|
||||
value: {{ .Values.happyHomeDir | quote }}
|
||||
value: {{ .Values.happy.homeDir | quote }}
|
||||
- name: HAPPY_EXPERIMENTAL
|
||||
value: {{ .Values.happyExperimental | quote }}
|
||||
value: {{ .Values.happy.experimental | quote }}
|
||||
- name: HAPPY_SERVER_URL
|
||||
value: {{ .Values.happyServerUrl | quote }}
|
||||
value: {{ .Values.happy.serverUrl | quote }}
|
||||
- name: HAPPY_WEBAPP_URL
|
||||
value: {{ .Values.happyWebappUrl | quote }}
|
||||
value: {{ .Values.happy.webappUrl | quote }}
|
||||
- name: GITHUB_REPO
|
||||
value: {{ .Values.githubRepo | quote }}
|
||||
envFrom:
|
||||
- secretRef:
|
||||
name: {{ include "antigravity.envSecretName" . }}
|
||||
name: {{ include "devcontainer.envSecretName" . }}
|
||||
optional: true
|
||||
resources:
|
||||
{{- toYaml .Values.resources | nindent 12 }}
|
||||
@@ -73,7 +103,7 @@ spec:
|
||||
mountPath: /workspace
|
||||
- name: shm
|
||||
mountPath: /dev/shm
|
||||
{{- if ne (.Values.ide | default "vscode") "none" }}
|
||||
{{- if ne (.Values.ide.type | default "vscode") "none" }}
|
||||
livenessProbe:
|
||||
httpGet:
|
||||
path: /
|
||||
@@ -86,7 +116,7 @@ spec:
|
||||
port: 5800
|
||||
initialDelaySeconds: 10
|
||||
periodSeconds: 5
|
||||
{{- else if .Values.ssh }}
|
||||
{{- else if .Values.ssh.enabled }}
|
||||
livenessProbe:
|
||||
tcpSocket:
|
||||
port: 22
|
||||
@@ -98,88 +128,156 @@ spec:
|
||||
initialDelaySeconds: 5
|
||||
periodSeconds: 5
|
||||
{{- end }}
|
||||
{{- if and .Values.mcpSidecars.kubernetes.enabled (ne .Values.clusterAccess "none") }}
|
||||
{{- if and .Values.mcp.sidecars.kubernetes.enabled (ne .Values.clusterAccess "none") }}
|
||||
- name: kubernetes-mcp
|
||||
image: "{{ .Values.mcpSidecars.kubernetes.image.repository }}:{{ .Values.mcpSidecars.kubernetes.image.tag }}"
|
||||
image: "{{ .Values.mcp.sidecars.kubernetes.image.repository }}:{{ .Values.mcp.sidecars.kubernetes.image.tag }}"
|
||||
args:
|
||||
- --port
|
||||
- {{ .Values.mcpSidecars.kubernetes.port | quote }}
|
||||
- {{ .Values.mcp.sidecars.kubernetes.port | quote }}
|
||||
ports:
|
||||
- containerPort: {{ .Values.mcpSidecars.kubernetes.port }}
|
||||
- containerPort: {{ .Values.mcp.sidecars.kubernetes.port }}
|
||||
name: k8s-mcp
|
||||
protocol: TCP
|
||||
livenessProbe:
|
||||
httpGet:
|
||||
path: /healthz
|
||||
port: {{ .Values.mcpSidecars.kubernetes.port }}
|
||||
port: {{ .Values.mcp.sidecars.kubernetes.port }}
|
||||
initialDelaySeconds: 10
|
||||
periodSeconds: 10
|
||||
readinessProbe:
|
||||
httpGet:
|
||||
path: /healthz
|
||||
port: {{ .Values.mcpSidecars.kubernetes.port }}
|
||||
port: {{ .Values.mcp.sidecars.kubernetes.port }}
|
||||
initialDelaySeconds: 5
|
||||
periodSeconds: 5
|
||||
resources:
|
||||
{{- toYaml .Values.mcpSidecars.kubernetes.resources | nindent 12 }}
|
||||
{{- toYaml .Values.mcp.sidecars.kubernetes.resources | nindent 12 }}
|
||||
{{- end }}
|
||||
{{- if and .Values.mcpSidecars.flux.enabled (ne .Values.clusterAccess "none") }}
|
||||
{{- if and .Values.mcp.sidecars.flux.enabled (ne .Values.clusterAccess "none") }}
|
||||
- name: flux-mcp
|
||||
image: "{{ .Values.mcpSidecars.flux.image.repository }}:{{ .Values.mcpSidecars.flux.image.tag }}"
|
||||
image: "{{ .Values.mcp.sidecars.flux.image.repository }}:{{ .Values.mcp.sidecars.flux.image.tag }}"
|
||||
args:
|
||||
- serve
|
||||
- --transport=sse
|
||||
- --port={{ .Values.mcpSidecars.flux.port }}
|
||||
- --port={{ .Values.mcp.sidecars.flux.port }}
|
||||
ports:
|
||||
- containerPort: {{ .Values.mcpSidecars.flux.port }}
|
||||
- containerPort: {{ .Values.mcp.sidecars.flux.port }}
|
||||
name: flux-mcp
|
||||
protocol: TCP
|
||||
livenessProbe:
|
||||
tcpSocket:
|
||||
port: {{ .Values.mcpSidecars.flux.port }}
|
||||
port: {{ .Values.mcp.sidecars.flux.port }}
|
||||
initialDelaySeconds: 10
|
||||
periodSeconds: 10
|
||||
readinessProbe:
|
||||
tcpSocket:
|
||||
port: {{ .Values.mcpSidecars.flux.port }}
|
||||
port: {{ .Values.mcp.sidecars.flux.port }}
|
||||
initialDelaySeconds: 5
|
||||
periodSeconds: 5
|
||||
resources:
|
||||
{{- toYaml .Values.mcpSidecars.flux.resources | nindent 12 }}
|
||||
{{- toYaml .Values.mcp.sidecars.flux.resources | nindent 12 }}
|
||||
{{- end }}
|
||||
{{- if .Values.mcpSidecars.homeassistant.enabled }}
|
||||
{{- if .Values.mcp.sidecars.homeassistant.enabled }}
|
||||
- name: homeassistant-mcp
|
||||
image: "{{ .Values.mcpSidecars.homeassistant.image.repository }}:{{ .Values.mcpSidecars.homeassistant.image.tag }}"
|
||||
imagePullPolicy: Always
|
||||
command: ["fastmcp", "run", "ha_mcp.main:app", "--transport", "sse", "--sse-server-host", "0.0.0.0", "--sse-server-port", "{{ .Values.mcpSidecars.homeassistant.port }}"]
|
||||
image: "{{ .Values.mcp.sidecars.homeassistant.image.repository }}:{{ .Values.mcp.sidecars.homeassistant.image.tag }}"
|
||||
imagePullPolicy: IfNotPresent
|
||||
command: ["fastmcp", "run", "--transport", "sse", "--host", "0.0.0.0", "--port", "{{ .Values.mcp.sidecars.homeassistant.port }}"]
|
||||
ports:
|
||||
- name: homeassistant
|
||||
containerPort: {{ .Values.mcpSidecars.homeassistant.port }}
|
||||
containerPort: {{ .Values.mcp.sidecars.homeassistant.port }}
|
||||
env:
|
||||
- name: HOMEASSISTANT_URL
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: {{ include "antigravity.envSecretName" . }}
|
||||
key: homeassistant-url
|
||||
name: {{ include "devcontainer.envSecretName" . }}
|
||||
key: HOMEASSISTANT_URL
|
||||
optional: true
|
||||
- name: HOMEASSISTANT_TOKEN
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: {{ include "antigravity.envSecretName" . }}
|
||||
key: homeassistant-token
|
||||
name: {{ include "devcontainer.envSecretName" . }}
|
||||
key: HOMEASSISTANT_TOKEN
|
||||
optional: true
|
||||
livenessProbe:
|
||||
tcpSocket:
|
||||
port: {{ .Values.mcpSidecars.homeassistant.port }}
|
||||
port: {{ .Values.mcp.sidecars.homeassistant.port }}
|
||||
initialDelaySeconds: 10
|
||||
periodSeconds: 10
|
||||
readinessProbe:
|
||||
tcpSocket:
|
||||
port: {{ .Values.mcpSidecars.homeassistant.port }}
|
||||
port: {{ .Values.mcp.sidecars.homeassistant.port }}
|
||||
initialDelaySeconds: 5
|
||||
periodSeconds: 5
|
||||
resources:
|
||||
{{- toYaml .Values.mcpSidecars.homeassistant.resources | nindent 12 }}
|
||||
{{- toYaml .Values.mcp.sidecars.homeassistant.resources | nindent 12 }}
|
||||
{{- end }}
|
||||
{{- if .Values.mcp.sidecars.pgtuner.enabled }}
|
||||
- name: pgtuner-mcp
|
||||
image: "{{ .Values.mcp.sidecars.pgtuner.image.repository }}:{{ .Values.mcp.sidecars.pgtuner.image.tag }}"
|
||||
imagePullPolicy: Always # pgtuner uses `latest` tag (no versioned releases available)
|
||||
command: ["python", "-m", "pgtuner_mcp", "--mode", "sse", "--host", "0.0.0.0", "--port", "{{ .Values.mcp.sidecars.pgtuner.port }}"]
|
||||
ports:
|
||||
- name: pgtuner
|
||||
containerPort: {{ .Values.mcp.sidecars.pgtuner.port }}
|
||||
env:
|
||||
- name: DATABASE_URI
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: {{ include "devcontainer.envSecretName" . }}
|
||||
key: DATABASE_URI
|
||||
optional: true
|
||||
- name: PGTUNER_EXCLUDE_USERIDS
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: {{ include "devcontainer.envSecretName" . }}
|
||||
key: PGTUNER_EXCLUDE_USERIDS
|
||||
optional: true
|
||||
livenessProbe:
|
||||
tcpSocket:
|
||||
port: {{ .Values.mcp.sidecars.pgtuner.port }}
|
||||
initialDelaySeconds: 10
|
||||
periodSeconds: 10
|
||||
readinessProbe:
|
||||
tcpSocket:
|
||||
port: {{ .Values.mcp.sidecars.pgtuner.port }}
|
||||
initialDelaySeconds: 5
|
||||
periodSeconds: 5
|
||||
resources:
|
||||
{{- toYaml .Values.mcp.sidecars.pgtuner.resources | nindent 12 }}
|
||||
{{- end }}
|
||||
{{- if .Values.mcp.sidecars.playwright.enabled }}
|
||||
- name: playwright-mcp
|
||||
image: "{{ .Values.mcp.sidecars.playwright.image.repository }}:{{ .Values.mcp.sidecars.playwright.image.tag }}"
|
||||
imagePullPolicy: IfNotPresent
|
||||
command: ["node"]
|
||||
args:
|
||||
- cli.js
|
||||
- --headless
|
||||
- --browser
|
||||
- chromium
|
||||
- --no-sandbox
|
||||
- --host
|
||||
- 0.0.0.0
|
||||
- --port
|
||||
- {{ .Values.mcp.sidecars.playwright.port | quote }}
|
||||
ports:
|
||||
- name: playwright
|
||||
containerPort: {{ .Values.mcp.sidecars.playwright.port }}
|
||||
livenessProbe:
|
||||
tcpSocket:
|
||||
port: {{ .Values.mcp.sidecars.playwright.port }}
|
||||
initialDelaySeconds: 15
|
||||
periodSeconds: 10
|
||||
readinessProbe:
|
||||
tcpSocket:
|
||||
port: {{ .Values.mcp.sidecars.playwright.port }}
|
||||
initialDelaySeconds: 10
|
||||
periodSeconds: 5
|
||||
resources:
|
||||
{{- toYaml .Values.mcp.sidecars.playwright.resources | nindent 12 }}
|
||||
securityContext:
|
||||
runAsUser: 1000
|
||||
runAsGroup: 1000
|
||||
{{- end }}
|
||||
volumes:
|
||||
- name: workspace
|
||||
@@ -190,4 +288,5 @@ spec:
|
||||
sizeLimit: {{ .Values.shm.sizeLimit }}
|
||||
- name: userhome
|
||||
persistentVolumeClaim:
|
||||
claimName: {{ include "antigravity.pvcName" . }}
|
||||
claimName: {{ include "devcontainer.pvcName" . }}
|
||||
{{- end }}
|
||||
|
||||
@@ -0,0 +1,68 @@
|
||||
{{- if and (eq .Values.deploymentMode "dynamic") .Values.dynamic.ingress.enabled .Values.dynamic.ingress.host }}
|
||||
apiVersion: networking.k8s.io/v1
|
||||
kind: Ingress
|
||||
metadata:
|
||||
name: {{ include "devcontainer.fullname" . }}-dynamic
|
||||
labels:
|
||||
{{- include "devcontainer.labels" . | nindent 4 }}
|
||||
app.kubernetes.io/component: dynamic-ingress
|
||||
annotations:
|
||||
{{- if .Values.dynamic.ingress.className }}
|
||||
kubernetes.io/ingress.class: {{ .Values.dynamic.ingress.className }}
|
||||
{{- end }}
|
||||
|
||||
# SSL configuration
|
||||
{{- if .Values.dynamic.ingress.tls.enabled }}
|
||||
cert-manager.io/cluster-issuer: {{ .Values.dynamic.ingress.tls.issuer | quote }}
|
||||
nginx.ingress.kubernetes.io/ssl-redirect: "true"
|
||||
nginx.ingress.kubernetes.io/force-ssl-redirect: "true"
|
||||
{{- end }}
|
||||
|
||||
# Authentik forward auth (if enabled)
|
||||
{{- if .Values.dynamic.ingress.authentik.enabled }}
|
||||
nginx.ingress.kubernetes.io/auth-url: {{ .Values.dynamic.ingress.authentik.authUrl | quote }}
|
||||
nginx.ingress.kubernetes.io/auth-signin: {{ .Values.dynamic.ingress.authentik.signIn | quote }}
|
||||
nginx.ingress.kubernetes.io/auth-response-headers: "X-Authentik-Username,X-Authentik-Groups,X-Authentik-Email,X-Authentik-Name"
|
||||
nginx.ingress.kubernetes.io/auth-snippet: |
|
||||
proxy_set_header X-Forwarded-Host $http_host;
|
||||
{{- end }}
|
||||
|
||||
# WebSocket support for VNC connections
|
||||
nginx.ingress.kubernetes.io/proxy-read-timeout: "3600"
|
||||
nginx.ingress.kubernetes.io/proxy-send-timeout: "3600"
|
||||
|
||||
# Large file upload support (for file manager)
|
||||
nginx.ingress.kubernetes.io/client-max-body-size: "100m"
|
||||
nginx.ingress.kubernetes.io/proxy-body-size: "100m"
|
||||
|
||||
# Custom server snippet for GitHub repo logging
|
||||
nginx.ingress.kubernetes.io/server-snippet: |
|
||||
location ~ ^/github/([^/]+/[^/]+) {
|
||||
# Log the GitHub repo being accessed
|
||||
access_log /var/log/nginx/devcontainer-access.log combined;
|
||||
|
||||
# Set additional headers for audit/monitoring
|
||||
proxy_set_header X-GitHub-Repo-Requested https://github.com/$1;
|
||||
proxy_set_header X-Request-Timestamp $time_iso8601;
|
||||
proxy_set_header X-Client-IP $remote_addr;
|
||||
}
|
||||
|
||||
spec:
|
||||
{{- if .Values.dynamic.ingress.tls.enabled }}
|
||||
tls:
|
||||
- hosts:
|
||||
- {{ .Values.dynamic.ingress.host }}
|
||||
secretName: {{ .Values.dynamic.ingress.tls.secretName | default (printf "%s-tls" (include "devcontainer.fullname" .)) }}
|
||||
{{- end }}
|
||||
rules:
|
||||
- host: {{ .Values.dynamic.ingress.host }}
|
||||
http:
|
||||
paths:
|
||||
- path: /
|
||||
pathType: Prefix
|
||||
backend:
|
||||
service:
|
||||
name: {{ include "devcontainer.fullname" . }}-routing-proxy
|
||||
port:
|
||||
number: 80
|
||||
{{- end }}
|
||||
@@ -0,0 +1,111 @@
|
||||
{{- if eq .Values.deploymentMode "dynamic" }}
|
||||
apiVersion: serving.knative.dev/v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: {{ include "devcontainer.fullname" . }}
|
||||
labels:
|
||||
{{- include "devcontainer.labels" . | nindent 4 }}
|
||||
annotations:
|
||||
# Knative scaling annotations
|
||||
autoscaling.knative.dev/minScale: {{ .Values.dynamic.knative.minScale | quote }}
|
||||
autoscaling.knative.dev/maxScale: {{ .Values.dynamic.knative.maxScale | quote }}
|
||||
autoscaling.knative.dev/target: {{ .Values.dynamic.knative.target | quote }}
|
||||
autoscaling.knative.dev/scale-to-zero-grace-period: {{ .Values.dynamic.knative.scaleToZeroGracePeriod | quote }}
|
||||
spec:
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
{{- include "devcontainer.labels" . | nindent 8 }}
|
||||
annotations:
|
||||
# Container configuration
|
||||
autoscaling.knative.dev/targetPort: "5800"
|
||||
serving.knative.dev/timeoutSeconds: {{ .Values.dynamic.knative.timeoutSeconds | quote }}
|
||||
# Scaling configuration
|
||||
autoscaling.knative.dev/class: "kpa.autoscaling.knative.dev"
|
||||
autoscaling.knative.dev/metric: "concurrency"
|
||||
spec:
|
||||
# Container startup timeout
|
||||
timeoutSeconds: {{ .Values.dynamic.knative.timeoutSeconds }}
|
||||
containers:
|
||||
- name: devcontainer
|
||||
image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}"
|
||||
imagePullPolicy: {{ .Values.image.pullPolicy }}
|
||||
ports:
|
||||
- containerPort: 5800
|
||||
name: vnc-web
|
||||
env:
|
||||
# Dynamic mode flags
|
||||
- name: SERVERLESS_MODE
|
||||
value: "true"
|
||||
- name: DYNAMIC_GITHUB_ROUTING
|
||||
value: "true"
|
||||
- name: DEPLOYMENT_MODE
|
||||
value: "dynamic"
|
||||
# Standard configuration
|
||||
- name: IDE
|
||||
value: {{ .Values.ide.type | default "vscode" | quote }}
|
||||
- name: USER_ID
|
||||
value: {{ .Values.user.id | quote }}
|
||||
- name: GROUP_ID
|
||||
value: {{ .Values.user.groupId | quote }}
|
||||
- name: DISPLAY_WIDTH
|
||||
value: {{ .Values.display.width | quote }}
|
||||
- name: DISPLAY_HEIGHT
|
||||
value: {{ .Values.display.height | quote }}
|
||||
- name: SECURE_CONNECTION
|
||||
value: {{ .Values.display.secureConnection | quote }}
|
||||
# File manager (always enabled in dynamic mode for easy file transfer)
|
||||
- name: WEB_FILE_MANAGER
|
||||
value: "1"
|
||||
- name: WEB_FILE_MANAGER_ALLOWED_PATHS
|
||||
value: "/workspace,/tmp" # No persistent /config in dynamic mode
|
||||
# Happy Coder (ephemeral in dynamic mode)
|
||||
- name: HAPPY_HOME_DIR
|
||||
value: "/tmp/.happy"
|
||||
- name: HAPPY_EXPERIMENTAL
|
||||
value: {{ .Values.happy.experimental | quote }}
|
||||
{{- if .Values.happy.serverUrl }}
|
||||
- name: HAPPY_SERVER_URL
|
||||
value: {{ .Values.happy.serverUrl | quote }}
|
||||
{{- end }}
|
||||
{{- if .Values.happy.webappUrl }}
|
||||
- name: HAPPY_WEBAPP_URL
|
||||
value: {{ .Values.happy.webappUrl | quote }}
|
||||
{{- end }}
|
||||
# Secret environment variables
|
||||
envFrom:
|
||||
- secretRef:
|
||||
name: {{ include "devcontainer.envSecretName" . }}
|
||||
optional: true
|
||||
resources:
|
||||
{{- toYaml .Values.dynamic.knative.resources | nindent 10 }}
|
||||
volumeMounts:
|
||||
- name: tmp-home
|
||||
mountPath: /config
|
||||
- name: shm
|
||||
mountPath: /dev/shm
|
||||
# Health probes (adjusted for dynamic mode startup time)
|
||||
readinessProbe:
|
||||
httpGet:
|
||||
path: /
|
||||
port: 5800
|
||||
initialDelaySeconds: 60
|
||||
periodSeconds: 10
|
||||
timeoutSeconds: 5
|
||||
failureThreshold: 10
|
||||
livenessProbe:
|
||||
httpGet:
|
||||
path: /
|
||||
port: 5800
|
||||
initialDelaySeconds: 120
|
||||
periodSeconds: 30
|
||||
timeoutSeconds: 10
|
||||
failureThreshold: 3
|
||||
volumes:
|
||||
- name: tmp-home
|
||||
emptyDir: {} # Ephemeral - each instance gets fresh home
|
||||
- name: shm
|
||||
emptyDir:
|
||||
medium: Memory
|
||||
sizeLimit: {{ .Values.shm.sizeLimit }}
|
||||
{{- end }}
|
||||
@@ -1,13 +1,19 @@
|
||||
{{- if eq .Values.deploymentMode "persistent" }}
|
||||
apiVersion: v1
|
||||
kind: PersistentVolumeClaim
|
||||
metadata:
|
||||
name: {{ include "antigravity.pvcName" . }}
|
||||
name: {{ include "devcontainer.pvcName" . }}
|
||||
annotations:
|
||||
helm.sh/resource-policy: keep
|
||||
labels:
|
||||
{{- include "antigravity.labels" . | nindent 4 }}
|
||||
{{- include "devcontainer.labels" . | nindent 4 }}
|
||||
spec:
|
||||
accessModes:
|
||||
- ReadWriteMany
|
||||
{{- if .Values.storage.className }}
|
||||
storageClassName: {{ .Values.storage.className }}
|
||||
{{- end }}
|
||||
resources:
|
||||
requests:
|
||||
storage: {{ .Values.storage.size }}
|
||||
{{- end }}
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
{{- if eq .Values.deploymentMode "persistent" }}
|
||||
{{- $access := .Values.clusterAccess | default "none" }}
|
||||
{{- $name := include "antigravity.fullname" . }}
|
||||
{{- $name := include "devcontainer.fullname" . }}
|
||||
{{- $ns := .Release.Namespace }}
|
||||
{{- $labels := include "antigravity.labels" . }}
|
||||
{{- $labels := include "devcontainer.labels" . }}
|
||||
|
||||
{{- if ne $access "none" }}
|
||||
---
|
||||
@@ -95,3 +96,4 @@ roleRef:
|
||||
{{- end }}
|
||||
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
|
||||
@@ -0,0 +1,66 @@
|
||||
{{- if and (eq .Values.deploymentMode "dynamic") .Values.dynamic.routingProxy.enabled }}
|
||||
---
|
||||
# Routing proxy deployment for dynamic GitHub repo extraction
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: {{ include "devcontainer.fullname" . }}-routing-proxy
|
||||
labels:
|
||||
{{- include "devcontainer.labels" . | nindent 4 }}
|
||||
app.kubernetes.io/component: routing-proxy
|
||||
spec:
|
||||
replicas: {{ .Values.dynamic.routingProxy.replicas }}
|
||||
selector:
|
||||
matchLabels:
|
||||
{{- include "devcontainer.selectorLabels" . | nindent 6 }}
|
||||
app.kubernetes.io/component: routing-proxy
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
{{- include "devcontainer.labels" . | nindent 8 }}
|
||||
app.kubernetes.io/component: routing-proxy
|
||||
spec:
|
||||
containers:
|
||||
- name: routing-proxy
|
||||
image: "{{ .Values.dynamic.routingProxy.image.repository }}:{{ .Values.dynamic.routingProxy.image.tag }}"
|
||||
imagePullPolicy: {{ .Values.dynamic.routingProxy.image.pullPolicy }}
|
||||
ports:
|
||||
- containerPort: 8080
|
||||
name: http
|
||||
env:
|
||||
- name: DEVCONTAINER_SERVICE_URL
|
||||
value: "{{ include "devcontainer.fullname" . }}.{{ .Release.Namespace }}.svc.cluster.local"
|
||||
resources:
|
||||
{{- toYaml .Values.dynamic.routingProxy.resources | nindent 10 }}
|
||||
livenessProbe:
|
||||
httpGet:
|
||||
path: /health
|
||||
port: 8080
|
||||
initialDelaySeconds: 5
|
||||
periodSeconds: 10
|
||||
readinessProbe:
|
||||
httpGet:
|
||||
path: /health
|
||||
port: 8080
|
||||
initialDelaySeconds: 2
|
||||
periodSeconds: 5
|
||||
|
||||
---
|
||||
# Service for routing proxy
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: {{ include "devcontainer.fullname" . }}-routing-proxy
|
||||
labels:
|
||||
{{- include "devcontainer.labels" . | nindent 4 }}
|
||||
app.kubernetes.io/component: routing-proxy
|
||||
spec:
|
||||
type: ClusterIP
|
||||
ports:
|
||||
- port: 80
|
||||
targetPort: 8080
|
||||
name: http
|
||||
selector:
|
||||
{{- include "devcontainer.selectorLabels" . | nindent 4 }}
|
||||
app.kubernetes.io/component: routing-proxy
|
||||
{{- end }}
|
||||
@@ -1,22 +1,24 @@
|
||||
{{- if eq .Values.deploymentMode "persistent" }}
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: {{ include "antigravity.fullname" . }}
|
||||
name: {{ include "devcontainer.fullname" . }}
|
||||
labels:
|
||||
{{- include "antigravity.labels" . | nindent 4 }}
|
||||
{{- include "devcontainer.labels" . | nindent 4 }}
|
||||
spec:
|
||||
ports:
|
||||
{{- if ne (.Values.ide | default "vscode") "none" }}
|
||||
{{- if ne (.Values.ide.type | default "vscode") "none" }}
|
||||
- port: 5800
|
||||
name: vnc-web
|
||||
protocol: TCP
|
||||
targetPort: vnc-web
|
||||
{{- end }}
|
||||
{{- if .Values.ssh }}
|
||||
{{- if .Values.ssh.enabled }}
|
||||
- port: 22
|
||||
name: ssh
|
||||
protocol: TCP
|
||||
targetPort: ssh
|
||||
{{- end }}
|
||||
selector:
|
||||
{{- include "antigravity.labels" . | nindent 4 }}
|
||||
{{- include "devcontainer.labels" . | nindent 4 }}
|
||||
{{- end }}
|
||||
|
||||
@@ -0,0 +1,122 @@
|
||||
# Example values for dynamic (serverless) deployment mode
|
||||
# Copy this file and customize for your environment:
|
||||
# cp values-dynamic.yaml my-dynamic-values.yaml
|
||||
|
||||
# =============================================================================
|
||||
# BASIC CONFIGURATION
|
||||
# =============================================================================
|
||||
|
||||
name: "mydev" # REQUIRED: Instance name
|
||||
deploymentMode: dynamic # Use serverless/dynamic mode
|
||||
|
||||
# Container images
|
||||
image:
|
||||
repository: ghcr.io/cpfarhood/devcontainer
|
||||
tag: "2.0.0-dev"
|
||||
pullPolicy: Always
|
||||
|
||||
# githubRepo is ignored in dynamic mode - repos are specified via URL routing
|
||||
|
||||
# =============================================================================
|
||||
# ACCESS & INTERFACE
|
||||
# =============================================================================
|
||||
|
||||
ide:
|
||||
type: vscode # vscode | antigravity | none
|
||||
|
||||
# SSH not supported in dynamic mode (ephemeral containers)
|
||||
ssh:
|
||||
enabled: false
|
||||
|
||||
# File manager automatically enabled in dynamic mode for file transfer
|
||||
fileManager:
|
||||
enabled: true
|
||||
|
||||
# =============================================================================
|
||||
# DYNAMIC MODE CONFIGURATION
|
||||
# =============================================================================
|
||||
|
||||
dynamic:
|
||||
# Knative Service auto-scaling configuration
|
||||
knative:
|
||||
minScale: 0 # Scale to zero when not in use
|
||||
maxScale: 10 # Maximum concurrent instances
|
||||
target: 1 # Requests per instance (1 = perfect isolation)
|
||||
scaleToZeroGracePeriod: "5m" # Keep instances warm for 5 minutes
|
||||
timeoutSeconds: 600 # 10 minutes for repo cloning + IDE startup
|
||||
|
||||
# Resources per container instance
|
||||
resources:
|
||||
requests:
|
||||
memory: "1Gi"
|
||||
cpu: "500m"
|
||||
limits:
|
||||
memory: "4Gi"
|
||||
cpu: "2000m"
|
||||
|
||||
# Routing proxy (extracts GitHub repo from URL path)
|
||||
routingProxy:
|
||||
enabled: true
|
||||
replicas: 2 # High availability
|
||||
image:
|
||||
repository: ghcr.io/cpfarhood/devcontainer-routing-proxy
|
||||
tag: latest
|
||||
pullPolicy: Always
|
||||
|
||||
# Ingress configuration
|
||||
ingress:
|
||||
enabled: true
|
||||
className: nginx
|
||||
host: "devcontainer.example.com" # REQUIRED: Set your domain
|
||||
|
||||
# SSL with cert-manager
|
||||
tls:
|
||||
enabled: true
|
||||
# secretName: "" # Auto-generated if empty
|
||||
issuer: "letsencrypt-prod"
|
||||
|
||||
# Authentik forward auth (configure after Authentik setup)
|
||||
authentik:
|
||||
enabled: false # Set to true when ready
|
||||
authUrl: "http://authentik.authentik.svc.cluster.local/outpost.goauthentik.io/auth/nginx"
|
||||
signIn: "https://auth.example.com/outpost.goauthentik.io/start?rd=$escaped_request_uri"
|
||||
|
||||
# =============================================================================
|
||||
# STANDARD CONFIGURATION (applies to both modes)
|
||||
# =============================================================================
|
||||
|
||||
# Display settings
|
||||
display:
|
||||
width: "1920"
|
||||
height: "1080"
|
||||
secureConnection: "0"
|
||||
|
||||
# User configuration
|
||||
user:
|
||||
id: "1000"
|
||||
groupId: "1000"
|
||||
|
||||
# Resource allocation (container shared memory)
|
||||
shm:
|
||||
sizeLimit: 2Gi
|
||||
|
||||
# Happy Coder (ephemeral in dynamic mode)
|
||||
happy:
|
||||
serverUrl: ""
|
||||
webappUrl: ""
|
||||
homeDir: "/tmp/.happy" # Ephemeral location in dynamic mode
|
||||
experimental: "true"
|
||||
|
||||
# MCP sidecars are not supported in dynamic mode (Knative limitation)
|
||||
mcp:
|
||||
sidecars:
|
||||
kubernetes:
|
||||
enabled: false
|
||||
flux:
|
||||
enabled: false
|
||||
homeassistant:
|
||||
enabled: false
|
||||
pgtuner:
|
||||
enabled: false
|
||||
playwright:
|
||||
enabled: false
|
||||
@@ -0,0 +1,58 @@
|
||||
# =============================================================================
|
||||
# QUICKSTART VALUES - Just set these 3 essentials!
|
||||
# =============================================================================
|
||||
|
||||
# Instance name (required)
|
||||
name: mydev
|
||||
|
||||
# GitHub repository to clone (required)
|
||||
githubRepo: https://github.com/youruser/yourrepo
|
||||
|
||||
# IDE choice (optional - defaults to vscode)
|
||||
# Options: vscode | antigravity | none
|
||||
ide:
|
||||
type: vscode
|
||||
|
||||
# =============================================================================
|
||||
# COMMON CUSTOMIZATIONS (optional)
|
||||
# =============================================================================
|
||||
|
||||
# Enable SSH access
|
||||
# ssh:
|
||||
# enabled: true
|
||||
|
||||
# Adjust resources for smaller/larger workloads
|
||||
# resources:
|
||||
# requests:
|
||||
# memory: "1Gi" # Smaller
|
||||
# cpu: "500m"
|
||||
# limits:
|
||||
# memory: "4Gi" # Smaller
|
||||
# cpu: "2000m"
|
||||
|
||||
# Different storage size
|
||||
# storage:
|
||||
# size: 16Gi # Smaller
|
||||
|
||||
# Disable some MCP sidecars to save resources
|
||||
# mcp:
|
||||
# sidecars:
|
||||
# kubernetes:
|
||||
# enabled: false
|
||||
# flux:
|
||||
# enabled: false
|
||||
|
||||
# =============================================================================
|
||||
# USAGE INSTRUCTIONS
|
||||
# =============================================================================
|
||||
|
||||
# 1. Copy this file: cp values-quickstart.yaml my-values.yaml
|
||||
# 2. Edit the 'name' and 'githubRepo' fields above
|
||||
# 3. Deploy: helm install mydev ./chart -f my-values.yaml
|
||||
# 4. Access: kubectl port-forward deployment/devcontainer-mydev 5800:5800
|
||||
# 5. Open: http://localhost:5800
|
||||
|
||||
# For secrets (GitHub token, passwords):
|
||||
# kubectl create secret generic devcontainer-mydev-secrets-env \
|
||||
# --from-literal=GITHUB_TOKEN='ghp_...' \
|
||||
# --from-literal=VNC_PASSWORD='changeme'
|
||||
@@ -0,0 +1,351 @@
|
||||
{
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"$id": "https://github.com/cpfarhood/devcontainer/chart/values.schema.json",
|
||||
"title": "Dev Container Helm Chart Values Schema",
|
||||
"description": "Schema for validating values.yaml in the Dev Container Helm chart",
|
||||
"type": "object",
|
||||
"additionalProperties": true,
|
||||
"properties": {
|
||||
"name": {
|
||||
"type": "string",
|
||||
"description": "Instance name used to generate resource names",
|
||||
"pattern": "^[a-z0-9][a-z0-9-]*[a-z0-9]$",
|
||||
"minLength": 1,
|
||||
"maxLength": 63
|
||||
},
|
||||
"image": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"repository": {
|
||||
"type": "string",
|
||||
"description": "Container image repository"
|
||||
},
|
||||
"tag": {
|
||||
"type": "string",
|
||||
"description": "Container image tag"
|
||||
},
|
||||
"pullPolicy": {
|
||||
"type": "string",
|
||||
"enum": ["Always", "IfNotPresent", "Never"],
|
||||
"description": "Image pull policy"
|
||||
}
|
||||
},
|
||||
"required": ["repository", "tag"]
|
||||
},
|
||||
"deploymentMode": {
|
||||
"type": "string",
|
||||
"enum": ["persistent", "dynamic"],
|
||||
"description": "Deployment mode: persistent (PVC-based) or dynamic (Knative serverless)"
|
||||
},
|
||||
"githubRepo": {
|
||||
"type": "string",
|
||||
"description": "GitHub repository URL to clone (required in persistent mode, ignored in dynamic mode)"
|
||||
},
|
||||
"fileManager": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"enabled": {
|
||||
"type": "boolean",
|
||||
"description": "Enable the built-in web file manager"
|
||||
},
|
||||
"allowedPaths": {
|
||||
"type": "string",
|
||||
"description": "Paths accessible by the file manager (AUTO, ALL, or comma-separated list)"
|
||||
},
|
||||
"deniedPaths": {
|
||||
"type": "string",
|
||||
"description": "Paths to deny access to (takes precedence over allowedPaths)"
|
||||
}
|
||||
},
|
||||
"required": ["enabled"]
|
||||
},
|
||||
"dynamic": {
|
||||
"type": "object",
|
||||
"description": "Configuration for dynamic (serverless) deployment mode",
|
||||
"properties": {
|
||||
"knative": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"minScale": { "type": "integer", "minimum": 0 },
|
||||
"maxScale": { "type": "integer", "minimum": 1 },
|
||||
"target": { "type": "integer", "minimum": 1 },
|
||||
"scaleToZeroGracePeriod": { "type": "string" },
|
||||
"timeoutSeconds": { "type": "integer", "minimum": 60 },
|
||||
"resources": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"requests": { "$ref": "#/$defs/resourceSpec" },
|
||||
"limits": { "$ref": "#/$defs/resourceSpec" }
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"routingProxy": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"enabled": { "type": "boolean" },
|
||||
"replicas": { "type": "integer", "minimum": 1 },
|
||||
"image": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"repository": { "type": "string" },
|
||||
"tag": { "type": "string" },
|
||||
"pullPolicy": { "type": "string", "enum": ["Always", "IfNotPresent", "Never"] }
|
||||
}
|
||||
},
|
||||
"resources": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"requests": { "$ref": "#/$defs/resourceSpec" },
|
||||
"limits": { "$ref": "#/$defs/resourceSpec" }
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"ingress": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"enabled": { "type": "boolean" },
|
||||
"className": { "type": "string" },
|
||||
"host": { "type": "string" },
|
||||
"tls": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"enabled": { "type": "boolean" },
|
||||
"secretName": { "type": "string" },
|
||||
"issuer": { "type": "string" }
|
||||
}
|
||||
},
|
||||
"authentik": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"enabled": { "type": "boolean" },
|
||||
"authUrl": { "type": "string" },
|
||||
"signIn": { "type": "string" }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"ide": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"type": {
|
||||
"type": "string",
|
||||
"enum": ["vscode", "antigravity", "none"],
|
||||
"description": "IDE to launch in the container"
|
||||
}
|
||||
},
|
||||
"required": ["type"]
|
||||
},
|
||||
"ssh": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"enabled": {
|
||||
"type": "boolean",
|
||||
"description": "Enable SSH server on port 22"
|
||||
}
|
||||
},
|
||||
"required": ["enabled"]
|
||||
},
|
||||
"display": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"width": {
|
||||
"type": "string",
|
||||
"pattern": "^[0-9]+$",
|
||||
"description": "VNC display width in pixels"
|
||||
},
|
||||
"height": {
|
||||
"type": "string",
|
||||
"pattern": "^[0-9]+$",
|
||||
"description": "VNC display height in pixels"
|
||||
},
|
||||
"secureConnection": {
|
||||
"type": "string",
|
||||
"enum": ["0", "1"],
|
||||
"description": "Enable secure VNC connection"
|
||||
}
|
||||
},
|
||||
"required": ["width", "height", "secureConnection"]
|
||||
},
|
||||
"user": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "string",
|
||||
"pattern": "^[0-9]+$",
|
||||
"description": "User ID (UID)"
|
||||
},
|
||||
"groupId": {
|
||||
"type": "string",
|
||||
"pattern": "^[0-9]+$",
|
||||
"description": "Group ID (GID)"
|
||||
}
|
||||
},
|
||||
"required": ["id", "groupId"]
|
||||
},
|
||||
"storage": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"size": {
|
||||
"type": "string",
|
||||
"pattern": "^[0-9]+[KMGT]i$",
|
||||
"description": "Storage size (e.g., 32Gi)"
|
||||
},
|
||||
"className": {
|
||||
"type": "string",
|
||||
"description": "Storage class name (must support ReadWriteMany)"
|
||||
}
|
||||
},
|
||||
"required": ["size"]
|
||||
},
|
||||
"resources": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"requests": {
|
||||
"$ref": "#/$defs/resourceSpec"
|
||||
},
|
||||
"limits": {
|
||||
"$ref": "#/$defs/resourceSpec"
|
||||
}
|
||||
},
|
||||
"required": ["requests", "limits"]
|
||||
},
|
||||
"shm": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"sizeLimit": {
|
||||
"type": "string",
|
||||
"pattern": "^[0-9]+[KMGT]i$",
|
||||
"description": "Shared memory size limit"
|
||||
}
|
||||
},
|
||||
"required": ["sizeLimit"]
|
||||
},
|
||||
"clusterAccess": {
|
||||
"type": "string",
|
||||
"enum": ["none", "readonlyns", "readwritens", "readonly", "readwrite"],
|
||||
"description": "Kubernetes cluster access level"
|
||||
},
|
||||
"happy": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"serverUrl": {
|
||||
"type": "string",
|
||||
"description": "Happy Coder server URL"
|
||||
},
|
||||
"webappUrl": {
|
||||
"type": "string",
|
||||
"description": "Happy Coder webapp URL"
|
||||
},
|
||||
"homeDir": {
|
||||
"type": "string",
|
||||
"description": "Happy Coder home directory"
|
||||
},
|
||||
"experimental": {
|
||||
"type": "string",
|
||||
"enum": ["true", "false"],
|
||||
"description": "Enable experimental Happy features"
|
||||
}
|
||||
},
|
||||
"required": ["homeDir", "experimental"]
|
||||
},
|
||||
"mcp": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"sidecars": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"kubernetes": {
|
||||
"$ref": "#/$defs/mcpSidecar"
|
||||
},
|
||||
"flux": {
|
||||
"$ref": "#/$defs/mcpSidecar"
|
||||
},
|
||||
"homeassistant": {
|
||||
"$ref": "#/$defs/mcpSidecar"
|
||||
},
|
||||
"pgtuner": {
|
||||
"$ref": "#/$defs/mcpSidecar"
|
||||
},
|
||||
"playwright": {
|
||||
"$ref": "#/$defs/mcpSidecar"
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
}
|
||||
},
|
||||
"required": ["sidecars"]
|
||||
},
|
||||
"envSecretName": {
|
||||
"type": "string",
|
||||
"description": "Custom environment secret name"
|
||||
},
|
||||
"resourceProfile": {
|
||||
"type": "string",
|
||||
"enum": ["auto", "small", "medium", "large", "xlarge"],
|
||||
"description": "Resource profile preset"
|
||||
}
|
||||
},
|
||||
"required": ["name"],
|
||||
"$defs": {
|
||||
"resourceSpec": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"memory": {
|
||||
"type": "string",
|
||||
"pattern": "^[0-9]+[KMGT]i$",
|
||||
"description": "Memory resource specification"
|
||||
},
|
||||
"cpu": {
|
||||
"type": "string",
|
||||
"pattern": "^[0-9]+m?$",
|
||||
"description": "CPU resource specification"
|
||||
}
|
||||
},
|
||||
"required": ["memory", "cpu"]
|
||||
},
|
||||
"mcpSidecar": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"enabled": {
|
||||
"type": "boolean",
|
||||
"description": "Enable this MCP sidecar"
|
||||
},
|
||||
"image": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"repository": {
|
||||
"type": "string"
|
||||
},
|
||||
"tag": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": ["repository", "tag"]
|
||||
},
|
||||
"port": {
|
||||
"type": "integer",
|
||||
"minimum": 1,
|
||||
"maximum": 65535,
|
||||
"description": "Port for the MCP sidecar"
|
||||
},
|
||||
"resources": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"requests": {
|
||||
"$ref": "#/$defs/resourceSpec"
|
||||
},
|
||||
"limits": {
|
||||
"$ref": "#/$defs/resourceSpec"
|
||||
}
|
||||
},
|
||||
"required": ["requests", "limits"]
|
||||
}
|
||||
},
|
||||
"required": ["enabled", "image", "port", "resources"]
|
||||
}
|
||||
}
|
||||
}
|
||||
+213
-69
@@ -1,51 +1,68 @@
|
||||
# =============================================================================
|
||||
# BASIC CONFIGURATION
|
||||
# =============================================================================
|
||||
|
||||
# Instance name — used to generate resource names (devcontainer-{name}, userhome-{name})
|
||||
name: ""
|
||||
|
||||
# Deployment mode controls the infrastructure pattern
|
||||
# - persistent: Traditional model with PVC storage, single long-lived deployment
|
||||
# - dynamic: Serverless model with Knative, auto-scaling from 0, dynamic GitHub routing
|
||||
deploymentMode: persistent # persistent | dynamic
|
||||
|
||||
# Container image configuration
|
||||
image:
|
||||
repository: ghcr.io/cpfarhood/devcontainer
|
||||
tag: latest
|
||||
tag: 2.0.0-dev
|
||||
pullPolicy: Always
|
||||
|
||||
# GitHub repository to clone into /workspace
|
||||
# GitHub repository to clone into /workspace (ignored in dynamic mode - uses URL routing)
|
||||
githubRepo: ""
|
||||
|
||||
# IDE to launch inside the container.
|
||||
# Options:
|
||||
# vscode — VSCode via VNC browser UI on port 5800 (default)
|
||||
# antigravity — Google Antigravity (VSCode fork) via VNC on port 5800
|
||||
# none — no IDE; useful when ssh: true is the sole access method
|
||||
ide: vscode
|
||||
# =============================================================================
|
||||
# ACCESS & INTERFACE
|
||||
# =============================================================================
|
||||
|
||||
# Start an OpenSSH server on port 22 in addition to the IDE.
|
||||
# Set SSH_AUTHORIZED_KEYS in the env secret to allow key-based login.
|
||||
ssh: false
|
||||
# IDE configuration
|
||||
ide:
|
||||
# Options: vscode | antigravity | none
|
||||
type: vscode
|
||||
|
||||
# Happy Coder endpoints
|
||||
happyServerUrl: "https://happy.farh.net"
|
||||
happyWebappUrl: "https://happy-coder.farh.net"
|
||||
happyHomeDir: "/config/userdata/.happy"
|
||||
happyExperimental: "true"
|
||||
# SSH access configuration
|
||||
ssh:
|
||||
enabled: false
|
||||
|
||||
# VNC display
|
||||
# Web file manager — built-in upload/download via the VNC web interface (port 5800)
|
||||
# Uses the base image's WEB_FILE_MANAGER feature (no extra sidecar needed)
|
||||
fileManager:
|
||||
enabled: false
|
||||
# Paths the file manager can access (default: AUTO = mapped volumes)
|
||||
# Options: AUTO | ALL | comma-separated list of paths
|
||||
allowedPaths: "/workspace,/config"
|
||||
# Paths to deny (takes precedence over allowedPaths)
|
||||
deniedPaths: ""
|
||||
|
||||
# VNC display settings
|
||||
display:
|
||||
width: "1920"
|
||||
height: "1080"
|
||||
secureConnection: "0" # Set to "1" when TLS is not terminated upstream
|
||||
|
||||
# Set to "0" when TLS is terminated at the gateway layer
|
||||
secureConnection: "0"
|
||||
# User configuration
|
||||
user:
|
||||
id: "1000"
|
||||
groupId: "1000"
|
||||
|
||||
userId: "1000"
|
||||
groupId: "1000"
|
||||
# =============================================================================
|
||||
# INFRASTRUCTURE & RESOURCES
|
||||
# =============================================================================
|
||||
|
||||
# Storage configuration
|
||||
storage:
|
||||
size: 32Gi
|
||||
className: ceph-filesystem
|
||||
|
||||
# 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
|
||||
className: "" # Empty string uses the cluster's default StorageClass (must support ReadWriteMany)
|
||||
|
||||
# Resource allocation
|
||||
resources:
|
||||
requests:
|
||||
memory: "2Gi"
|
||||
@@ -54,57 +71,184 @@ resources:
|
||||
memory: "8Gi"
|
||||
cpu: "4000m"
|
||||
|
||||
# Kubernetes cluster access granted to the devcontainer pod via RBAC.
|
||||
# Options:
|
||||
# none — no cluster access (default)
|
||||
# readonlyns — get/list/watch all resources in the release namespace
|
||||
# readwritens — full access to all resources in the release namespace
|
||||
# readonly — get/list/watch all resources cluster-wide
|
||||
# readwrite — full access to all resources cluster-wide
|
||||
# Shared memory for Electron apps (Chrome, Antigravity)
|
||||
shm:
|
||||
sizeLimit: 2Gi
|
||||
|
||||
# Kubernetes cluster access via RBAC
|
||||
# Options: none | readonlyns | readwritens | readonly | readwrite
|
||||
clusterAccess: none
|
||||
|
||||
# Name of existing Secret containing env vars (GITHUB_TOKEN, VNC_PASSWORD, etc.)
|
||||
# Defaults to: devcontainer-{name}-secrets-env
|
||||
envSecretName: ""
|
||||
# =============================================================================
|
||||
# INTEGRATIONS
|
||||
# =============================================================================
|
||||
|
||||
# MCP server sidecars — run alongside the devcontainer to inherit pod RBAC.
|
||||
mcpSidecars:
|
||||
kubernetes:
|
||||
# Happy Coder AI assistant configuration
|
||||
happy:
|
||||
serverUrl: ""
|
||||
webappUrl: ""
|
||||
homeDir: "/config/userdata/.happy"
|
||||
experimental: "true"
|
||||
|
||||
# MCP (Model Context Protocol) server sidecars
|
||||
mcp:
|
||||
sidecars:
|
||||
# Kubernetes API access
|
||||
kubernetes:
|
||||
enabled: true
|
||||
image:
|
||||
repository: quay.io/containers/kubernetes_mcp_server
|
||||
tag: v0.0.57
|
||||
port: 8080
|
||||
resources:
|
||||
requests:
|
||||
memory: "64Mi"
|
||||
cpu: "50m"
|
||||
limits:
|
||||
memory: "256Mi"
|
||||
cpu: "500m"
|
||||
|
||||
# Flux GitOps operations
|
||||
flux:
|
||||
enabled: true
|
||||
image:
|
||||
repository: ghcr.io/controlplaneio-fluxcd/flux-operator-mcp
|
||||
tag: v0.41.1
|
||||
port: 8081
|
||||
resources:
|
||||
requests:
|
||||
memory: "64Mi"
|
||||
cpu: "50m"
|
||||
limits:
|
||||
memory: "256Mi"
|
||||
cpu: "500m"
|
||||
|
||||
|
||||
|
||||
# Home Assistant smart home control
|
||||
homeassistant:
|
||||
enabled: false # Requires HOMEASSISTANT_URL and HOMEASSISTANT_TOKEN
|
||||
image:
|
||||
repository: ghcr.io/homeassistant-ai/ha-mcp
|
||||
tag: v6.7.1
|
||||
port: 8087
|
||||
resources:
|
||||
requests:
|
||||
memory: "64Mi"
|
||||
cpu: "50m"
|
||||
limits:
|
||||
memory: "256Mi"
|
||||
cpu: "500m"
|
||||
|
||||
# PostgreSQL performance tuning
|
||||
pgtuner:
|
||||
enabled: false # Requires DATABASE_URI in secrets
|
||||
image:
|
||||
repository: dog830228/pgtuner_mcp
|
||||
tag: latest
|
||||
port: 8085
|
||||
resources:
|
||||
requests:
|
||||
memory: "64Mi"
|
||||
cpu: "50m"
|
||||
limits:
|
||||
memory: "256Mi"
|
||||
cpu: "500m"
|
||||
|
||||
# Browser automation and web testing
|
||||
playwright:
|
||||
enabled: true
|
||||
image:
|
||||
repository: mcr.microsoft.com/playwright/mcp
|
||||
tag: v0.0.68
|
||||
port: 8086
|
||||
resources:
|
||||
requests:
|
||||
memory: "128Mi"
|
||||
cpu: "100m"
|
||||
limits:
|
||||
memory: "512Mi"
|
||||
cpu: "1000m"
|
||||
|
||||
# =============================================================================
|
||||
# SMART DEFAULTS & AUTO-DETECTION
|
||||
# =============================================================================
|
||||
|
||||
# Environment auto-detection based on name patterns
|
||||
# Automatically adjusts defaults for dev/test/prod/team environments
|
||||
autoDetect:
|
||||
environment: true # Auto-detect dev/prod/team from name
|
||||
storageClass: true # Auto-detect ReadWriteMany storage class
|
||||
resources: true # Auto-size resources based on enabled features
|
||||
|
||||
# Resource profiles (auto-selected based on environment and features)
|
||||
# Override specific values above to customize
|
||||
resourceProfile: auto # auto | small | medium | large | xlarge
|
||||
|
||||
# =============================================================================
|
||||
# DYNAMIC MODE CONFIGURATION (deploymentMode: dynamic)
|
||||
# =============================================================================
|
||||
|
||||
# Dynamic mode uses Knative Services and routing proxy for serverless operation
|
||||
dynamic:
|
||||
# Knative Service configuration
|
||||
knative:
|
||||
# Scaling configuration
|
||||
minScale: 0 # Scale to zero when not in use
|
||||
maxScale: 10 # Maximum number of concurrent instances
|
||||
target: 1 # Requests per instance (isolation = 1 request per pod)
|
||||
scaleToZeroGracePeriod: "5m" # Keep instances warm for 5 minutes
|
||||
|
||||
# Container startup timeout (repo cloning + IDE startup)
|
||||
timeoutSeconds: 600 # 10 minutes
|
||||
|
||||
# Resource configuration (per instance)
|
||||
resources:
|
||||
requests:
|
||||
memory: "1Gi"
|
||||
cpu: "500m"
|
||||
limits:
|
||||
memory: "4Gi"
|
||||
cpu: "2000m"
|
||||
|
||||
# Routing proxy configuration (extracts GitHub repo from URL)
|
||||
routingProxy:
|
||||
enabled: true
|
||||
replicas: 2 # High availability
|
||||
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
|
||||
repository: ghcr.io/cpfarhood/devcontainer-routing-proxy
|
||||
tag: 2.0.0-dev
|
||||
pullPolicy: Always
|
||||
|
||||
resources:
|
||||
requests:
|
||||
memory: "64Mi"
|
||||
cpu: "50m"
|
||||
cpu: "100m"
|
||||
limits:
|
||||
memory: "256Mi"
|
||||
cpu: "500m"
|
||||
flux:
|
||||
|
||||
# Ingress configuration for dynamic mode
|
||||
ingress:
|
||||
enabled: true
|
||||
image:
|
||||
repository: ghcr.io/controlplaneio-fluxcd/flux-operator-mcp
|
||||
tag: v0.41.1
|
||||
port: 8081
|
||||
resources:
|
||||
requests:
|
||||
memory: "64Mi"
|
||||
cpu: "50m"
|
||||
limits:
|
||||
memory: "256Mi"
|
||||
cpu: "500m"
|
||||
homeassistant:
|
||||
enabled: false # Disabled by default, requires HOMEASSISTANT_URL and HOMEASSISTANT_TOKEN
|
||||
image:
|
||||
repository: ghcr.io/homeassistant-ai/ha-mcp
|
||||
tag: 6.7.1 # Pinned version (Feb 20, 2026) - latest stable release
|
||||
port: 8087
|
||||
resources:
|
||||
requests:
|
||||
memory: "64Mi"
|
||||
cpu: "50m"
|
||||
limits:
|
||||
memory: "256Mi"
|
||||
cpu: "500m"
|
||||
className: nginx
|
||||
host: "" # Set this to your domain (e.g., devcontainer.farh.net)
|
||||
|
||||
# TLS configuration
|
||||
tls:
|
||||
enabled: true
|
||||
secretName: "" # Auto-generated if empty
|
||||
issuer: "letsencrypt-prod" # cert-manager ClusterIssuer
|
||||
|
||||
# Authentik forward auth configuration
|
||||
authentik:
|
||||
enabled: false # Set to true when Authentik is configured
|
||||
authUrl: "http://authentik.authentik.svc.cluster.local/outpost.goauthentik.io/auth/nginx"
|
||||
signIn: "https://auth.example.com/outpost.goauthentik.io/start?rd=$escaped_request_uri"
|
||||
|
||||
# =============================================================================
|
||||
# ADVANCED CONFIGURATION
|
||||
# =============================================================================
|
||||
|
||||
# Custom env secret name (defaults to: devcontainer-{name}-secrets-env)
|
||||
envSecretName: ""
|
||||
@@ -2,5 +2,9 @@
|
||||
# Fix the app user (UID 1000) created by baseimage-gui at runtime.
|
||||
# baseimage-gui sets shell=/sbin/nologin and home=/dev/null, which
|
||||
# prevents VSCode from opening terminals.
|
||||
usermod -s /bin/bash app
|
||||
usermod -d /config/userdata app
|
||||
if id app >/dev/null 2>&1; then
|
||||
usermod -s /bin/bash app
|
||||
usermod -d /config/userdata app
|
||||
else
|
||||
echo "WARNING: 'app' user not found, skipping usermod" >&2
|
||||
fi
|
||||
|
||||
+3
-13
@@ -1,5 +1,5 @@
|
||||
#!/bin/bash
|
||||
# Initialize repository and start Happy Coder
|
||||
# Initialize repository
|
||||
set -e
|
||||
|
||||
echo "=== Repository Initialization ==="
|
||||
@@ -22,6 +22,7 @@ if [ -n "$GITHUB_TOKEN" ]; then
|
||||
|
||||
# Create or update the credentials file
|
||||
CREDENTIALS_FILE="/config/userdata/.git-credentials"
|
||||
mkdir -p "$(dirname "$CREDENTIALS_FILE")"
|
||||
|
||||
# Support multiple git hosting providers
|
||||
# GitHub supports both oauth2 and token as username
|
||||
@@ -51,6 +52,7 @@ else
|
||||
|
||||
# Create an empty credentials file with proper permissions
|
||||
CREDENTIALS_FILE="/config/userdata/.git-credentials"
|
||||
mkdir -p "$(dirname "$CREDENTIALS_FILE")"
|
||||
touch "$CREDENTIALS_FILE"
|
||||
chmod 600 "$CREDENTIALS_FILE"
|
||||
|
||||
@@ -102,18 +104,6 @@ chown -R "$RUN_UID:$RUN_GID" "$WORKSPACE_DIR"
|
||||
mkdir -p "$HOME"
|
||||
chown "$RUN_UID:$RUN_GID" "$HOME"
|
||||
|
||||
# Start Happy Coder daemon. startapp.sh already runs as the app user (UID 1000),
|
||||
# so no sudo needed — Happy/Claude Code will find credentials in the correct home dir.
|
||||
echo "Starting Happy Coder..."
|
||||
|
||||
# Remove stale lock file. HAPPY_HOME_DIR lives on the home PVC so it survives
|
||||
# pod restarts — without this cleanup the daemon refuses to start after a crash.
|
||||
rm -f "${HAPPY_HOME_DIR:-$HOME/.happy}/daemon.state.json.lock"
|
||||
|
||||
cd "$WORKSPACE_DIR"
|
||||
happy daemon start || echo "Happy Coder daemon failed to start, continuing anyway..."
|
||||
|
||||
echo "Happy Coder daemon started"
|
||||
|
||||
# Export workspace directory for startapp.sh
|
||||
echo "$WORKSPACE_DIR" > /tmp/workspace-dir
|
||||
|
||||
+11
-2
@@ -2,9 +2,15 @@
|
||||
# Start application script for baseimage-gui
|
||||
set -e
|
||||
|
||||
echo "=== Starting Antigravity Dev Container ==="
|
||||
echo "=== Starting Dev Container ==="
|
||||
|
||||
# Initialize repository and Happy Coder
|
||||
# Check if we're in serverless mode
|
||||
if [[ "$SERVERLESS_MODE" == "true" ]]; then
|
||||
echo "Serverless mode detected, using serverless startup script..."
|
||||
exec /usr/local/bin/serverless-startapp
|
||||
fi
|
||||
|
||||
# Traditional mode - initialize repository
|
||||
/usr/local/bin/init-repo
|
||||
|
||||
# Get workspace directory
|
||||
@@ -34,6 +40,9 @@ case "$IDE" in
|
||||
exec sleep infinity
|
||||
;;
|
||||
*)
|
||||
if [ "$IDE" != "vscode" ]; then
|
||||
echo "WARNING: Unknown IDE value '$IDE', defaulting to VSCode"
|
||||
fi
|
||||
echo "Opening VSCode in: $WORKSPACE_DIR"
|
||||
exec code --new-window --wait "$WORKSPACE_DIR"
|
||||
;;
|
||||
|
||||
@@ -0,0 +1,173 @@
|
||||
# DevContainer Serverless 2.0 Makefile
|
||||
|
||||
# Configuration
|
||||
REGISTRY ?= ghcr.io/cpfarhood
|
||||
ROUTING_PROXY_IMAGE := $(REGISTRY)/devcontainer-routing-proxy
|
||||
DEVCONTAINER_IMAGE := $(REGISTRY)/devcontainer
|
||||
VERSION ?= 2.0.0-alpha
|
||||
NAMESPACE := devcontainers
|
||||
|
||||
# Knative service name
|
||||
KN_SERVICE := devcontainer-serverless
|
||||
|
||||
.PHONY: help build push deploy test clean
|
||||
|
||||
help: ## Display this help message
|
||||
@echo "DevContainer Serverless 2.0"
|
||||
@echo ""
|
||||
@echo "Available targets:"
|
||||
@awk 'BEGIN {FS = ":.*?## "} /^[a-zA-Z_-]+:.*?## / {printf " %-15s %s\n", $$1, $$2}' $(MAKEFILE_LIST)
|
||||
|
||||
# Build targets
|
||||
build-routing-proxy: ## Build the routing proxy image
|
||||
@echo "Building routing proxy image..."
|
||||
cd routing-proxy && docker build -t $(ROUTING_PROXY_IMAGE):$(VERSION) .
|
||||
docker tag $(ROUTING_PROXY_IMAGE):$(VERSION) $(ROUTING_PROXY_IMAGE):latest
|
||||
|
||||
build-devcontainer: ## Build the main devcontainer image (from parent directory)
|
||||
@echo "Building devcontainer image..."
|
||||
cd .. && docker build -t $(DEVCONTAINER_IMAGE):$(VERSION) .
|
||||
docker tag $(DEVCONTAINER_IMAGE):$(VERSION) $(DEVCONTAINER_IMAGE):latest
|
||||
|
||||
build: build-routing-proxy build-devcontainer ## Build all images
|
||||
|
||||
# Push targets
|
||||
push-routing-proxy: build-routing-proxy ## Push routing proxy image
|
||||
@echo "Pushing routing proxy image..."
|
||||
docker push $(ROUTING_PROXY_IMAGE):$(VERSION)
|
||||
docker push $(ROUTING_PROXY_IMAGE):latest
|
||||
|
||||
push-devcontainer: build-devcontainer ## Push devcontainer image
|
||||
@echo "Pushing devcontainer image..."
|
||||
docker push $(DEVCONTAINER_IMAGE):$(VERSION)
|
||||
docker push $(DEVCONTAINER_IMAGE):latest
|
||||
|
||||
push: push-routing-proxy push-devcontainer ## Push all images
|
||||
|
||||
# Deployment targets
|
||||
create-namespace: ## Create the devcontainers namespace
|
||||
@echo "Creating namespace..."
|
||||
kubectl create namespace $(NAMESPACE) --dry-run=client -o yaml | kubectl apply -f -
|
||||
|
||||
deploy-secrets: create-namespace ## Deploy secrets (update values first!)
|
||||
@echo "Deploying secrets..."
|
||||
@echo "WARNING: Update the secret values in deployment.yaml first!"
|
||||
kubectl apply -f deployment.yaml
|
||||
@echo "Don't forget to update the secret with real values:"
|
||||
@echo "kubectl edit secret devcontainer-serverless-secrets -n $(NAMESPACE)"
|
||||
|
||||
deploy-components: create-namespace ## Deploy routing proxy and Knative service
|
||||
@echo "Deploying serverless components..."
|
||||
kubectl apply -f deployment.yaml
|
||||
|
||||
deploy: deploy-secrets deploy-components ## Deploy everything
|
||||
|
||||
# Configuration targets
|
||||
configure-authentik: ## Apply Authentik configuration
|
||||
@echo "Applying Authentik configuration..."
|
||||
kubectl apply -f authentik-config.yaml
|
||||
@echo "Complete the setup in Authentik web UI:"
|
||||
@echo "1. Create Forward Auth Provider"
|
||||
@echo "2. Create Application"
|
||||
@echo "3. Create Outpost"
|
||||
|
||||
# Testing targets
|
||||
test-routing-proxy: ## Test routing proxy locally
|
||||
@echo "Testing routing proxy..."
|
||||
@echo "Starting local test..."
|
||||
cd routing-proxy && docker run --rm -d --name devcontainer-routing-test \
|
||||
-p 8080:8080 \
|
||||
-e DEVCONTAINER_SERVICE_URL=httpbin.org \
|
||||
$(ROUTING_PROXY_IMAGE):latest
|
||||
@echo "Testing GitHub repo extraction..."
|
||||
sleep 2
|
||||
curl -v "http://localhost:8080/github/microsoft/vscode" || true
|
||||
docker stop devcontainer-routing-test
|
||||
@echo "Test complete!"
|
||||
|
||||
test-knative: ## Test Knative service deployment
|
||||
@echo "Testing Knative service..."
|
||||
kubectl get ksvc $(KN_SERVICE) -n $(NAMESPACE)
|
||||
kubectl describe ksvc $(KN_SERVICE) -n $(NAMESPACE)
|
||||
|
||||
test: test-routing-proxy test-knative ## Run all tests
|
||||
|
||||
# Status and debugging targets
|
||||
status: ## Show status of all components
|
||||
@echo "=== Namespace ==="
|
||||
kubectl get ns $(NAMESPACE) || echo "Namespace not found"
|
||||
@echo ""
|
||||
@echo "=== Routing Proxy ==="
|
||||
kubectl get deployment devcontainer-routing-proxy -n $(NAMESPACE) || echo "Routing proxy not found"
|
||||
@echo ""
|
||||
@echo "=== Knative Service ==="
|
||||
kubectl get ksvc $(KN_SERVICE) -n $(NAMESPACE) || echo "Knative service not found"
|
||||
@echo ""
|
||||
@echo "=== Pods ==="
|
||||
kubectl get pods -n $(NAMESPACE)
|
||||
@echo ""
|
||||
@echo "=== Ingress ==="
|
||||
kubectl get ingress -n $(NAMESPACE)
|
||||
|
||||
logs-routing-proxy: ## Show routing proxy logs
|
||||
kubectl logs -n $(NAMESPACE) deployment/devcontainer-routing-proxy -f
|
||||
|
||||
logs-knative: ## Show Knative service logs
|
||||
kubectl logs -n $(NAMESPACE) -l serving.knative.dev/service=$(KN_SERVICE) -f
|
||||
|
||||
# Cleanup targets
|
||||
clean-pods: ## Delete all pods in the namespace
|
||||
kubectl delete pods --all -n $(NAMESPACE)
|
||||
|
||||
clean-deployment: ## Delete the serverless deployment
|
||||
kubectl delete -f deployment.yaml --ignore-not-found
|
||||
|
||||
clean-namespace: ## Delete the entire namespace
|
||||
kubectl delete namespace $(NAMESPACE) --ignore-not-found
|
||||
|
||||
clean: clean-deployment ## Clean up deployment
|
||||
|
||||
# Development targets
|
||||
dev-setup: ## Set up development environment
|
||||
@echo "Setting up development environment..."
|
||||
@echo "Prerequisites:"
|
||||
@echo "- Kubernetes cluster with Knative Serving"
|
||||
@echo "- kubectl configured"
|
||||
@echo "- Docker for building images"
|
||||
@echo ""
|
||||
@echo "Run 'make build deploy' to get started"
|
||||
|
||||
scale-to-zero: ## Force Knative service to scale to zero
|
||||
@echo "Scaling Knative service to zero..."
|
||||
kubectl patch ksvc $(KN_SERVICE) -n $(NAMESPACE) --type='merge' -p='{"spec":{"template":{"metadata":{"annotations":{"autoscaling.knative.dev/minScale":"0"}}}}}'
|
||||
|
||||
scale-up: ## Trigger a scale-up of the Knative service
|
||||
@echo "Triggering scale-up..."
|
||||
curl -H "X-GitHub-Repo: https://github.com/microsoft/vscode" \
|
||||
"http://devcontainer-routing-proxy.$(NAMESPACE).svc.cluster.local/github/microsoft/vscode" || \
|
||||
kubectl run curl --rm -i --restart=Never --image=curlimages/curl -- \
|
||||
-H "X-GitHub-Repo: https://github.com/microsoft/vscode" \
|
||||
"http://devcontainer-routing-proxy.$(NAMESPACE).svc.cluster.local/github/microsoft/vscode"
|
||||
|
||||
# Documentation targets
|
||||
docs: ## Generate documentation
|
||||
@echo "Documentation files:"
|
||||
@echo "- README.md: Main documentation"
|
||||
@echo "- deployment.yaml: Kubernetes manifests"
|
||||
@echo "- authentik-config.yaml: Authentik configuration"
|
||||
@echo ""
|
||||
@echo "View online documentation at: https://github.com/cpfarhood/devcontainer/tree/feature/serverless-2.0.0/serverless"
|
||||
|
||||
# Version management
|
||||
version: ## Show current version
|
||||
@echo "Version: $(VERSION)"
|
||||
@echo "Registry: $(REGISTRY)"
|
||||
@echo "Images:"
|
||||
@echo " - $(ROUTING_PROXY_IMAGE):$(VERSION)"
|
||||
@echo " - $(DEVCONTAINER_IMAGE):$(VERSION)"
|
||||
|
||||
# Quick development workflow
|
||||
dev: build deploy status ## Quick development: build, deploy, show status
|
||||
|
||||
# Production deployment workflow
|
||||
prod: build push deploy configure-authentik status ## Production deployment workflow
|
||||
@@ -0,0 +1,376 @@
|
||||
# DevContainer Serverless 2.0
|
||||
|
||||
A serverless, auto-scaling development container platform with dynamic GitHub repository routing, secured by Authentik authentication.
|
||||
|
||||
## Architecture Overview
|
||||
|
||||
```
|
||||
User Request: https://devcontainer.farh.net/github/microsoft/vscode
|
||||
↓
|
||||
Authentik (Authentication & Authorization)
|
||||
↓ (authenticated request with user headers)
|
||||
NGINX Ingress (SSL termination, rate limiting)
|
||||
↓
|
||||
Routing Proxy (extracts GitHub repo from URL, adds headers)
|
||||
↓ (with X-GitHub-Repo header)
|
||||
Knative Service (devcontainer-serverless)
|
||||
↓ (auto-scales from 0 to N instances)
|
||||
Dev Container Instances (ephemeral, repo-specific)
|
||||
```
|
||||
|
||||
### Key Features
|
||||
|
||||
- 🚀 **Scale to Zero**: Containers automatically scale down to zero when not in use
|
||||
- 🔐 **Authentik Integration**: Full authentication and authorization via Authentik
|
||||
- 🐙 **Dynamic GitHub Routing**: Access any repo via `/github/{owner}/{repo}`
|
||||
- ⚡ **Fast Cold Start**: Optimized startup for quick repository access
|
||||
- 📁 **Built-in File Manager**: Upload/download files via web interface
|
||||
- 🛠️ **Multiple IDEs**: VSCode, Antigravity, or headless mode
|
||||
- 🎯 **Per-User Isolation**: Each request gets its own container instance
|
||||
|
||||
## Quick Start
|
||||
|
||||
### Prerequisites
|
||||
|
||||
- Kubernetes cluster with Knative Serving installed
|
||||
- Authentik deployed and configured
|
||||
- NGINX Ingress Controller
|
||||
- cert-manager for SSL certificates
|
||||
|
||||
### 1. Deploy the Serverless Components
|
||||
|
||||
```bash
|
||||
# Create namespace and deploy all components
|
||||
kubectl apply -f serverless/deployment.yaml
|
||||
|
||||
# Build and push the routing proxy image
|
||||
cd serverless/routing-proxy
|
||||
docker build -t ghcr.io/cpfarhood/devcontainer-routing-proxy:latest .
|
||||
docker push ghcr.io/cpfarhood/devcontainer-routing-proxy:latest
|
||||
```
|
||||
|
||||
### 2. Configure Authentik
|
||||
|
||||
```bash
|
||||
# Apply Authentik configuration
|
||||
kubectl apply -f serverless/authentik-config.yaml
|
||||
|
||||
# Configure the application via Authentik web UI:
|
||||
# 1. Go to Applications > Providers > Create
|
||||
# 2. Type: Forward Auth (single application)
|
||||
# 3. Name: devcontainer-forward-auth-provider
|
||||
# 4. External host: https://devcontainer.farh.net
|
||||
# 5. Create the Application pointing to this provider
|
||||
```
|
||||
|
||||
### 3. Update DNS and SSL
|
||||
|
||||
```bash
|
||||
# Point devcontainer.farh.net to your ingress controller
|
||||
# The cert-manager will automatically provision SSL certificates
|
||||
```
|
||||
|
||||
### 4. Test the Deployment
|
||||
|
||||
```bash
|
||||
# Visit in browser (will redirect to Authentik for login)
|
||||
https://devcontainer.farh.net/github/microsoft/vscode
|
||||
|
||||
# Check pod scaling
|
||||
kubectl get pods -n devcontainers -w
|
||||
|
||||
# View logs
|
||||
kubectl logs -n devcontainers deployment/devcontainer-routing-proxy -f
|
||||
kubectl logs -n devcontainers -l serving.knative.dev/service=devcontainer-serverless -f
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
### URL Format
|
||||
|
||||
```
|
||||
https://devcontainer.farh.net/github/{owner}/{repo}
|
||||
```
|
||||
|
||||
### Examples
|
||||
|
||||
```bash
|
||||
# Microsoft VSCode
|
||||
https://devcontainer.farh.net/github/microsoft/vscode
|
||||
|
||||
# Kubernetes
|
||||
https://devcontainer.farh.net/github/kubernetes/kubernetes
|
||||
|
||||
# Your private repo (requires GitHub token)
|
||||
https://devcontainer.farh.net/github/yourorg/private-repo
|
||||
```
|
||||
|
||||
### Authentication Flow
|
||||
|
||||
1. User visits `https://devcontainer.farh.net/github/owner/repo`
|
||||
2. NGINX Ingress checks with Authentik for authentication
|
||||
3. If not authenticated, redirects to Authentik login
|
||||
4. After successful login, request proceeds with user headers
|
||||
5. Routing proxy extracts repository from URL
|
||||
6. Knative spins up (or reuses) a container instance
|
||||
7. Container clones the specified repository and starts IDE
|
||||
|
||||
### File Upload/Download
|
||||
|
||||
Each container includes a built-in file manager accessible via the VNC web interface:
|
||||
|
||||
1. Connect to your dev container via the browser
|
||||
2. Look for the file manager icon in the VNC toolbar
|
||||
3. Upload/download files directly through the web interface
|
||||
|
||||
## Configuration
|
||||
|
||||
### Environment Variables (Secret)
|
||||
|
||||
Update the secret in `serverless/deployment.yaml`:
|
||||
|
||||
```yaml
|
||||
stringData:
|
||||
GITHUB_TOKEN: "ghp_your_github_token" # For private repositories
|
||||
VNC_PASSWORD: "your_secure_password" # VNC access password
|
||||
ANTHROPIC_API_KEY: "sk-ant-your_key" # Claude API key
|
||||
GIT_USER_NAME: "Your Name" # Git commit author
|
||||
GIT_USER_EMAIL: "your.email@example.com" # Git commit email
|
||||
```
|
||||
|
||||
### Scaling Configuration
|
||||
|
||||
Modify the Knative Service annotations in `deployment.yaml`:
|
||||
|
||||
```yaml
|
||||
annotations:
|
||||
autoscaling.knative.dev/minScale: "0" # Scale to zero
|
||||
autoscaling.knative.dev/maxScale: "20" # Max instances
|
||||
autoscaling.knative.dev/target: "1" # 1 request per pod
|
||||
autoscaling.knative.dev/scale-to-zero-grace-period: "10m"
|
||||
```
|
||||
|
||||
### Resource Limits
|
||||
|
||||
Adjust per-instance resources:
|
||||
|
||||
```yaml
|
||||
resources:
|
||||
requests:
|
||||
memory: "1Gi"
|
||||
cpu: "500m"
|
||||
limits:
|
||||
memory: "8Gi" # More memory for large repos
|
||||
cpu: "4000m" # More CPU for compilation tasks
|
||||
```
|
||||
|
||||
### IDE Selection
|
||||
|
||||
Set the default IDE via environment variable:
|
||||
|
||||
```yaml
|
||||
env:
|
||||
- name: IDE
|
||||
value: "vscode" # Options: vscode, antigravity, none
|
||||
```
|
||||
|
||||
## Monitoring and Observability
|
||||
|
||||
### Health Checks
|
||||
|
||||
```bash
|
||||
# Routing proxy health
|
||||
curl http://devcontainer-routing-proxy.devcontainers.svc.cluster.local/health
|
||||
|
||||
# Knative service status
|
||||
kn service describe devcontainer-serverless -n devcontainers
|
||||
|
||||
# Check container logs
|
||||
kubectl logs -n devcontainers -l serving.knative.dev/service=devcontainer-serverless -f
|
||||
```
|
||||
|
||||
### Metrics
|
||||
|
||||
The setup includes Prometheus integration:
|
||||
|
||||
- **Authentik metrics**: User authentication events
|
||||
- **Knative metrics**: Container scaling, cold starts, request latency
|
||||
- **NGINX metrics**: Request rates, response times
|
||||
- **Container metrics**: Resource usage per repository
|
||||
|
||||
### Grafana Dashboards
|
||||
|
||||
Import the provided dashboard for monitoring:
|
||||
|
||||
```bash
|
||||
# TODO: Create Grafana dashboard JSON
|
||||
```
|
||||
|
||||
## Security Considerations
|
||||
|
||||
### Network Policies
|
||||
|
||||
```yaml
|
||||
# Restrict networking between components
|
||||
apiVersion: networking.k8s.io/v1
|
||||
kind: NetworkPolicy
|
||||
metadata:
|
||||
name: devcontainer-serverless-network-policy
|
||||
namespace: devcontainers
|
||||
spec:
|
||||
podSelector:
|
||||
matchLabels:
|
||||
serving.knative.dev/service: devcontainer-serverless
|
||||
policyTypes:
|
||||
- Ingress
|
||||
- Egress
|
||||
ingress:
|
||||
- from:
|
||||
- podSelector:
|
||||
matchLabels:
|
||||
app.kubernetes.io/component: routing-proxy
|
||||
ports:
|
||||
- protocol: TCP
|
||||
port: 5800
|
||||
egress:
|
||||
- to: [] # Allow all outbound (needed for git clone, package installs)
|
||||
ports:
|
||||
- protocol: TCP
|
||||
port: 443
|
||||
- protocol: TCP
|
||||
port: 80
|
||||
```
|
||||
|
||||
### Repository Access Control
|
||||
|
||||
Configure Authentik policies to control repository access:
|
||||
|
||||
```python
|
||||
# Example Authentik expression policy
|
||||
github_repo = request.http_request.headers.get('X-GitHub-Repo', '')
|
||||
user_groups = [g.name for g in request.user.ak_groups.all()]
|
||||
|
||||
# Allow admins access to everything
|
||||
if 'admins' in user_groups:
|
||||
return True
|
||||
|
||||
# Allow developers access to public repos and specific private repos
|
||||
if 'developers' in user_groups:
|
||||
# Add logic for private repository access based on user attributes
|
||||
if 'private-repo-access' in user.ak_attributes:
|
||||
allowed_repos = user.ak_attributes['private-repo-access']
|
||||
return github_repo in allowed_repos
|
||||
return True # Public repos only
|
||||
|
||||
return False
|
||||
```
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Common Issues
|
||||
|
||||
1. **Container won't start**
|
||||
```bash
|
||||
# Check Knative service status
|
||||
kn service describe devcontainer-serverless -n devcontainers
|
||||
|
||||
# Check pod events
|
||||
kubectl describe pod -n devcontainers -l serving.knative.dev/service=devcontainer-serverless
|
||||
```
|
||||
|
||||
2. **Repository clone fails**
|
||||
```bash
|
||||
# Check GitHub token in secret
|
||||
kubectl get secret devcontainer-serverless-secrets -n devcontainers -o yaml
|
||||
|
||||
# Check container logs for git errors
|
||||
kubectl logs -n devcontainers -l serving.knative.dev/service=devcontainer-serverless --tail=100
|
||||
```
|
||||
|
||||
3. **Authentik authentication loop**
|
||||
```bash
|
||||
# Check Authentik outpost logs
|
||||
kubectl logs -n authentik -l app.kubernetes.io/name=authentik
|
||||
|
||||
# Verify ingress annotations
|
||||
kubectl describe ingress devcontainer-serverless-ingress -n devcontainers
|
||||
```
|
||||
|
||||
4. **Slow cold starts**
|
||||
```bash
|
||||
# Check container startup time
|
||||
kubectl logs -n devcontainers -l serving.knative.dev/service=devcontainer-serverless --timestamps
|
||||
|
||||
# Consider increasing timeout
|
||||
# serving.knative.dev/timeoutSeconds: "900" # 15 minutes
|
||||
```
|
||||
|
||||
### Performance Tuning
|
||||
|
||||
1. **Reduce cold start time**:
|
||||
- Use minimal base image layers
|
||||
- Pre-install common development tools
|
||||
- Optimize git clone (shallow clone for large repos)
|
||||
|
||||
2. **Resource optimization**:
|
||||
- Set appropriate resource requests/limits
|
||||
- Use `autoscaling.knative.dev/target-utilization-percentage`
|
||||
- Consider persistent volumes for frequently accessed repos
|
||||
|
||||
3. **Network optimization**:
|
||||
- Use private container registry for faster image pulls
|
||||
- Configure image pull policies appropriately
|
||||
- Consider using a git cache proxy
|
||||
|
||||
## Development
|
||||
|
||||
### Building the Routing Proxy
|
||||
|
||||
```bash
|
||||
cd serverless/routing-proxy
|
||||
docker build -t ghcr.io/cpfarhood/devcontainer-routing-proxy:v2.0.0 .
|
||||
docker push ghcr.io/cpfarhood/devcontainer-routing-proxy:v2.0.0
|
||||
```
|
||||
|
||||
### Testing Locally
|
||||
|
||||
```bash
|
||||
# Run the routing proxy locally
|
||||
cd serverless/routing-proxy
|
||||
docker run -p 8080:8080 \
|
||||
-e DEVCONTAINER_SERVICE_URL=host.docker.internal:5800 \
|
||||
ghcr.io/cpfarhood/devcontainer-routing-proxy:latest
|
||||
|
||||
# Test routing
|
||||
curl -H "X-GitHub-Repo: https://github.com/microsoft/vscode" \
|
||||
http://localhost:8080/github/microsoft/vscode
|
||||
```
|
||||
|
||||
### Contributing
|
||||
|
||||
1. Create feature branch from `feature/serverless-2.0.0`
|
||||
2. Make changes to serverless components
|
||||
3. Test with local Knative setup
|
||||
4. Submit pull request
|
||||
|
||||
## Migration from 1.x
|
||||
|
||||
The serverless 2.0 architecture is a complete redesign. Migration steps:
|
||||
|
||||
1. **Backup existing data**: Export user configs, git credentials
|
||||
2. **Deploy 2.0 components**: Following the quick start guide
|
||||
3. **Migrate users**: Update Authentik with existing user accounts
|
||||
4. **Test extensively**: Verify repository access and functionality
|
||||
5. **Switch DNS**: Point domain to new infrastructure
|
||||
6. **Cleanup 1.x**: Remove old Helm deployments
|
||||
|
||||
## Roadmap
|
||||
|
||||
- [ ] GitLab support (`/gitlab/group/project`)
|
||||
- [ ] Bitbucket support
|
||||
- [ ] Repository templates and scaffolding
|
||||
- [ ] Collaborative editing features
|
||||
- [ ] IDE plugins and extensions management
|
||||
- [ ] Resource quotas per user/group
|
||||
- [ ] Repository caching and optimization
|
||||
- [ ] Integration with CI/CD pipelines
|
||||
@@ -0,0 +1,168 @@
|
||||
# Authentik configuration for DevContainer serverless auth
|
||||
# This assumes Authentik is already deployed in the 'authentik' namespace
|
||||
|
||||
---
|
||||
# Application definition for DevContainer Serverless
|
||||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
name: authentik-devcontainer-app-config
|
||||
namespace: authentik
|
||||
data:
|
||||
# This will be applied via Authentik API or web interface
|
||||
application.yaml: |
|
||||
name: DevContainer Serverless
|
||||
slug: devcontainer-serverless
|
||||
provider: devcontainer-forward-auth-provider
|
||||
launch_url: https://devcontainer.farh.net/
|
||||
open_in_new_tab: true
|
||||
meta_description: "Serverless development containers with dynamic GitHub repository routing"
|
||||
meta_publisher: "DevContainer Team"
|
||||
policy_engine_mode: "all"
|
||||
group: "Development Tools"
|
||||
|
||||
---
|
||||
# Forward Auth Provider configuration
|
||||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
name: authentik-devcontainer-provider-config
|
||||
namespace: authentik
|
||||
data:
|
||||
provider.yaml: |
|
||||
name: devcontainer-forward-auth-provider
|
||||
authorization_flow: default-authorization-flow # Use your default flow
|
||||
external_host: https://devcontainer.farh.net
|
||||
|
||||
# Advanced settings
|
||||
token_validity: hours=24 # Long-lived sessions for dev work
|
||||
|
||||
# Headers to forward to the application
|
||||
# These will be available as HTTP_* environment variables in containers
|
||||
property_mappings:
|
||||
- "authentik_core.x-authentik-username"
|
||||
- "authentik_core.x-authentik-email"
|
||||
- "authentik_core.x-authentik-name"
|
||||
- "authentik_core.x-authentik-groups"
|
||||
|
||||
---
|
||||
# Outpost configuration for forward auth
|
||||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
name: authentik-devcontainer-outpost-config
|
||||
namespace: authentik
|
||||
data:
|
||||
outpost.yaml: |
|
||||
name: devcontainer-forward-auth-outpost
|
||||
type: proxy
|
||||
providers:
|
||||
- devcontainer-forward-auth-provider
|
||||
|
||||
# Outpost configuration
|
||||
config:
|
||||
authentik_host: https://auth.farh.net
|
||||
authentik_host_insecure: false
|
||||
authentik_host_browser: https://auth.farh.net
|
||||
|
||||
# Log level for debugging
|
||||
log_level: info
|
||||
|
||||
# Cookie settings
|
||||
cookie_domain: .farh.net
|
||||
cookie_secure: true
|
||||
|
||||
# NGINX ingress integration
|
||||
external_host: https://devcontainer.farh.net
|
||||
internal_host: http://authentik.authentik.svc.cluster.local
|
||||
|
||||
# Forward auth specific settings
|
||||
mode: forward_single
|
||||
skip_path_regex: "^/(health|metrics)$" # Skip auth for health checks
|
||||
|
||||
---
|
||||
# Example NGINX Ingress annotations for reference
|
||||
# (These go in the main ingress resource)
|
||||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
name: authentik-nginx-annotations
|
||||
namespace: devcontainers
|
||||
data:
|
||||
annotations.yaml: |
|
||||
# Forward auth configuration
|
||||
nginx.ingress.kubernetes.io/auth-url: http://authentik.authentik.svc.cluster.local/outpost.goauthentik.io/auth/nginx
|
||||
nginx.ingress.kubernetes.io/auth-signin: https://auth.farh.net/outpost.goauthentik.io/start?rd=$escaped_request_uri
|
||||
nginx.ingress.kubernetes.io/auth-response-headers: X-Authentik-Username,X-Authentik-Groups,X-Authentik-Email,X-Authentik-Name
|
||||
nginx.ingress.kubernetes.io/auth-snippet: |
|
||||
proxy_set_header X-Forwarded-Host $http_host;
|
||||
|
||||
# Additional headers for the application
|
||||
nginx.ingress.kubernetes.io/server-snippet: |
|
||||
location ~ ^/github/([^/]+/[^/]+) {
|
||||
# Log the GitHub repo being accessed
|
||||
access_log /var/log/nginx/devcontainer-access.log combined;
|
||||
|
||||
# Set additional headers for audit/monitoring
|
||||
proxy_set_header X-GitHub-Repo-Requested https://github.com/$1;
|
||||
proxy_set_header X-Request-Timestamp $time_iso8601;
|
||||
proxy_set_header X-Client-IP $remote_addr;
|
||||
}
|
||||
|
||||
---
|
||||
# Policy for controlling access (optional - can be configured via Authentik UI)
|
||||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
name: authentik-devcontainer-policies
|
||||
namespace: authentik
|
||||
data:
|
||||
# Example group-based access policy
|
||||
group-access-policy.yaml: |
|
||||
name: DevContainer Access Policy
|
||||
policy_type: group_membership
|
||||
groups:
|
||||
- developers
|
||||
- devops
|
||||
- admins
|
||||
|
||||
# Example expression policy for advanced access control
|
||||
repo-access-policy.yaml: |
|
||||
name: Repository Access Policy
|
||||
policy_type: expression
|
||||
expression: |
|
||||
# Allow access to public repositories for all authenticated users
|
||||
# Require specific groups for private repositories
|
||||
|
||||
github_repo = request.http_request.headers.get('X-GitHub-Repo', '')
|
||||
|
||||
# Check if user has access to private repositories
|
||||
if 'private-repo-access' in user.ak_groups.values_list('name', flat=True):
|
||||
return True
|
||||
|
||||
# For now, allow all authenticated users to access any repository
|
||||
# You can customize this based on your needs
|
||||
return True
|
||||
|
||||
---
|
||||
# Service Monitor for Prometheus (optional)
|
||||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
name: authentik-devcontainer-monitoring
|
||||
namespace: authentik
|
||||
data:
|
||||
servicemonitor.yaml: |
|
||||
apiVersion: monitoring.coreos.com/v1
|
||||
kind: ServiceMonitor
|
||||
metadata:
|
||||
name: devcontainer-authentik
|
||||
namespace: authentik
|
||||
spec:
|
||||
selector:
|
||||
matchLabels:
|
||||
app.kubernetes.io/name: authentik
|
||||
endpoints:
|
||||
- port: http
|
||||
interval: 30s
|
||||
path: /metrics
|
||||
@@ -0,0 +1,248 @@
|
||||
---
|
||||
# Namespace for serverless components
|
||||
apiVersion: v1
|
||||
kind: Namespace
|
||||
metadata:
|
||||
name: devcontainers
|
||||
labels:
|
||||
app.kubernetes.io/name: devcontainer
|
||||
app.kubernetes.io/component: serverless
|
||||
|
||||
---
|
||||
# Secret for GitHub tokens, VNC passwords, etc.
|
||||
apiVersion: v1
|
||||
kind: Secret
|
||||
metadata:
|
||||
name: devcontainer-serverless-secrets
|
||||
namespace: devcontainers
|
||||
type: Opaque
|
||||
stringData:
|
||||
# Update these values as needed
|
||||
GITHUB_TOKEN: ""
|
||||
VNC_PASSWORD: "changeme"
|
||||
ANTHROPIC_API_KEY: ""
|
||||
GIT_USER_NAME: "DevContainer User"
|
||||
GIT_USER_EMAIL: "devcontainer@example.com"
|
||||
|
||||
---
|
||||
# Routing proxy deployment (handles GitHub repo extraction)
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: devcontainer-routing-proxy
|
||||
namespace: devcontainers
|
||||
labels:
|
||||
app.kubernetes.io/name: devcontainer
|
||||
app.kubernetes.io/component: routing-proxy
|
||||
spec:
|
||||
replicas: 2 # High availability
|
||||
selector:
|
||||
matchLabels:
|
||||
app.kubernetes.io/name: devcontainer
|
||||
app.kubernetes.io/component: routing-proxy
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app.kubernetes.io/name: devcontainer
|
||||
app.kubernetes.io/component: routing-proxy
|
||||
spec:
|
||||
containers:
|
||||
- name: routing-proxy
|
||||
image: ghcr.io/cpfarhood/devcontainer-routing-proxy:latest
|
||||
ports:
|
||||
- containerPort: 8080
|
||||
name: http
|
||||
env:
|
||||
- name: DEVCONTAINER_SERVICE_URL
|
||||
value: "devcontainer-serverless.devcontainers.svc.cluster.local"
|
||||
resources:
|
||||
requests:
|
||||
memory: "64Mi"
|
||||
cpu: "100m"
|
||||
limits:
|
||||
memory: "256Mi"
|
||||
cpu: "500m"
|
||||
livenessProbe:
|
||||
httpGet:
|
||||
path: /health
|
||||
port: 8080
|
||||
initialDelaySeconds: 5
|
||||
periodSeconds: 10
|
||||
readinessProbe:
|
||||
httpGet:
|
||||
path: /health
|
||||
port: 8080
|
||||
initialDelaySeconds: 2
|
||||
periodSeconds: 5
|
||||
|
||||
---
|
||||
# Service for routing proxy
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: devcontainer-routing-proxy
|
||||
namespace: devcontainers
|
||||
labels:
|
||||
app.kubernetes.io/name: devcontainer
|
||||
app.kubernetes.io/component: routing-proxy
|
||||
spec:
|
||||
type: ClusterIP
|
||||
ports:
|
||||
- port: 80
|
||||
targetPort: 8080
|
||||
name: http
|
||||
selector:
|
||||
app.kubernetes.io/name: devcontainer
|
||||
app.kubernetes.io/component: routing-proxy
|
||||
|
||||
---
|
||||
# Knative Service (auto-scaling devcontainer instances)
|
||||
apiVersion: serving.knative.dev/v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: devcontainer-serverless
|
||||
namespace: devcontainers
|
||||
annotations:
|
||||
# Scale to zero when not in use (saves resources)
|
||||
autoscaling.knative.dev/minScale: "0"
|
||||
autoscaling.knative.dev/maxScale: "10"
|
||||
# Keep instances warm for 5 minutes after last request
|
||||
autoscaling.knative.dev/scale-to-zero-grace-period: "5m"
|
||||
# Target 1 concurrent request per pod (ensures isolation)
|
||||
autoscaling.knative.dev/target: "1"
|
||||
# Custom domain (optional - configure after Authentik setup)
|
||||
# serving.knative.dev/domain: "devcontainer.farh.net"
|
||||
spec:
|
||||
template:
|
||||
metadata:
|
||||
annotations:
|
||||
# Container port for VNC web interface
|
||||
autoscaling.knative.dev/targetPort: "5800"
|
||||
# Timeout for cold starts (dev containers need time to initialize)
|
||||
serving.knative.dev/timeoutSeconds: "600" # 10 minutes for repo cloning
|
||||
# Resource allocation per instance
|
||||
autoscaling.knative.dev/class: "kpa.autoscaling.knative.dev"
|
||||
autoscaling.knative.dev/metric: "concurrency"
|
||||
spec:
|
||||
# Give containers more time to start (repo cloning + IDE launch)
|
||||
timeoutSeconds: 600 # 10 minutes
|
||||
containers:
|
||||
- name: devcontainer
|
||||
image: ghcr.io/cpfarhood/devcontainer:latest
|
||||
ports:
|
||||
- containerPort: 5800
|
||||
name: vnc-web
|
||||
env:
|
||||
# Flag to indicate serverless mode
|
||||
- name: SERVERLESS_MODE
|
||||
value: "true"
|
||||
- name: DYNAMIC_GITHUB_ROUTING
|
||||
value: "true"
|
||||
- name: IDE
|
||||
value: "vscode"
|
||||
- name: DISPLAY_WIDTH
|
||||
value: "1920"
|
||||
- name: DISPLAY_HEIGHT
|
||||
value: "1080"
|
||||
- name: SECURE_CONNECTION
|
||||
value: "0"
|
||||
- name: USER_ID
|
||||
value: "1000"
|
||||
- name: GROUP_ID
|
||||
value: "1000"
|
||||
# Enable file manager for easy upload/download
|
||||
- name: WEB_FILE_MANAGER
|
||||
value: "1"
|
||||
- name: WEB_FILE_MANAGER_ALLOWED_PATHS
|
||||
value: "/workspace,/config"
|
||||
# Happy Coder config (ephemeral in serverless mode)
|
||||
- name: HAPPY_HOME_DIR
|
||||
value: "/tmp/.happy"
|
||||
- name: HAPPY_EXPERIMENTAL
|
||||
value: "true"
|
||||
# Use secrets for sensitive data
|
||||
envFrom:
|
||||
- secretRef:
|
||||
name: devcontainer-serverless-secrets
|
||||
optional: false
|
||||
resources:
|
||||
requests:
|
||||
memory: "1Gi"
|
||||
cpu: "500m"
|
||||
limits:
|
||||
memory: "4Gi"
|
||||
cpu: "2000m"
|
||||
volumeMounts:
|
||||
- name: tmp-home
|
||||
mountPath: /config
|
||||
- name: shm
|
||||
mountPath: /dev/shm
|
||||
# Readiness probe - VNC must be ready
|
||||
readinessProbe:
|
||||
httpGet:
|
||||
path: /
|
||||
port: 5800
|
||||
initialDelaySeconds: 60
|
||||
periodSeconds: 10
|
||||
timeoutSeconds: 5
|
||||
failureThreshold: 10
|
||||
# Liveness probe - ensure container stays healthy
|
||||
livenessProbe:
|
||||
httpGet:
|
||||
path: /
|
||||
port: 5800
|
||||
initialDelaySeconds: 120
|
||||
periodSeconds: 30
|
||||
timeoutSeconds: 10
|
||||
failureThreshold: 3
|
||||
volumes:
|
||||
- name: tmp-home
|
||||
emptyDir: {} # Ephemeral - each instance gets fresh home
|
||||
- name: shm
|
||||
emptyDir:
|
||||
medium: Memory
|
||||
sizeLimit: 2Gi
|
||||
|
||||
---
|
||||
# Ingress for the routing proxy (will be secured by Authentik)
|
||||
apiVersion: networking.k8s.io/v1
|
||||
kind: Ingress
|
||||
metadata:
|
||||
name: devcontainer-serverless-ingress
|
||||
namespace: devcontainers
|
||||
annotations:
|
||||
# Authentik forward auth annotations
|
||||
nginx.ingress.kubernetes.io/auth-url: http://authentik.authentik.svc.cluster.local/outpost.goauthentik.io/auth/nginx
|
||||
nginx.ingress.kubernetes.io/auth-signin: https://auth.farh.net/outpost.goauthentik.io/start?rd=$escaped_request_uri
|
||||
nginx.ingress.kubernetes.io/auth-response-headers: X-Authentik-Username,X-Authentik-Groups,X-Authentik-Email,X-Authentik-Name
|
||||
nginx.ingress.kubernetes.io/auth-snippet: |
|
||||
proxy_set_header X-Forwarded-Host $http_host;
|
||||
|
||||
# SSL and general settings
|
||||
cert-manager.io/cluster-issuer: "letsencrypt-prod"
|
||||
nginx.ingress.kubernetes.io/ssl-redirect: "true"
|
||||
nginx.ingress.kubernetes.io/force-ssl-redirect: "true"
|
||||
|
||||
# WebSocket support for VNC
|
||||
nginx.ingress.kubernetes.io/proxy-read-timeout: "3600"
|
||||
nginx.ingress.kubernetes.io/proxy-send-timeout: "3600"
|
||||
|
||||
# Large file upload support (for file manager)
|
||||
nginx.ingress.kubernetes.io/client-max-body-size: "100m"
|
||||
nginx.ingress.kubernetes.io/proxy-body-size: "100m"
|
||||
spec:
|
||||
tls:
|
||||
- hosts:
|
||||
- devcontainer.farh.net
|
||||
secretName: devcontainer-serverless-tls
|
||||
rules:
|
||||
- host: devcontainer.farh.net
|
||||
http:
|
||||
paths:
|
||||
- path: /
|
||||
pathType: Prefix
|
||||
backend:
|
||||
service:
|
||||
name: devcontainer-routing-proxy
|
||||
port:
|
||||
number: 80
|
||||
@@ -0,0 +1,112 @@
|
||||
apiVersion: serving.knative.dev/v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: devcontainer-serverless
|
||||
namespace: devcontainers
|
||||
annotations:
|
||||
# Scale to zero when not in use (saves resources)
|
||||
autoscaling.knative.dev/minScale: "0"
|
||||
autoscaling.knative.dev/maxScale: "10"
|
||||
# Keep instances warm for 5 minutes after last request
|
||||
autoscaling.knative.dev/scale-to-zero-grace-period: "5m"
|
||||
# Target 1 concurrent request per pod (ensures isolation)
|
||||
autoscaling.knative.dev/target: "1"
|
||||
spec:
|
||||
template:
|
||||
metadata:
|
||||
annotations:
|
||||
# Container port for VNC web interface
|
||||
autoscaling.knative.dev/targetPort: "5800"
|
||||
# Timeout for cold starts (dev containers need time to initialize)
|
||||
serving.knative.dev/timeoutSeconds: "300"
|
||||
spec:
|
||||
# Give containers more time to start (repo cloning + IDE launch)
|
||||
timeoutSeconds: 300
|
||||
containers:
|
||||
- name: devcontainer
|
||||
image: ghcr.io/cpfarhood/devcontainer:latest
|
||||
ports:
|
||||
- containerPort: 5800
|
||||
name: vnc-web
|
||||
env:
|
||||
# Dynamic repo extraction will be handled by a startup script
|
||||
- name: DYNAMIC_GITHUB_ROUTING
|
||||
value: "true"
|
||||
- name: IDE
|
||||
value: "vscode"
|
||||
- name: DISPLAY_WIDTH
|
||||
value: "1920"
|
||||
- name: DISPLAY_HEIGHT
|
||||
value: "1080"
|
||||
- name: SECURE_CONNECTION
|
||||
value: "0"
|
||||
- name: USER_ID
|
||||
value: "1000"
|
||||
- name: GROUP_ID
|
||||
value: "1000"
|
||||
# Enable file manager for easy upload/download
|
||||
- name: WEB_FILE_MANAGER
|
||||
value: "1"
|
||||
- name: WEB_FILE_MANAGER_ALLOWED_PATHS
|
||||
value: "/workspace,/config"
|
||||
# Happy Coder config
|
||||
- name: HAPPY_HOME_DIR
|
||||
value: "/config/userdata/.happy"
|
||||
- name: HAPPY_EXPERIMENTAL
|
||||
value: "true"
|
||||
# Use secrets for sensitive data
|
||||
envFrom:
|
||||
- secretRef:
|
||||
name: devcontainer-serverless-secrets
|
||||
optional: true
|
||||
resources:
|
||||
requests:
|
||||
memory: "1Gi"
|
||||
cpu: "500m"
|
||||
limits:
|
||||
memory: "4Gi"
|
||||
cpu: "2000m"
|
||||
volumeMounts:
|
||||
- name: userhome
|
||||
mountPath: /config
|
||||
- name: shm
|
||||
mountPath: /dev/shm
|
||||
# Readiness probe - VNC must be ready
|
||||
readinessProbe:
|
||||
httpGet:
|
||||
path: /
|
||||
port: 5800
|
||||
initialDelaySeconds: 30
|
||||
periodSeconds: 5
|
||||
timeoutSeconds: 3
|
||||
# Liveness probe - ensure container stays healthy
|
||||
livenessProbe:
|
||||
httpGet:
|
||||
path: /
|
||||
port: 5800
|
||||
initialDelaySeconds: 60
|
||||
periodSeconds: 10
|
||||
timeoutSeconds: 5
|
||||
volumes:
|
||||
- name: userhome
|
||||
emptyDir: {} # Ephemeral - each instance gets fresh home
|
||||
- name: shm
|
||||
emptyDir:
|
||||
medium: Memory
|
||||
sizeLimit: 2Gi
|
||||
---
|
||||
# Secret template for GitHub tokens, VNC passwords, etc.
|
||||
apiVersion: v1
|
||||
kind: Secret
|
||||
metadata:
|
||||
name: devcontainer-serverless-secrets
|
||||
namespace: devcontainers
|
||||
type: Opaque
|
||||
data:
|
||||
# Base64 encoded values - update as needed
|
||||
# echo -n "your-github-token" | base64
|
||||
GITHUB_TOKEN: ""
|
||||
# echo -n "your-vnc-password" | base64
|
||||
VNC_PASSWORD: ""
|
||||
# echo -n "your-anthropic-key" | base64
|
||||
ANTHROPIC_API_KEY: ""
|
||||
@@ -0,0 +1,16 @@
|
||||
# Lightweight routing proxy for dynamic GitHub repo routing
|
||||
FROM nginx:1.27-alpine
|
||||
|
||||
# Install envsubst for template rendering
|
||||
RUN apk add --no-cache gettext
|
||||
|
||||
# Copy nginx configuration template
|
||||
COPY nginx.conf.template /etc/nginx/nginx.conf.template
|
||||
COPY entrypoint.sh /entrypoint.sh
|
||||
|
||||
RUN chmod +x /entrypoint.sh
|
||||
|
||||
EXPOSE 8080
|
||||
|
||||
ENTRYPOINT ["/entrypoint.sh"]
|
||||
CMD ["nginx", "-g", "daemon off;"]
|
||||
@@ -0,0 +1,16 @@
|
||||
#!/bin/sh
|
||||
|
||||
# Set default values for environment variables
|
||||
DEVCONTAINER_SERVICE_URL=${DEVCONTAINER_SERVICE_URL:-"devcontainer-serverless.devcontainers.svc.cluster.local"}
|
||||
|
||||
# Create temp directories
|
||||
mkdir -p /tmp/client_temp /tmp/proxy_temp /tmp/fastcgi_temp /tmp/uwsgi_temp /tmp/scgi_temp
|
||||
|
||||
# Substitute environment variables in nginx config
|
||||
envsubst '$DEVCONTAINER_SERVICE_URL' < /etc/nginx/nginx.conf.template > /etc/nginx/nginx.conf
|
||||
|
||||
echo "Starting routing proxy..."
|
||||
echo "Routing to: $DEVCONTAINER_SERVICE_URL"
|
||||
|
||||
# Start nginx
|
||||
exec "$@"
|
||||
@@ -0,0 +1,124 @@
|
||||
worker_processes auto;
|
||||
error_log /var/log/nginx/error.log warn;
|
||||
pid /tmp/nginx.pid;
|
||||
|
||||
events {
|
||||
worker_connections 1024;
|
||||
}
|
||||
|
||||
http {
|
||||
include /etc/nginx/mime.types;
|
||||
default_type application/octet-stream;
|
||||
|
||||
# Logging format
|
||||
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
|
||||
'$status $body_bytes_sent "$http_referer" '
|
||||
'"$http_user_agent" "$http_x_forwarded_for" '
|
||||
'repo="$github_repo" user="$authentik_user"';
|
||||
|
||||
access_log /var/log/nginx/access.log main;
|
||||
|
||||
# Basic settings
|
||||
sendfile on;
|
||||
tcp_nopush on;
|
||||
tcp_nodelay on;
|
||||
keepalive_timeout 65;
|
||||
types_hash_max_size 2048;
|
||||
client_max_body_size 100M; # Allow large file uploads via file manager
|
||||
|
||||
# Temp directories (writable in container)
|
||||
client_body_temp_path /tmp/client_temp;
|
||||
proxy_temp_path /tmp/proxy_temp;
|
||||
fastcgi_temp_path /tmp/fastcgi_temp;
|
||||
uwsgi_temp_path /tmp/uwsgi_temp;
|
||||
scgi_temp_path /tmp/scgi_temp;
|
||||
|
||||
# Upstream Knative service (will be resolved by Knative networking)
|
||||
upstream devcontainer_serverless {
|
||||
server ${DEVCONTAINER_SERVICE_URL};
|
||||
}
|
||||
|
||||
# Map to extract GitHub repo from URL path
|
||||
map $request_uri $github_repo {
|
||||
~^/github/([^/]+/[^/]+)(/.*)?$ https://github.com/$1;
|
||||
default "";
|
||||
}
|
||||
|
||||
# Extract Authentik user info from headers (set by Authentik forward auth)
|
||||
map $http_x_authentik_username $authentik_user {
|
||||
default $http_x_authentik_username;
|
||||
}
|
||||
|
||||
server {
|
||||
listen 8080;
|
||||
server_name _;
|
||||
|
||||
# Health check endpoint
|
||||
location /health {
|
||||
access_log off;
|
||||
return 200 "OK\n";
|
||||
add_header Content-Type text/plain;
|
||||
}
|
||||
|
||||
# GitHub repo routing
|
||||
location ~ ^/github/([^/]+/[^/]+)(/.*)?$ {
|
||||
# Validate the repo format
|
||||
if ($github_repo = "") {
|
||||
return 400 "Invalid GitHub repository format. Use: /github/owner/repo\n";
|
||||
}
|
||||
|
||||
# Log the routing decision
|
||||
access_log /var/log/nginx/routing.log main;
|
||||
|
||||
# Set headers for the devcontainer
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
|
||||
# Custom headers for dynamic repo routing
|
||||
proxy_set_header X-GitHub-Repo $github_repo;
|
||||
proxy_set_header X-Authentik-User $authentik_user;
|
||||
proxy_set_header X-Request-Path $request_uri;
|
||||
|
||||
# Preserve Authentik auth headers
|
||||
proxy_set_header X-Authentik-Username $http_x_authentik_username;
|
||||
proxy_set_header X-Authentik-Email $http_x_authentik_email;
|
||||
proxy_set_header X-Authentik-Name $http_x_authentik_name;
|
||||
proxy_set_header X-Authentik-Groups $http_x_authentik_groups;
|
||||
|
||||
# Proxy settings for long-running connections (VNC)
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Upgrade $http_upgrade;
|
||||
proxy_set_header Connection $connection_upgrade;
|
||||
proxy_read_timeout 86400; # 24 hours
|
||||
proxy_send_timeout 86400;
|
||||
proxy_connect_timeout 30;
|
||||
|
||||
# Buffer settings for file uploads
|
||||
proxy_buffering off;
|
||||
proxy_request_buffering off;
|
||||
|
||||
# Forward to the devcontainer
|
||||
proxy_pass http://devcontainer_serverless$2;
|
||||
}
|
||||
|
||||
# Root path - show available repositories or redirect to auth
|
||||
location = / {
|
||||
return 200 "DevContainer Serverless\nUsage: /github/{owner}/{repo}\nExample: /github/microsoft/vscode\n";
|
||||
add_header Content-Type text/plain;
|
||||
}
|
||||
|
||||
# Anything else
|
||||
location / {
|
||||
return 404 "Not found. Use /github/{owner}/{repo} to access repositories.\n";
|
||||
add_header Content-Type text/plain;
|
||||
}
|
||||
}
|
||||
|
||||
# WebSocket upgrade handling
|
||||
map $http_upgrade $connection_upgrade {
|
||||
default upgrade;
|
||||
'' close;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,124 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Dynamic GitHub repository initialization for serverless mode
|
||||
# This script extracts the GitHub repo from HTTP headers set by the routing proxy
|
||||
|
||||
set -e
|
||||
|
||||
log() {
|
||||
echo "[$(date '+%Y-%m-%d %H:%M:%S')] DYNAMIC-INIT: $*" >&2
|
||||
}
|
||||
|
||||
log "Starting dynamic repository initialization..."
|
||||
|
||||
# In serverless mode, we expect the routing proxy to have set these environment variables
|
||||
# from the HTTP headers. If running standalone, fallback to GITHUB_REPO env var.
|
||||
|
||||
if [[ "$SERVERLESS_MODE" == "true" ]]; then
|
||||
log "Serverless mode detected"
|
||||
|
||||
# The routing proxy should have set these via HTTP headers -> env vars
|
||||
# Check if we have the GitHub repo from the X-GitHub-Repo header
|
||||
if [[ -n "$HTTP_X_GITHUB_REPO" ]]; then
|
||||
GITHUB_REPO="$HTTP_X_GITHUB_REPO"
|
||||
log "Using GitHub repo from header: $GITHUB_REPO"
|
||||
elif [[ -n "$X_GITHUB_REPO" ]]; then
|
||||
GITHUB_REPO="$X_GITHUB_REPO"
|
||||
log "Using GitHub repo from X-GitHub-Repo: $GITHUB_REPO"
|
||||
else
|
||||
# Try to extract from a file written by an init container or sidecar
|
||||
if [[ -f "/tmp/github-repo" ]]; then
|
||||
GITHUB_REPO=$(cat /tmp/github-repo)
|
||||
log "Using GitHub repo from file: $GITHUB_REPO"
|
||||
else
|
||||
log "ERROR: No GitHub repository specified in serverless mode"
|
||||
log "Expected HTTP_X_GITHUB_REPO or X_GITHUB_REPO header from routing proxy"
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
# Extract user info if available
|
||||
if [[ -n "$HTTP_X_AUTHENTIK_USERNAME" ]]; then
|
||||
export GIT_USER_NAME="${HTTP_X_AUTHENTIK_NAME:-$HTTP_X_AUTHENTIK_USERNAME}"
|
||||
export GIT_USER_EMAIL="${HTTP_X_AUTHENTIK_EMAIL:-${HTTP_X_AUTHENTIK_USERNAME}@devcontainer.local}"
|
||||
log "Using Authentik user: $GIT_USER_NAME <$GIT_USER_EMAIL>"
|
||||
fi
|
||||
else
|
||||
log "Traditional mode - using GITHUB_REPO environment variable"
|
||||
if [[ -z "$GITHUB_REPO" ]]; then
|
||||
log "ERROR: GITHUB_REPO environment variable is required"
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
# Validate the GitHub repo URL
|
||||
if [[ ! "$GITHUB_REPO" =~ ^https://github\.com/[^/]+/[^/]+/?$ ]]; then
|
||||
log "ERROR: Invalid GitHub repository URL: $GITHUB_REPO"
|
||||
log "Expected format: https://github.com/owner/repo"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Extract owner and repo name for workspace directory
|
||||
REPO_OWNER=$(echo "$GITHUB_REPO" | sed 's|https://github.com/\([^/]*\)/.*|\1|')
|
||||
REPO_NAME=$(echo "$GITHUB_REPO" | sed 's|https://github.com/[^/]*/\([^/]*\)/?|\1|')
|
||||
WORKSPACE_DIR="/workspace/${REPO_OWNER}-${REPO_NAME}"
|
||||
|
||||
log "Repository: $GITHUB_REPO"
|
||||
log "Owner: $REPO_OWNER"
|
||||
log "Name: $REPO_NAME"
|
||||
log "Workspace: $WORKSPACE_DIR"
|
||||
|
||||
# Configure git user (use defaults if not set via Authentik)
|
||||
GIT_USER_NAME="${GIT_USER_NAME:-DevContainer User}"
|
||||
GIT_USER_EMAIL="${GIT_USER_EMAIL:-devcontainer@example.com}"
|
||||
|
||||
log "Configuring git user: $GIT_USER_NAME <$GIT_USER_EMAIL>"
|
||||
git config --global user.name "$GIT_USER_NAME"
|
||||
git config --global user.email "$GIT_USER_EMAIL"
|
||||
|
||||
# Configure git credentials if GitHub token is available
|
||||
if [[ -n "$GITHUB_TOKEN" ]]; then
|
||||
log "Configuring GitHub credentials..."
|
||||
git config --global credential.helper store
|
||||
echo "https://oauth2:${GITHUB_TOKEN}@github.com" > ~/.git-credentials
|
||||
chmod 600 ~/.git-credentials
|
||||
else
|
||||
log "No GitHub token provided - using public access only"
|
||||
fi
|
||||
|
||||
# Create workspace directory
|
||||
mkdir -p "$(dirname "$WORKSPACE_DIR")"
|
||||
cd "$(dirname "$WORKSPACE_DIR")"
|
||||
|
||||
# Clone the repository
|
||||
if [[ -d "$WORKSPACE_DIR" ]]; then
|
||||
log "Repository directory exists, pulling latest changes..."
|
||||
cd "$WORKSPACE_DIR"
|
||||
git pull --ff-only || {
|
||||
log "WARNING: Could not fast-forward, repository may have diverged"
|
||||
log "Continuing with existing state..."
|
||||
}
|
||||
else
|
||||
log "Cloning repository..."
|
||||
git clone "$GITHUB_REPO" "$WORKSPACE_DIR" || {
|
||||
log "ERROR: Failed to clone repository $GITHUB_REPO"
|
||||
log "This may be a private repository or the URL may be incorrect"
|
||||
exit 1
|
||||
}
|
||||
cd "$WORKSPACE_DIR"
|
||||
fi
|
||||
|
||||
# Set the workspace directory for the IDE
|
||||
export WORKSPACE_DIR
|
||||
|
||||
log "Repository initialization complete!"
|
||||
log "Workspace directory: $WORKSPACE_DIR"
|
||||
|
||||
# Change to the workspace directory so the IDE opens in the right place
|
||||
cd "$WORKSPACE_DIR"
|
||||
|
||||
# Export variables for the parent script
|
||||
export GITHUB_REPO
|
||||
export WORKSPACE_DIR
|
||||
export REPO_OWNER
|
||||
export REPO_NAME
|
||||
@@ -0,0 +1,86 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Serverless-aware startup script for devcontainer
|
||||
# This replaces the standard /startapp.sh when in serverless mode
|
||||
|
||||
set -e
|
||||
|
||||
log() {
|
||||
echo "[$(date '+%Y-%m-%d %H:%M:%S')] SERVERLESS-START: $*" >&2
|
||||
}
|
||||
|
||||
log "Starting serverless devcontainer..."
|
||||
log "Mode: ${SERVERLESS_MODE:-traditional}"
|
||||
log "IDE: ${IDE:-vscode}"
|
||||
|
||||
# Wait for HTTP headers to be available (in case of init container pattern)
|
||||
# In Knative, the headers should be available immediately as env vars
|
||||
sleep 2
|
||||
|
||||
# Check if we're in serverless mode with dynamic routing
|
||||
if [[ "$SERVERLESS_MODE" == "true" && "$DYNAMIC_GITHUB_ROUTING" == "true" ]]; then
|
||||
log "Dynamic GitHub routing enabled"
|
||||
|
||||
# In Knative, HTTP headers become environment variables with HTTP_ prefix
|
||||
# But we also check for the unprefixed versions set by proxies
|
||||
AVAILABLE_VARS=$(env | grep -E "(GITHUB|AUTHENTIK|X_)" | sort)
|
||||
if [[ -n "$AVAILABLE_VARS" ]]; then
|
||||
log "Available routing variables:"
|
||||
echo "$AVAILABLE_VARS" | while read -r var; do
|
||||
log " $var"
|
||||
done
|
||||
else
|
||||
log "No routing variables found, checking for alternatives..."
|
||||
# Check if there's a file with the repo info
|
||||
if [[ -f "/tmp/github-repo" ]]; then
|
||||
export GITHUB_REPO=$(cat /tmp/github-repo)
|
||||
log "Found repo file: $GITHUB_REPO"
|
||||
else
|
||||
log "ERROR: No GitHub repository information available"
|
||||
log "Expected routing headers or /tmp/github-repo file"
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
# Use the dynamic initialization script
|
||||
source /usr/local/bin/dynamic-init-repo
|
||||
else
|
||||
log "Using standard initialization..."
|
||||
# Use the standard initialization
|
||||
source /usr/local/bin/init-repo
|
||||
fi
|
||||
|
||||
# At this point, WORKSPACE_DIR should be set by the init script
|
||||
WORKSPACE_DIR="${WORKSPACE_DIR:-/workspace}"
|
||||
log "Working directory: $WORKSPACE_DIR"
|
||||
|
||||
# Ensure we're in the workspace directory
|
||||
cd "$WORKSPACE_DIR"
|
||||
|
||||
# Launch the appropriate IDE based on the IDE environment variable
|
||||
case "${IDE:-vscode}" in
|
||||
"vscode")
|
||||
log "Starting VSCode..."
|
||||
exec code --new-window --wait "$WORKSPACE_DIR"
|
||||
;;
|
||||
"antigravity")
|
||||
log "Starting Antigravity..."
|
||||
exec antigravity \
|
||||
--no-sandbox \
|
||||
--user-data-dir ~/.config/antigravity \
|
||||
--disable-dev-shm-usage \
|
||||
--disable-gpu \
|
||||
--disable-features=VizDisplayCompositor \
|
||||
--new-window \
|
||||
"$WORKSPACE_DIR"
|
||||
;;
|
||||
"none")
|
||||
log "No IDE requested, keeping container alive..."
|
||||
exec sleep infinity
|
||||
;;
|
||||
*)
|
||||
log "ERROR: Unknown IDE type: $IDE"
|
||||
log "Valid options: vscode, antigravity, none"
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
Reference in New Issue
Block a user