Compare commits

..

47 Commits

Author SHA1 Message Date
DevContainer User cc38a07168 fix: update values schema for deploymentMode and dynamic config
- Add deploymentMode enum (persistent | dynamic)
- Add fileManager schema (enabled, allowedPaths, deniedPaths)
- Add full dynamic mode schema (knative, routingProxy, ingress, authentik)
- Relax githubRepo pattern — not required in dynamic mode

Validated: helm template renders cleanly for both modes.

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

Co-Authored-By: Claude <noreply@anthropic.com>
Co-Authored-By: Happy <yesreply@happy.engineering>
2026-02-25 13:20:08 +00:00
DevContainer User 5c3600a424 feat: add CI/CD support for 2.0.0-dev builds
Updates GitHub Actions to build development images from serverless feature branch:

## GitHub Actions Updates
- Trigger builds on feature/serverless-* branches
- Add 2.0.0-dev tag for feature/serverless-2.0.0 branch
- New routing proxy build job for serverless features
- Parallel builds: main devcontainer + routing proxy

## Chart Updates
- Default image tag changed to 2.0.0-dev
- Routing proxy tag updated to 2.0.0-dev
- Ready for development testing

## Build Outputs
When pushed to feature/serverless-2.0.0:
- ghcr.io/cpfarhood/devcontainer:2.0.0-dev
- ghcr.io/cpfarhood/devcontainer-routing-proxy:2.0.0-dev

This enables immediate testing of serverless features without manual builds.

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

Co-Authored-By: Claude <noreply@anthropic.com>
Co-Authored-By: Happy <yesreply@happy.engineering>
2026-02-25 13:17:37 +00:00
DevContainer User 5565354127 feat: integrate dynamic mode into Helm chart v2.0.0-dev
Implements unified Helm chart supporting both deployment modes:
- persistent: Traditional PVC-based deployment (v1.x behavior)
- dynamic: Serverless Knative with auto-scaling and dynamic routing

## Chart Changes
- Chart.yaml: Bump to v2.0.0-dev with deployment mode support
- values.yaml: Add deploymentMode field and dynamic configuration
- All templates: Conditional rendering based on deploymentMode

## Dynamic Mode Templates
- knative-service.yaml: Auto-scaling dev containers with repo routing
- routing-proxy.yaml: GitHub repo extraction service
- dynamic-ingress.yaml: Ingress with Authentik auth support

## Usage Examples
```bash
# Traditional persistent mode (default)
helm install mydev ./chart --set name=mydev --set githubRepo=...

# Dynamic serverless mode
helm install mydev ./chart -f values-dynamic.yaml \
  --set name=mydev --set dynamic.ingress.host=devcontainer.example.com

# Development builds
helm install mydev ./chart --set deploymentMode=dynamic \
  --set image.tag=2.0.0-dev --set dynamic.ingress.host=...
```

All existing persistent deployments remain compatible (deploymentMode defaults to "persistent").

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

Co-Authored-By: Claude <noreply@anthropic.com>
Co-Authored-By: Happy <yesreply@happy.engineering>
2026-02-25 13:12:46 +00:00
DevContainer User b69cd80cae feat: serverless 2.0.0 architecture with Authentik auth proxy
Implements a complete serverless development container platform:

## Architecture
- Authentik forward auth for authentication/authorization
- NGINX routing proxy extracts GitHub repo from URL path
- Knative Service auto-scales dev container instances from 0
- Dynamic GitHub repo routing via /github/{owner}/{repo}

## Components
- routing-proxy: NGINX-based service for repo extraction and forwarding
- deployment.yaml: Complete K8s manifests (proxy, Knative, ingress, secrets)
- authentik-config.yaml: Authentik application and provider configs
- serverless scripts: Dynamic repo initialization and startup handling
- Comprehensive documentation and Makefile for ops

## Key Features
- Scale to zero when not in use (cost-effective)
- Per-request isolation (each repo gets own container)
- Built-in file manager for upload/download
- Support for private repos via GitHub tokens
- User attribution via Authentik headers
- WebSocket support for VNC connections

Example usage: https://devcontainer.farh.net/github/microsoft/vscode

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

