Compare commits
21 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| b27487d844 | |||
| 24b31e061a | |||
| aad4ec4287 | |||
| 4915fce118 | |||
| 7940419c4e | |||
| 9ddbcc1e68 | |||
| 07a6baefd7 | |||
| 720e6d7de9 | |||
| c89d7e4ed6 | |||
| 80763b817d | |||
| 2bb60745ce | |||
| 46cef775b1 | |||
| b83265f131 | |||
| 695850469d | |||
| eca8d10772 | |||
| 0eaf1645e1 | |||
| 8b8af85558 | |||
| a9593a763f | |||
| 129f49abe8 | |||
| 58ee2d6770 | |||
| 955f26ab37 |
@@ -1,6 +0,0 @@
|
||||
{
|
||||
"enabledPlugins": {
|
||||
"voltagent-dev-exp@voltagent-subagents": true,
|
||||
"voltagent-lang@voltagent-subagents": true
|
||||
}
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
{
|
||||
"enabledMcpjsonServers": [
|
||||
"kubernetes",
|
||||
"flux",
|
||||
"playwright",
|
||||
"github",
|
||||
"pgtuner",
|
||||
"fetch",
|
||||
"sequentialthinking"
|
||||
],
|
||||
"permissions": {
|
||||
"allow": [
|
||||
"Bash(git add .claude/settings.local.json .claude/settings.json && git commit -m \"$\\(cat <<'EOF'\nchore: update Claude Code settings and enable voltagent plugins\n\nAdd fetch and sequentialthinking MCP servers to allowed list, and enable\nvoltagent dev-exp and lang subagent plugins.\n\nCo-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>\nEOF\n\\)\" && git status)"
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -1,24 +0,0 @@
|
||||
.git
|
||||
.gitignore
|
||||
.dockerignore
|
||||
*.md
|
||||
README.md
|
||||
LICENSE
|
||||
Makefile
|
||||
docker-compose.yml
|
||||
|
||||
# Kubernetes files
|
||||
k8s/
|
||||
|
||||
# Local development
|
||||
home/
|
||||
workspace/
|
||||
*.log
|
||||
|
||||
# IDE
|
||||
.vscode/
|
||||
.idea/
|
||||
|
||||
# OS
|
||||
.DS_Store
|
||||
Thumbs.db
|
||||
@@ -1,36 +0,0 @@
|
||||
## Description
|
||||
|
||||
<!-- Describe your changes in detail -->
|
||||
|
||||
## Type of Change
|
||||
|
||||
<!-- Mark with an `x` all that apply -->
|
||||
|
||||
- [ ] Bug fix (non-breaking change which fixes an issue)
|
||||
- [ ] New feature (non-breaking change which adds functionality)
|
||||
- [ ] Breaking change (fix or feature that would cause existing functionality to not work as expected)
|
||||
- [ ] Documentation update
|
||||
- [ ] CI/CD update
|
||||
|
||||
## Testing
|
||||
|
||||
<!-- Describe the tests you ran to verify your changes -->
|
||||
|
||||
- [ ] Built Docker image locally
|
||||
- [ ] Tested container startup
|
||||
- [ ] Tested repository cloning
|
||||
- [ ] Tested Happy Coder integration
|
||||
- [ ] Tested VNC web interface
|
||||
|
||||
## Checklist
|
||||
|
||||
- [ ] My code follows the style guidelines of this project
|
||||
- [ ] I have performed a self-review of my own code
|
||||
- [ ] I have commented my code, particularly in hard-to-understand areas
|
||||
- [ ] I have made corresponding changes to the documentation
|
||||
- [ ] My changes generate no new warnings
|
||||
- [ ] I have tested that the Docker image builds successfully
|
||||
|
||||
## Screenshots (if applicable)
|
||||
|
||||
<!-- Add screenshots to help explain your changes -->
|
||||
@@ -1,93 +0,0 @@
|
||||
# CI/CD Pipeline Guide
|
||||
|
||||
## 🚀 Simplified Pipeline - Only 3 Workflows!
|
||||
|
||||
### 1️⃣ For Releases → **Unified Release**
|
||||
Use this for all version releases:
|
||||
1. Go to [Actions → Unified Release](https://github.com/cpfarhood/devcontainer/actions/workflows/release-unified.yaml)
|
||||
2. Click "Run workflow"
|
||||
3. Either:
|
||||
- Enter specific version (e.g., `0.2.1`), OR
|
||||
- Choose release type (patch/minor/major) for auto-increment
|
||||
4. Click "Run workflow"
|
||||
|
||||
**This single workflow does EVERYTHING:**
|
||||
- ✅ Updates chart version
|
||||
- ✅ Creates git tag
|
||||
- ✅ Builds Docker image with all proper tags
|
||||
- ✅ Publishes Helm chart to GitHub Pages (`https://cpfarhood.github.io/devcontainer`)
|
||||
- ✅ Creates GitHub Release with changelog
|
||||
|
||||
### 2️⃣ For Quick Fixes → **Quick Fix Build**
|
||||
Use this for emergency fixes without version changes:
|
||||
1. Go to [Actions → Quick Fix Build](https://github.com/cpfarhood/devcontainer/actions/workflows/quick-fix.yaml)
|
||||
2. Click "Run workflow"
|
||||
3. Enter tag (default: `latest`)
|
||||
4. Click "Run workflow"
|
||||
|
||||
**Just builds and pushes Docker image** - no version bumps, no releases.
|
||||
|
||||
### 3️⃣ Automatic CI → **Build and Push**
|
||||
Runs automatically on:
|
||||
- Pushes to `main` (builds and pushes; skipped for release commits via `[skip ci]`)
|
||||
- Pull requests (builds but doesn't push)
|
||||
- Manual trigger available
|
||||
|
||||
## Workflow Files
|
||||
|
||||
| Workflow | File | Purpose | When to Use |
|
||||
|----------|------|---------|-------------|
|
||||
| **Unified Release** | `release-unified.yaml` | Full release process | New versions |
|
||||
| **Quick Fix Build** | `quick-fix.yaml` | Docker build only | Hotfixes |
|
||||
| **Build and Push** | `build-and-push.yaml` | CI/CD automation | PRs & tags |
|
||||
|
||||
## Examples
|
||||
|
||||
### Release a new version
|
||||
```bash
|
||||
# Via GitHub UI (Recommended):
|
||||
# Go to Actions → Unified Release → Run workflow
|
||||
|
||||
# Via GitHub CLI:
|
||||
gh workflow run release-unified.yaml -f version=0.2.1
|
||||
# OR auto-increment:
|
||||
gh workflow run release-unified.yaml -f release_type=patch
|
||||
```
|
||||
|
||||
### Push a quick fix
|
||||
```bash
|
||||
# Via GitHub UI:
|
||||
# Go to Actions → Quick Fix Build → Run workflow
|
||||
|
||||
# Via GitHub CLI:
|
||||
gh workflow run quick-fix.yaml -f tag=hotfix-1
|
||||
```
|
||||
|
||||
### Check workflow status
|
||||
```bash
|
||||
# List all recent runs
|
||||
gh run list --limit 5
|
||||
|
||||
# Watch a specific workflow
|
||||
gh run watch
|
||||
```
|
||||
|
||||
## Version Strategy
|
||||
|
||||
- **Major** (1.0.0): Breaking changes
|
||||
- **Minor** (0.2.0): New features
|
||||
- **Patch** (0.2.1): Bug fixes
|
||||
|
||||
## What We Fixed
|
||||
|
||||
### Before (Nightmare 😱)
|
||||
- Auto-version-bump with `[skip ci]` prevented Docker builds
|
||||
- 6+ disconnected workflows
|
||||
- Manual tag deletion and re-pushing
|
||||
- Version conflicts everywhere
|
||||
|
||||
### After (Simple! 🎉)
|
||||
- **3 total workflows** (down from 6+)
|
||||
- **1 button** for complete releases
|
||||
- Release builds its own Docker image — `[skip ci]` on the version commit prevents duplicate CI builds
|
||||
- **Clear separation** of concerns
|
||||
@@ -1,61 +0,0 @@
|
||||
name: Build and Push Docker Image
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
pull_request:
|
||||
branches:
|
||||
- main
|
||||
workflow_dispatch:
|
||||
|
||||
env:
|
||||
REGISTRY: ghcr.io
|
||||
IMAGE_NAME: ${{ github.repository }}
|
||||
|
||||
jobs:
|
||||
build-and-push:
|
||||
runs-on: ubuntu-latest
|
||||
if: >-
|
||||
github.event_name != 'push'
|
||||
|| !contains(github.event.head_commit.message, '[skip ci]')
|
||||
permissions:
|
||||
contents: read
|
||||
packages: write
|
||||
|
||||
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
|
||||
if: github.event_name != 'pull_request'
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
registry: ${{ env.REGISTRY }}
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Extract metadata (tags, labels)
|
||||
id: meta
|
||||
uses: docker/metadata-action@v5
|
||||
with:
|
||||
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
|
||||
tags: |
|
||||
type=ref,event=branch
|
||||
type=ref,event=pr
|
||||
type=sha,prefix=sha-
|
||||
type=raw,value=latest,enable={{is_default_branch}}
|
||||
|
||||
- name: Build and push Docker image
|
||||
uses: docker/build-push-action@v6
|
||||
with:
|
||||
context: .
|
||||
push: ${{ github.event_name != 'pull_request' }}
|
||||
tags: ${{ steps.meta.outputs.tags }}
|
||||
labels: ${{ steps.meta.outputs.labels }}
|
||||
cache-from: type=gha
|
||||
cache-to: type=gha,mode=max
|
||||
platforms: linux/amd64
|
||||
@@ -1,56 +0,0 @@
|
||||
name: Quick Fix Build
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
tag:
|
||||
description: 'Tag for the image (defaults to latest)'
|
||||
required: false
|
||||
default: 'latest'
|
||||
type: string
|
||||
|
||||
env:
|
||||
REGISTRY: ghcr.io
|
||||
IMAGE_NAME: ${{ github.repository }}
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
if: github.ref == 'refs/heads/main'
|
||||
permissions:
|
||||
contents: read
|
||||
packages: write
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
|
||||
- name: Log in to GitHub Container Registry
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
registry: ${{ env.REGISTRY }}
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Build and Push
|
||||
uses: docker/build-push-action@v6
|
||||
with:
|
||||
context: .
|
||||
push: true
|
||||
tags: |
|
||||
${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.event.inputs.tag }}
|
||||
${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.sha }}
|
||||
cache-from: type=gha
|
||||
cache-to: type=gha,mode=max
|
||||
platforms: linux/amd64
|
||||
|
||||
- name: Summary
|
||||
run: |
|
||||
echo "## ✅ Quick Fix Build Complete" >> $GITHUB_STEP_SUMMARY
|
||||
echo "" >> $GITHUB_STEP_SUMMARY
|
||||
echo "### Images Published:" >> $GITHUB_STEP_SUMMARY
|
||||
echo "- \`${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.event.inputs.tag }}\`" >> $GITHUB_STEP_SUMMARY
|
||||
echo "- \`${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.sha }}\`" >> $GITHUB_STEP_SUMMARY
|
||||
@@ -1,193 +0,0 @@
|
||||
name: Unified Release
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
version:
|
||||
description: 'Explicit version (e.g., 1.2.3). Leave blank to auto-increment.'
|
||||
required: false
|
||||
type: string
|
||||
release_type:
|
||||
description: 'Release type (used when version is blank)'
|
||||
required: true
|
||||
default: 'patch'
|
||||
type: choice
|
||||
options:
|
||||
- patch
|
||||
- minor
|
||||
- major
|
||||
|
||||
env:
|
||||
REGISTRY: ghcr.io
|
||||
IMAGE_NAME: ${{ github.repository }}
|
||||
|
||||
jobs:
|
||||
release:
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: write
|
||||
packages: write
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
fetch-depth: 0
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
|
||||
- name: Set up Helm
|
||||
uses: azure/setup-helm@v4
|
||||
|
||||
- name: Configure Git
|
||||
run: |
|
||||
git config user.name "github-actions[bot]"
|
||||
git config user.email "github-actions[bot]@users.noreply.github.com"
|
||||
|
||||
- name: Determine Version
|
||||
id: version
|
||||
run: |
|
||||
INPUT_VERSION="${{ github.event.inputs.version }}"
|
||||
if [ -n "$INPUT_VERSION" ]; then
|
||||
VERSION="$INPUT_VERSION"
|
||||
else
|
||||
# Auto-increment based on release_type
|
||||
CURRENT=$(grep '^version:' chart/Chart.yaml | awk '{print $2}')
|
||||
# Strip any pre-release suffix (e.g., 2.0.0-dev -> 2.0.0)
|
||||
CURRENT=$(echo "$CURRENT" | sed 's/-.*//')
|
||||
MAJOR=$(echo "$CURRENT" | cut -d. -f1)
|
||||
MINOR=$(echo "$CURRENT" | cut -d. -f2)
|
||||
PATCH=$(echo "$CURRENT" | cut -d. -f3)
|
||||
|
||||
case "${{ github.event.inputs.release_type }}" in
|
||||
major) VERSION="$((MAJOR + 1)).0.0" ;;
|
||||
minor) VERSION="${MAJOR}.$((MINOR + 1)).0" ;;
|
||||
patch) VERSION="${MAJOR}.${MINOR}.$((PATCH + 1))" ;;
|
||||
esac
|
||||
fi
|
||||
|
||||
echo "version=${VERSION}" >> $GITHUB_OUTPUT
|
||||
echo "tag=v${VERSION}" >> $GITHUB_OUTPUT
|
||||
echo "Releasing version ${VERSION}"
|
||||
|
||||
- name: Update Chart Version
|
||||
run: |
|
||||
sed -i "s/^version: .*/version: ${{ steps.version.outputs.version }}/" chart/Chart.yaml
|
||||
git add chart/Chart.yaml
|
||||
git diff --quiet --staged || git commit -m "chore(release): ${{ steps.version.outputs.version }} [skip ci]"
|
||||
|
||||
- name: Create and Push Tag
|
||||
run: |
|
||||
git tag -a "${{ steps.version.outputs.tag }}" -m "Release ${{ steps.version.outputs.tag }}"
|
||||
git push origin main
|
||||
git push origin "${{ steps.version.outputs.tag }}"
|
||||
|
||||
- name: Log in to GitHub Container Registry
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
registry: ${{ env.REGISTRY }}
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Build and Push Docker Image
|
||||
uses: docker/build-push-action@v6
|
||||
with:
|
||||
context: .
|
||||
push: true
|
||||
tags: |
|
||||
${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.version.outputs.tag }}
|
||||
${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.version.outputs.version }}
|
||||
${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest
|
||||
cache-from: type=gha
|
||||
cache-to: type=gha,mode=max
|
||||
platforms: linux/amd64
|
||||
|
||||
- name: Publish Helm Chart to GitHub Pages
|
||||
run: |
|
||||
helm package chart/
|
||||
CHART_TGZ="devcontainer-${{ steps.version.outputs.version }}.tgz"
|
||||
|
||||
# Set up gh-pages in a temporary directory
|
||||
PAGES_DIR=$(mktemp -d)
|
||||
if git ls-remote --heads origin gh-pages | grep -q gh-pages; then
|
||||
# gh-pages exists — shallow clone just that branch
|
||||
git clone --single-branch --branch gh-pages \
|
||||
"https://x-access-token:${{ secrets.GITHUB_TOKEN }}@github.com/${{ github.repository }}.git" \
|
||||
"$PAGES_DIR"
|
||||
else
|
||||
# First time — initialize gh-pages
|
||||
git init "$PAGES_DIR"
|
||||
git -C "$PAGES_DIR" checkout --orphan gh-pages
|
||||
git -C "$PAGES_DIR" remote add origin \
|
||||
"https://x-access-token:${{ secrets.GITHUB_TOKEN }}@github.com/${{ github.repository }}.git"
|
||||
cat > "$PAGES_DIR/index.html" <<'HTMLEOF'
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head><title>Dev Container Helm Chart Repository</title></head>
|
||||
<body>
|
||||
<h1>Dev Container Helm Chart Repository</h1>
|
||||
<p>Add this repository to Helm:</p>
|
||||
<pre>helm repo add devcontainer https://cpfarhood.github.io/devcontainer</pre>
|
||||
<p>Install the chart:</p>
|
||||
<pre>helm install mydev devcontainer/devcontainer --set name=mydev</pre>
|
||||
</body>
|
||||
</html>
|
||||
HTMLEOF
|
||||
fi
|
||||
|
||||
git -C "$PAGES_DIR" config user.name "github-actions[bot]"
|
||||
git -C "$PAGES_DIR" config user.email "github-actions[bot]@users.noreply.github.com"
|
||||
|
||||
# Copy chart package and rebuild index
|
||||
cp "$CHART_TGZ" "$PAGES_DIR/"
|
||||
if [ -f "$PAGES_DIR/index.yaml" ]; then
|
||||
helm repo index "$PAGES_DIR" --url https://cpfarhood.github.io/devcontainer --merge "$PAGES_DIR/index.yaml"
|
||||
else
|
||||
helm repo index "$PAGES_DIR" --url https://cpfarhood.github.io/devcontainer
|
||||
fi
|
||||
|
||||
# Commit and push
|
||||
git -C "$PAGES_DIR" add .
|
||||
git -C "$PAGES_DIR" commit -m "Publish chart ${{ steps.version.outputs.version }}"
|
||||
git -C "$PAGES_DIR" push origin gh-pages
|
||||
|
||||
- name: Create GitHub Release
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
run: |
|
||||
# Build release notes
|
||||
PREV_TAG=$(git describe --tags --abbrev=0 HEAD~1 2>/dev/null || echo "")
|
||||
if [ -z "$PREV_TAG" ]; then
|
||||
COMMITS=$(git log --pretty=format:"- %s (%h)" HEAD)
|
||||
else
|
||||
COMMITS=$(git log --pretty=format:"- %s (%h)" "${PREV_TAG}..HEAD")
|
||||
fi
|
||||
|
||||
cat > release-notes.md <<'NOTESEOF'
|
||||
## Release RELEASE_VERSION
|
||||
|
||||
### Changes
|
||||
RELEASE_COMMITS
|
||||
|
||||
### Docker Image
|
||||
```bash
|
||||
docker pull RELEASE_IMAGE
|
||||
```
|
||||
|
||||
### Helm Chart
|
||||
```bash
|
||||
helm repo add devcontainer https://cpfarhood.github.io/devcontainer
|
||||
helm repo update
|
||||
helm install mydev devcontainer/devcontainer --version RELEASE_VERSION --set name=mydev
|
||||
```
|
||||
NOTESEOF
|
||||
sed -i 's/^ //' release-notes.md
|
||||
sed -i "s|RELEASE_VERSION|${{ steps.version.outputs.version }}|g" release-notes.md
|
||||
sed -i "s|RELEASE_IMAGE|${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.version.outputs.tag }}|g" release-notes.md
|
||||
sed -i "s|RELEASE_COMMITS|${COMMITS}|g" release-notes.md
|
||||
|
||||
gh release create "${{ steps.version.outputs.tag }}" \
|
||||
--title "Release ${{ steps.version.outputs.tag }}" \
|
||||
--notes-file release-notes.md
|
||||
-24
@@ -1,24 +0,0 @@
|
||||
# Secrets
|
||||
*.env
|
||||
.env.local
|
||||
secrets.yaml
|
||||
k8s/sealedsecrets.yaml
|
||||
|
||||
# Local volumes
|
||||
home/
|
||||
workspace/
|
||||
|
||||
# IDE
|
||||
.vscode/
|
||||
.idea/
|
||||
|
||||
# OS
|
||||
.DS_Store
|
||||
Thumbs.db
|
||||
|
||||
# Logs
|
||||
*.log
|
||||
|
||||
# Build artifacts
|
||||
*.tar
|
||||
*.tar.gz
|
||||
@@ -1,27 +0,0 @@
|
||||
{
|
||||
"mcpServers": {
|
||||
"github": {
|
||||
"type": "http",
|
||||
"url": "https://api.githubcopilot.com/mcp/",
|
||||
"headers": {
|
||||
"Authorization": "Bearer ${GITHUB_TOKEN}"
|
||||
}
|
||||
},
|
||||
"kubernetes": {
|
||||
"type": "sse",
|
||||
"url": "http://localhost:8080/sse"
|
||||
},
|
||||
"flux": {
|
||||
"type": "sse",
|
||||
"url": "http://localhost:8081/sse"
|
||||
},
|
||||
"playwright": {
|
||||
"type": "sse",
|
||||
"url": "http://localhost:8086/sse"
|
||||
},
|
||||
"helm": {
|
||||
"type": "sse",
|
||||
"url": "http://localhost:8088/sse"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,98 +0,0 @@
|
||||
# Changelog
|
||||
|
||||
All notable changes to this project will be documented in this file.
|
||||
|
||||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
||||
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
### Added
|
||||
- Initial project setup
|
||||
- Antigravity IDE (VSCode) with web-based VNC access
|
||||
- Happy Coder AI assistant integration
|
||||
- Automatic GitHub repository cloning on startup
|
||||
- Persistent home directory with ReadWriteMany PVC support
|
||||
- Secure non-root execution (claude user, UID 1000, GID 1000)
|
||||
- Support for private repositories via GitHub token
|
||||
- HTTPRoute (Gateway API) support
|
||||
- VNC password protection
|
||||
- Multi-platform Docker image builds
|
||||
- GitHub Actions CI/CD pipeline
|
||||
- Automated releases on version tags
|
||||
- Comprehensive deployment documentation (DEPLOYMENT.md)
|
||||
- Complete variables reference (VARIABLES.md)
|
||||
|
||||
### Container Features
|
||||
- Base: jlesage/baseimage-gui:ubuntu-22.04-v4
|
||||
- Antigravity IDE (VSCode)
|
||||
- Happy Coder npm package
|
||||
- Chrome browser
|
||||
- Node.js (LTS)
|
||||
- Python 3
|
||||
- Git
|
||||
|
||||
### Kubernetes Resources
|
||||
- StatefulSet with volumeClaimTemplates
|
||||
- ReadWriteMany PVC for /home directory
|
||||
- ConfigMap for configuration
|
||||
- Sealed Secrets support
|
||||
- HTTPRoute for external access
|
||||
- Service (headless)
|
||||
|
||||
### Configuration Options
|
||||
- GitHub repository URL (required)
|
||||
- GitHub token (optional, for private repos)
|
||||
- VNC password (optional)
|
||||
- Happy Coder server URL (optional)
|
||||
- Happy Coder webapp URL (optional)
|
||||
- Display resolution (configurable)
|
||||
- Resource limits (configurable)
|
||||
- Storage size (configurable)
|
||||
|
||||
### Documentation
|
||||
- README.md with quick start guide
|
||||
- DEPLOYMENT.md with step-by-step instructions
|
||||
- VARIABLES.md with complete variable reference
|
||||
- Release process documentation
|
||||
- Pull request template
|
||||
- Dependabot configuration
|
||||
|
||||
## Version History
|
||||
|
||||
No releases yet. See [Unreleased] section above for planned v1.0.0 features.
|
||||
|
||||
---
|
||||
|
||||
## Release Template
|
||||
|
||||
Use this template for future releases:
|
||||
|
||||
```markdown
|
||||
## [1.0.0] - YYYY-MM-DD
|
||||
|
||||
### Added
|
||||
- New features
|
||||
- New configuration options
|
||||
|
||||
### Changed
|
||||
- Changes to existing features
|
||||
- Updated dependencies
|
||||
|
||||
### Deprecated
|
||||
- Features that will be removed in future versions
|
||||
|
||||
### Removed
|
||||
- Removed features
|
||||
- Breaking changes
|
||||
|
||||
### Fixed
|
||||
- Bug fixes
|
||||
- Security patches
|
||||
|
||||
### Security
|
||||
- Security improvements
|
||||
- Vulnerability fixes
|
||||
```
|
||||
|
||||
[Unreleased]: https://github.com/cpfarhood/devcontainer/compare/v1.0.0...HEAD
|
||||
@@ -1,213 +0,0 @@
|
||||
# CLAUDE.md
|
||||
|
||||
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
|
||||
|
||||
## Project Overview
|
||||
|
||||
The Dev Container is a Docker-based cloud development environment that provides:
|
||||
- Web-based GUI IDE (VSCode/Antigravity) via VNC on port 5800
|
||||
- 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.
|
||||
|
||||
## Common Commands
|
||||
|
||||
### Building
|
||||
|
||||
```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/devcontainer:latest . # Direct build
|
||||
```
|
||||
|
||||
### Running Locally
|
||||
|
||||
```bash
|
||||
GITHUB_REPO="https://github.com/user/repo" make run # Run with Docker
|
||||
make stop # Stop container
|
||||
make clean # Remove volumes
|
||||
```
|
||||
|
||||
### Kubernetes Deployment
|
||||
|
||||
```bash
|
||||
GITHUB_REPO="https://github.com/user/repo" make helm-deploy # Deploy with Helm
|
||||
make helm-delete # Tear down Helm release
|
||||
make helm-port-forward # Forward port 5800 to localhost
|
||||
make helm-logs # Stream container logs
|
||||
make helm-shell # Open interactive shell in pod
|
||||
|
||||
# Or use Helm directly
|
||||
helm install mydev ./chart --set name=mydev --set githubRepo=https://github.com/user/repo
|
||||
```
|
||||
|
||||
### Other Useful Targets
|
||||
|
||||
```bash
|
||||
make help # List all Makefile targets with descriptions
|
||||
make push # Push image to registry (build first)
|
||||
```
|
||||
|
||||
## Architecture
|
||||
|
||||
### Startup Flow
|
||||
|
||||
```
|
||||
Container start
|
||||
→ scripts/startapp.sh
|
||||
→ scripts/init-repo.sh
|
||||
→ Configure git user & credentials
|
||||
→ Clone GITHUB_REPO (if set)
|
||||
→ Launch VSCode as user `user` in /workspace
|
||||
```
|
||||
|
||||
### Key Files
|
||||
|
||||
| File | Purpose |
|
||||
|------|---------|
|
||||
| `Dockerfile` | Image definition — installs Chrome, Node.js, VSCode, Helm, gh CLI, kubeseal, 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 |
|
||||
| `chart/templates/rbac.yaml` | ServiceAccount, Role/ClusterRole based on `clusterAccess` value |
|
||||
| `chart/templates/pvc.yaml` | PersistentVolumeClaim for user home |
|
||||
| `chart/templates/service.yaml` | ClusterIP Service (VNC + optional SSH) |
|
||||
| `chart/values.yaml` | Default Helm values |
|
||||
| `.mcp.json` | MCP server connection config (GitHub Copilot, Kubernetes, Flux, Helm, Fetch, Sequential Thinking, Playwright, pgtuner) |
|
||||
| `Makefile` | Build/deploy automation |
|
||||
|
||||
### MCP Sidecars
|
||||
|
||||
MCP (Model Context Protocol) servers run as sidecar containers in the pod, enabling AI assistants to interact with various services:
|
||||
|
||||
| Sidecar | Image | Version | Port | Endpoint | Default |
|
||||
|---------|-------|---------|------|----------|---------|
|
||||
| `kubernetes-mcp` | `quay.io/containers/kubernetes_mcp_server` | v0.0.57 | 8080 | `http://localhost:8080/sse` | Enabled |
|
||||
| `flux-mcp` | `ghcr.io/controlplaneio-fluxcd/flux-operator-mcp` | v0.41.1 | 8081 | `http://localhost:8081/sse` | Enabled |
|
||||
| `helm-mcp` | `ghcr.io/zekker6/mcp-helm` | v1.3.1 | 8088 | `http://localhost:8088/sse` | Enabled |
|
||||
| `fetch-mcp` | `mcp/fetch` | latest | 8082 | `http://localhost:8082/sse` | Enabled |
|
||||
| `sequentialthinking-mcp` | `mcp/sequentialthinking` | latest | 8083 | `http://localhost:8083/sse` | Enabled |
|
||||
| `homeassistant-mcp` | `ghcr.io/homeassistant-ai/ha-mcp` | stable | 8087 | `http://localhost:8087/sse` | Disabled |
|
||||
| `pgtuner-mcp` | `dog830228/pgtuner_mcp` | latest | 8085 | `http://localhost:8085/sse` | Disabled |
|
||||
| `playwright-mcp` | `mcr.microsoft.com/playwright/mcp` | latest | 8086 | `http://localhost:8086/sse` | Enabled |
|
||||
|
||||
**Note:**
|
||||
- GitHub MCP is accessed via the Copilot API (`https://api.githubcopilot.com/mcp/`), not as a sidecar
|
||||
- Kubernetes and Flux sidecars require `clusterAccess` != `none` to be deployed (they need RBAC permissions)
|
||||
- Kubernetes and Flux sidecars inherit the pod's ServiceAccount RBAC permissions
|
||||
- Helm sidecar enables browsing Helm repositories and chart metadata
|
||||
- Fetch sidecar provides web content fetching capabilities and HTML to markdown conversion
|
||||
- Sequential thinking sidecar enables structured thinking and problem-solving processes
|
||||
- Home Assistant sidecar requires `HOMEASSISTANT_URL` and `HOMEASSISTANT_TOKEN` in the env secret
|
||||
- PostgreSQL tuner sidecar requires `DATABASE_URI` in the env secret (PostgreSQL connection string)
|
||||
- Playwright sidecar provides browser automation and web testing capabilities
|
||||
|
||||
#### Enabling/Disabling MCP Servers
|
||||
|
||||
To control MCP sidecars, set the `enabled` flag in your values override:
|
||||
|
||||
```yaml
|
||||
# Disable all MCP sidecars
|
||||
mcp:
|
||||
sidecars:
|
||||
kubernetes:
|
||||
enabled: false
|
||||
flux:
|
||||
enabled: false
|
||||
helm:
|
||||
enabled: false
|
||||
fetch:
|
||||
enabled: false
|
||||
sequentialthinking:
|
||||
enabled: false
|
||||
homeassistant:
|
||||
enabled: false
|
||||
pgtuner:
|
||||
enabled: false
|
||||
playwright:
|
||||
enabled: false
|
||||
|
||||
# Or selectively enable/disable
|
||||
mcp:
|
||||
sidecars:
|
||||
kubernetes:
|
||||
enabled: true # Keep Kubernetes MCP enabled
|
||||
flux:
|
||||
enabled: false # Disable Flux MCP
|
||||
helm:
|
||||
enabled: true # Enable Helm MCP for chart browsing
|
||||
fetch:
|
||||
enabled: true # Enable Fetch MCP for web content fetching
|
||||
sequentialthinking:
|
||||
enabled: true # Enable Sequential Thinking MCP for problem-solving
|
||||
homeassistant:
|
||||
enabled: true # Enable Home Assistant MCP (requires secrets)
|
||||
pgtuner:
|
||||
enabled: true # Enable PostgreSQL tuner MCP (requires DATABASE_URI)
|
||||
playwright:
|
||||
enabled: true # Enable Playwright MCP for browser automation
|
||||
```
|
||||
|
||||
When deploying via Helm:
|
||||
```bash
|
||||
# Quick start (recommended)
|
||||
cp chart/values-quickstart.yaml my-values.yaml
|
||||
# Edit name and githubRepo in my-values.yaml
|
||||
helm install my-devcontainer ./chart -f my-values.yaml
|
||||
|
||||
# Using --set flags
|
||||
helm install my-devcontainer ./chart \
|
||||
--set name=mydev \
|
||||
--set githubRepo=https://github.com/user/repo \
|
||||
--set mcp.sidecars.kubernetes.enabled=false
|
||||
|
||||
# Full customization
|
||||
helm install my-devcontainer ./chart -f custom-values.yaml
|
||||
```
|
||||
|
||||
### Storage Model
|
||||
|
||||
- `/config` — ReadWriteMany PVC (persists across pod restarts, holds user config/dotfiles)
|
||||
- `/workspace` — emptyDir by default (ephemeral; can be changed to PVC)
|
||||
|
||||
### Environment Variables
|
||||
|
||||
**Required:**
|
||||
- `GITHUB_REPO` — URL of repository to clone into `/workspace`
|
||||
|
||||
**Optional:**
|
||||
- `GITHUB_TOKEN` — PAT for private repo access (automatically configures git credentials)
|
||||
- `GIT_USER_NAME` — Git user name for commits (default: "DevContainer User")
|
||||
- `GIT_USER_EMAIL` — Git user email for commits (default: "devcontainer@example.com")
|
||||
- `GITLAB_HOST` — GitLab hostname if using GitLab with same token
|
||||
- `VNC_PASSWORD` — VNC web interface password
|
||||
- `DISPLAY_WIDTH` / `DISPLAY_HEIGHT` — VNC resolution
|
||||
- `USER_ID` / `GROUP_ID` — Override UID/GID (default 1000)
|
||||
- `HAPPY_SERVER_URL` / `HAPPY_WEBAPP_URL` — Custom Happy Coder endpoints
|
||||
- `HAPPY_HOME_DIR` / `HAPPY_EXPERIMENTAL`
|
||||
- `WEB_FILE_MANAGER` — Set to `1` to enable the built-in web file manager (controlled via `fileManager.enabled` in Helm values)
|
||||
- `WEB_FILE_MANAGER_ALLOWED_PATHS` — Paths accessible by the file manager (default: `/workspace,/config`)
|
||||
- `WEB_FILE_MANAGER_DENIED_PATHS` — Paths to deny access to (takes precedence over allowed)
|
||||
|
||||
### CI/CD
|
||||
|
||||
- **`build-and-push.yaml`** — Builds and pushes to GHCR on every push to `main`, version tags (`v*`), and PRs. Tags: `latest` (main), semver, branch name, commit SHA.
|
||||
- **`release-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 to GitHub Pages Helm repo, reconciled by Flux
|
||||
- Storage class is `ceph-filesystem` by default — change via `storage.className` in values
|
||||
- Resource limits: 1–4 CPU, 2–8Gi memory
|
||||
- Health checks (liveness/readiness probes) on port 5800
|
||||
- Secrets: optional env Secret (`devcontainer-{name}-secrets-env`) for `GITHUB_TOKEN`, `VNC_PASSWORD`, etc.
|
||||
- RBAC: controlled by `clusterAccess` value (`none`, `readonlyns`, `readwritens`, `readonly`, `readwrite`)
|
||||
-449
@@ -1,449 +0,0 @@
|
||||
# Deployment Guide
|
||||
|
||||
This guide provides step-by-step instructions for deploying the Antigravity Dev Container using Helm.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- Kubernetes cluster (1.19+)
|
||||
- `kubectl` configured to access your cluster
|
||||
- `helm` CLI installed (3.0+)
|
||||
- ReadWriteMany storage class available (e.g., `ceph-filesystem`, `nfs-client`, `efs-sc`)
|
||||
- GitHub Container Registry access (images are public)
|
||||
|
||||
## Quick Start
|
||||
|
||||
### 1. Clone the Repository
|
||||
|
||||
```bash
|
||||
git clone https://github.com/cpfarhood/devcontainer.git
|
||||
cd devcontainer
|
||||
```
|
||||
|
||||
### 2. Create Secret (Optional)
|
||||
|
||||
For private repos or VNC password:
|
||||
|
||||
```bash
|
||||
kubectl create secret generic devcontainer-mydev-secrets-env \
|
||||
--from-literal=GITHUB_TOKEN='ghp_...' \
|
||||
--from-literal=VNC_PASSWORD='changeme' \
|
||||
--from-literal=ANTHROPIC_API_KEY='sk-ant-...'
|
||||
```
|
||||
|
||||
### 3. Deploy with Helm
|
||||
|
||||
```bash
|
||||
# Basic deployment
|
||||
helm install mydev ./chart \
|
||||
--set name=mydev \
|
||||
--set githubRepo=https://github.com/youruser/yourrepo
|
||||
|
||||
# With custom storage class
|
||||
helm install mydev ./chart \
|
||||
--set name=mydev \
|
||||
--set githubRepo=https://github.com/youruser/yourrepo \
|
||||
--set storage.className=nfs-client
|
||||
|
||||
# With cluster access for kubectl
|
||||
helm install mydev ./chart \
|
||||
--set name=mydev \
|
||||
--set githubRepo=https://github.com/youruser/yourrepo \
|
||||
--set clusterAccess=readwritens
|
||||
```
|
||||
|
||||
### 4. Access the Container
|
||||
|
||||
```bash
|
||||
# Port forward
|
||||
kubectl port-forward deployment/devcontainer-mydev 5800:5800
|
||||
open http://localhost:5800
|
||||
```
|
||||
|
||||
## Deployment Options
|
||||
|
||||
### Using Values File
|
||||
|
||||
Create a custom `values.yaml`:
|
||||
|
||||
```yaml
|
||||
name: mydev
|
||||
githubRepo: https://github.com/youruser/yourrepo
|
||||
ide: vscode
|
||||
ssh: false
|
||||
|
||||
# Storage
|
||||
storage:
|
||||
size: 32Gi
|
||||
className: ceph-filesystem
|
||||
|
||||
# Resources
|
||||
resources:
|
||||
requests:
|
||||
memory: "4Gi"
|
||||
cpu: "2000m"
|
||||
limits:
|
||||
memory: "16Gi"
|
||||
cpu: "8000m"
|
||||
|
||||
# Kubernetes access
|
||||
clusterAccess: readwritens
|
||||
|
||||
# MCP sidecars
|
||||
mcpSidecars:
|
||||
kubernetes:
|
||||
enabled: true
|
||||
flux:
|
||||
enabled: false
|
||||
```
|
||||
|
||||
Deploy:
|
||||
|
||||
```bash
|
||||
helm install mydev ./chart -f values.yaml
|
||||
```
|
||||
|
||||
### SSH Access Setup
|
||||
|
||||
Enable SSH and add your public key:
|
||||
|
||||
```bash
|
||||
# Create secret with SSH key
|
||||
kubectl create secret generic devcontainer-mydev-secrets-env \
|
||||
--from-literal=SSH_AUTHORIZED_KEYS='ssh-ed25519 AAAA...'
|
||||
|
||||
# Deploy with SSH enabled
|
||||
helm install mydev ./chart \
|
||||
--set name=mydev \
|
||||
--set githubRepo=https://github.com/youruser/yourrepo \
|
||||
--set ssh=true
|
||||
|
||||
# Connect via SSH
|
||||
kubectl port-forward deployment/devcontainer-mydev 2222:22
|
||||
ssh -p 2222 user@localhost
|
||||
```
|
||||
|
||||
### MCP Sidecar Configuration
|
||||
|
||||
Control MCP servers for AI-assisted operations.
|
||||
|
||||
**Important:** Kubernetes and Flux MCP sidecars are only deployed when:
|
||||
1. They are enabled in values (`mcpSidecars.<name>.enabled: true`)
|
||||
2. AND `clusterAccess` is not `none` (they need RBAC permissions to function)
|
||||
|
||||
```bash
|
||||
# Disable all MCP sidecars
|
||||
helm install mydev ./chart \
|
||||
--set name=mydev \
|
||||
--set githubRepo=https://github.com/youruser/yourrepo \
|
||||
--set mcpSidecars.kubernetes.enabled=false \
|
||||
--set mcpSidecars.flux.enabled=false \
|
||||
--set mcpSidecars.homeassistant.enabled=false
|
||||
|
||||
# Enable only Kubernetes MCP
|
||||
helm install mydev ./chart \
|
||||
--set name=mydev \
|
||||
--set githubRepo=https://github.com/youruser/yourrepo \
|
||||
--set mcpSidecars.kubernetes.enabled=true \
|
||||
--set mcpSidecars.flux.enabled=false
|
||||
|
||||
# Enable Home Assistant MCP (requires credentials)
|
||||
kubectl create secret generic devcontainer-mydev-secrets-env \
|
||||
--from-literal=homeassistant-url='http://homeassistant.local:8123' \
|
||||
--from-literal=homeassistant-token='your_long_lived_token'
|
||||
|
||||
helm install mydev ./chart \
|
||||
--set name=mydev \
|
||||
--set githubRepo=https://github.com/youruser/yourrepo \
|
||||
--set mcpSidecars.homeassistant.enabled=true
|
||||
```
|
||||
|
||||
### Cluster Access Levels
|
||||
|
||||
Configure Kubernetes RBAC permissions:
|
||||
|
||||
| Value | Scope | Permissions | Use Case |
|
||||
|-------|-------|-------------|----------|
|
||||
| `none` | No access | None | Default, isolated development |
|
||||
| `readonlyns` | Namespace | Read-only | View resources in namespace |
|
||||
| `readwritens` | Namespace | Full access | Deploy apps in namespace |
|
||||
| `readonly` | Cluster-wide | Read-only | View all cluster resources |
|
||||
| `readwrite` | Cluster-wide | Full access | Cluster administration |
|
||||
|
||||
```bash
|
||||
# Example: Full access within namespace
|
||||
helm install mydev ./chart \
|
||||
--set name=mydev \
|
||||
--set githubRepo=https://github.com/youruser/yourrepo \
|
||||
--set clusterAccess=readwritens
|
||||
```
|
||||
|
||||
## Ingress Configuration
|
||||
|
||||
### Using Gateway API HTTPRoute
|
||||
|
||||
Create an HTTPRoute for external access:
|
||||
|
||||
```yaml
|
||||
apiVersion: gateway.networking.k8s.io/v1beta1
|
||||
kind: HTTPRoute
|
||||
metadata:
|
||||
name: devcontainer-mydev
|
||||
spec:
|
||||
parentRefs:
|
||||
- name: your-gateway
|
||||
namespace: your-gateway-namespace
|
||||
hostnames:
|
||||
- devcontainer.example.com
|
||||
rules:
|
||||
- backendRefs:
|
||||
- name: devcontainer-mydev
|
||||
port: 5800
|
||||
```
|
||||
|
||||
### Using Traditional Ingress
|
||||
|
||||
Create an Ingress resource:
|
||||
|
||||
```yaml
|
||||
apiVersion: networking.k8s.io/v1
|
||||
kind: Ingress
|
||||
metadata:
|
||||
name: devcontainer-mydev
|
||||
spec:
|
||||
rules:
|
||||
- host: devcontainer.example.com
|
||||
http:
|
||||
paths:
|
||||
- path: /
|
||||
pathType: Prefix
|
||||
backend:
|
||||
service:
|
||||
name: devcontainer-mydev
|
||||
port:
|
||||
number: 5800
|
||||
```
|
||||
|
||||
## Advanced Configurations
|
||||
|
||||
### Custom Happy Coder Endpoints
|
||||
|
||||
For self-hosted Happy instances:
|
||||
|
||||
```bash
|
||||
helm install mydev ./chart \
|
||||
--set name=mydev \
|
||||
--set githubRepo=https://github.com/youruser/yourrepo \
|
||||
--set happyServerUrl=https://your-happy-server.com \
|
||||
--set happyWebappUrl=https://your-happy-webapp.com
|
||||
```
|
||||
|
||||
### Custom Display Resolution
|
||||
|
||||
```bash
|
||||
helm install mydev ./chart \
|
||||
--set name=mydev \
|
||||
--set githubRepo=https://github.com/youruser/yourrepo \
|
||||
--set display.width=2560 \
|
||||
--set display.height=1440
|
||||
```
|
||||
|
||||
### Different IDE Options
|
||||
|
||||
```bash
|
||||
# Use Google Antigravity
|
||||
helm install mydev ./chart \
|
||||
--set name=mydev \
|
||||
--set githubRepo=https://github.com/youruser/yourrepo \
|
||||
--set ide=antigravity
|
||||
|
||||
# SSH-only mode (no GUI)
|
||||
helm install mydev ./chart \
|
||||
--set name=mydev \
|
||||
--set githubRepo=https://github.com/youruser/yourrepo \
|
||||
--set ide=none \
|
||||
--set ssh=true
|
||||
```
|
||||
|
||||
## Helm Operations
|
||||
|
||||
### List Deployments
|
||||
|
||||
```bash
|
||||
helm list
|
||||
```
|
||||
|
||||
### Upgrade Deployment
|
||||
|
||||
```bash
|
||||
# Change values
|
||||
helm upgrade mydev ./chart \
|
||||
--set name=mydev \
|
||||
--set githubRepo=https://github.com/youruser/newrepo
|
||||
|
||||
# Upgrade with new chart version
|
||||
git pull
|
||||
helm upgrade mydev ./chart
|
||||
```
|
||||
|
||||
### Uninstall
|
||||
|
||||
```bash
|
||||
helm uninstall mydev
|
||||
|
||||
# Note: PVC persists by default
|
||||
kubectl delete pvc userhome-mydev
|
||||
```
|
||||
|
||||
### Rollback
|
||||
|
||||
```bash
|
||||
# View history
|
||||
helm history mydev
|
||||
|
||||
# Rollback to previous version
|
||||
helm rollback mydev
|
||||
|
||||
# Rollback to specific revision
|
||||
helm rollback mydev 3
|
||||
```
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Pod Not Starting
|
||||
|
||||
```bash
|
||||
# Check pod status
|
||||
kubectl get pods -l app.kubernetes.io/instance=mydev
|
||||
|
||||
# Describe pod for events
|
||||
kubectl describe pod -l app.kubernetes.io/instance=mydev
|
||||
|
||||
# Check logs
|
||||
kubectl logs deployment/devcontainer-mydev
|
||||
```
|
||||
|
||||
### Repository Not Cloning
|
||||
|
||||
```bash
|
||||
# Check init logs
|
||||
kubectl logs deployment/devcontainer-mydev | grep "Repository Initialization"
|
||||
|
||||
# Verify secret exists
|
||||
kubectl get secret devcontainer-mydev-secrets-env
|
||||
|
||||
# Check environment
|
||||
kubectl exec deployment/devcontainer-mydev -- env | grep GITHUB
|
||||
```
|
||||
|
||||
### VNC Not Accessible
|
||||
|
||||
```bash
|
||||
# Check service
|
||||
kubectl get svc devcontainer-mydev
|
||||
kubectl describe svc devcontainer-mydev
|
||||
|
||||
# Test with port-forward
|
||||
kubectl port-forward deployment/devcontainer-mydev 5800:5800
|
||||
```
|
||||
|
||||
### MCP Sidecar Issues
|
||||
|
||||
```bash
|
||||
# Check all containers
|
||||
kubectl get pod -l app.kubernetes.io/instance=mydev -o jsonpath='{.items[0].spec.containers[*].name}'
|
||||
|
||||
# Check MCP container logs
|
||||
kubectl logs deployment/devcontainer-mydev -c kubernetes-mcp
|
||||
kubectl logs deployment/devcontainer-mydev -c flux-mcp
|
||||
kubectl logs deployment/devcontainer-mydev -c homeassistant-mcp
|
||||
|
||||
# Verify RBAC permissions (for Kubernetes/Flux MCP)
|
||||
kubectl auth can-i --list --as system:serviceaccount:default:devcontainer-mydev
|
||||
|
||||
# Check Home Assistant MCP credentials
|
||||
kubectl get secret devcontainer-mydev-secrets-env -o jsonpath='{.data.homeassistant-url}' | base64 -d
|
||||
# Verify the URL is accessible from the pod
|
||||
kubectl exec deployment/devcontainer-mydev -- curl -s http://homeassistant.local:8123/api/
|
||||
```
|
||||
|
||||
### Storage Issues
|
||||
|
||||
```bash
|
||||
# Check PVC
|
||||
kubectl get pvc userhome-mydev
|
||||
kubectl describe pvc userhome-mydev
|
||||
|
||||
# Check available storage classes
|
||||
kubectl get storageclass
|
||||
|
||||
# Verify ReadWriteMany support
|
||||
kubectl get storageclass <class-name> -o yaml | grep -i accessmodes
|
||||
```
|
||||
|
||||
## Best Practices
|
||||
|
||||
### Production Deployment
|
||||
|
||||
1. **Use specific image tags** instead of `latest`:
|
||||
```bash
|
||||
helm install mydev ./chart --set image.tag=v1.0.0
|
||||
```
|
||||
|
||||
2. **Set resource limits** appropriately:
|
||||
```yaml
|
||||
resources:
|
||||
requests:
|
||||
memory: "4Gi"
|
||||
cpu: "2000m"
|
||||
limits:
|
||||
memory: "8Gi"
|
||||
cpu: "4000m"
|
||||
```
|
||||
|
||||
3. **Enable VNC password**:
|
||||
```bash
|
||||
kubectl create secret generic devcontainer-mydev-secrets-env \
|
||||
--from-literal=VNC_PASSWORD='strong-password-here'
|
||||
```
|
||||
|
||||
4. **Use dedicated namespace**:
|
||||
```bash
|
||||
kubectl create namespace dev-environments
|
||||
helm install mydev ./chart -n dev-environments
|
||||
```
|
||||
|
||||
5. **Configure appropriate cluster access**:
|
||||
- Use `readonlyns` or `readwritens` for namespace-scoped work
|
||||
- Avoid `readwrite` cluster-wide access unless necessary
|
||||
|
||||
### Multi-User Deployment
|
||||
|
||||
For teams, create separate deployments per user:
|
||||
|
||||
```bash
|
||||
# User 1
|
||||
helm install alice-dev ./chart \
|
||||
--set name=alice-dev \
|
||||
--set githubRepo=https://github.com/alice/project
|
||||
|
||||
# User 2
|
||||
helm install bob-dev ./chart \
|
||||
--set name=bob-dev \
|
||||
--set githubRepo=https://github.com/bob/project
|
||||
```
|
||||
|
||||
### Backup and Recovery
|
||||
|
||||
The home directory persists on PVC. To backup:
|
||||
|
||||
```bash
|
||||
# Create backup pod
|
||||
kubectl run backup --image=busybox --restart=Never --rm -i --tty \
|
||||
-- tar czf - -C /home . | gzip > home-backup.tar.gz
|
||||
```
|
||||
|
||||
## Support
|
||||
|
||||
For issues or questions:
|
||||
- GitHub Issues: https://github.com/cpfarhood/devcontainer/issues
|
||||
- Documentation: https://github.com/cpfarhood/devcontainer
|
||||
-184
@@ -1,184 +0,0 @@
|
||||
FROM jlesage/baseimage-gui:ubuntu-22.04-v4
|
||||
|
||||
# Set environment variables
|
||||
ENV APP_NAME="Dev Container" \
|
||||
KEEP_APP_RUNNING=1 \
|
||||
DISPLAY_WIDTH=1920 \
|
||||
DISPLAY_HEIGHT=1080 \
|
||||
SECURE_CONNECTION=1 \
|
||||
USER_ID=1000 \
|
||||
GROUP_ID=1000 \
|
||||
CLAUDE_USER=user
|
||||
|
||||
# Install system dependencies
|
||||
RUN apt-get update && apt-get install -y \
|
||||
curl \
|
||||
wget \
|
||||
gnupg \
|
||||
ca-certificates \
|
||||
git \
|
||||
build-essential \
|
||||
python3 \
|
||||
python3-pip \
|
||||
jq \
|
||||
unzip \
|
||||
sudo \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
# Install Chrome and xdg-utils (needed for xdg-open to work in VNC)
|
||||
RUN wget -q -O - https://dl.google.com/linux/linux_signing_key.pub | gpg --dearmor -o /usr/share/keyrings/google-chrome-keyring.gpg && \
|
||||
echo "deb [arch=amd64 signed-by=/usr/share/keyrings/google-chrome-keyring.gpg] http://dl.google.com/linux/chrome/deb/ stable main" > /etc/apt/sources.list.d/google-chrome.list && \
|
||||
apt-get update && \
|
||||
apt-get install -y google-chrome-stable xdg-utils && \
|
||||
rm -rf /var/lib/apt/lists/*
|
||||
|
||||
# Chrome wrapper: adds flags required for running inside a Docker container.
|
||||
# xdg-open (used by Claude Code on Linux) respects $BROWSER, so pointing it
|
||||
# here ensures the OAuth popup works without manual --no-sandbox invocations.
|
||||
# Cleans up crash lock files and suppresses the crash-restore bubble so that
|
||||
# sessions/cookies survive unclean pod shutdowns (SIGKILL).
|
||||
RUN printf '#!/bin/bash\n\
|
||||
CHROME_DIR="/config/userdata/.config/google-chrome"\n\
|
||||
mkdir -p "$CHROME_DIR"\n\
|
||||
# Remove stale lock files left by unclean container shutdown\n\
|
||||
rm -f "$CHROME_DIR/SingletonLock" "$CHROME_DIR/SingletonSocket" "$CHROME_DIR/SingletonCookie"\n\
|
||||
# Mark the previous session as clean so Chrome does not clear cookies\n\
|
||||
PREFS="$CHROME_DIR/Default/Preferences"\n\
|
||||
if [ -f "$PREFS" ]; then\n\
|
||||
sed -i '\''s/"exit_type":"Crashed"/"exit_type":"Normal"/g; s/"exited_cleanly":false/"exited_cleanly":true/g'\'' "$PREFS"\n\
|
||||
fi\n\
|
||||
exec /usr/bin/google-chrome-stable \\\n\
|
||||
--no-sandbox \\\n\
|
||||
--disable-dev-shm-usage \\\n\
|
||||
--disable-gpu \\\n\
|
||||
--disable-session-crashed-bubble \\\n\
|
||||
--user-data-dir="$CHROME_DIR" \\\n\
|
||||
"$@"\n' > /usr/local/bin/google-chrome && \
|
||||
chmod +x /usr/local/bin/google-chrome
|
||||
|
||||
# Install Node.js LTS (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 globally via npm (stable, rarely changes)
|
||||
RUN npm install -g happy-coder
|
||||
|
||||
# Install Claude Code native binary (npm wrapper breaks remote control)
|
||||
RUN curl -fsSL https://claude.ai/install.sh | bash && \
|
||||
cp /root/.local/bin/claude /usr/local/bin/claude && \
|
||||
rm -rf /root/.local/bin/claude && \
|
||||
claude --version
|
||||
|
||||
# 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 GitHub CLI (gh) via official APT repo
|
||||
RUN curl -fsSL https://cli.github.com/packages/githubcli-archive-keyring.gpg | dd of=/usr/share/keyrings/githubcli-archive-keyring.gpg && \
|
||||
chmod go+r /usr/share/keyrings/githubcli-archive-keyring.gpg && \
|
||||
echo "deb [arch=amd64 signed-by=/usr/share/keyrings/githubcli-archive-keyring.gpg] https://cli.github.com/packages stable main" > /etc/apt/sources.list.d/github-cli.list && \
|
||||
apt-get update && \
|
||||
apt-get install -y gh && \
|
||||
rm -rf /var/lib/apt/lists/*
|
||||
|
||||
# Install kubeseal CLI for Bitnami Sealed Secrets
|
||||
RUN KUBESEAL_VERSION=$(curl -sL https://api.github.com/repos/bitnami-labs/sealed-secrets/releases/latest | jq -r '.tag_name' | sed 's/^v//') && \
|
||||
curl -fsSL "https://github.com/bitnami-labs/sealed-secrets/releases/download/v${KUBESEAL_VERSION}/kubeseal-${KUBESEAL_VERSION}-linux-amd64.tar.gz" | \
|
||||
tar -xz -C /usr/local/bin kubeseal && \
|
||||
chmod +x /usr/local/bin/kubeseal
|
||||
|
||||
# Install VSCode (using Microsoft's current recommended setup)
|
||||
RUN wget -qO- https://packages.microsoft.com/keys/microsoft.asc | gpg --dearmor > /tmp/microsoft.gpg && \
|
||||
install -D -o root -g root -m 644 /tmp/microsoft.gpg /usr/share/keyrings/microsoft.gpg && \
|
||||
rm -f /tmp/microsoft.gpg && \
|
||||
printf 'Types: deb\nURIs: https://packages.microsoft.com/repos/code\nSuites: stable\nComponents: main\nArchitectures: amd64\nSigned-By: /usr/share/keyrings/microsoft.gpg\n' \
|
||||
> /etc/apt/sources.list.d/vscode.sources && \
|
||||
apt-get update && \
|
||||
apt-get install -y code && \
|
||||
rm -rf /var/lib/apt/lists/*
|
||||
|
||||
# Install Google Antigravity IDE
|
||||
RUN mkdir -p /etc/apt/keyrings && \
|
||||
curl -fsSL https://us-central1-apt.pkg.dev/doc/repo-signing-key.gpg | \
|
||||
gpg --dearmor --yes -o /etc/apt/keyrings/antigravity-repo-key.gpg && \
|
||||
echo "deb [signed-by=/etc/apt/keyrings/antigravity-repo-key.gpg] https://us-central1-apt.pkg.dev/projects/antigravity-auto-updater-dev/ antigravity-debian main" \
|
||||
> /etc/apt/sources.list.d/antigravity.list && \
|
||||
# Clear package cache to force fresh repository data
|
||||
rm -rf /var/lib/apt/lists/* && \
|
||||
apt-get update && \
|
||||
# Show available versions for debugging
|
||||
apt-cache policy antigravity && \
|
||||
# Install latest version
|
||||
apt-get install -y --no-install-recommends antigravity && \
|
||||
# Display installed version
|
||||
dpkg -l | grep antigravity && \
|
||||
rm -rf /var/lib/apt/lists/*
|
||||
|
||||
# Pre-configure Antigravity to skip onboarding/setup on first run
|
||||
RUN mkdir -p /etc/skel/.config/antigravity/User/globalStorage && \
|
||||
echo '{"antigravityUnifiedStateSync.seenNuxOneTimeMigration": true, "antigravityUnifiedStateSync.browserOnboarding.completed": true, "antigravityUnifiedStateSync.hasOnboardingCompleted": true, "browserOnboarding.hasSeenWelcome": true, "antigravityUnifiedStateSync.browserPreferences.hasAddedLocalhostToAllowlist": true, "antigravityUnifiedStateSync.oauthToken.hasLegacyMigrated": true, "antigravityUnifiedStateSync.auth.tokenSyncEnabled": true, "antigravityUnifiedStateSync.auth.cloudSyncEnabled": true, "theme": "vs-dark"}' \
|
||||
> /etc/skel/.config/antigravity/User/globalStorage/storage.json && \
|
||||
echo '{"workbench.startupEditor": "none", "workbench.welcomePage.walkthroughs.openOnInstall": false, "workbench.tips.enabled": false, "extensions.ignoreRecommendations": true, "telemetry.telemetryLevel": "off", "update.mode": "none", "extensions.autoUpdate": false, "extensions.autoCheckUpdates": false, "workbench.enableExperiments": true, "workbench.settings.enableNaturalLanguageSearch": true, "antigravity.onboarding.completed": true, "antigravity.browserOnboarding.completed": true, "antigravity.setup.completed": true, "antigravity.ai.enabled": true, "antigravity.ai.autoComplete.enabled": true, "antigravity.ai.chat.enabled": true, "antigravity.ai.codeActions.enabled": true, "antigravity.ai.explainCode.enabled": true, "antigravity.ai.generateCode.enabled": true, "antigravity.ai.optimizeCode.enabled": true, "antigravity.ai.autoSuggest.enabled": true, "antigravity.telemetry.crashReporter": "on", "antigravity.ai.acceptTerms": true, "antigravity.auth.syncState": true, "antigravity.auth.enableTokenSync": true, "antigravity.ai.enableCloudSync": true, "antigravity.settings.sync": true}' \
|
||||
> /etc/skel/.config/antigravity/User/settings.json && \
|
||||
# Validate Antigravity installation
|
||||
/usr/share/antigravity/antigravity --version || echo "WARNING: Antigravity version check failed"
|
||||
|
||||
# Install OpenSSH server (for SSH IDE mode)
|
||||
RUN apt-get update && \
|
||||
apt-get install -y openssh-server && \
|
||||
rm -rf /var/lib/apt/lists/* && \
|
||||
mkdir -p /var/run/sshd && \
|
||||
sed -i 's/#PubkeyAuthentication yes/PubkeyAuthentication yes/' /etc/ssh/sshd_config && \
|
||||
sed -i 's/#PasswordAuthentication yes/PasswordAuthentication no/' /etc/ssh/sshd_config && \
|
||||
sed -i 's/PasswordAuthentication yes/PasswordAuthentication no/' /etc/ssh/sshd_config && \
|
||||
echo "PermitRootLogin no" >> /etc/ssh/sshd_config
|
||||
|
||||
# Create user user with specific UID/GID
|
||||
RUN groupadd -g 1000 user && \
|
||||
useradd -u 1000 -g 1000 -m -s /bin/bash user && \
|
||||
echo "user ALL=(ALL) NOPASSWD:ALL" >> /etc/sudoers
|
||||
|
||||
# Create workspace directory
|
||||
RUN mkdir -p /workspace && \
|
||||
chown -R user:user /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
|
||||
|
||||
# Set working directory
|
||||
WORKDIR /workspace
|
||||
|
||||
# Configure container to run as user user
|
||||
ENV HOME=/config/userdata \
|
||||
USER=user \
|
||||
BROWSER=/usr/local/bin/google-chrome
|
||||
|
||||
# Expose VNC port (baseimage-gui default)
|
||||
EXPOSE 5800
|
||||
|
||||
# Set app name for baseimage-gui
|
||||
RUN set-cont-env APP_NAME "Dev Container"
|
||||
@@ -1,120 +0,0 @@
|
||||
.PHONY: build push run stop clean help
|
||||
|
||||
# Variables
|
||||
REGISTRY ?= ghcr.io/cpfarhood
|
||||
IMAGE_NAME ?= devcontainer
|
||||
IMAGE_TAG ?= latest
|
||||
FULL_IMAGE = $(REGISTRY)/$(IMAGE_NAME):$(IMAGE_TAG)
|
||||
|
||||
.DEFAULT_GOAL := help
|
||||
|
||||
# Build the Docker image
|
||||
build:
|
||||
@echo "Building $(FULL_IMAGE)..."
|
||||
docker build -t $(FULL_IMAGE) .
|
||||
|
||||
# Push the image to registry
|
||||
push: build
|
||||
@echo "Pushing $(FULL_IMAGE)..."
|
||||
docker push $(FULL_IMAGE)
|
||||
|
||||
# Run locally with Docker
|
||||
run:
|
||||
@echo "Running $(FULL_IMAGE) locally..."
|
||||
docker run -d \
|
||||
-p 5800:5800 \
|
||||
-e GITHUB_REPO="${GITHUB_REPO}" \
|
||||
-e GITHUB_TOKEN="${GITHUB_TOKEN}" \
|
||||
-e VNC_PASSWORD="${VNC_PASSWORD}" \
|
||||
-e HAPPY_EXPERIMENTAL="true" \
|
||||
-v $(PWD)/home:/home \
|
||||
-v $(PWD)/workspace:/workspace \
|
||||
--name devcontainer \
|
||||
$(FULL_IMAGE)
|
||||
@echo "Access at http://localhost:5800"
|
||||
|
||||
# Stop the running container
|
||||
stop:
|
||||
@echo "Stopping devcontainer..."
|
||||
docker stop devcontainer || true
|
||||
docker rm devcontainer || true
|
||||
|
||||
# Clean up local volumes
|
||||
clean: stop
|
||||
@echo "Cleaning up..."
|
||||
rm -rf ./home ./workspace
|
||||
|
||||
# Helm deployment
|
||||
RELEASE_NAME ?= mydev
|
||||
NAMESPACE ?= default
|
||||
|
||||
helm-deploy:
|
||||
@echo "Deploying with Helm (release: $(RELEASE_NAME))..."
|
||||
@if [ -z "$(GITHUB_REPO)" ]; then \
|
||||
echo "ERROR: GITHUB_REPO environment variable is required"; \
|
||||
echo "Usage: GITHUB_REPO=https://github.com/user/repo make helm-deploy"; \
|
||||
exit 1; \
|
||||
fi
|
||||
helm upgrade --install $(RELEASE_NAME) ./chart \
|
||||
--namespace $(NAMESPACE) \
|
||||
--set name=$(RELEASE_NAME) \
|
||||
--set githubRepo="$(GITHUB_REPO)" \
|
||||
--set image.repository=$(REGISTRY)/$(IMAGE_NAME) \
|
||||
--set image.tag=$(IMAGE_TAG)
|
||||
|
||||
helm-delete:
|
||||
@echo "Deleting Helm release $(RELEASE_NAME)..."
|
||||
helm uninstall $(RELEASE_NAME) --namespace $(NAMESPACE)
|
||||
@echo "Note: PVC persists. To delete: kubectl delete pvc userhome-$(RELEASE_NAME) -n $(NAMESPACE)"
|
||||
|
||||
helm-logs:
|
||||
@echo "Showing logs for $(RELEASE_NAME)..."
|
||||
kubectl logs -f deployment/devcontainer-$(RELEASE_NAME) -n $(NAMESPACE)
|
||||
|
||||
helm-shell:
|
||||
@echo "Opening shell in $(RELEASE_NAME)..."
|
||||
kubectl exec -it deployment/devcontainer-$(RELEASE_NAME) -n $(NAMESPACE) -- bash
|
||||
|
||||
helm-port-forward:
|
||||
@echo "Port forwarding $(RELEASE_NAME) to localhost:5800..."
|
||||
kubectl port-forward deployment/devcontainer-$(RELEASE_NAME) 5800:5800 -n $(NAMESPACE)
|
||||
|
||||
# Show help
|
||||
help:
|
||||
@echo "Dev Container Makefile"
|
||||
@echo ""
|
||||
@echo "Usage: make [target]"
|
||||
@echo ""
|
||||
@echo "Docker Targets:"
|
||||
@echo " build - Build the Docker image"
|
||||
@echo " push - Push image to registry"
|
||||
@echo " run - Run container locally (requires env vars)"
|
||||
@echo " stop - Stop running container"
|
||||
@echo " clean - Clean up containers and volumes"
|
||||
@echo ""
|
||||
@echo "Helm/Kubernetes Targets:"
|
||||
@echo " helm-deploy - Deploy with Helm chart (requires GITHUB_REPO)"
|
||||
@echo " helm-delete - Delete Helm release"
|
||||
@echo " helm-logs - Show container logs"
|
||||
@echo " helm-shell - Open shell in container"
|
||||
@echo " helm-port-forward - Port forward to localhost"
|
||||
@echo ""
|
||||
@echo "Variables:"
|
||||
@echo " REGISTRY - Docker registry (default: ghcr.io/cpfarhood)"
|
||||
@echo " IMAGE_NAME - Image name (default: devcontainer)"
|
||||
@echo " IMAGE_TAG - Image tag (default: latest)"
|
||||
@echo " RELEASE_NAME - Helm release name (default: mydev)"
|
||||
@echo " NAMESPACE - Kubernetes namespace (default: default)"
|
||||
@echo " GITHUB_REPO - GitHub repository URL (required for helm-deploy)"
|
||||
@echo ""
|
||||
@echo "Environment Variables for 'make run':"
|
||||
@echo " GITHUB_REPO - GitHub repository URL"
|
||||
@echo " GITHUB_TOKEN - GitHub token (optional)"
|
||||
@echo " VNC_PASSWORD - VNC password (optional)"
|
||||
@echo ""
|
||||
@echo "Examples:"
|
||||
@echo " make build"
|
||||
@echo " make push REGISTRY=ghcr.io/myuser IMAGE_TAG=v1.0"
|
||||
@echo " GITHUB_REPO=https://github.com/user/repo make run"
|
||||
@echo " GITHUB_REPO=https://github.com/user/repo make helm-deploy"
|
||||
@echo " RELEASE_NAME=alice-dev GITHUB_REPO=https://github.com/alice/project make helm-deploy"
|
||||
@@ -1,469 +0,0 @@
|
||||
# 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)
|
||||
- **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:
|
||||
|
||||
| Key | Purpose |
|
||||
|-----|---------|
|
||||
| `GITHUB_TOKEN` | PAT for private repo access (`repo` scope) |
|
||||
| `VNC_PASSWORD` | Password for the VNC web UI |
|
||||
| `ANTHROPIC_API_KEY` | API key — alternative to browser-based Claude login |
|
||||
| `SSH_AUTHORIZED_KEYS` | Public key(s) for SSH access (required when `ssh: true`) |
|
||||
| `HOMEASSISTANT_URL` | Home Assistant URL (required when `mcp.sidecars.homeassistant.enabled: true`) |
|
||||
| `HOMEASSISTANT_TOKEN` | Home Assistant long-lived access token (required when `mcp.sidecars.homeassistant.enabled: true`) |
|
||||
| `DATABASE_URI` | PostgreSQL connection string (required when `mcp.sidecars.pgtuner.enabled: true`) |
|
||||
| `PGTUNER_EXCLUDE_USERIDS` | Comma-separated PostgreSQL user OIDs to exclude from monitoring (optional) |
|
||||
|
||||
```bash
|
||||
kubectl create secret generic devcontainer-mydev-secrets-env \
|
||||
--from-literal=GITHUB_TOKEN='ghp_...' \
|
||||
--from-literal=VNC_PASSWORD='changeme'
|
||||
```
|
||||
|
||||
Or use SealedSecrets:
|
||||
|
||||
```bash
|
||||
kubectl create secret generic devcontainer-mydev-secrets-env \
|
||||
--from-literal=GITHUB_TOKEN='ghp_...' \
|
||||
--from-literal=VNC_PASSWORD='changeme' \
|
||||
--dry-run=client -o yaml | \
|
||||
kubeseal --format=yaml | kubectl apply -f -
|
||||
```
|
||||
|
||||
### 2. Deploy with Helm
|
||||
|
||||
```bash
|
||||
helm install mydev ./chart \
|
||||
--set name=mydev \
|
||||
--set githubRepo=https://github.com/youruser/yourrepo
|
||||
```
|
||||
|
||||
### 3. Access
|
||||
|
||||
```bash
|
||||
# Local port-forward
|
||||
kubectl port-forward deployment/devcontainer-mydev 5800:5800
|
||||
open http://localhost:5800
|
||||
```
|
||||
|
||||
Or configure an ingress / Gateway API HTTPRoute pointing at port 5800.
|
||||
|
||||
### 4. Authenticate Claude
|
||||
|
||||
On first launch, open a terminal in the VSCode GUI and run:
|
||||
|
||||
```bash
|
||||
claude
|
||||
```
|
||||
|
||||
A Chrome browser window will open inside VNC for the Claude Max OAuth login. Credentials are stored on the home PVC and persist across pod restarts.
|
||||
|
||||
---
|
||||
|
||||
## Helm Chart Reference
|
||||
|
||||
The Helm chart uses a logical organization with these main sections:
|
||||
- **Basic Configuration**: name, image, githubRepo
|
||||
- **Access & Interface**: IDE, SSH, display, user settings
|
||||
- **Infrastructure**: storage, resources, cluster access
|
||||
- **Integrations**: 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.type` | `vscode` | IDE to launch — `vscode`, `antigravity`, or `none` (see below) |
|
||||
| `ssh.enabled` | `false` | Also start an OpenSSH server on port 22 (additive, any IDE) |
|
||||
| `fileManager.enabled` | `false` | Enable the built-in web file manager for upload/download |
|
||||
| `image.repository` | `ghcr.io/cpfarhood/devcontainer` | Container image |
|
||||
| `image.tag` | `latest` | Image tag |
|
||||
|
||||
### IDE choice
|
||||
|
||||
`ide.type` controls what GUI is launched in the VNC session:
|
||||
|
||||
| Value | Port | Description |
|
||||
|-------|------|-------------|
|
||||
| `vscode` (default) | 5800 (VNC) | VSCode desktop via browser-based VNC |
|
||||
| `antigravity` | 5800 (VNC) | Google Antigravity (VSCode fork with AI) via VNC |
|
||||
| `none` | — | No IDE; container stays alive (useful when `ssh: true`) |
|
||||
|
||||
### SSH access
|
||||
|
||||
`ssh.enabled: true` starts OpenSSH on port 22 **in addition to** the IDE. It works with any `ide.type` value:
|
||||
|
||||
```bash
|
||||
# SSH-only (no VNC)
|
||||
helm install mydev ./chart --set name=mydev --set ide.type=none --set ssh.enabled=true
|
||||
|
||||
# VSCode in VNC + SSH access at the same time
|
||||
helm install mydev ./chart --set name=mydev --set ssh.enabled=true
|
||||
```
|
||||
|
||||
Add your public key to the env secret:
|
||||
|
||||
```bash
|
||||
kubectl create secret generic devcontainer-mydev-secrets-env \
|
||||
--from-literal=GITHUB_TOKEN='ghp_...' \
|
||||
--from-literal=SSH_AUTHORIZED_KEYS='ssh-ed25519 AAAA...'
|
||||
```
|
||||
|
||||
Then connect:
|
||||
|
||||
```bash
|
||||
kubectl port-forward deployment/devcontainer-mydev 2222:22
|
||||
ssh -p 2222 user@localhost
|
||||
```
|
||||
|
||||
### Web file manager
|
||||
|
||||
The base image includes a built-in web file manager for uploading and downloading files through the VNC web interface (port 5800). No additional sidecar is needed.
|
||||
|
||||
| Value | Default | Description |
|
||||
|-------|---------|-------------|
|
||||
| `fileManager.enabled` | `false` | Enable the web file manager |
|
||||
| `fileManager.allowedPaths` | `/workspace,/config` | Paths accessible by the file manager (`AUTO`, `ALL`, or comma-separated) |
|
||||
| `fileManager.deniedPaths` | `""` | Paths to deny (takes precedence over allowed) |
|
||||
|
||||
```bash
|
||||
# Enable the file manager
|
||||
helm install mydev ./chart \
|
||||
--set name=mydev \
|
||||
--set githubRepo=https://github.com/youruser/yourrepo \
|
||||
--set fileManager.enabled=true
|
||||
```
|
||||
|
||||
### Happy Coder
|
||||
|
||||
| Value | Default | Description |
|
||||
|-------|---------|-------------|
|
||||
| `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
|
||||
|
||||
The `clusterAccess` value provisions a ServiceAccount, Role/ClusterRole, and binding so the devcontainer pod can interact with the Kubernetes API. The default is `none` — no RBAC resources are created.
|
||||
|
||||
| Value | Scope | Verbs |
|
||||
|-------|-------|-------|
|
||||
| `none` (default) | — | no access |
|
||||
| `readonlyns` | release namespace | `get`, `list`, `watch` |
|
||||
| `readwritens` | release namespace | `*` |
|
||||
| `readonly` | cluster-wide | `get`, `list`, `watch` |
|
||||
| `readwrite` | cluster-wide | `*` |
|
||||
|
||||
```bash
|
||||
# Give the pod read-only access to its own namespace
|
||||
helm install mydev ./chart \
|
||||
--set name=mydev \
|
||||
--set githubRepo=https://github.com/youruser/yourrepo \
|
||||
--set clusterAccess=readonlyns
|
||||
```
|
||||
|
||||
With any non-`none` value, a `ServiceAccount` named `devcontainer-{name}` is created and set as the pod's `serviceAccountName`, so `kubectl` and any in-cluster API calls use it automatically.
|
||||
|
||||
### MCP Sidecars
|
||||
|
||||
The devcontainer includes MCP (Model Context Protocol) servers as sidecar containers that enable AI assistants to interact with various services:
|
||||
|
||||
| Sidecar | Default | Purpose |
|
||||
|---------|---------|---------|
|
||||
| `mcp.sidecars.kubernetes.enabled` | `true` | Kubernetes API access via MCP |
|
||||
| `mcp.sidecars.flux.enabled` | `true` | Flux GitOps operations via MCP |
|
||||
| `mcp.sidecars.homeassistant.enabled` | `false` | Home Assistant smart home control via MCP |
|
||||
| `mcp.sidecars.pgtuner.enabled` | `false` | PostgreSQL performance tuning and analysis via MCP |
|
||||
| `mcp.sidecars.playwright.enabled` | `true` | Browser automation and web testing via MCP |
|
||||
|
||||
**Notes:**
|
||||
- GitHub MCP is accessed via the Copilot API (`https://api.githubcopilot.com/mcp/`), not as a sidecar
|
||||
- Kubernetes and Flux sidecars require `clusterAccess` != `none` to be deployed (automatically disabled when no cluster access)
|
||||
- Kubernetes and Flux sidecars inherit the pod's ServiceAccount RBAC permissions (controlled by `clusterAccess`)
|
||||
- Home Assistant sidecar requires `HOMEASSISTANT_URL` and `HOMEASSISTANT_TOKEN` in the env secret
|
||||
- PostgreSQL tuner sidecar requires `DATABASE_URI` in the env secret (PostgreSQL connection string)
|
||||
- Playwright sidecar provides browser automation and web testing capabilities
|
||||
|
||||
**Disable MCP sidecars:**
|
||||
```bash
|
||||
# Disable multiple sidecars
|
||||
helm install mydev ./chart \
|
||||
--set name=mydev \
|
||||
--set githubRepo=https://github.com/youruser/yourrepo \
|
||||
--set mcp.sidecars.kubernetes.enabled=false \
|
||||
--set mcp.sidecars.flux.enabled=false \
|
||||
--set mcp.sidecars.playwright.enabled=false
|
||||
|
||||
# Or selectively disable
|
||||
helm install mydev ./chart \
|
||||
--set name=mydev \
|
||||
--set githubRepo=https://github.com/youruser/yourrepo \
|
||||
--set mcp.sidecars.flux.enabled=false # Disable only Flux MCP
|
||||
```
|
||||
|
||||
**Enable Home Assistant MCP:**
|
||||
```bash
|
||||
# Create secret with Home Assistant credentials
|
||||
kubectl create secret generic devcontainer-mydev-secrets-env \
|
||||
--from-literal=GITHUB_TOKEN='ghp_...' \
|
||||
--from-literal=HOMEASSISTANT_URL='http://homeassistant.local:8123' \
|
||||
--from-literal=HOMEASSISTANT_TOKEN='your_long_lived_access_token'
|
||||
|
||||
# Deploy with Home Assistant MCP enabled
|
||||
helm install mydev ./chart \
|
||||
--set name=mydev \
|
||||
--set githubRepo=https://github.com/youruser/yourrepo \
|
||||
--set mcp.sidecars.homeassistant.enabled=true
|
||||
```
|
||||
|
||||
**Enable PostgreSQL Tuner MCP:**
|
||||
```bash
|
||||
# Create secret with PostgreSQL connection string
|
||||
kubectl create secret generic devcontainer-mydev-secrets-env \
|
||||
--from-literal=GITHUB_TOKEN='ghp_...' \
|
||||
--from-literal=DATABASE_URI='postgresql://user:password@postgres.example.com:5432/dbname'
|
||||
|
||||
# Deploy with PostgreSQL tuner MCP enabled
|
||||
helm install mydev ./chart \
|
||||
--set name=mydev \
|
||||
--set githubRepo=https://github.com/youruser/yourrepo \
|
||||
--set mcp.sidecars.pgtuner.enabled=true
|
||||
```
|
||||
|
||||
**Custom MCP configuration:**
|
||||
```yaml
|
||||
# values.yaml override
|
||||
mcp:
|
||||
sidecars:
|
||||
kubernetes:
|
||||
enabled: true
|
||||
image:
|
||||
repository: quay.io/containers/kubernetes_mcp_server
|
||||
tag: v0.0.57
|
||||
port: 8080
|
||||
resources:
|
||||
requests:
|
||||
memory: "64Mi"
|
||||
cpu: "50m"
|
||||
limits:
|
||||
memory: "256Mi"
|
||||
cpu: "500m"
|
||||
flux:
|
||||
enabled: false # Disabled in this example
|
||||
homeassistant:
|
||||
enabled: true
|
||||
image:
|
||||
repository: ghcr.io/homeassistant-ai/ha-mcp
|
||||
tag: stable
|
||||
port: 8087
|
||||
pgtuner:
|
||||
enabled: true
|
||||
image:
|
||||
repository: dog830228/pgtuner_mcp
|
||||
tag: latest
|
||||
port: 8085
|
||||
playwright:
|
||||
enabled: true
|
||||
image:
|
||||
repository: mcr.microsoft.com/playwright/mcp
|
||||
tag: latest
|
||||
port: 8086
|
||||
resources:
|
||||
requests:
|
||||
memory: "128Mi"
|
||||
cpu: "100m"
|
||||
limits:
|
||||
memory: "512Mi"
|
||||
cpu: "1000m"
|
||||
```
|
||||
|
||||
### Display and resources
|
||||
|
||||
| Value | Default | Description |
|
||||
|-------|---------|-------------|
|
||||
| `display.width` | `1920` | VNC width (px) |
|
||||
| `display.height` | `1080` | VNC height (px) |
|
||||
| `display.secureConnection` | `0` | Set to `1` if TLS is not terminated upstream |
|
||||
| `user.id` | `1000` | UID for the app user |
|
||||
| `user.groupId` | `1000` | GID for the app user |
|
||||
| `storage.size` | `32Gi` | Home PVC size |
|
||||
| `storage.className` | `ceph-filesystem` | StorageClass (must be ReadWriteMany) |
|
||||
| `shm.sizeLimit` | `2Gi` | `/dev/shm` size (memory-backed; used by Electron apps) |
|
||||
| `resources.requests.memory` | `2Gi` | |
|
||||
| `resources.requests.cpu` | `1000m` | |
|
||||
| `resources.limits.memory` | `8Gi` | |
|
||||
| `resources.limits.cpu` | `4000m` | |
|
||||
| `envSecretName` | `devcontainer-{name}-secrets-env` | Override the secret name |
|
||||
|
||||
---
|
||||
|
||||
## Architecture
|
||||
|
||||
### Startup flow
|
||||
|
||||
```
|
||||
Container start
|
||||
→ cont-init.d/20-fix-user-shell.sh — fix shell/home on baseimage-gui app user
|
||||
→ cont-init.d/25-start-sshd.sh — start sshd if SSH=true
|
||||
→ /startapp.sh (runs as app user, UID 1000)
|
||||
→ init-repo.sh
|
||||
→ clone / pull GITHUB_REPO into /workspace/{repo}
|
||||
→ IDE=vscode: code --new-window --wait /workspace/{repo}
|
||||
IDE=antigravity: antigravity --no-sandbox --user-data-dir ~/.config/antigravity ... /workspace/{repo}
|
||||
IDE=none: sleep infinity
|
||||
(SSH=true: sshd also running as root on port 22; host keys persisted on PVC)
|
||||
```
|
||||
|
||||
### Storage
|
||||
|
||||
| Mount | Source | Persistence |
|
||||
|-------|--------|-------------|
|
||||
| `/config` | ReadWriteMany PVC (`userhome-{name}`) | Survives pod restarts — stores Claude credentials, dotfiles, git config |
|
||||
| `/workspace` | `emptyDir` | Ephemeral — repo is re-cloned on each pod start |
|
||||
|
||||
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 (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
|
||||
|
||||
# View daemon logs
|
||||
ls ~/.happy/logs/
|
||||
|
||||
# Stop daemon if needed
|
||||
happy daemon stop
|
||||
```
|
||||
|
||||
### Claude not authenticated
|
||||
|
||||
Browser-based OAuth login is the primary method (works inside VNC via the Chrome wrapper). If you prefer API key auth:
|
||||
|
||||
```bash
|
||||
kubectl patch secret devcontainer-mydev-secrets-env \
|
||||
--type='json' \
|
||||
-p='[{"op":"add","path":"/data/ANTHROPIC_API_KEY","value":"'$(echo -n "sk-ant-..." | base64)'"}]'
|
||||
```
|
||||
|
||||
Then restart the pod to pick up the new env var.
|
||||
|
||||
### VNC not loading
|
||||
|
||||
```bash
|
||||
kubectl port-forward deployment/devcontainer-mydev 5800:5800
|
||||
kubectl logs deployment/devcontainer-mydev
|
||||
kubectl describe pod -l app.kubernetes.io/instance=mydev
|
||||
```
|
||||
|
||||
### Pod not picking up new image after upgrade
|
||||
|
||||
The chart uses `image.tag: latest`. Kubernetes won't restart the pod on a Helm upgrade unless the Deployment spec changes. Force a restart manually:
|
||||
|
||||
```bash
|
||||
kubectl rollout restart deployment/devcontainer-mydev
|
||||
```
|
||||
|
||||
### Repository not cloning
|
||||
|
||||
```bash
|
||||
kubectl logs deployment/devcontainer-mydev | grep "Repository Initialization"
|
||||
kubectl exec deployment/devcontainer-mydev -- env | grep GITHUB
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Local Docker run
|
||||
|
||||
```bash
|
||||
docker run -d \
|
||||
-p 5800:5800 \
|
||||
-e GITHUB_REPO="https://github.com/youruser/yourrepo" \
|
||||
-e GITHUB_TOKEN="ghp_..." \
|
||||
-e VNC_PASSWORD="changeme" \
|
||||
-v $(pwd)/home:/home \
|
||||
ghcr.io/cpfarhood/devcontainer:latest
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Building
|
||||
|
||||
```bash
|
||||
docker build -t ghcr.io/cpfarhood/devcontainer:latest .
|
||||
docker push ghcr.io/cpfarhood/devcontainer:latest
|
||||
```
|
||||
|
||||
The image is also built and pushed automatically by CI on every push to `main` and on version tags (`v*`).
|
||||
|
||||
---
|
||||
|
||||
## Credits
|
||||
|
||||
- Base image: [jlesage/docker-baseimage-gui](https://github.com/jlesage/docker-baseimage-gui)
|
||||
- AI assistant: [Happy Coder](https://happy.engineering) + [Claude](https://claude.ai)
|
||||
-444
@@ -1,444 +0,0 @@
|
||||
# Helm Chart Values Reference
|
||||
|
||||
Complete reference for all configurable values in the Antigravity Dev Container Helm chart.
|
||||
|
||||
## Core Configuration
|
||||
|
||||
### name
|
||||
- **Type:** String
|
||||
- **Default:** `""`
|
||||
- **Required:** Yes
|
||||
- **Description:** Instance name used to generate resource names (`devcontainer-{name}`, `userhome-{name}`)
|
||||
- **Example:** `mydev`, `alice-dev`, `team-workspace`
|
||||
|
||||
### githubRepo
|
||||
- **Type:** String
|
||||
- **Default:** `""`
|
||||
- **Required:** Yes
|
||||
- **Description:** GitHub repository URL to clone into `/workspace`
|
||||
- **Example:** `https://github.com/username/repository`
|
||||
|
||||
### ide
|
||||
- **Type:** String
|
||||
- **Default:** `vscode`
|
||||
- **Options:** `vscode`, `antigravity`, `none`
|
||||
- **Description:** IDE to launch inside the container
|
||||
- `vscode` — VSCode via VNC browser UI on port 5800
|
||||
- `antigravity` — Google Antigravity (VSCode fork) via VNC on port 5800
|
||||
- `none` — No IDE; useful when `ssh: true` is the sole access method
|
||||
|
||||
### ssh
|
||||
- **Type:** Boolean
|
||||
- **Default:** `false`
|
||||
- **Description:** Start an OpenSSH server on port 22 in addition to the IDE
|
||||
- **Note:** Requires `SSH_AUTHORIZED_KEYS` in env secret for key-based login
|
||||
|
||||
## Image Configuration
|
||||
|
||||
### image.repository
|
||||
- **Type:** String
|
||||
- **Default:** `ghcr.io/cpfarhood/devcontainer`
|
||||
- **Description:** Container image repository
|
||||
|
||||
### image.tag
|
||||
- **Type:** String
|
||||
- **Default:** `latest`
|
||||
- **Description:** Container image tag
|
||||
- **Best Practice:** Use specific version tags for production
|
||||
|
||||
### image.pullPolicy
|
||||
- **Type:** String
|
||||
- **Default:** `Always`
|
||||
- **Options:** `Always`, `IfNotPresent`, `Never`
|
||||
- **Description:** Image pull policy
|
||||
|
||||
## Happy Coder Configuration
|
||||
|
||||
### happyServerUrl
|
||||
- **Type:** String
|
||||
- **Default:** `https://happy.farh.net`
|
||||
- **Description:** Happy Coder server endpoint
|
||||
- **When to Change:** Self-hosted Happy instance
|
||||
|
||||
### happyWebappUrl
|
||||
- **Type:** String
|
||||
- **Default:** `https://happy-coder.farh.net`
|
||||
- **Description:** Happy Coder webapp URL
|
||||
- **When to Change:** Self-hosted Happy instance
|
||||
|
||||
### happyHomeDir
|
||||
- **Type:** String
|
||||
- **Default:** `/config/userdata/.happy`
|
||||
- **Description:** Happy runtime state directory (persists on PVC)
|
||||
|
||||
### happyExperimental
|
||||
- **Type:** String
|
||||
- **Default:** `"true"`
|
||||
- **Description:** Enable experimental Happy features
|
||||
|
||||
## Display Configuration
|
||||
|
||||
### display.width
|
||||
- **Type:** String
|
||||
- **Default:** `"1920"`
|
||||
- **Description:** VNC display width in pixels
|
||||
|
||||
### display.height
|
||||
- **Type:** String
|
||||
- **Default:** `"1080"`
|
||||
- **Description:** VNC display height in pixels
|
||||
|
||||
### secureConnection
|
||||
- **Type:** String
|
||||
- **Default:** `"0"`
|
||||
- **Options:** `"0"`, `"1"`
|
||||
- **Description:** Set to `"0"` when TLS is terminated at the gateway layer
|
||||
|
||||
## User Configuration
|
||||
|
||||
### userId
|
||||
- **Type:** String
|
||||
- **Default:** `"1000"`
|
||||
- **Description:** UID for the app user
|
||||
|
||||
### groupId
|
||||
- **Type:** String
|
||||
- **Default:** `"1000"`
|
||||
- **Description:** GID for the app user
|
||||
|
||||
## Storage Configuration
|
||||
|
||||
### storage.size
|
||||
- **Type:** String
|
||||
- **Default:** `32Gi`
|
||||
- **Description:** Size of the persistent home directory
|
||||
- **Format:** Kubernetes quantity (e.g., `10Gi`, `100Gi`, `1Ti`)
|
||||
|
||||
### storage.className
|
||||
- **Type:** String
|
||||
- **Default:** `ceph-filesystem`
|
||||
- **Description:** StorageClass name (must support ReadWriteMany)
|
||||
- **Examples:** `ceph-filesystem`, `nfs-client`, `efs-sc`, `azurefile`
|
||||
|
||||
### shm.sizeLimit
|
||||
- **Type:** String
|
||||
- **Default:** `2Gi`
|
||||
- **Description:** `/dev/shm` size (memory-backed emptyDir for Electron apps)
|
||||
|
||||
## Resource Limits
|
||||
|
||||
### resources.requests.memory
|
||||
- **Type:** String
|
||||
- **Default:** `2Gi`
|
||||
- **Description:** Minimum memory to reserve
|
||||
- **Format:** Kubernetes quantity
|
||||
|
||||
### resources.requests.cpu
|
||||
- **Type:** String
|
||||
- **Default:** `1000m`
|
||||
- **Description:** Minimum CPU to reserve
|
||||
- **Format:** Millicores (`1000m` = 1 CPU core)
|
||||
|
||||
### resources.limits.memory
|
||||
- **Type:** String
|
||||
- **Default:** `8Gi`
|
||||
- **Description:** Maximum memory allowed
|
||||
- **Format:** Kubernetes quantity
|
||||
|
||||
### resources.limits.cpu
|
||||
- **Type:** String
|
||||
- **Default:** `4000m`
|
||||
- **Description:** Maximum CPU allowed
|
||||
- **Format:** Millicores (`4000m` = 4 CPU cores)
|
||||
|
||||
## Kubernetes Access
|
||||
|
||||
### clusterAccess
|
||||
- **Type:** String
|
||||
- **Default:** `none`
|
||||
- **Options:**
|
||||
- `none` — No cluster access
|
||||
- `readonlyns` — Read-only access to release namespace
|
||||
- `readwritens` — Full access to release namespace
|
||||
- `readonly` — Read-only access cluster-wide
|
||||
- `readwrite` — Full access cluster-wide
|
||||
- **Description:** RBAC permissions for the pod's ServiceAccount
|
||||
|
||||
## Secrets
|
||||
|
||||
### envSecretName
|
||||
- **Type:** String
|
||||
- **Default:** `""` (auto-generates as `devcontainer-{name}-secrets-env`)
|
||||
- **Description:** Name of existing Secret containing environment variables
|
||||
- **Keys Recognized:**
|
||||
- `GITHUB_TOKEN` — PAT for private repo access
|
||||
- `VNC_PASSWORD` — Password for VNC web UI
|
||||
- `ANTHROPIC_API_KEY` — API key for Claude
|
||||
- `SSH_AUTHORIZED_KEYS` — Public keys for SSH access
|
||||
- `homeassistant-url` — Home Assistant base URL (e.g., http://homeassistant.local:8123)
|
||||
- `homeassistant-token` — Home Assistant long-lived access token
|
||||
|
||||
## MCP Sidecars
|
||||
|
||||
### mcpSidecars.kubernetes.enabled
|
||||
- **Type:** Boolean
|
||||
- **Default:** `true`
|
||||
- **Description:** Enable Kubernetes MCP server sidecar
|
||||
|
||||
### mcpSidecars.kubernetes.image.repository
|
||||
- **Type:** String
|
||||
- **Default:** `quay.io/containers/kubernetes_mcp_server`
|
||||
- **Description:** Kubernetes MCP server image
|
||||
|
||||
### mcpSidecars.kubernetes.image.tag
|
||||
- **Type:** String
|
||||
- **Default:** `latest`
|
||||
- **Description:** Kubernetes MCP server image tag
|
||||
|
||||
### mcpSidecars.kubernetes.port
|
||||
- **Type:** Integer
|
||||
- **Default:** `8080`
|
||||
- **Description:** Port for Kubernetes MCP server
|
||||
|
||||
### mcpSidecars.kubernetes.resources
|
||||
- **Type:** Object
|
||||
- **Default:**
|
||||
```yaml
|
||||
requests:
|
||||
memory: "64Mi"
|
||||
cpu: "50m"
|
||||
limits:
|
||||
memory: "256Mi"
|
||||
cpu: "500m"
|
||||
```
|
||||
- **Description:** Resource limits for Kubernetes MCP sidecar
|
||||
|
||||
### mcpSidecars.flux.enabled
|
||||
- **Type:** Boolean
|
||||
- **Default:** `true`
|
||||
- **Description:** Enable Flux MCP server sidecar
|
||||
|
||||
### mcpSidecars.flux.image.repository
|
||||
- **Type:** String
|
||||
- **Default:** `ghcr.io/controlplaneio-fluxcd/flux-operator-mcp`
|
||||
- **Description:** Flux MCP server image
|
||||
|
||||
### mcpSidecars.flux.image.tag
|
||||
- **Type:** String
|
||||
- **Default:** `v0.41.1`
|
||||
- **Description:** Flux MCP server image tag
|
||||
|
||||
### mcpSidecars.flux.port
|
||||
- **Type:** Integer
|
||||
- **Default:** `8081`
|
||||
- **Description:** Port for Flux MCP server
|
||||
|
||||
### mcpSidecars.flux.resources
|
||||
- **Type:** Object
|
||||
- **Default:**
|
||||
```yaml
|
||||
requests:
|
||||
memory: "64Mi"
|
||||
cpu: "50m"
|
||||
limits:
|
||||
memory: "256Mi"
|
||||
cpu: "500m"
|
||||
```
|
||||
- **Description:** Resource limits for Flux MCP sidecar
|
||||
|
||||
### mcpSidecars.homeassistant.enabled
|
||||
- **Type:** Boolean
|
||||
- **Default:** `false`
|
||||
- **Description:** Enable Home Assistant MCP server sidecar
|
||||
- **Note:** Requires `homeassistant-url` and `homeassistant-token` in env secret
|
||||
|
||||
### mcpSidecars.homeassistant.image.repository
|
||||
- **Type:** String
|
||||
- **Default:** `ghcr.io/homeassistant-ai/ha-mcp`
|
||||
- **Description:** Home Assistant MCP server image
|
||||
|
||||
### mcpSidecars.homeassistant.image.tag
|
||||
- **Type:** String
|
||||
- **Default:** `stable`
|
||||
- **Description:** Home Assistant MCP server image tag
|
||||
- **Options:** `stable` (recommended), `latest` (dev builds), `v{version}` (specific version)
|
||||
|
||||
### mcpSidecars.homeassistant.port
|
||||
- **Type:** Integer
|
||||
- **Default:** `8087`
|
||||
- **Description:** Port for Home Assistant MCP server (SSE mode)
|
||||
|
||||
### mcpSidecars.homeassistant.resources
|
||||
- **Type:** Object
|
||||
- **Default:**
|
||||
```yaml
|
||||
requests:
|
||||
memory: "64Mi"
|
||||
cpu: "50m"
|
||||
limits:
|
||||
memory: "256Mi"
|
||||
cpu: "500m"
|
||||
```
|
||||
- **Description:** Resource limits for Home Assistant MCP sidecar
|
||||
|
||||
## Usage Examples
|
||||
|
||||
### Minimal Configuration
|
||||
|
||||
```yaml
|
||||
name: mydev
|
||||
githubRepo: https://github.com/user/repo
|
||||
```
|
||||
|
||||
### Production Configuration
|
||||
|
||||
```yaml
|
||||
name: prod-workspace
|
||||
githubRepo: https://github.com/company/application
|
||||
ide: vscode
|
||||
ssh: true
|
||||
|
||||
image:
|
||||
tag: v1.0.0
|
||||
|
||||
storage:
|
||||
size: 100Gi
|
||||
className: ceph-filesystem
|
||||
|
||||
resources:
|
||||
requests:
|
||||
memory: "4Gi"
|
||||
cpu: "2000m"
|
||||
limits:
|
||||
memory: "16Gi"
|
||||
cpu: "8000m"
|
||||
|
||||
clusterAccess: readwritens
|
||||
|
||||
mcpSidecars:
|
||||
kubernetes:
|
||||
enabled: true
|
||||
flux:
|
||||
enabled: false
|
||||
```
|
||||
|
||||
### Development Team Configuration
|
||||
|
||||
```yaml
|
||||
name: team-dev
|
||||
githubRepo: https://github.com/team/project
|
||||
ide: antigravity
|
||||
|
||||
display:
|
||||
width: "2560"
|
||||
height: "1440"
|
||||
|
||||
storage:
|
||||
size: 50Gi
|
||||
className: nfs-client
|
||||
|
||||
clusterAccess: readonly
|
||||
|
||||
happyServerUrl: https://happy.internal.company.com
|
||||
happyWebappUrl: https://happy-app.internal.company.com
|
||||
```
|
||||
|
||||
### Smart Home Development Configuration
|
||||
|
||||
```yaml
|
||||
name: smarthome-dev
|
||||
githubRepo: https://github.com/user/home-automation
|
||||
ide: vscode
|
||||
|
||||
clusterAccess: readwritens
|
||||
|
||||
mcpSidecars:
|
||||
kubernetes:
|
||||
enabled: true
|
||||
flux:
|
||||
enabled: false
|
||||
homeassistant:
|
||||
enabled: true
|
||||
image:
|
||||
tag: stable
|
||||
|
||||
# Requires secrets:
|
||||
# homeassistant-url: http://homeassistant.local:8123
|
||||
# homeassistant-token: <long-lived-access-token>
|
||||
```
|
||||
|
||||
## Helm CLI Examples
|
||||
|
||||
### Using --set Flags
|
||||
|
||||
```bash
|
||||
# Basic deployment
|
||||
helm install mydev ./chart \
|
||||
--set name=mydev \
|
||||
--set githubRepo=https://github.com/user/repo
|
||||
|
||||
# With multiple values
|
||||
helm install mydev ./chart \
|
||||
--set name=mydev \
|
||||
--set githubRepo=https://github.com/user/repo \
|
||||
--set ide=antigravity \
|
||||
--set storage.size=50Gi \
|
||||
--set clusterAccess=readwritens \
|
||||
--set mcpSidecars.flux.enabled=false
|
||||
```
|
||||
|
||||
### Using Values File
|
||||
|
||||
Create `custom-values.yaml`:
|
||||
```yaml
|
||||
name: mydev
|
||||
githubRepo: https://github.com/user/repo
|
||||
storage:
|
||||
size: 50Gi
|
||||
clusterAccess: readwritens
|
||||
```
|
||||
|
||||
Deploy:
|
||||
```bash
|
||||
helm install mydev ./chart -f custom-values.yaml
|
||||
```
|
||||
|
||||
### Combining Methods
|
||||
|
||||
```bash
|
||||
helm install mydev ./chart \
|
||||
-f base-values.yaml \
|
||||
-f prod-values.yaml \
|
||||
--set githubRepo=https://github.com/user/repo \
|
||||
--set image.tag=v2.0.0
|
||||
```
|
||||
|
||||
## Value Precedence
|
||||
|
||||
Values are applied in order of precedence (highest to lowest):
|
||||
1. `--set` flags on command line
|
||||
2. `-f` values files (later files override earlier)
|
||||
3. `chart/values.yaml` defaults
|
||||
|
||||
## Environment Variables
|
||||
|
||||
These environment variables are set in the container based on chart values:
|
||||
|
||||
| Environment Variable | Source Value | Description |
|
||||
|---------------------|--------------|-------------|
|
||||
| `GITHUB_REPO` | `githubRepo` | Repository to clone |
|
||||
| `GITHUB_TOKEN` | Secret: `github-token` | PAT for private repos |
|
||||
| `VNC_PASSWORD` | Secret: `vnc-password` | VNC access password |
|
||||
| `ANTHROPIC_API_KEY` | Secret: `anthropic-api-key` | Claude API key |
|
||||
| `SSH_AUTHORIZED_KEYS` | Secret: `ssh-authorized-keys` | SSH public keys |
|
||||
| `HAPPY_SERVER_URL` | `happyServerUrl` | Happy server endpoint |
|
||||
| `HAPPY_WEBAPP_URL` | `happyWebappUrl` | Happy webapp URL |
|
||||
| `HAPPY_HOME_DIR` | `happyHomeDir` | Happy data directory |
|
||||
| `HAPPY_EXPERIMENTAL` | `happyExperimental` | Experimental features |
|
||||
| `DISPLAY_WIDTH` | `display.width` | VNC width |
|
||||
| `DISPLAY_HEIGHT` | `display.height` | VNC height |
|
||||
| `SECURE_CONNECTION` | `secureConnection` | TLS termination |
|
||||
| `USER_ID` | `userId` | App user UID |
|
||||
| `GROUP_ID` | `groupId` | App user GID |
|
||||
| `IDE` | `ide` | IDE to launch |
|
||||
| `SSH` | `ssh` | SSH server enabled |
|
||||
@@ -1,13 +0,0 @@
|
||||
apiVersion: v2
|
||||
name: devcontainer
|
||||
description: Dev Container with AI coding agents and MCP sidecars - supports persistent and dynamic deployment modes
|
||||
type: application
|
||||
version: 2.1.0
|
||||
appVersion: "latest"
|
||||
keywords:
|
||||
- development
|
||||
- devcontainer
|
||||
- vscode
|
||||
- ai
|
||||
- knative
|
||||
- serverless
|
||||
-381
@@ -1,381 +0,0 @@
|
||||
# Dev Container Helm Chart Usage Guide
|
||||
|
||||
This guide provides common usage patterns and examples for the Dev Container Helm chart.
|
||||
|
||||
## Quick Start
|
||||
|
||||
### 1. Minimal Installation (Recommended)
|
||||
|
||||
Use the quickstart values for the simplest setup:
|
||||
|
||||
```bash
|
||||
# Copy and customize quickstart values
|
||||
cp values-quickstart.yaml my-values.yaml
|
||||
|
||||
# Edit my-values.yaml to set your name and repo:
|
||||
# name: myproject
|
||||
# githubRepo: https://github.com/youruser/yourproject
|
||||
|
||||
# Install
|
||||
helm install myproject ./chart -f my-values.yaml
|
||||
```
|
||||
|
||||
### 2. One-Command Installation
|
||||
|
||||
```bash
|
||||
helm install mydev ./chart \
|
||||
--set name=mydev \
|
||||
--set githubRepo=https://github.com/youruser/yourrepo
|
||||
```
|
||||
|
||||
## Common Use Cases
|
||||
|
||||
### Development Environment
|
||||
|
||||
**Scenario**: Standard development with GitHub integration
|
||||
|
||||
```yaml
|
||||
name: dev-environment
|
||||
githubRepo: https://github.com/company/project
|
||||
|
||||
ide:
|
||||
type: vscode
|
||||
|
||||
mcp:
|
||||
sidecars:
|
||||
kubernetes:
|
||||
enabled: true
|
||||
playwright:
|
||||
enabled: true
|
||||
flux:
|
||||
enabled: false # Disable if not using Flux
|
||||
```
|
||||
|
||||
### Team Workspace
|
||||
|
||||
**Scenario**: Shared development environment with more resources
|
||||
|
||||
```yaml
|
||||
name: team-workspace
|
||||
githubRepo: https://github.com/company/project
|
||||
|
||||
resources:
|
||||
requests:
|
||||
memory: "4Gi"
|
||||
cpu: "2000m"
|
||||
limits:
|
||||
memory: "16Gi"
|
||||
cpu: "8000m"
|
||||
|
||||
storage:
|
||||
size: 64Gi
|
||||
|
||||
ssh:
|
||||
enabled: true # Enable SSH access for team
|
||||
|
||||
clusterAccess: readwrite # Full cluster access
|
||||
```
|
||||
|
||||
### Kubernetes Admin Environment
|
||||
|
||||
**Scenario**: Platform engineering with full cluster access
|
||||
|
||||
```yaml
|
||||
name: k8s-admin
|
||||
githubRepo: https://github.com/company/k8s-configs
|
||||
|
||||
clusterAccess: readwrite
|
||||
|
||||
mcp:
|
||||
sidecars:
|
||||
kubernetes:
|
||||
enabled: true
|
||||
flux:
|
||||
enabled: true
|
||||
pgtuner:
|
||||
enabled: true # Database administration
|
||||
playwright:
|
||||
enabled: false # Save resources
|
||||
```
|
||||
|
||||
### AI/ML Development
|
||||
|
||||
**Scenario**: AI development with browser automation
|
||||
|
||||
```yaml
|
||||
name: ai-playground
|
||||
githubRepo: https://github.com/company/ai-project
|
||||
|
||||
resources:
|
||||
requests:
|
||||
memory: "8Gi" # More memory for ML workloads
|
||||
cpu: "4000m"
|
||||
limits:
|
||||
memory: "32Gi"
|
||||
cpu: "16000m"
|
||||
|
||||
storage:
|
||||
size: 128Gi # Large datasets
|
||||
|
||||
mcp:
|
||||
sidecars:
|
||||
playwright:
|
||||
enabled: true # Web scraping, testing
|
||||
kubernetes:
|
||||
enabled: false # Save resources
|
||||
flux:
|
||||
enabled: false
|
||||
```
|
||||
|
||||
### Lightweight Environment
|
||||
|
||||
**Scenario**: Resource-constrained setup
|
||||
|
||||
```yaml
|
||||
name: lightweight
|
||||
githubRepo: https://github.com/youruser/small-project
|
||||
|
||||
resources:
|
||||
requests:
|
||||
memory: "1Gi"
|
||||
cpu: "500m"
|
||||
limits:
|
||||
memory: "2Gi"
|
||||
cpu: "1000m"
|
||||
|
||||
storage:
|
||||
size: 8Gi
|
||||
|
||||
mcp:
|
||||
sidecars:
|
||||
kubernetes:
|
||||
enabled: false
|
||||
flux:
|
||||
enabled: false
|
||||
playwright:
|
||||
enabled: false
|
||||
# Only keep essential sidecars enabled
|
||||
```
|
||||
|
||||
## Secret Configuration
|
||||
|
||||
### Basic Secrets
|
||||
|
||||
```bash
|
||||
# GitHub access only
|
||||
kubectl create secret generic devcontainer-mydev-secrets-env \
|
||||
--from-literal=GITHUB_TOKEN='ghp_...' \
|
||||
--from-literal=VNC_PASSWORD='changeme'
|
||||
```
|
||||
|
||||
### Extended Secrets
|
||||
|
||||
```bash
|
||||
# Full feature set
|
||||
kubectl create secret generic devcontainer-mydev-secrets-env \
|
||||
--from-literal=GITHUB_TOKEN='ghp_...' \
|
||||
--from-literal=VNC_PASSWORD='changeme' \
|
||||
--from-literal=SSH_AUTHORIZED_KEYS='ssh-ed25519 AAAA...' \
|
||||
--from-literal=HOMEASSISTANT_URL='http://homeassistant.local:8123' \
|
||||
--from-literal=HOMEASSISTANT_TOKEN='eyJ...' \
|
||||
--from-literal=DATABASE_URI='postgresql://user:pass@postgres:5432/db'
|
||||
```
|
||||
|
||||
## Storage Configuration
|
||||
|
||||
### Different Storage Classes
|
||||
|
||||
```yaml
|
||||
# For different Kubernetes distributions
|
||||
storage:
|
||||
className: "" # Auto-detect (recommended)
|
||||
# className: longhorn # Longhorn
|
||||
# className: nfs-client # NFS
|
||||
# className: fast-ssd # Custom fast storage
|
||||
```
|
||||
|
||||
### Storage Sizes by Use Case
|
||||
|
||||
```yaml
|
||||
# Small projects
|
||||
storage:
|
||||
size: 8Gi
|
||||
|
||||
# Standard development
|
||||
storage:
|
||||
size: 32Gi
|
||||
|
||||
# Large projects / datasets
|
||||
storage:
|
||||
size: 128Gi
|
||||
|
||||
# Team environments
|
||||
storage:
|
||||
size: 256Gi
|
||||
```
|
||||
|
||||
## Access Patterns
|
||||
|
||||
### VNC Only (Default)
|
||||
|
||||
```yaml
|
||||
ide:
|
||||
type: vscode
|
||||
|
||||
# Access via: kubectl port-forward deployment/devcontainer-mydev 5800:5800
|
||||
```
|
||||
|
||||
### SSH Only
|
||||
|
||||
```yaml
|
||||
ide:
|
||||
type: none
|
||||
|
||||
ssh:
|
||||
enabled: true
|
||||
|
||||
# Access via: kubectl port-forward deployment/devcontainer-mydev 2222:22
|
||||
# ssh -p 2222 user@localhost
|
||||
```
|
||||
|
||||
### Both VNC and SSH
|
||||
|
||||
```yaml
|
||||
ide:
|
||||
type: vscode
|
||||
|
||||
ssh:
|
||||
enabled: true
|
||||
|
||||
# VNC: kubectl port-forward deployment/devcontainer-mydev 5800:5800
|
||||
# SSH: kubectl port-forward deployment/devcontainer-mydev 2222:22
|
||||
```
|
||||
|
||||
## Resource Profiles
|
||||
|
||||
### Small (1-2 developers)
|
||||
```yaml
|
||||
resources:
|
||||
requests:
|
||||
memory: "1Gi"
|
||||
cpu: "500m"
|
||||
limits:
|
||||
memory: "4Gi"
|
||||
cpu: "2000m"
|
||||
```
|
||||
|
||||
### Medium (standard development)
|
||||
```yaml
|
||||
resources:
|
||||
requests:
|
||||
memory: "2Gi"
|
||||
cpu: "1000m"
|
||||
limits:
|
||||
memory: "8Gi"
|
||||
cpu: "4000m"
|
||||
```
|
||||
|
||||
### Large (intensive workloads)
|
||||
```yaml
|
||||
resources:
|
||||
requests:
|
||||
memory: "4Gi"
|
||||
cpu: "2000m"
|
||||
limits:
|
||||
memory: "16Gi"
|
||||
cpu: "8000m"
|
||||
```
|
||||
|
||||
### XLarge (AI/ML, data processing)
|
||||
```yaml
|
||||
resources:
|
||||
requests:
|
||||
memory: "8Gi"
|
||||
cpu: "4000m"
|
||||
limits:
|
||||
memory: "32Gi"
|
||||
cpu: "16000m"
|
||||
```
|
||||
|
||||
## MCP Sidecar Combinations
|
||||
|
||||
### Minimal (basic development)
|
||||
```yaml
|
||||
mcp:
|
||||
sidecars:
|
||||
kubernetes:
|
||||
enabled: false
|
||||
flux:
|
||||
enabled: false
|
||||
playwright:
|
||||
enabled: true # Keep for web testing
|
||||
```
|
||||
|
||||
### Standard (full-stack development)
|
||||
```yaml
|
||||
mcp:
|
||||
sidecars:
|
||||
kubernetes:
|
||||
enabled: true
|
||||
flux:
|
||||
enabled: false
|
||||
playwright:
|
||||
enabled: true
|
||||
```
|
||||
|
||||
### DevOps/Platform (infrastructure work)
|
||||
```yaml
|
||||
mcp:
|
||||
sidecars:
|
||||
kubernetes:
|
||||
enabled: true
|
||||
flux:
|
||||
enabled: true
|
||||
pgtuner:
|
||||
enabled: true
|
||||
playwright:
|
||||
enabled: false
|
||||
```
|
||||
|
||||
### All Features
|
||||
```yaml
|
||||
mcp:
|
||||
sidecars:
|
||||
kubernetes:
|
||||
enabled: true
|
||||
flux:
|
||||
enabled: true
|
||||
homeassistant:
|
||||
enabled: true
|
||||
pgtuner:
|
||||
enabled: true
|
||||
playwright:
|
||||
enabled: true
|
||||
```
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Values Validation
|
||||
|
||||
Your IDE should automatically validate values.yaml against the schema. If not:
|
||||
|
||||
```bash
|
||||
# Manual validation (if you have a JSON schema validator)
|
||||
helm template ./chart -f values.yaml > /dev/null
|
||||
```
|
||||
|
||||
### Common Issues
|
||||
|
||||
**Resource Limits**: Start with smaller resource requests and increase as needed.
|
||||
|
||||
**Storage Class**: Use `className: ""` for auto-detection.
|
||||
|
||||
**GitHub Access**: Ensure GITHUB_TOKEN has `repo` scope.
|
||||
|
||||
**MCP Sidecars**: Disable unused sidecars to save resources.
|
||||
|
||||
### Getting Help
|
||||
|
||||
1. Check the main [README.md](../README.md) for detailed documentation
|
||||
2. Review [values.yaml](values.yaml) for all available options
|
||||
3. Use [values-quickstart.yaml](values-quickstart.yaml) as a starting point
|
||||
@@ -1,31 +0,0 @@
|
||||
Dev Container "{{ .Values.name }}" has been deployed.
|
||||
|
||||
{{- if ne (.Values.ide.type | default "vscode") "none" }}
|
||||
|
||||
Access the IDE:
|
||||
kubectl port-forward deployment/{{ include "devcontainer.fullname" . }} 5800:5800 -n {{ .Release.Namespace }}
|
||||
Then open: http://localhost:5800
|
||||
{{- end }}
|
||||
|
||||
{{- if .Values.ssh.enabled }}
|
||||
|
||||
SSH access:
|
||||
kubectl port-forward deployment/{{ include "devcontainer.fullname" . }} 2222:22 -n {{ .Release.Namespace }}
|
||||
Then: ssh -p 2222 user@localhost
|
||||
{{- end }}
|
||||
|
||||
Useful commands:
|
||||
Logs: kubectl logs -f deployment/{{ include "devcontainer.fullname" . }} -n {{ .Release.Namespace }}
|
||||
Shell: kubectl exec -it deployment/{{ include "devcontainer.fullname" . }} -n {{ .Release.Namespace }} -- bash
|
||||
|
||||
{{- if not (lookup "v1" "Secret" .Release.Namespace (include "devcontainer.envSecretName" .)) }}
|
||||
|
||||
Optional: Create a secret for GITHUB_TOKEN, VNC_PASSWORD, etc:
|
||||
kubectl create secret generic {{ include "devcontainer.envSecretName" . }} \
|
||||
--from-literal=GITHUB_TOKEN=ghp_xxx \
|
||||
--from-literal=VNC_PASSWORD=changeme \
|
||||
-n {{ .Release.Namespace }}
|
||||
{{- end }}
|
||||
|
||||
Note: The PVC "{{ include "devcontainer.pvcName" . }}" is protected from deletion on helm uninstall.
|
||||
To remove it manually: kubectl delete pvc {{ include "devcontainer.pvcName" . }} -n {{ .Release.Namespace }}
|
||||
@@ -1,111 +0,0 @@
|
||||
{{/*
|
||||
Resource name prefix: devcontainer-{name}
|
||||
*/}}
|
||||
{{- 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 "devcontainer.pvcName" -}}
|
||||
{{- printf "userhome-%s" .Values.name }}
|
||||
{{- end }}
|
||||
|
||||
{{/*
|
||||
Secret name for env vars, default to devcontainer-{name}-secrets-env
|
||||
*/}}
|
||||
{{- define "devcontainer.envSecretName" -}}
|
||||
{{- .Values.envSecretName | default (printf "devcontainer-%s-secrets-env" .Values.name) }}
|
||||
{{- end }}
|
||||
|
||||
{{/*
|
||||
Common 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 }}
|
||||
@@ -1,317 +0,0 @@
|
||||
{{- if eq .Values.deploymentMode "persistent" }}
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: {{ include "devcontainer.fullname" . }}
|
||||
labels:
|
||||
{{- include "devcontainer.labels" . | nindent 4 }}
|
||||
spec:
|
||||
replicas: 1
|
||||
selector:
|
||||
matchLabels:
|
||||
{{- include "devcontainer.selectorLabels" . | nindent 6 }}
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
{{- include "devcontainer.labels" . | nindent 8 }}
|
||||
spec:
|
||||
{{- if ne (.Values.clusterAccess | default "none") "none" }}
|
||||
serviceAccountName: {{ include "devcontainer.fullname" . }}
|
||||
{{- end }}
|
||||
securityContext:
|
||||
fsGroup: 1000
|
||||
fsGroupChangePolicy: "OnRootMismatch"
|
||||
{{- if and .Values.ide.type (eq .Values.ide.type "antigravity") }}
|
||||
initContainers:
|
||||
- name: setup-userdata
|
||||
image: busybox:1.37
|
||||
command: ['sh', '-c']
|
||||
args:
|
||||
- |
|
||||
echo "Setting up userdata directory..."
|
||||
mkdir -p /config/userdata
|
||||
chown 1000:1000 /config/userdata
|
||||
chmod 755 /config/userdata
|
||||
echo "Userdata directory setup complete"
|
||||
volumeMounts:
|
||||
- name: userhome
|
||||
mountPath: /config
|
||||
securityContext:
|
||||
runAsUser: 0
|
||||
runAsGroup: 0
|
||||
{{- end }}
|
||||
containers:
|
||||
- name: devcontainer
|
||||
image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}"
|
||||
imagePullPolicy: {{ .Values.image.pullPolicy }}
|
||||
ports:
|
||||
{{- if ne (.Values.ide.type | default "vscode") "none" }}
|
||||
- containerPort: 5800
|
||||
name: vnc-web
|
||||
protocol: TCP
|
||||
{{- end }}
|
||||
{{- if .Values.ssh.enabled }}
|
||||
- containerPort: 22
|
||||
name: ssh
|
||||
protocol: TCP
|
||||
{{- end }}
|
||||
env:
|
||||
- name: IDE
|
||||
value: {{ .Values.ide.type | default "vscode" | quote }}
|
||||
- name: SSH
|
||||
value: {{ .Values.ssh.enabled | toString | quote }}
|
||||
- name: USER_ID
|
||||
value: {{ .Values.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 }}
|
||||
{{- 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.happy.homeDir | quote }}
|
||||
- name: HAPPY_EXPERIMENTAL
|
||||
value: {{ .Values.happy.experimental | quote }}
|
||||
- name: HAPPY_SERVER_URL
|
||||
value: {{ .Values.happy.serverUrl | quote }}
|
||||
- name: HAPPY_WEBAPP_URL
|
||||
value: {{ .Values.happy.webappUrl | quote }}
|
||||
{{- if .Values.githubRepo }}
|
||||
- name: GITHUB_REPO
|
||||
value: {{ .Values.githubRepo | quote }}
|
||||
{{- end }}
|
||||
envFrom:
|
||||
- secretRef:
|
||||
name: {{ include "devcontainer.envSecretName" . }}
|
||||
optional: true
|
||||
resources:
|
||||
{{- toYaml .Values.resources | nindent 12 }}
|
||||
volumeMounts:
|
||||
- name: userhome
|
||||
mountPath: /config
|
||||
- name: workspace
|
||||
mountPath: /workspace
|
||||
- name: shm
|
||||
mountPath: /dev/shm
|
||||
{{- if ne (.Values.ide.type | default "vscode") "none" }}
|
||||
livenessProbe:
|
||||
httpGet:
|
||||
path: /
|
||||
port: 5800
|
||||
initialDelaySeconds: 30
|
||||
periodSeconds: 10
|
||||
readinessProbe:
|
||||
httpGet:
|
||||
path: /
|
||||
port: 5800
|
||||
initialDelaySeconds: 10
|
||||
periodSeconds: 5
|
||||
{{- else if .Values.ssh.enabled }}
|
||||
livenessProbe:
|
||||
tcpSocket:
|
||||
port: 22
|
||||
initialDelaySeconds: 15
|
||||
periodSeconds: 10
|
||||
readinessProbe:
|
||||
tcpSocket:
|
||||
port: 22
|
||||
initialDelaySeconds: 5
|
||||
periodSeconds: 5
|
||||
{{- end }}
|
||||
{{- if and .Values.mcp.sidecars.kubernetes.enabled (ne .Values.clusterAccess "none") }}
|
||||
- name: kubernetes-mcp
|
||||
image: "{{ .Values.mcp.sidecars.kubernetes.image.repository }}:{{ .Values.mcp.sidecars.kubernetes.image.tag }}"
|
||||
args:
|
||||
- --port
|
||||
- {{ .Values.mcp.sidecars.kubernetes.port | quote }}
|
||||
ports:
|
||||
- containerPort: {{ .Values.mcp.sidecars.kubernetes.port }}
|
||||
name: k8s-mcp
|
||||
protocol: TCP
|
||||
livenessProbe:
|
||||
httpGet:
|
||||
path: /healthz
|
||||
port: {{ .Values.mcp.sidecars.kubernetes.port }}
|
||||
initialDelaySeconds: 10
|
||||
periodSeconds: 10
|
||||
readinessProbe:
|
||||
httpGet:
|
||||
path: /healthz
|
||||
port: {{ .Values.mcp.sidecars.kubernetes.port }}
|
||||
initialDelaySeconds: 5
|
||||
periodSeconds: 5
|
||||
resources:
|
||||
{{- toYaml .Values.mcp.sidecars.kubernetes.resources | nindent 12 }}
|
||||
{{- end }}
|
||||
{{- if and .Values.mcp.sidecars.flux.enabled (ne .Values.clusterAccess "none") }}
|
||||
- name: flux-mcp
|
||||
image: "{{ .Values.mcp.sidecars.flux.image.repository }}:{{ .Values.mcp.sidecars.flux.image.tag }}"
|
||||
args:
|
||||
- serve
|
||||
- --transport=sse
|
||||
- --port={{ .Values.mcp.sidecars.flux.port }}
|
||||
ports:
|
||||
- containerPort: {{ .Values.mcp.sidecars.flux.port }}
|
||||
name: flux-mcp
|
||||
protocol: TCP
|
||||
livenessProbe:
|
||||
tcpSocket:
|
||||
port: {{ .Values.mcp.sidecars.flux.port }}
|
||||
initialDelaySeconds: 10
|
||||
periodSeconds: 10
|
||||
readinessProbe:
|
||||
tcpSocket:
|
||||
port: {{ .Values.mcp.sidecars.flux.port }}
|
||||
initialDelaySeconds: 5
|
||||
periodSeconds: 5
|
||||
resources:
|
||||
{{- toYaml .Values.mcp.sidecars.flux.resources | nindent 12 }}
|
||||
{{- end }}
|
||||
{{- if .Values.mcp.sidecars.helm.enabled }}
|
||||
- name: helm-mcp
|
||||
image: "{{ .Values.mcp.sidecars.helm.image.repository }}:{{ .Values.mcp.sidecars.helm.image.tag }}"
|
||||
args:
|
||||
- -mode=sse
|
||||
- -port={{ .Values.mcp.sidecars.helm.port }}
|
||||
ports:
|
||||
- containerPort: {{ .Values.mcp.sidecars.helm.port }}
|
||||
name: helm-mcp
|
||||
protocol: TCP
|
||||
livenessProbe:
|
||||
tcpSocket:
|
||||
port: {{ .Values.mcp.sidecars.helm.port }}
|
||||
initialDelaySeconds: 10
|
||||
periodSeconds: 10
|
||||
readinessProbe:
|
||||
tcpSocket:
|
||||
port: {{ .Values.mcp.sidecars.helm.port }}
|
||||
initialDelaySeconds: 5
|
||||
periodSeconds: 5
|
||||
resources:
|
||||
{{- toYaml .Values.mcp.sidecars.helm.resources | nindent 12 }}
|
||||
{{- end }}
|
||||
{{- if .Values.mcp.sidecars.homeassistant.enabled }}
|
||||
- name: homeassistant-mcp
|
||||
image: "{{ .Values.mcp.sidecars.homeassistant.image.repository }}:{{ .Values.mcp.sidecars.homeassistant.image.tag }}"
|
||||
imagePullPolicy: IfNotPresent
|
||||
command: ["fastmcp", "run", "--transport", "sse", "--host", "0.0.0.0", "--port", "{{ .Values.mcp.sidecars.homeassistant.port }}"]
|
||||
ports:
|
||||
- name: homeassistant
|
||||
containerPort: {{ .Values.mcp.sidecars.homeassistant.port }}
|
||||
env:
|
||||
- name: HOMEASSISTANT_URL
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: {{ include "devcontainer.envSecretName" . }}
|
||||
key: HOMEASSISTANT_URL
|
||||
optional: true
|
||||
- name: HOMEASSISTANT_TOKEN
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: {{ include "devcontainer.envSecretName" . }}
|
||||
key: HOMEASSISTANT_TOKEN
|
||||
optional: true
|
||||
livenessProbe:
|
||||
tcpSocket:
|
||||
port: {{ .Values.mcp.sidecars.homeassistant.port }}
|
||||
initialDelaySeconds: 10
|
||||
periodSeconds: 10
|
||||
readinessProbe:
|
||||
tcpSocket:
|
||||
port: {{ .Values.mcp.sidecars.homeassistant.port }}
|
||||
initialDelaySeconds: 5
|
||||
periodSeconds: 5
|
||||
resources:
|
||||
{{- toYaml .Values.mcp.sidecars.homeassistant.resources | nindent 12 }}
|
||||
{{- end }}
|
||||
{{- if .Values.mcp.sidecars.pgtuner.enabled }}
|
||||
- name: pgtuner-mcp
|
||||
image: "{{ .Values.mcp.sidecars.pgtuner.image.repository }}:{{ .Values.mcp.sidecars.pgtuner.image.tag }}"
|
||||
imagePullPolicy: Always # pgtuner uses `latest` tag (no versioned releases available)
|
||||
command: ["python", "-m", "pgtuner_mcp", "--mode", "sse", "--host", "0.0.0.0", "--port", "{{ .Values.mcp.sidecars.pgtuner.port }}"]
|
||||
ports:
|
||||
- name: pgtuner
|
||||
containerPort: {{ .Values.mcp.sidecars.pgtuner.port }}
|
||||
env:
|
||||
- name: DATABASE_URI
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: {{ include "devcontainer.envSecretName" . }}
|
||||
key: DATABASE_URI
|
||||
optional: true
|
||||
- name: PGTUNER_EXCLUDE_USERIDS
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: {{ include "devcontainer.envSecretName" . }}
|
||||
key: PGTUNER_EXCLUDE_USERIDS
|
||||
optional: true
|
||||
livenessProbe:
|
||||
tcpSocket:
|
||||
port: {{ .Values.mcp.sidecars.pgtuner.port }}
|
||||
initialDelaySeconds: 10
|
||||
periodSeconds: 10
|
||||
readinessProbe:
|
||||
tcpSocket:
|
||||
port: {{ .Values.mcp.sidecars.pgtuner.port }}
|
||||
initialDelaySeconds: 5
|
||||
periodSeconds: 5
|
||||
resources:
|
||||
{{- toYaml .Values.mcp.sidecars.pgtuner.resources | nindent 12 }}
|
||||
{{- end }}
|
||||
{{- if .Values.mcp.sidecars.playwright.enabled }}
|
||||
- name: playwright-mcp
|
||||
image: "{{ .Values.mcp.sidecars.playwright.image.repository }}:{{ .Values.mcp.sidecars.playwright.image.tag }}"
|
||||
imagePullPolicy: IfNotPresent
|
||||
command: ["node"]
|
||||
args:
|
||||
- cli.js
|
||||
- --headless
|
||||
- --browser
|
||||
- chromium
|
||||
- --no-sandbox
|
||||
- --host
|
||||
- 0.0.0.0
|
||||
- --port
|
||||
- {{ .Values.mcp.sidecars.playwright.port | quote }}
|
||||
ports:
|
||||
- name: playwright
|
||||
containerPort: {{ .Values.mcp.sidecars.playwright.port }}
|
||||
livenessProbe:
|
||||
tcpSocket:
|
||||
port: {{ .Values.mcp.sidecars.playwright.port }}
|
||||
initialDelaySeconds: 15
|
||||
periodSeconds: 10
|
||||
readinessProbe:
|
||||
tcpSocket:
|
||||
port: {{ .Values.mcp.sidecars.playwright.port }}
|
||||
initialDelaySeconds: 10
|
||||
periodSeconds: 5
|
||||
resources:
|
||||
{{- toYaml .Values.mcp.sidecars.playwright.resources | nindent 12 }}
|
||||
securityContext:
|
||||
runAsUser: 1000
|
||||
runAsGroup: 1000
|
||||
{{- end }}
|
||||
volumes:
|
||||
- name: workspace
|
||||
emptyDir: {}
|
||||
- name: shm
|
||||
emptyDir:
|
||||
medium: Memory
|
||||
sizeLimit: {{ .Values.shm.sizeLimit }}
|
||||
- name: userhome
|
||||
persistentVolumeClaim:
|
||||
claimName: {{ include "devcontainer.pvcName" . }}
|
||||
{{- end }}
|
||||
@@ -1,68 +0,0 @@
|
||||
{{- 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 }}
|
||||
@@ -1,111 +0,0 @@
|
||||
{{- 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,19 +0,0 @@
|
||||
{{- if eq .Values.deploymentMode "persistent" }}
|
||||
apiVersion: v1
|
||||
kind: PersistentVolumeClaim
|
||||
metadata:
|
||||
name: {{ include "devcontainer.pvcName" . }}
|
||||
annotations:
|
||||
helm.sh/resource-policy: keep
|
||||
labels:
|
||||
{{- 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,99 +0,0 @@
|
||||
{{- if eq .Values.deploymentMode "persistent" }}
|
||||
{{- $access := .Values.clusterAccess | default "none" }}
|
||||
{{- $name := include "devcontainer.fullname" . }}
|
||||
{{- $ns := .Release.Namespace }}
|
||||
{{- $labels := include "devcontainer.labels" . }}
|
||||
|
||||
{{- if ne $access "none" }}
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: ServiceAccount
|
||||
metadata:
|
||||
name: {{ $name }}
|
||||
namespace: {{ $ns }}
|
||||
labels:
|
||||
{{- $labels | nindent 4 }}
|
||||
|
||||
{{- if or (eq $access "readonlyns") (eq $access "readwritens") }}
|
||||
---
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: Role
|
||||
metadata:
|
||||
name: {{ $name }}
|
||||
namespace: {{ $ns }}
|
||||
labels:
|
||||
{{- $labels | nindent 4 }}
|
||||
rules:
|
||||
- apiGroups: ["*"]
|
||||
resources: ["*"]
|
||||
verbs:
|
||||
{{- if eq $access "readonlyns" }}
|
||||
- get
|
||||
- list
|
||||
- watch
|
||||
{{- else }}
|
||||
- "*"
|
||||
{{- end }}
|
||||
---
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: RoleBinding
|
||||
metadata:
|
||||
name: {{ $name }}
|
||||
namespace: {{ $ns }}
|
||||
labels:
|
||||
{{- $labels | nindent 4 }}
|
||||
subjects:
|
||||
- kind: ServiceAccount
|
||||
name: {{ $name }}
|
||||
namespace: {{ $ns }}
|
||||
roleRef:
|
||||
kind: Role
|
||||
name: {{ $name }}
|
||||
apiGroup: rbac.authorization.k8s.io
|
||||
{{- end }}
|
||||
|
||||
{{- if or (eq $access "readonly") (eq $access "readwrite") }}
|
||||
---
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: ClusterRole
|
||||
metadata:
|
||||
name: {{ $name }}
|
||||
labels:
|
||||
{{- $labels | nindent 4 }}
|
||||
rules:
|
||||
- apiGroups: ["*"]
|
||||
resources: ["*"]
|
||||
verbs:
|
||||
{{- if eq $access "readonly" }}
|
||||
- get
|
||||
- list
|
||||
- watch
|
||||
{{- else }}
|
||||
- "*"
|
||||
{{- end }}
|
||||
- nonResourceURLs: ["*"]
|
||||
verbs:
|
||||
{{- if eq $access "readonly" }}
|
||||
- get
|
||||
{{- else }}
|
||||
- "*"
|
||||
{{- end }}
|
||||
---
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: ClusterRoleBinding
|
||||
metadata:
|
||||
name: {{ $name }}
|
||||
labels:
|
||||
{{- $labels | nindent 4 }}
|
||||
subjects:
|
||||
- kind: ServiceAccount
|
||||
name: {{ $name }}
|
||||
namespace: {{ $ns }}
|
||||
roleRef:
|
||||
kind: ClusterRole
|
||||
name: {{ $name }}
|
||||
apiGroup: rbac.authorization.k8s.io
|
||||
{{- end }}
|
||||
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
@@ -1,66 +0,0 @@
|
||||
{{- 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,24 +0,0 @@
|
||||
{{- if eq .Values.deploymentMode "persistent" }}
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: {{ include "devcontainer.fullname" . }}
|
||||
labels:
|
||||
{{- include "devcontainer.labels" . | nindent 4 }}
|
||||
spec:
|
||||
ports:
|
||||
{{- if ne (.Values.ide.type | default "vscode") "none" }}
|
||||
- port: 5800
|
||||
name: vnc-web
|
||||
protocol: TCP
|
||||
targetPort: vnc-web
|
||||
{{- end }}
|
||||
{{- if .Values.ssh.enabled }}
|
||||
- port: 22
|
||||
name: ssh
|
||||
protocol: TCP
|
||||
targetPort: ssh
|
||||
{{- end }}
|
||||
selector:
|
||||
{{- include "devcontainer.labels" . | nindent 4 }}
|
||||
{{- end }}
|
||||
@@ -1,122 +0,0 @@
|
||||
# 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
|
||||
@@ -1,58 +0,0 @@
|
||||
# =============================================================================
|
||||
# QUICKSTART VALUES - Just set these 3 essentials!
|
||||
# =============================================================================
|
||||
|
||||
# Instance name (required)
|
||||
name: mydev
|
||||
|
||||
# GitHub repository to clone (required)
|
||||
githubRepo: https://github.com/youruser/yourrepo
|
||||
|
||||
# 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'
|
||||
@@ -1,354 +0,0 @@
|
||||
{
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"$id": "https://github.com/cpfarhood/devcontainer/chart/values.schema.json",
|
||||
"title": "Dev Container Helm Chart Values Schema",
|
||||
"description": "Schema for validating values.yaml in the Dev Container Helm chart",
|
||||
"type": "object",
|
||||
"additionalProperties": true,
|
||||
"properties": {
|
||||
"name": {
|
||||
"type": "string",
|
||||
"description": "Instance name used to generate resource names",
|
||||
"pattern": "^[a-z0-9][a-z0-9-]*[a-z0-9]$",
|
||||
"minLength": 1,
|
||||
"maxLength": 63
|
||||
},
|
||||
"image": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"repository": {
|
||||
"type": "string",
|
||||
"description": "Container image repository"
|
||||
},
|
||||
"tag": {
|
||||
"type": "string",
|
||||
"description": "Container image tag"
|
||||
},
|
||||
"pullPolicy": {
|
||||
"type": "string",
|
||||
"enum": ["Always", "IfNotPresent", "Never"],
|
||||
"description": "Image pull policy"
|
||||
}
|
||||
},
|
||||
"required": ["repository", "tag"]
|
||||
},
|
||||
"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"
|
||||
},
|
||||
"helm": {
|
||||
"$ref": "#/$defs/mcpSidecar"
|
||||
},
|
||||
"playwright": {
|
||||
"$ref": "#/$defs/mcpSidecar"
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
}
|
||||
},
|
||||
"required": ["sidecars"]
|
||||
},
|
||||
"envSecretName": {
|
||||
"type": "string",
|
||||
"description": "Custom environment secret name"
|
||||
},
|
||||
"resourceProfile": {
|
||||
"type": "string",
|
||||
"enum": ["auto", "small", "medium", "large", "xlarge"],
|
||||
"description": "Resource profile preset"
|
||||
}
|
||||
},
|
||||
"required": ["name"],
|
||||
"$defs": {
|
||||
"resourceSpec": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"memory": {
|
||||
"type": "string",
|
||||
"pattern": "^[0-9]+[KMGT]i$",
|
||||
"description": "Memory resource specification"
|
||||
},
|
||||
"cpu": {
|
||||
"type": "string",
|
||||
"pattern": "^[0-9]+m?$",
|
||||
"description": "CPU resource specification"
|
||||
}
|
||||
},
|
||||
"required": ["memory", "cpu"]
|
||||
},
|
||||
"mcpSidecar": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"enabled": {
|
||||
"type": "boolean",
|
||||
"description": "Enable this MCP sidecar"
|
||||
},
|
||||
"image": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"repository": {
|
||||
"type": "string"
|
||||
},
|
||||
"tag": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": ["repository", "tag"]
|
||||
},
|
||||
"port": {
|
||||
"type": "integer",
|
||||
"minimum": 1,
|
||||
"maximum": 65535,
|
||||
"description": "Port for the MCP sidecar"
|
||||
},
|
||||
"resources": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"requests": {
|
||||
"$ref": "#/$defs/resourceSpec"
|
||||
},
|
||||
"limits": {
|
||||
"$ref": "#/$defs/resourceSpec"
|
||||
}
|
||||
},
|
||||
"required": ["requests", "limits"]
|
||||
}
|
||||
},
|
||||
"required": ["enabled", "image", "port", "resources"]
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,268 +0,0 @@
|
||||
# =============================================================================
|
||||
# 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
|
||||
pullPolicy: Always
|
||||
|
||||
# GitHub repository to clone into /workspace (ignored in dynamic mode - uses URL routing)
|
||||
githubRepo: ""
|
||||
|
||||
# =============================================================================
|
||||
# ACCESS & INTERFACE
|
||||
# =============================================================================
|
||||
|
||||
# IDE configuration
|
||||
ide:
|
||||
# Options: vscode | antigravity | none
|
||||
type: vscode
|
||||
|
||||
# SSH access configuration
|
||||
ssh:
|
||||
enabled: false
|
||||
|
||||
# Web file manager — built-in upload/download via the VNC web interface (port 5800)
|
||||
# Uses the base image's WEB_FILE_MANAGER feature (no extra sidecar needed)
|
||||
fileManager:
|
||||
enabled: false
|
||||
# Paths the file manager can access (default: AUTO = mapped volumes)
|
||||
# Options: AUTO | ALL | comma-separated list of paths
|
||||
allowedPaths: "/workspace,/config"
|
||||
# Paths to deny (takes precedence over allowedPaths)
|
||||
deniedPaths: ""
|
||||
|
||||
# VNC display settings
|
||||
display:
|
||||
width: "1920"
|
||||
height: "1080"
|
||||
secureConnection: "0" # Set to "1" when TLS is not terminated upstream
|
||||
|
||||
# User configuration
|
||||
user:
|
||||
id: "1000"
|
||||
groupId: "1000"
|
||||
|
||||
# =============================================================================
|
||||
# INFRASTRUCTURE & RESOURCES
|
||||
# =============================================================================
|
||||
|
||||
# Storage configuration
|
||||
storage:
|
||||
size: 32Gi
|
||||
className: "" # Empty string uses the cluster's default StorageClass (must support ReadWriteMany)
|
||||
|
||||
# Resource allocation
|
||||
resources:
|
||||
requests:
|
||||
memory: "2Gi"
|
||||
cpu: "1000m"
|
||||
limits:
|
||||
memory: "8Gi"
|
||||
cpu: "4000m"
|
||||
|
||||
# Shared memory for Electron apps (Chrome, Antigravity)
|
||||
shm:
|
||||
sizeLimit: 2Gi
|
||||
|
||||
# Kubernetes cluster access via RBAC
|
||||
# Options: none | readonlyns | readwritens | readonly | readwrite
|
||||
clusterAccess: none
|
||||
|
||||
# =============================================================================
|
||||
# INTEGRATIONS
|
||||
# =============================================================================
|
||||
|
||||
# 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"
|
||||
|
||||
|
||||
# Helm chart browsing and management
|
||||
helm:
|
||||
enabled: true
|
||||
image:
|
||||
repository: ghcr.io/zekker6/mcp-helm
|
||||
tag: v1.3.1
|
||||
port: 8088
|
||||
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: ghcr.io/cpfarhood/devcontainer-routing-proxy
|
||||
tag: latest
|
||||
pullPolicy: Always
|
||||
|
||||
resources:
|
||||
requests:
|
||||
memory: "64Mi"
|
||||
cpu: "100m"
|
||||
limits:
|
||||
memory: "256Mi"
|
||||
cpu: "500m"
|
||||
|
||||
# Ingress configuration for dynamic mode
|
||||
ingress:
|
||||
enabled: true
|
||||
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: ""
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+11
@@ -0,0 +1,11 @@
|
||||
<!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://farhoodliquor.github.io/devcontainer</pre>
|
||||
<p>Install the chart:</p>
|
||||
<pre>helm install mydev devcontainer/devcontainer --set name=mydev</pre>
|
||||
</body>
|
||||
</html>
|
||||
+286
@@ -0,0 +1,286 @@
|
||||
apiVersion: v1
|
||||
entries:
|
||||
devcontainer:
|
||||
- apiVersion: v2
|
||||
appVersion: latest
|
||||
created: "2026-03-11T12:15:44.634530101Z"
|
||||
description: Dev Container with AI coding agents and MCP sidecars
|
||||
digest: 0c46b5d7a889cc21c28b8b088eb6c0c785a3f9324a4b39d14813c4eefe3f8e7a
|
||||
keywords:
|
||||
- development
|
||||
- devcontainer
|
||||
- vscode
|
||||
- ai
|
||||
name: devcontainer
|
||||
type: application
|
||||
urls:
|
||||
- https://farhoodliquor.github.io/devcontainer/devcontainer-2.6.0.tgz
|
||||
version: 2.6.0
|
||||
- apiVersion: v2
|
||||
appVersion: latest
|
||||
created: "2026-03-11T12:15:44.633861353Z"
|
||||
description: Dev Container with AI coding agents and MCP sidecars
|
||||
digest: 010294a1a9b4bb6a92c685d819a0cd34dd03bc111da715d0807799168d0aba66
|
||||
keywords:
|
||||
- development
|
||||
- devcontainer
|
||||
- vscode
|
||||
- ai
|
||||
name: devcontainer
|
||||
type: application
|
||||
urls:
|
||||
- https://farhoodliquor.github.io/devcontainer/devcontainer-2.4.0.tgz
|
||||
version: 2.4.0
|
||||
- apiVersion: v2
|
||||
appVersion: latest
|
||||
created: "2026-03-11T12:15:44.633145718Z"
|
||||
description: Dev Container with AI coding agents and MCP sidecars - supports persistent
|
||||
and dynamic deployment modes
|
||||
digest: a650e9c7a8feb961232aee4048d3bf0ff1d04c55100f51a7db138e1a0f8b524e
|
||||
keywords:
|
||||
- development
|
||||
- devcontainer
|
||||
- vscode
|
||||
- ai
|
||||
- knative
|
||||
- serverless
|
||||
name: devcontainer
|
||||
type: application
|
||||
urls:
|
||||
- https://farhoodliquor.github.io/devcontainer/devcontainer-2.3.0.tgz
|
||||
version: 2.3.0
|
||||
- apiVersion: v2
|
||||
appVersion: latest
|
||||
created: "2026-03-11T12:15:44.631873087Z"
|
||||
description: Dev Container with AI coding agents and MCP sidecars - supports persistent
|
||||
and dynamic deployment modes
|
||||
digest: f5c440846e7672239a6f7b14a393888988ef627d896bc967bfc018130d65921d
|
||||
keywords:
|
||||
- development
|
||||
- devcontainer
|
||||
- vscode
|
||||
- ai
|
||||
- knative
|
||||
- serverless
|
||||
name: devcontainer
|
||||
type: application
|
||||
urls:
|
||||
- https://farhoodliquor.github.io/devcontainer/devcontainer-2.2.5.tgz
|
||||
version: 2.2.5
|
||||
- apiVersion: v2
|
||||
appVersion: latest
|
||||
created: "2026-03-11T12:15:44.6304485Z"
|
||||
description: Dev Container with AI coding agents and MCP sidecars - supports persistent
|
||||
and dynamic deployment modes
|
||||
digest: 2bea7dc5c198a5b4dab0b74f0a75089210c7ba49b56176ba2af205b7dac3fe23
|
||||
keywords:
|
||||
- development
|
||||
- devcontainer
|
||||
- vscode
|
||||
- ai
|
||||
- knative
|
||||
- serverless
|
||||
name: devcontainer
|
||||
type: application
|
||||
urls:
|
||||
- https://farhoodliquor.github.io/devcontainer/devcontainer-2.2.4.tgz
|
||||
version: 2.2.4
|
||||
- apiVersion: v2
|
||||
appVersion: latest
|
||||
created: "2026-03-11T12:15:44.62965028Z"
|
||||
description: Dev Container with AI coding agents and MCP sidecars - supports persistent
|
||||
and dynamic deployment modes
|
||||
digest: afde89b22d7e4a5dfd4c918a06258d9f27f1b17493a70dba98d1ae544280505a
|
||||
keywords:
|
||||
- development
|
||||
- devcontainer
|
||||
- vscode
|
||||
- ai
|
||||
- knative
|
||||
- serverless
|
||||
name: devcontainer
|
||||
type: application
|
||||
urls:
|
||||
- https://farhoodliquor.github.io/devcontainer/devcontainer-2.2.3.tgz
|
||||
version: 2.2.3
|
||||
- apiVersion: v2
|
||||
appVersion: latest
|
||||
created: "2026-03-11T12:15:44.628801186Z"
|
||||
description: Dev Container with AI coding agents and MCP sidecars - supports persistent
|
||||
and dynamic deployment modes
|
||||
digest: 5b3b994b74da01579156021fcfb718c61989def7c16cafadb36e7ddc90cbeea7
|
||||
keywords:
|
||||
- development
|
||||
- devcontainer
|
||||
- vscode
|
||||
- ai
|
||||
- knative
|
||||
- serverless
|
||||
name: devcontainer
|
||||
type: application
|
||||
urls:
|
||||
- https://farhoodliquor.github.io/devcontainer/devcontainer-2.2.2.tgz
|
||||
version: 2.2.2
|
||||
- apiVersion: v2
|
||||
appVersion: latest
|
||||
created: "2026-03-11T12:15:44.627970857Z"
|
||||
description: Dev Container with AI coding agents and MCP sidecars - supports persistent
|
||||
and dynamic deployment modes
|
||||
digest: 8db383b24252edd37998c56bbba76793d1b6eeb37365a6894a713eef6af81210
|
||||
keywords:
|
||||
- development
|
||||
- devcontainer
|
||||
- vscode
|
||||
- ai
|
||||
- knative
|
||||
- serverless
|
||||
name: devcontainer
|
||||
type: application
|
||||
urls:
|
||||
- https://farhoodliquor.github.io/devcontainer/devcontainer-2.2.1.tgz
|
||||
version: 2.2.1
|
||||
- apiVersion: v2
|
||||
appVersion: latest
|
||||
created: "2026-03-11T12:15:44.627169642Z"
|
||||
description: Dev Container with AI coding agents and MCP sidecars - supports persistent
|
||||
and dynamic deployment modes
|
||||
digest: a1ea207bc96a35cc545d12fa8aca00452792de54e3fae74993260cd69afee0fa
|
||||
keywords:
|
||||
- development
|
||||
- devcontainer
|
||||
- vscode
|
||||
- ai
|
||||
- knative
|
||||
- serverless
|
||||
name: devcontainer
|
||||
type: application
|
||||
urls:
|
||||
- https://farhoodliquor.github.io/devcontainer/devcontainer-2.2.0.tgz
|
||||
version: 2.2.0
|
||||
- apiVersion: v2
|
||||
appVersion: latest
|
||||
created: "2026-03-11T12:15:44.626328122Z"
|
||||
description: Dev Container with AI coding agents and MCP sidecars - supports persistent
|
||||
and dynamic deployment modes
|
||||
digest: beac1182a39f158fb9aa1f3308b4b030bf378d612f2aa860f792fad62aa30321
|
||||
keywords:
|
||||
- development
|
||||
- devcontainer
|
||||
- vscode
|
||||
- ai
|
||||
- knative
|
||||
- serverless
|
||||
name: devcontainer
|
||||
type: application
|
||||
urls:
|
||||
- https://farhoodliquor.github.io/devcontainer/devcontainer-2.1.1.tgz
|
||||
version: 2.1.1
|
||||
- apiVersion: v2
|
||||
appVersion: latest
|
||||
created: "2026-03-11T12:15:44.625121312Z"
|
||||
description: Dev Container with AI coding agents and MCP sidecars - supports persistent
|
||||
and dynamic deployment modes
|
||||
digest: efb6cbd932a7ac082853d305e970db72e0086543a6963baabef16ebd2e8498f8
|
||||
keywords:
|
||||
- development
|
||||
- devcontainer
|
||||
- vscode
|
||||
- ai
|
||||
- knative
|
||||
- serverless
|
||||
name: devcontainer
|
||||
type: application
|
||||
urls:
|
||||
- https://farhoodliquor.github.io/devcontainer/devcontainer-2.1.0.tgz
|
||||
version: 2.1.0
|
||||
- apiVersion: v2
|
||||
appVersion: latest
|
||||
created: "2026-03-11T12:15:44.623735055Z"
|
||||
description: Dev Container with AI coding agents and MCP sidecars - supports persistent
|
||||
and dynamic deployment modes
|
||||
digest: 46f25124d9045802d0e50d34209d15a7fa15e1ef1c9d3f0e93ac4bb39b7c9b17
|
||||
keywords:
|
||||
- development
|
||||
- devcontainer
|
||||
- vscode
|
||||
- ai
|
||||
- knative
|
||||
- serverless
|
||||
name: devcontainer
|
||||
type: application
|
||||
urls:
|
||||
- https://farhoodliquor.github.io/devcontainer/devcontainer-2.0.5.tgz
|
||||
version: 2.0.5
|
||||
- apiVersion: v2
|
||||
appVersion: latest
|
||||
created: "2026-03-11T12:15:44.622870733Z"
|
||||
description: Dev Container with AI coding agents and MCP sidecars - supports persistent
|
||||
and dynamic deployment modes
|
||||
digest: db82381ffe831b07ce7777e8e6e05455a8eaeccfcd0afcd87825433a96cb2d65
|
||||
keywords:
|
||||
- development
|
||||
- devcontainer
|
||||
- vscode
|
||||
- ai
|
||||
- knative
|
||||
- serverless
|
||||
name: devcontainer
|
||||
type: application
|
||||
urls:
|
||||
- https://farhoodliquor.github.io/devcontainer/devcontainer-2.0.4.tgz
|
||||
version: 2.0.4
|
||||
- apiVersion: v2
|
||||
appVersion: latest
|
||||
created: "2026-03-11T12:15:44.622004867Z"
|
||||
description: Dev Container with AI coding agents and MCP sidecars - supports persistent
|
||||
and dynamic deployment modes
|
||||
digest: 872919ea64531b35dfa7f956d8a6e4130a1c7f0f80c50141b6f2d1cddd49682e
|
||||
keywords:
|
||||
- development
|
||||
- devcontainer
|
||||
- vscode
|
||||
- ai
|
||||
- knative
|
||||
- serverless
|
||||
name: devcontainer
|
||||
type: application
|
||||
urls:
|
||||
- https://farhoodliquor.github.io/devcontainer/devcontainer-2.0.3.tgz
|
||||
version: 2.0.3
|
||||
- apiVersion: v2
|
||||
appVersion: latest
|
||||
created: "2026-03-11T12:15:44.621180038Z"
|
||||
description: Dev Container with AI coding agents and MCP sidecars - supports persistent
|
||||
and dynamic deployment modes
|
||||
digest: 7cbb5379f8b41bc938a29b44c563757358c25fd843c75fb6d2f1a2b9365c0cf1
|
||||
keywords:
|
||||
- development
|
||||
- devcontainer
|
||||
- vscode
|
||||
- ai
|
||||
- knative
|
||||
- serverless
|
||||
name: devcontainer
|
||||
type: application
|
||||
urls:
|
||||
- https://farhoodliquor.github.io/devcontainer/devcontainer-2.0.1-dev.tgz
|
||||
version: 2.0.1-dev
|
||||
- apiVersion: v2
|
||||
appVersion: latest
|
||||
created: "2026-03-11T12:15:44.620303012Z"
|
||||
description: Dev Container with AI coding agents and MCP sidecars - supports persistent
|
||||
and dynamic deployment modes
|
||||
digest: 5736c92626e2c3edb8e81ddba5be581360dbf3901ccc607db8bad238ea831cd0
|
||||
keywords:
|
||||
- development
|
||||
- devcontainer
|
||||
- vscode
|
||||
- ai
|
||||
- knative
|
||||
- serverless
|
||||
name: devcontainer
|
||||
type: application
|
||||
urls:
|
||||
- https://farhoodliquor.github.io/devcontainer/devcontainer-2.0.0-dev.tgz
|
||||
version: 2.0.0-dev
|
||||
generated: "2026-03-11T12:15:44.619306973Z"
|
||||
@@ -1,30 +0,0 @@
|
||||
# Antigravity Dev Container - Session Notes
|
||||
|
||||
## Key Architecture Facts
|
||||
- Image: `ghcr.io/cpfarhood/devcontainer:latest` (repo name is `devcontainer`, not `antigravity`)
|
||||
- Deployed via Helm chart (`chart/`), not kustomize anymore
|
||||
- Service must NOT be headless (`clusterIP: None`) — Cilium gateway can't route to headless services
|
||||
- `SECURE_CONNECTION=0` — TLS is terminated at the gateway, not the app
|
||||
- Container user is `user` (UID 1000) — baseimage-gui runs startapp.sh as `app` user, sudo is not available
|
||||
|
||||
## Deployment Method
|
||||
- **Primary**: Helm chart in `chart/` directory
|
||||
- **Makefile targets**: `helm-deploy`, `helm-delete`, `helm-logs`, `helm-shell`, `helm-port-forward`
|
||||
- **Old kustomize** (`k8s/` directory) has been removed — all deployments use Helm now
|
||||
- Chart published as OCI artifact to GHCR, reconciled by Flux
|
||||
|
||||
## MCP Sidecars
|
||||
- **Kubernetes MCP** (v0.0.57, port 8080): Only deployed when enabled AND `clusterAccess` != `none`
|
||||
- **Flux MCP** (v0.41.1, port 8081): Only deployed when enabled AND `clusterAccess` != `none`
|
||||
- **Home Assistant MCP** (6.7.1, port 8087): Disabled by default, requires secrets:
|
||||
- `homeassistant-url`: Base URL like `http://homeassistant.local:8123`
|
||||
- `homeassistant-token`: Long-lived access token
|
||||
- **Playwright MCP**: External service, not a sidecar
|
||||
- Configure via `mcpSidecars.<name>.enabled` in values
|
||||
- **Version Strategy**: All MCP images use pinned versions for stability (no `latest` tags)
|
||||
|
||||
## Common Gotchas
|
||||
- `baseimage-gui` creates user dynamically — don't hardcode usernames in scripts, use numeric UID/GID
|
||||
- `chown /home` fails (PVC root not owned by container) — only chown subdirectories
|
||||
- `sudo` not available in startapp.sh — script already runs as correct user
|
||||
- MCP sidecars need appropriate secrets and RBAC permissions to function
|
||||
@@ -1,72 +0,0 @@
|
||||
{
|
||||
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
|
||||
"extends": [
|
||||
"config:recommended",
|
||||
":gitSignOff"
|
||||
],
|
||||
"semanticCommits": "enabled",
|
||||
"dependencyDashboard": true,
|
||||
"suppressNotifications": [
|
||||
"prEditedNotification"
|
||||
],
|
||||
"rebaseWhen": "conflicted",
|
||||
"commitMessagePrefix": "chore(deps):",
|
||||
"commitMessageAction": "update",
|
||||
"commitMessageTopic": "{{depName}}",
|
||||
"prConcurrentLimit": 5,
|
||||
"prHourlyLimit": 2,
|
||||
"schedule": [
|
||||
"before 6am on monday"
|
||||
],
|
||||
"packageRules": [
|
||||
{
|
||||
"description": "GitHub Actions",
|
||||
"matchManagers": [
|
||||
"github-actions"
|
||||
],
|
||||
"groupName": "github-actions",
|
||||
"additionalBranchPrefix": "github-actions-",
|
||||
"semanticCommitScope": "github-actions",
|
||||
"pinDigests": true
|
||||
},
|
||||
{
|
||||
"description": "Docker base image",
|
||||
"matchManagers": [
|
||||
"dockerfile"
|
||||
],
|
||||
"groupName": "docker",
|
||||
"additionalBranchPrefix": "docker-",
|
||||
"semanticCommitScope": "docker"
|
||||
},
|
||||
{
|
||||
"description": "Automerge patch updates",
|
||||
"matchUpdateTypes": [
|
||||
"patch"
|
||||
],
|
||||
"automerge": true,
|
||||
"automergeType": "pr",
|
||||
"platformAutomerge": true
|
||||
},
|
||||
{
|
||||
"description": "Automerge minor updates for stable packages",
|
||||
"matchUpdateTypes": [
|
||||
"minor"
|
||||
],
|
||||
"matchCurrentVersion": "!/^0/",
|
||||
"automerge": true,
|
||||
"automergeType": "pr",
|
||||
"platformAutomerge": true
|
||||
},
|
||||
{
|
||||
"description": "Separate major updates - require manual review",
|
||||
"matchUpdateTypes": [
|
||||
"major"
|
||||
],
|
||||
"automerge": false,
|
||||
"additionalBranchPrefix": "major-"
|
||||
}
|
||||
],
|
||||
"ignorePaths": [
|
||||
"**/node_modules/**"
|
||||
]
|
||||
}
|
||||
@@ -1,45 +0,0 @@
|
||||
#!/bin/sh
|
||||
# Start OpenSSH server when SSH=true.
|
||||
# Runs as root during container initialisation (cont-init.d).
|
||||
[ "${SSH:-false}" = "true" ] || exit 0
|
||||
|
||||
echo "=== SSH enabled: starting sshd ==="
|
||||
|
||||
HOME_DIR="/config/userdata"
|
||||
HOST_KEY_STORE="$HOME_DIR/.ssh/host_keys"
|
||||
|
||||
# Persist host keys on the home PVC so clients don't see a "host key
|
||||
# changed" warning after pod restarts.
|
||||
if [ -d "$HOST_KEY_STORE" ] && [ -n "$(ls "$HOST_KEY_STORE"/ssh_host_* 2>/dev/null)" ]; then
|
||||
# Restore previously generated host keys
|
||||
echo "Restoring SSH host keys from PVC..."
|
||||
cp "$HOST_KEY_STORE"/ssh_host_* /etc/ssh/
|
||||
chmod 600 /etc/ssh/ssh_host_*_key
|
||||
chmod 644 /etc/ssh/ssh_host_*_key.pub
|
||||
else
|
||||
# First boot: generate and save host keys to PVC
|
||||
echo "Generating SSH host keys (first boot)..."
|
||||
ssh-keygen -A 2>/dev/null || true
|
||||
mkdir -p "$HOST_KEY_STORE"
|
||||
cp /etc/ssh/ssh_host_* "$HOST_KEY_STORE/"
|
||||
chmod 700 "$HOST_KEY_STORE"
|
||||
chown -R 1000:1000 "$HOST_KEY_STORE"
|
||||
echo "SSH host keys saved to PVC."
|
||||
fi
|
||||
|
||||
# Populate authorized_keys from env var (injected via Kubernetes secret)
|
||||
if [ -n "$SSH_AUTHORIZED_KEYS" ]; then
|
||||
mkdir -p "$HOME_DIR/.ssh"
|
||||
chmod 700 "$HOME_DIR/.ssh"
|
||||
printf '%s\n' "$SSH_AUTHORIZED_KEYS" > "$HOME_DIR/.ssh/authorized_keys"
|
||||
chmod 600 "$HOME_DIR/.ssh/authorized_keys"
|
||||
chown -R 1000:1000 "$HOME_DIR/.ssh"
|
||||
echo "SSH authorized keys configured."
|
||||
else
|
||||
echo "WARNING: SSH_AUTHORIZED_KEYS not set — you will not be able to log in."
|
||||
fi
|
||||
|
||||
# Start sshd in background (root required to bind :22 and fork sessions)
|
||||
/usr/sbin/sshd -D &
|
||||
|
||||
echo "sshd started (PID $!)"
|
||||
@@ -1,10 +0,0 @@
|
||||
#!/bin/sh
|
||||
# Fix the app user (UID 1000) created by baseimage-gui at runtime.
|
||||
# baseimage-gui sets shell=/sbin/nologin and home=/dev/null, which
|
||||
# prevents VSCode from opening terminals.
|
||||
if id app >/dev/null 2>&1; then
|
||||
usermod -s /bin/bash app
|
||||
usermod -d /config/userdata app
|
||||
else
|
||||
echo "WARNING: 'app' user not found, skipping usermod" >&2
|
||||
fi
|
||||
@@ -1,111 +0,0 @@
|
||||
#!/bin/bash
|
||||
# Initialize repository
|
||||
set -e
|
||||
|
||||
echo "=== Repository Initialization ==="
|
||||
|
||||
# Set up basic git configuration
|
||||
echo "Configuring git user settings..."
|
||||
# Use environment variables if provided, otherwise use defaults
|
||||
GIT_USER_NAME="${GIT_USER_NAME:-DevContainer User}"
|
||||
GIT_USER_EMAIL="${GIT_USER_EMAIL:-devcontainer@example.com}"
|
||||
|
||||
git config --global user.name "$GIT_USER_NAME"
|
||||
git config --global user.email "$GIT_USER_EMAIL"
|
||||
|
||||
# Set up git credentials early if GITHUB_TOKEN is provided
|
||||
# This ensures all git operations have proper authentication
|
||||
if [ -n "$GITHUB_TOKEN" ]; then
|
||||
echo "Setting up git credentials..."
|
||||
# Configure git to use credential store globally
|
||||
git config --global credential.helper store
|
||||
|
||||
# Create or update the credentials file
|
||||
CREDENTIALS_FILE="/config/userdata/.git-credentials"
|
||||
mkdir -p "$(dirname "$CREDENTIALS_FILE")"
|
||||
|
||||
# Support multiple git hosting providers
|
||||
# GitHub supports both oauth2 and token as username
|
||||
echo "https://oauth2:${GITHUB_TOKEN}@github.com" > "$CREDENTIALS_FILE"
|
||||
echo "https://${GITHUB_TOKEN}:x-oauth-basic@github.com" >> "$CREDENTIALS_FILE"
|
||||
echo "https://token:${GITHUB_TOKEN}@github.com" >> "$CREDENTIALS_FILE"
|
||||
|
||||
# GitLab format (if same token works)
|
||||
if [ -n "$GITLAB_HOST" ]; then
|
||||
echo "https://oauth2:${GITHUB_TOKEN}@${GITLAB_HOST}" >> "$CREDENTIALS_FILE"
|
||||
fi
|
||||
|
||||
chmod 600 "$CREDENTIALS_FILE"
|
||||
|
||||
# Also create a symlink in the home directory if it doesn't exist
|
||||
# This handles cases where git might look in different locations
|
||||
if [ ! -f "$HOME/.git-credentials" ] && [ "$HOME" != "/config/userdata" ]; then
|
||||
ln -sf "$CREDENTIALS_FILE" "$HOME/.git-credentials"
|
||||
fi
|
||||
|
||||
echo "Git credentials configured"
|
||||
else
|
||||
# Even without a token, ensure git has a proper credential helper configured
|
||||
# This prevents errors when credentials are added later
|
||||
echo "No GITHUB_TOKEN provided, configuring basic git settings..."
|
||||
git config --global credential.helper store
|
||||
|
||||
# Create an empty credentials file with proper permissions
|
||||
CREDENTIALS_FILE="/config/userdata/.git-credentials"
|
||||
mkdir -p "$(dirname "$CREDENTIALS_FILE")"
|
||||
touch "$CREDENTIALS_FILE"
|
||||
chmod 600 "$CREDENTIALS_FILE"
|
||||
|
||||
# Create symlink if needed
|
||||
if [ ! -f "$HOME/.git-credentials" ] && [ "$HOME" != "/config/userdata" ]; then
|
||||
ln -sf "$CREDENTIALS_FILE" "$HOME/.git-credentials"
|
||||
fi
|
||||
fi
|
||||
|
||||
# Check if GITHUB_REPO is set
|
||||
if [ -z "$GITHUB_REPO" ]; then
|
||||
echo "GITHUB_REPO not set, skipping repository clone"
|
||||
WORKSPACE_DIR="/workspace/default"
|
||||
mkdir -p "$WORKSPACE_DIR"
|
||||
else
|
||||
# Parse repo name from URL
|
||||
REPO_NAME=$(basename "$GITHUB_REPO" .git)
|
||||
WORKSPACE_DIR="/workspace/$REPO_NAME"
|
||||
|
||||
echo "Repository: $GITHUB_REPO"
|
||||
echo "Target directory: $WORKSPACE_DIR"
|
||||
|
||||
# Check if repo already exists
|
||||
if [ -d "$WORKSPACE_DIR/.git" ]; then
|
||||
echo "Repository already exists, pulling latest changes..."
|
||||
cd "$WORKSPACE_DIR"
|
||||
git pull || echo "Pull failed, continuing anyway..."
|
||||
else
|
||||
echo "Cloning repository..."
|
||||
mkdir -p "$(dirname "$WORKSPACE_DIR")"
|
||||
|
||||
# Clone with token if provided
|
||||
if [ -n "$GITHUB_TOKEN" ]; then
|
||||
# Replace https://github.com/ with https://oauth2:token@github.com/
|
||||
CLONE_URL=$(echo "$GITHUB_REPO" | sed "s|https://github.com/|https://oauth2:${GITHUB_TOKEN}@github.com/|")
|
||||
git clone "$CLONE_URL" "$WORKSPACE_DIR"
|
||||
else
|
||||
git clone "$GITHUB_REPO" "$WORKSPACE_DIR"
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
|
||||
# Set ownership using numeric IDs (username may not exist yet in baseimage-gui)
|
||||
RUN_UID="${USER_ID:-1000}"
|
||||
RUN_GID="${GROUP_ID:-1000}"
|
||||
chown -R "$RUN_UID:$RUN_GID" "$WORKSPACE_DIR"
|
||||
|
||||
# Ensure home directory exists on the PVC (may be absent on a fresh volume)
|
||||
mkdir -p "$HOME"
|
||||
chown "$RUN_UID:$RUN_GID" "$HOME"
|
||||
|
||||
|
||||
# Export workspace directory for startapp.sh
|
||||
echo "$WORKSPACE_DIR" > /tmp/workspace-dir
|
||||
|
||||
echo "=== Initialization Complete ==="
|
||||
@@ -1,49 +0,0 @@
|
||||
#!/bin/bash
|
||||
# Start application script for baseimage-gui
|
||||
set -e
|
||||
|
||||
echo "=== Starting Dev Container ==="
|
||||
|
||||
# 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
|
||||
if [ -f /tmp/workspace-dir ]; then
|
||||
WORKSPACE_DIR=$(cat /tmp/workspace-dir)
|
||||
else
|
||||
WORKSPACE_DIR="/workspace/default"
|
||||
fi
|
||||
|
||||
IDE="${IDE:-vscode}"
|
||||
echo "IDE mode: $IDE"
|
||||
echo "Workspace: $WORKSPACE_DIR"
|
||||
|
||||
case "$IDE" in
|
||||
antigravity)
|
||||
echo "Opening Google Antigravity in: $WORKSPACE_DIR"
|
||||
# --no-sandbox is required for Electron apps in Docker (no kernel sandbox available).
|
||||
# Explicit --user-data-dir and --extensions-dir pin config to the home PVC so
|
||||
# settings and the setup wizard state survive pod restarts.
|
||||
exec antigravity --no-sandbox \
|
||||
--user-data-dir "$HOME/.config/antigravity" \
|
||||
--extensions-dir "$HOME/.antigravity/extensions" \
|
||||
--new-window --wait "$WORKSPACE_DIR"
|
||||
;;
|
||||
none)
|
||||
echo "IDE=none: no IDE launched, keeping container alive."
|
||||
exec sleep infinity
|
||||
;;
|
||||
*)
|
||||
if [ "$IDE" != "vscode" ]; then
|
||||
echo "WARNING: Unknown IDE value '$IDE', defaulting to VSCode"
|
||||
fi
|
||||
echo "Opening VSCode in: $WORKSPACE_DIR"
|
||||
exec code --new-window --wait "$WORKSPACE_DIR"
|
||||
;;
|
||||
esac
|
||||
@@ -1,46 +0,0 @@
|
||||
#!/bin/bash
|
||||
# Test script to verify git credentials configuration
|
||||
|
||||
set -e
|
||||
|
||||
echo "=== Git Credentials Test ==="
|
||||
|
||||
# Check git configuration
|
||||
echo "1. Git user configuration:"
|
||||
git config --global user.name || echo " ❌ user.name not set"
|
||||
git config --global user.email || echo " ❌ user.email not set"
|
||||
|
||||
echo ""
|
||||
echo "2. Git credential helper:"
|
||||
git config --global credential.helper || echo " ❌ credential.helper not set"
|
||||
|
||||
echo ""
|
||||
echo "3. Credentials file locations:"
|
||||
CREDENTIALS_FILE="/config/userdata/.git-credentials"
|
||||
if [ -f "$CREDENTIALS_FILE" ]; then
|
||||
echo " ✓ $CREDENTIALS_FILE exists"
|
||||
echo " Permissions: $(stat -c %a $CREDENTIALS_FILE)"
|
||||
echo " Lines in file: $(wc -l < $CREDENTIALS_FILE)"
|
||||
else
|
||||
echo " ❌ $CREDENTIALS_FILE does not exist"
|
||||
fi
|
||||
|
||||
if [ -f "$HOME/.git-credentials" ]; then
|
||||
if [ -L "$HOME/.git-credentials" ]; then
|
||||
echo " ✓ $HOME/.git-credentials is a symlink to $(readlink -f $HOME/.git-credentials)"
|
||||
else
|
||||
echo " ✓ $HOME/.git-credentials exists (not a symlink)"
|
||||
fi
|
||||
else
|
||||
echo " ❌ $HOME/.git-credentials does not exist"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "4. Environment check:"
|
||||
echo " HOME=$HOME"
|
||||
echo " GITHUB_TOKEN=${GITHUB_TOKEN:+[SET]}"
|
||||
echo " GIT_USER_NAME=${GIT_USER_NAME:-[NOT SET]}"
|
||||
echo " GIT_USER_EMAIL=${GIT_USER_EMAIL:-[NOT SET]}"
|
||||
|
||||
echo ""
|
||||
echo "=== Test Complete ==="
|
||||
@@ -1,173 +0,0 @@
|
||||
# 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
|
||||
@@ -1,376 +0,0 @@
|
||||
# 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
|
||||
@@ -1,168 +0,0 @@
|
||||
# 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
|
||||
@@ -1,248 +0,0 @@
|
||||
---
|
||||
# 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
|
||||
@@ -1,112 +0,0 @@
|
||||
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: ""
|
||||
@@ -1,16 +0,0 @@
|
||||
# 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;"]
|
||||
@@ -1,16 +0,0 @@
|
||||
#!/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 "$@"
|
||||
@@ -1,124 +0,0 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -1,124 +0,0 @@
|
||||
#!/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
|
||||
@@ -1,86 +0,0 @@
|
||||
#!/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