Co-Authored-By: Claude <noreply@anthropic.com>
Co-Authored-By: Happy <yesreply@happy.engineering>
2026-02-25 13:04:25 +00:00
DevContainer User 3e46bf5ec1 feat: add Helm CLI and built-in web file manager
- Install Helm v3.17.1 in Dockerfile for chart development (closes #49)
- Add fileManager toggle using base image's WEB_FILE_MANAGER (closes #11)
- Wire WEB_FILE_MANAGER env vars in deployment template
- Update CLAUDE.md, README.md with new features and values

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

Co-Authored-By: Claude <noreply@anthropic.com>
Co-Authored-By: Happy <yesreply@happy.engineering>
2026-02-24 14:49:03 +00:00
DevContainer User c8a7bbcd6e fix: phase 0 quick wins — safety, naming, and portability
- Add helm.sh/resource-policy: keep to PVC (prevent data loss on uninstall)
- Add fail guard for empty name value in Helm templates
- Fix Makefile IMAGE_NAME from antigravity to devcontainer
- Pin busybox:1.37, homeassistant:v6.7.1, playwright:v0.0.68 (was latest/stable)
- Set imagePullPolicy: IfNotPresent on pinned sidecars
- Remove fetch/sequentialthinking from .mcp.json (sidecars removed from chart)
- Default storage.className to empty (use cluster default, was ceph-filesystem)
- Default Happy Coder URLs to empty (was private farh.net endpoints)
- Broaden githubRepo schema to accept GitLab/Gitea URLs
- Add unknown IDE warning before VSCode fallback
- Add mkdir -p before credential file write (fix fresh PVC boot)
- Guard app user existence in cont-init-user.sh
- Add NOTES.txt post-install template with port-forward and secret hints
- Add standard app.kubernetes.io/* labels and separate selectorLabels

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-24 04:13:24 +00:00
DevContainer User adb2ee4817 chore: update Claude Code settings and enable voltagent plugins
Add fetch and sequentialthinking MCP servers to allowed list, and enable
voltagent dev-exp and lang subagent plugins.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-24 03:50:22 +00:00
github-actions[bot] 3637a0a6fc chore: release version 1.0.2 2026-02-24 02:53:07 +00:00
DevContainer User f67066823b fix(docker): correct Crush installation tar extraction
Fixed the Crush AI assistant installation in Dockerfile by replacing the
single-line tar extraction with --strip-components (which was failing)
with a multi-step approach: download to temp, extract, move binary,
and cleanup. This ensures the binary is properly extracted from the
versioned directory structure in the tarball.

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

Co-Authored-By: Claude <noreply@anthropic.com>
Co-Authored-By: Happy <yesreply@happy.engineering>
2026-02-24 02:42:44 +00:00
Chris Farhood 50560652cb feat(helm): rip out sequentialthinking MCP server sidecar 2026-02-23 21:05:40 -05:00
Chris Farhood 0fc4ff503b ci: fix chart version update failing when version already matches 2026-02-23 20:55:38 -05:00
Chris Farhood 04203e4efb feat(helm): rip out fetch MCP server as requested 2026-02-23 20:54:15 -05:00
Chris Farhood b710daac05 fix(helm): allow additionalProperties in values schema to prevent Flux dropping variables and change sidecar container configs 2026-02-23 20:54:15 -05:00
github-actions[bot] 52a29da38d chore: release version 0.4.11 2026-02-24 01:45:24 +00:00
github-actions[bot] ea71f71c74 chore: release version 0.4.9 2026-02-24 01:25:29 +00:00
Chris Farhood f6eceb4d94 fix(helm): shorten sequentialthinking port name (fixes #48) 2026-02-23 20:24:27 -05:00
github-actions[bot] 84bf7841c3 chore: release version 0.4.8 2026-02-23 23:59:03 +00:00
Chris Farhood c823a30c2a fix(chart): add missing MCP sidecars to values schema (#47)
PR #45 added fetch and sequentialthinking MCP sidecars to values.yaml
and the deployment template but missed updating values.schema.json.
The schema has additionalProperties: false on mcp.sidecars, causing
Helm upgrade to fail with validation errors.

Also adds resourceProfile to the schema as it was missing.

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

Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: Happy <yesreply@happy.engineering>
2026-02-23 18:57:41 -05:00
github-actions[bot] 27af9dc9c4 chore: release version 0.4.7 2026-02-23 23:35:13 +00:00
github-actions[bot] 0944dcec1c chore: release version 0.4.6 2026-02-23 21:19:15 +00:00
Chris Farhood 60a2689658 Merge pull request #46 from cpfarhood/fix/ci-race-condition
fix(ci): resolve Docker build race condition
2026-02-23 16:18:04 -05:00
Chris Farhood 53bc4b68a6 fix(ci): resolve race condition between build and release workflows
Remove tag triggers and duplicate release job from build-and-push.yaml.
The release-unified.yaml workflow handles the full release flow (Docker
build, Helm chart, GitHub release) when triggered via workflow_dispatch.

Previously, release-unified.yaml pushing a commit to main AND a v* tag
would trigger build-and-push.yaml up to twice, causing multiple Docker
builds to race for the :latest tag. The stale GHA layer cache in the
racing build could overwrite :latest with an image missing new tools
(e.g., crush, opencode).

Changes:
- Remove tags: ['v*'] trigger (release-unified handles tag-based releases)
- Remove duplicate release job (Helm chart + GitHub release)
- Remove semver tag patterns from metadata (not needed without tag trigger)
- Skip builds from github-actions[bot] to avoid racing with release commits

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

Co-Authored-By: Claude <noreply@anthropic.com>
Co-Authored-By: Happy <yesreply@happy.engineering>
2026-02-23 16:05:53 -05:00
Chris Farhood d526a445fd Merge pull request #45 from cpfarhood/feature/add-fetch-sequential-thinking-mcp
feat: add Fetch and Sequential Thinking MCP sidecars
2026-02-23 13:41:46 -05:00
DevContainer User f56b3efb66 feat: add Fetch and Sequential Thinking MCP sidecars
Add two new MCP (Model Context Protocol) sidecars to enable web content fetching
and structured problem-solving capabilities:

- **Fetch MCP**: Web content fetching and HTML to markdown conversion on port 8082
- **Sequential Thinking MCP**: Structured thinking and problem-solving processes on port 8083

Both sidecars are enabled by default and use the official MCP Docker images
(mcp/fetch and mcp/sequentialthinking) with fastmcp SSE transport.

Changes:
- Add fetch and sequentialthinking sidecars to values.yaml
- Add sidecar containers to deployment.yaml template
- Update .mcp.json with new server endpoints
- Update CLAUDE.md documentation with new sidecar details

Closes #43, #44

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

Co-Authored-By: Claude <noreply@anthropic.com>
Co-Authored-By: Happy <yesreply@happy.engineering>
2026-02-23 18:37:12 +00:00
github-actions[bot] a778d32b3b chore: release version 0.4.5 2026-02-23 01:17:43 +00:00
DevContainer User b48fce97d5 fix: improve Antigravity IDE installation to resolve AI chat issues
- Force fresh APT repository data by clearing package cache before update
- Add debugging output to show available and installed versions
- Use --no-install-recommends to avoid unnecessary packages
- Add version validation during build process
- Disable auto-updates in Antigravity settings to prevent container conflicts

This should resolve the "agentSessions service not found" error that prevents
the AI chat window from responding.

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

Co-Authored-By: Claude <noreply@anthropic.com>
Co-Authored-By: Happy <yesreply@happy.engineering>
2026-02-23 01:13:41 +00:00
github-actions[bot] 47af7acc5e chore: release version 0.4.4 2026-02-23 01:08:30 +00:00
DevContainer User da45415cfe fix: playwright MCP sidecar bind to 0.0.0.0 for probe access (#42)
The Playwright MCP server binds to 127.0.0.1 by default, which makes
it unreachable by Kubernetes liveness/readiness probes from the kubelet.
Add --host 0.0.0.0 to bind to all interfaces.

Fixes #42

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

Co-Authored-By: Claude <noreply@anthropic.com>
Co-Authored-By: Happy <yesreply@happy.engineering>
2026-02-22 20:43:28 +00:00
DevContainer User 897555b1dc chore: bump chart version to 0.4.2
Generated with [Claude Code](https://claude.ai/code)
via [Happy](https://happy.engineering)

Co-Authored-By: Claude <noreply@anthropic.com>
Co-Authored-By: Happy <yesreply@happy.engineering>
2026-02-22 20:27:12 +00:00
DevContainer User df1f4d9b50 fix: ensure Docker image is built before release creation
- Move release job into build-and-push workflow with dependency
- Remove separate release.yaml workflow to prevent race condition
- Ensures Docker image is available before GitHub release is published

Fixes the issue where clients see release with docker pull instructions
before the image is uploaded to GHCR.

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

Co-Authored-By: Claude <noreply@anthropic.com>
Co-Authored-By: Happy <yesreply@happy.engineering>
2026-02-22 20:20:26 +00:00
DevContainer User 2f5a8d65d5 fix: playwright MCP sidecar startup args (#41)
The Playwright MCP image (mcr.microsoft.com/playwright/mcp) doesn't
support --transport flag. Use node cli.js entrypoint with --headless,
--browser chromium, --no-sandbox, and --port for SSE mode.

Fixes #41

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

Co-Authored-By: Claude <noreply@anthropic.com>
Co-Authored-By: Happy <yesreply@happy.engineering>
2026-02-22 20:04:16 +00:00
github-actions[bot] 0d8fe1ec64 chore: release version 0.4.0 2026-02-22 19:39:04 +00:00
DevContainer User 00638d372c docs: comprehensive update for new values format and template names
- Fix CLAUDE.md: update Docker build example from antigravity to devcontainer
- Fix README.md stale value references throughout:
  - ide → ide.type, ssh → ssh.enabled
  - happyServerUrl → happy.serverUrl (and all happy.* values)
  - secureConnection → display.secureConnection
  - userId/groupId → user.id/user.groupId
  - mcpSidecars.* → mcp.sidecars.*
- Remove GitHub MCP sidecar from custom config example (discontinued)
- Fix YAML indentation in MCP config examples
- Fix storage mount path from /home to /config
- Fix Happy state directory from /home/user/.happy to /config/userdata/.happy

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

Co-Authored-By: Claude <noreply@anthropic.com>
Co-Authored-By: Happy <yesreply@happy.engineering>
2026-02-22 19:38:11 +00:00
DevContainer User 31ec139a8a refactor: rename template helpers from antigravity to devcontainer
Rename all Helm template helper functions from antigravity.* to
devcontainer.* to match the actual chart name. Also update Chart.yaml
description to remove "Antigravity" branding.

Renamed helpers:
- antigravity.fullname → devcontainer.fullname
- antigravity.pvcName → devcontainer.pvcName
- antigravity.envSecretName → devcontainer.envSecretName
- antigravity.labels → devcontainer.labels
- antigravity.smartResources → devcontainer.smartResources
- antigravity.smartDefaults → devcontainer.smartDefaults
- antigravity.mcpDefaults → devcontainer.mcpDefaults

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

Co-Authored-By: Claude <noreply@anthropic.com>
Co-Authored-By: Happy <yesreply@happy.engineering>
2026-02-22 19:34:09 +00:00
DevContainer User 71c6ca70cc fix: resolve MCP sidecar image failures (issue #40)
Three fixes for broken MCP sidecars:

1. Remove GitHub MCP sidecar entirely - the upstream image
   (ghcr.io/modelcontextprotocol/servers/github) is discontinued.
   GitHub MCP is now accessed via Copilot API instead.

2. Fix Playwright MCP image - changed from non-existent
   microsoft/playwright-mcp to mcr.microsoft.com/playwright/mcp
   (the official Microsoft Container Registry image).

3. Fix pgtuner MCP startup command - changed --transport to --mode
   and added --host 0.0.0.0 to match the current pgtuner_mcp CLI.

Bumps chart version to 0.3.3.

Closes #40

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

Co-Authored-By: Claude <noreply@anthropic.com>
Co-Authored-By: Happy <yesreply@happy.engineering>
2026-02-22 19:31:08 +00:00
github-actions[bot] b9c30b8e4d chore: release version 0.3.2 2026-02-22 18:05:08 +00:00
DevContainer User 794de6d0e5 fix: update service.yaml to use new nested values format
service.yaml was still using the old flat values format (.Values.ide
and .Values.ssh) while values.yaml had been restructured to nested
objects (ide.type and ssh.enabled). This caused a type comparison
error breaking all devcontainer deployments across cpfarhood and
homeassistant namespaces.

Bumps chart version to 0.3.1.

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

Co-Authored-By: Claude <noreply@anthropic.com>
Co-Authored-By: Happy <yesreply@happy.engineering>
2026-02-22 18:04:23 +00:00
github-actions[bot] fbcd9c1f72 chore: release version 0.3.0 2026-02-22 16:27:17 +00:00
DevContainer User 3be59e56eb docs: add Claude Code to documented AI coding agents
Claude Code was already installed via npm (line 65 of Dockerfile) but
was missing from the feature lists in README.md and CLAUDE.md.

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

Co-Authored-By: Claude <noreply@anthropic.com>
Co-Authored-By: Happy <yesreply@happy.engineering>
2026-02-22 16:25:46 +00:00
DevContainer User be9479ef75 feat: add OpenCode and Crush AI coding agents to Docker image
Install both terminal-based AI coding agents alongside Happy Coder:
- OpenCode (opencode-ai/opencode) - open-source AI coding agent
- Crush (charmbracelet/crush) - OpenCode's active successor by Charm

Both are installed as Go binaries to /usr/local/bin from latest GitHub
releases and available to users via `opencode` and `crush` commands.

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

Co-Authored-By: Claude <noreply@anthropic.com>
Co-Authored-By: Happy <yesreply@happy.engineering>
2026-02-22 16:24:19 +00:00
DevContainer User 065a6534e3 docs: comprehensive updates for new Helm chart structure
Major documentation updates to reflect the reorganized Helm chart:

- Update README.md and CLAUDE.md to use new mcp.sidecars structure
- Reflect removal of Happy daemon automatic startup
- Document new logical values organization (Basic → Access → Infrastructure → Integrations → Smart Defaults)
- Update all MCP sidecar examples to use mcp.sidecars instead of mcpSidecars
- Document new quickstart deployment options and progressive disclosure
- Update troubleshooting sections with current configurations
- Standardize all secret environment variable references to SCREAMING_SNAKE_CASE

This completes the documentation sync following the major Helm chart
user experience improvements implemented in previous commits.

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

Co-Authored-By: Claude <noreply@anthropic.com>
Co-Authored-By: Happy <yesreply@happy.engineering>
2026-02-22 13:32:49 +00:00
DevContainer User d8d83ffa47 feat: major Helm chart user-friendliness improvements
Implements comprehensive enhancements to make the Helm chart more user-friendly
and easier to deploy across different scenarios.

🎯 **IMPLEMENTED IMPROVEMENTS:**

**1. Values Organization & Grouping**
- Reorganized values.yaml with logical sections:
  - Basic Configuration (name, image, githubRepo)
  - Access & Interface (ide, ssh, display, user)
  - Infrastructure & Resources (storage, resources, shm, clusterAccess)
  - Integrations (happy, mcp sidecars)
  - Smart Defaults & Auto-Detection
- Updated deployment templates to use new structure
- Maintains clean, navigable configuration

**2. Simplified Quick-Start Values**
- Added values-quickstart.yaml for 80% of users
- Just 2 required fields: name + githubRepo
- Includes usage instructions and common customizations
- Copy-paste ready deployment experience

**3. Better Documentation Structure**
- Added values.schema.json for IDE validation and autocomplete
- Created comprehensive USAGE.md with real-world examples:
  - Development, team, K8s admin, AI/ML, lightweight scenarios
  - Secret configuration examples
  - Resource sizing by use case
  - Common troubleshooting patterns

**4. Smart Defaults & Auto-Detection**
- Added template helpers for intelligent resource sizing
- Environment auto-detection based on naming patterns
- Smart MCP sidecar selection based on cluster access
- Resource profile auto-selection (auto/small/medium/large/xlarge)
- Enhanced _helpers.tpl with smart default functions

🎫 **CREATED GITHUB ISSUES** for future enhancements:
- #32: Helm chart preset profiles
- #33: Split large deployment.yaml template
- #34: Advanced auto-detection for Kubernetes environments
- #35: Specialized Helm chart variants (basic/team/k8s/ai)
- #36: Installation and configuration helper scripts
- #37: Comprehensive validation and health monitoring
- #38: User experience improvements with better error messages
- #39: Comprehensive examples and template library

📊 **IMPACT:**
- Reduced required configuration from ~20 values to 2 essential fields
- Added IDE support with schema validation
- Created guided examples for common scenarios
- Established foundation for advanced auto-detection
- Planned comprehensive tooling ecosystem

🚀 **USAGE:**
```bash
# Quick start (new users)
cp chart/values-quickstart.yaml my-values.yaml
# Edit name and githubRepo
helm install mydev ./chart -f my-values.yaml

# Full customization (power users)
# Edit chart/values.yaml with organized sections
helm install mydev ./chart
```

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

Co-Authored-By: Claude <noreply@anthropic.com>
Co-Authored-By: Happy <yesreply@happy.engineering>
2026-02-22 13:16:08 +00:00
DevContainer User 9535886945 feat: remove automatic Happy daemon startup
Removes unreliable automatic Happy Coder daemon startup, allowing users
to start it manually when needed for better reliability and control.

**Changes:**
- scripts/init-repo.sh: Removed Happy daemon startup code and lock cleanup
- scripts/startapp.sh: Updated comment to reflect init-repo only handles git
- README.md: Updated startup flow documentation and troubleshooting section
- CLAUDE.md: Updated startup flow and file descriptions

**Benefits:**
- No more unreliable automatic daemon startup failures
- Users can start Happy daemon manually when needed: `happy daemon start`
- Cleaner container startup without Happy-related delays or errors
- Happy configuration and credentials still persist on PVC when used

**Usage:**
Users can now manually start Happy Coder when needed:
```bash
happy daemon start  # Start when needed
happy daemon status # Check status
happy daemon stop   # Stop if needed
```

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

Co-Authored-By: Claude <noreply@anthropic.com>
Co-Authored-By: Happy <yesreply@happy.engineering>
2026-02-22 13:01:19 +00:00
DevContainer User 44f30ec03f fix: standardize secret key names to SCREAMING_SNAKE_CASE
Fixes inconsistent variable naming across the codebase by standardizing
all secret keys to use SCREAMING_SNAKE_CASE format.

**Changes:**
- homeassistant-url → HOMEASSISTANT_URL
- homeassistant-token → HOMEASSISTANT_TOKEN
- database-uri → DATABASE_URI
- pgtuner-exclude-userids → PGTUNER_EXCLUDE_USERIDS
- github-token → GITHUB_TOKEN

**Files updated:**
- chart/templates/deployment.yaml - Fixed secret key references
- README.md - Updated documentation and examples

**Result:**
All secret keys now follow the established SCREAMING_SNAKE_CASE
convention (GITHUB_TOKEN, VNC_PASSWORD, SSH_AUTHORIZED_KEYS, etc.)
for consistency and clarity.

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

Co-Authored-By: Claude <noreply@anthropic.com>
Co-Authored-By: Happy <yesreply@happy.engineering>
2026-02-22 12:59:16 +00:00
DevContainer User 76391a8ed0 feat: major improvements to dev container and MCP sidecars
This commit addresses multiple GitHub issues and adds significant enhancements:

🔧 **Issue #8 - Browser Window Title Fix**
- Updated browser window title from "Antigravity Dev Container" to "Dev Container"
- Changed APP_NAME in Dockerfile and startup script for consistency

🚀 **Issue #30 - PostgreSQL Tuner MCP Sidecar**
- Added PostgreSQL performance tuning MCP sidecar (dog830228/pgtuner_mcp)
- Provides AI assistant with database analysis capabilities:
  - Slow query analysis and optimization suggestions
  - Index recommendations with HypoPG virtual testing
  - Table and index bloat detection
  - Vacuum operation tracking and health scoring
- Requires DATABASE_URI in env secret, optional PGTUNER_EXCLUDE_USERIDS
- Disabled by default, configurable via mcpSidecars.pgtuner.enabled
- Updated CLAUDE.md documentation with full configuration examples

🎭 **Playwright: Centralized Service → Sidecar Conversion**
- Converted Playwright from external service to self-contained sidecar
- Updated .mcp.json endpoint: cluster service → http://localhost:8086/sse
- Added deployment configuration with proper health checks
- Enabled by default for immediate browser automation capabilities
- Higher resource allocation (512Mi memory, 1 CPU) for browser workloads

📚 **Documentation Updates**
- Updated README.md: "Antigravity Dev Container" → "Dev Container"
- Added comprehensive MCP sidecars documentation
- Updated secret keys table with database-uri and pgtuner-exclude-userids
- Added configuration examples for all 6 MCP sidecars:
  - kubernetes-mcp (enabled)
  - flux-mcp (enabled)
  - github-mcp (disabled - archived)
  - homeassistant-mcp (disabled - needs secrets)
  - pgtuner-mcp (disabled - needs DATABASE_URI)
  - playwright-mcp (enabled - browser automation)
- Updated CLAUDE.md with complete sidecar reference table
- Added Helm deployment examples and troubleshooting

🏗️ **Architecture Improvements**
- All MCP sidecars now self-contained within pod
- Consistent SSE transport configuration across all sidecars
- Proper health checks and resource limits for all services
- Simplified deployment with no external service dependencies

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

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