Compare commits

...

105 Commits

Author SHA1 Message Date
DevContainer User 7940e80cf0 chore: bump chart version to 2.2.2
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-28 16:10:39 +00:00
DevContainer User f6cbec05f6 feat: disable Claude Code auto-updater by default
Auto-updater doesn't work inside Docker and produces annoying errors.
Seed ~/.claude/settings.json with DISABLE_AUTOUPDATER=1 via /etc/skel
(new PVCs) and init-repo.sh (existing PVCs).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-28 15:27:17 +00:00
Chris Farhood 9175d48844 fix: correct ha-mcp image tag from v6.7.1 to 6.7.1 (no v prefix) 2026-02-28 09:23:34 -05:00
DevContainer User cb60f2a428 chore: bump chart version to 2.2.0
Breaking change: removed Happy Coder and Node.js.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-27 14:22:09 +00:00
DevContainer User 1179897cba feat: remove Happy Coder and Node.js from devcontainer
Happy Coder is no longer used. Node.js was only installed as a
dependency for `npm install -g happy-coder`, so both are removed.
This shrinks the Docker image and simplifies the configuration.

Removed from: Dockerfile, Helm values/schema/templates, serverless
manifests, Makefile, and all documentation.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-27 13:17:47 +00:00
DevContainer User 46dc486cb4 fix: use mcp-helm hardcoded port 8012 and remove invalid -port arg
mcp-helm does not support a -port flag — it always listens on 8012.
The invalid argument caused the container to crashloop.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-27 11:41:31 +00:00
github-actions[bot] 41ec70c7da chore(release): 2.1.1 [skip ci] 2026-02-27 02:46:33 +00:00
DevContainer User e3f751240a fix: use expanding heredoc for release notes to avoid sed failure
The multi-line COMMITS variable broke sed substitution due to embedded
newlines. Switch to an expanding heredoc that interpolates variables
directly, removing the fragile sed placeholder replacement.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-27 02:30:03 +00:00
github-actions[bot] e6c3b7f7bf chore(release): 2.1.0 [skip ci] 2026-02-27 02:11:16 +00:00
DevContainer User 41e270ec32 docs: update CLAUDE.md with gh, kubeseal, and Helm MCP sidecar
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-27 02:08:24 +00:00
DevContainer User 05b06d1d90 feat: add gh CLI, kubeseal CLI, and Helm MCP sidecar
Install GitHub CLI (gh) via official APT repo and kubeseal via GitHub
Releases binary in the Dockerfile. Add mcp-helm sidecar on port 8088
for AI-assisted Helm chart browsing, with corresponding values, schema,
deployment template, and .mcp.json configuration.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-27 01:26:05 +00:00
DevContainer User 8736d5b500 fix: clean up GitHub Actions workflows
- Enable GHA build cache across all workflows (replace no-cache: true)
- Add [skip ci] guard to build-and-push to prevent duplicate latest
  builds during releases
- Remove dead serverless branch trigger and build-routing-proxy job
- Remove unused id-token: write permission
- Add branch guard and contents: read permission to quick-fix workflow
- Fix release notes heredoc indentation so markdown renders correctly
- Fix git describe to use HEAD~1 for accurate changelog after version bump

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-27 01:17:33 +00:00
github-actions[bot] 713e5eebe6 chore(release): 2.0.5 [skip ci] 2026-02-27 00:59:31 +00:00
Chris Farhood 276477e245 fix: copy claude binary to /usr/local/bin instead of symlinking
Symlink left the original in ~/.local/bin which triggered a PATH
warning at runtime. Copy the binary and remove the original.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-25 19:11:33 -05:00
Chris Farhood 2136976b8e fix: symlink claude binary to /usr/local/bin after install
The installer puts claude in ~/.local/bin which isn't in PATH during
Docker build.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-25 18:54:45 -05:00
Chris Farhood e269e19f23 fix: pipe install script to bash, not sh
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-25 18:51:25 -05:00
Chris Farhood 3109de7e2e fix: switch Claude Code to native binary — npm wrapper breaks remote control
The npm-installed Claude Code runs via Node.js, which causes remote
control to fail with '/usr/bin/node: bad option: --sdk-url'. The native
binary handles subprocess spawning correctly.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-25 18:48:57 -05:00
Chris Farhood 2b9350c86d fix: pin Claude Code to @latest tag and print version at build time
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-25 18:28:27 -05:00
Chris Farhood 5d62842aec fix: force fresh npm registry lookup for Claude Code install
npm was serving a cached older version even with Docker no-cache.
Clear npm cache and use --prefer-online to force a fresh registry fetch.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-25 18:23:06 -05:00
Chris Farhood 58719cf262 fix: disable all Docker layer caching in CI
GHA cache was serving stale npm install layers despite cache-bust ARG.
Remove all caching — every build is now fully clean.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-25 18:14:15 -05:00
github-actions[bot] c066aa49be chore(release): 2.0.4 [skip ci] 2026-02-25 23:00:36 +00:00
Chris Farhood 204a673b3d chore: remove 2.0.0-dev image tag from CI
No longer needed — main builds tag as latest only.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-25 17:52:35 -05:00
Chris Farhood 04ed52bc8d fix: default image tag to latest — 2.0.0-dev was stale
The 2.0.0-dev tag was only built from the now-merged
feature/serverless-2.0.0 branch. Pushes to main only tagged latest,
so the 2.0.0-dev image in the registry was frozen and missing all
recent fixes. Default to latest and also tag main builds as 2.0.0-dev
for backwards compatibility.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-25 17:50:19 -05:00
Chris Farhood c670dd124f fix: ensure Claude Code updates on rebuild and allow GITHUB_REPO from secret
Two fixes:
- Move Claude Code npm install below TOOLS_CACHEBUST ARG so it actually
  gets refreshed when the cache-bust value changes
- Make GITHUB_REPO env conditional so an empty Helm value no longer
  overrides the value provided via the Kubernetes secret (envFrom)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-25 17:36:57 -05:00
Chris Farhood 219af987ae fix: revert Claude Code back to npm install — binary download breaks container
The direct GCS binary download approach has been unreliable across
multiple attempts. Revert to the proven npm install method. Node.js
is already required for Happy Coder so there is no extra dependency.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-25 15:03:47 -05:00
DevContainer User c70352dc41 fix: use direct binary download for Claude Code instead of npm
npm install fails in CI due to native dependency compilation issues.
Download the pre-built binary directly from the official GCS distribution
bucket with SHA256 checksum verification. This approach worked previously
(run #135) and avoids npm entirely — Node.js is only needed for Happy Coder.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-25 19:20:46 +00:00
DevContainer User f689b27b78 fix: revert Claude Code to npm install — native installer unreliable
The native binary installer (both direct GCS download and claude.ai/install.sh)
has been unreliable during Docker builds. Revert to the proven npm approach.
Node.js is already required for Happy Coder, so there's no extra dependency.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-25 19:11:36 +00:00
DevContainer User a978b505d0 fix: use official Claude Code installer instead of raw GCS bucket URL
The previous native installer approach used a direct GCS bucket download
that was fragile and failing during builds. Switch to the official
install script (claude.ai/install.sh) which handles version discovery,
platform detection, and checksum verification properly.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-25 18:42:17 +00:00
github-actions[bot] 69497b1ec6 chore(release): 2.0.3 [skip ci] 2026-02-25 16:46:05 +00:00
DevContainer User 698c5810a0 fix: update VSCode install to use Microsoft's current repo setup
The legacy GPG key import and .list format was failing with exit code 100
in CI. Switch to the DEB822 .sources format and install -D key method
per Microsoft's current documentation.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-25 16:39:24 +00:00
github-actions[bot] 2582c1d824 chore(release): 2.0.2 [skip ci] 2026-02-25 16:29:21 +00:00
DevContainer User 6dd560f2ad fix: bust Docker cache for tools that fetch latest versions
The native Claude Code installer (and other tools) fetch "latest" at
build time, but Docker layer caching serves stale layers because the
RUN command text never changes. Add TOOLS_CACHEBUST build arg with
github.run_id so every CI run re-downloads fresh tool binaries.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-25 16:16:14 +00:00
github-actions[bot] 19d47da079 chore(release): 2.0.1-dev [skip ci] 2026-02-25 15:30:05 +00:00
DevContainer User 12d3444cc5 feat: switch Claude Code to native installer
Replace npm-based Claude Code installation with the native binary
installer. Downloads directly from Anthropic's distribution bucket to
/usr/local/bin/claude — no Node.js dependency for Claude Code anymore.
Node.js is retained for Happy Coder only.

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 15:29:14 +00:00
Chris Farhood eeb995e1fc Merge pull request #53 from cpfarhood/feat/helm-github-pages
feat: switch Helm repo to GitHub Pages
2026-02-25 08:55:27 -05:00
Chris Farhood d26b69c587 Merge pull request #52 from cpfarhood/feature/serverless-2.0.0
feat: DevContainer 2.0.0-dev with serverless architecture and unified Helm chart
2026-02-25 08:55:16 -05:00
DevContainer User da40d57e07 fix: overhaul release pipeline — 5 issues resolved
1. version input now optional — auto-increment from release_type works
2. replaced deprecated actions/create-release@v1 with gh release create
3. race condition fixed — release commit uses [skip ci], removed fragile
   github.actor guard from build-and-push.yaml
4. simplified gh-pages publishing — uses clean temp dir + shallow clone
   instead of convoluted git worktree fallback
5. version parsing strips pre-release suffixes (e.g., 2.0.0-dev → 2.0.0)

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:53:58 +00:00
DevContainer User e99ec65cd9 docs: update all references from OCI registry to GitHub Pages Helm repo
Update CLAUDE.md, README.md, and workflows README to reference the new
GitHub Pages Helm repository at https://cpfarhood.github.io/devcontainer
instead of the old OCI registry at oci://ghcr.io/cpfarhood/charts.

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:38:03 +00:00
DevContainer User 38e481484e feat: switch Helm chart publishing from OCI registry to GitHub Pages
Replaces OCI push (oci://ghcr.io/cpfarhood/charts) with GitHub Pages
Helm repository at https://cpfarhood.github.io/devcontainer. The release
workflow now packages the chart, maintains an index.yaml on the gh-pages
branch, and auto-creates the branch on first run.

Usage: helm repo add devcontainer https://cpfarhood.github.io/devcontainer

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:34:56 +00:00
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
DevContainer User 5da23def5b fix: add automatic release workflow for tag-based releases
- Triggers on tag pushes (v*) to create GitHub releases
- Publishes Helm chart to OCI registry
- Generates release notes with commit history
- Complements existing build-and-push workflow

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

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

Co-Authored-By: Claude <noreply@anthropic.com>
Co-Authored-By: Happy <yesreply@happy.engineering>
2026-02-21 21:17:37 +00:00
DevContainer User 5532eee8cd chore: release version 0.2.4
Generated with [Claude Code](https://claude.ai/code)
via [Happy](https://happy.engineering)

Co-Authored-By: Claude <noreply@anthropic.com>
Co-Authored-By: Happy <yesreply@happy.engineering>
2026-02-21 19:13:41 +00:00
DevContainer User d32e453f93 feat: add PostgreSQL tuner (pgtuner) MCP sidecar
- Add pgtuner MCP sidecar configuration (disabled by default)
- Supports PostgreSQL performance tuning and optimization
- Analyzes slow queries, recommends indexes, detects bloat
- Requires DATABASE_URI secret to be configured
- Runs in SSE mode on port 8085

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

Co-Authored-By: Claude <noreply@anthropic.com>
Co-Authored-By: Happy <yesreply@happy.engineering>
2026-02-21 19:11:50 +00:00
DevContainer User f95e8877e8 chore: release version 0.2.3
Generated with [Claude Code](https://claude.ai/code)
via [Happy](https://happy.engineering)

Co-Authored-By: Claude <noreply@anthropic.com>
Co-Authored-By: Happy <yesreply@happy.engineering>
2026-02-21 17:31:09 +00:00
DevContainer User 46267b6e26 fix: resolve antigravity IDE init and homeassistant MCP issues (fixes #27, #28)
- Fix homeassistant MCP sidecar command by removing incorrect module specification
- Add init container for antigravity IDE to create /config/userdata directory
- Bump chart version to 0.2.2

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

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

Generated with Claude Code via Happy

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

Disabling by default to prevent ImagePullBackOff errors.

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

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

Co-Authored-By: Claude <noreply@anthropic.com>
Co-Authored-By: Happy <yesreply@happy.engineering>
2026-02-21 16:25:45 +00:00
github-actions[bot] 45b8e5e95e chore: release version 0.2.1 2026-02-21 16:20:17 +00:00
DevContainer User a0b409239e feat: add GitHub MCP sidecar and fix Home Assistant MCP command (fixes #26)
- Fixed Home Assistant MCP command flags from --sse-server-host/port to --host/port
- Added GitHub MCP server as new sidecar (enabled by default)
- Uses existing GITHUB_TOKEN from environment
- Updated documentation and .mcp.json configuration

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

Fixes #26

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

Co-Authored-By: Claude <noreply@anthropic.com>
Co-Authored-By: Happy <yesreply@happy.engineering>
2026-02-21 16:19:11 +00:00
DevContainer User eacf41302c cleanup: remove redundant workflows and simplify CI/CD
Removed 4 redundant workflow files:
- helm-publish.yaml (integrated into release-unified)
- helm-release.yaml.old (the auto-version-bump nightmare)
- release.yaml (replaced by release-unified)
- tag-release.md (old docs, replaced by README.md)

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

Also updated documentation to reflect the simplified pipeline.

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

Co-Authored-By: Claude <noreply@anthropic.com>
Co-Authored-By: Happy <yesreply@happy.engineering>
2026-02-21 16:12:35 +00:00
github-actions[bot] cbdee590bf chore: release version 0.2.0 2026-02-21 16:09:49 +00:00
DevContainer User 5570b2c617 fix: completely overhaul the CI/CD pipeline to not suck
BREAKING CHANGE: Removed auto-version-bump workflow that was causing [skip ci] hell

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

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

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

Co-Authored-By: Claude <noreply@anthropic.com>
Co-Authored-By: Happy <yesreply@happy.engineering>
2026-02-21 16:04:46 +00:00
DevContainer User c3f8421d60 cleanup: remove CI trigger file 2026-02-21 15:29:29 +00:00
DevContainer User 21d8fc73e6 ci: trigger build for v0.1.24
Force CI/CD pipeline to build the Docker image for v0.1.24 which includes:
- Git credentials automation fix
- Home Assistant MCP sidecar command fix

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

Co-Authored-By: Claude <noreply@anthropic.com>
Co-Authored-By: Happy <yesreply@happy.engineering>
2026-02-21 15:29:19 +00:00
DevContainer User 0a63894f6d chore: bump chart version to 0.1.22 [skip ci] 2026-02-21 15:10:36 +00:00
DevContainer User a50a1815e0 fix: correct Home Assistant MCP sidecar command for SSE mode
The Home Assistant MCP was failing with 'fastmcp-sse.json not found' error.
Updated command to use proper fastmcp arguments for SSE transport mode
without requiring a configuration file.

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

Co-Authored-By: Claude <noreply@anthropic.com>
Co-Authored-By: Happy <yesreply@happy.engineering>
2026-02-21 15:10:17 +00:00
github-actions[bot] 131dad8611 chore: bump chart version to 0.1.23 [skip ci] 2026-02-21 15:00:25 +00:00
Chris Farhood 581d0737e4 fix(chart): correct ha-mcp image tag from v6.7.1 to 6.7.1
The ghcr.io/homeassistant-ai/ha-mcp registry publishes tags without
the 'v' prefix, causing ErrImagePull when the sidecar is enabled.

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

Co-Authored-By: Claude <noreply@anthropic.com>
Co-Authored-By: Happy <yesreply@happy.engineering>
2026-02-21 15:00:13 +00:00
DevContainer User 68110d911f chore: bump chart version to 0.1.21 [skip ci] 2026-02-21 14:33:16 +00:00
46 changed files with 3783 additions and 703 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": [ "enabledMcpjsonServers": [
"kubernetes", "kubernetes",
"flux", "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)"
]
}
} }
-1
View File
@@ -19,7 +19,6 @@
- [ ] Built Docker image locally - [ ] Built Docker image locally
- [ ] Tested container startup - [ ] Tested container startup
- [ ] Tested repository cloning - [ ] Tested repository cloning
- [ ] Tested Happy Coder integration
- [ ] Tested VNC web interface - [ ] Tested VNC web interface
## Checklist ## Checklist
+93
View File
@@ -0,0 +1,93 @@
# CI/CD Pipeline Guide
## 🚀 Simplified Pipeline - Only 3 Workflows!
### 1️⃣ For Releases → **Unified Release**
Use this for all version releases:
1. Go to [Actions → Unified Release](https://github.com/cpfarhood/devcontainer/actions/workflows/release-unified.yaml)
2. Click "Run workflow"
3. Either:
- Enter specific version (e.g., `0.2.1`), OR
- Choose release type (patch/minor/major) for auto-increment
4. Click "Run workflow"
**This single workflow does EVERYTHING:**
- ✅ Updates chart version
- ✅ Creates git tag
- ✅ Builds Docker image with all proper tags
- ✅ Publishes Helm chart to GitHub Pages (`https://cpfarhood.github.io/devcontainer`)
- ✅ Creates GitHub Release with changelog
### 2️⃣ For Quick Fixes → **Quick Fix Build**
Use this for emergency fixes without version changes:
1. Go to [Actions → Quick Fix Build](https://github.com/cpfarhood/devcontainer/actions/workflows/quick-fix.yaml)
2. Click "Run workflow"
3. Enter tag (default: `latest`)
4. Click "Run workflow"
**Just builds and pushes Docker image** - no version bumps, no releases.
### 3️⃣ Automatic CI → **Build and Push**
Runs automatically on:
- Pushes to `main` (builds and pushes; skipped for release commits via `[skip ci]`)
- Pull requests (builds but doesn't push)
- Manual trigger available
## Workflow Files
| Workflow | File | Purpose | When to Use |
|----------|------|---------|-------------|
| **Unified Release** | `release-unified.yaml` | Full release process | New versions |
| **Quick Fix Build** | `quick-fix.yaml` | Docker build only | Hotfixes |
| **Build and Push** | `build-and-push.yaml` | CI/CD automation | PRs & tags |
## Examples
### Release a new version
```bash
# Via GitHub UI (Recommended):
# Go to Actions → Unified Release → Run workflow
# Via GitHub CLI:
gh workflow run release-unified.yaml -f version=0.2.1
# OR auto-increment:
gh workflow run release-unified.yaml -f release_type=patch
```
### Push a quick fix
```bash
# Via GitHub UI:
# Go to Actions → Quick Fix Build → Run workflow
# Via GitHub CLI:
gh workflow run quick-fix.yaml -f tag=hotfix-1
```
### Check workflow status
```bash
# List all recent runs
gh run list --limit 5
# Watch a specific workflow
gh run watch
```
## Version Strategy
- **Major** (1.0.0): Breaking changes
- **Minor** (0.2.0): New features
- **Patch** (0.2.1): Bug fixes
## What We Fixed
### Before (Nightmare 😱)
- Auto-version-bump with `[skip ci]` prevented Docker builds
- 6+ disconnected workflows
- Manual tag deletion and re-pushing
- Version conflicts everywhere
### After (Simple! 🎉)
- **3 total workflows** (down from 6+)
- **1 button** for complete releases
- Release builds its own Docker image — `[skip ci]` on the version commit prevents duplicate CI builds
- **Clear separation** of concerns
+3 -6
View File
@@ -4,8 +4,6 @@ on:
push: push:
branches: branches:
- main - main
tags:
- 'v*'
pull_request: pull_request:
branches: branches:
- main - main
@@ -18,10 +16,12 @@ env:
jobs: jobs:
build-and-push: build-and-push:
runs-on: ubuntu-latest runs-on: ubuntu-latest
if: >-
github.event_name != 'push'
|| !contains(github.event.head_commit.message, '[skip ci]')
permissions: permissions:
contents: read contents: read
packages: write packages: write
id-token: write
steps: steps:
- name: Checkout repository - name: Checkout repository
@@ -46,9 +46,6 @@ jobs:
tags: | tags: |
type=ref,event=branch type=ref,event=branch
type=ref,event=pr type=ref,event=pr
type=semver,pattern={{version}}
type=semver,pattern={{major}}.{{minor}}
type=semver,pattern={{major}}
type=sha,prefix=sha- type=sha,prefix=sha-
type=raw,value=latest,enable={{is_default_branch}} type=raw,value=latest,enable={{is_default_branch}}
-57
View File
@@ -1,57 +0,0 @@
name: Publish Helm Chart
on:
push:
branches:
- main
paths:
- 'chart/**'
workflow_dispatch:
permissions:
contents: write
packages: write
jobs:
publish:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
with:
token: ${{ secrets.GITHUB_TOKEN }}
- name: Set up Helm
uses: azure/setup-helm@v4
- name: Bump patch version
id: bump
run: |
CURRENT=$(grep '^version:' chart/Chart.yaml | awk '{print $2}')
MAJOR=$(echo $CURRENT | cut -d. -f1)
MINOR=$(echo $CURRENT | cut -d. -f2)
PATCH=$(echo $CURRENT | cut -d. -f3)
NEW_VERSION="${MAJOR}.${MINOR}.$((PATCH + 1))"
sed -i "s/^version: .*/version: ${NEW_VERSION}/" chart/Chart.yaml
echo "version=${NEW_VERSION}" >> $GITHUB_OUTPUT
- name: Commit version bump
run: |
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"
git add chart/Chart.yaml
git commit -m "chore: bump chart version to ${{ steps.bump.outputs.version }} [skip ci]"
git push
- name: Log in to GHCR
run: |
helm registry login ghcr.io \
--username ${{ github.actor }} \
--password ${{ secrets.GITHUB_TOKEN }}
- name: Package chart
run: helm package chart/
- name: Push chart to GHCR
run: |
helm push devcontainer-${{ steps.bump.outputs.version }}.tgz oci://ghcr.io/cpfarhood/charts
+56
View File
@@ -0,0 +1,56 @@
name: Quick Fix Build
on:
workflow_dispatch:
inputs:
tag:
description: 'Tag for the image (defaults to latest)'
required: false
default: 'latest'
type: string
env:
REGISTRY: ghcr.io
IMAGE_NAME: ${{ github.repository }}
jobs:
build:
runs-on: ubuntu-latest
if: github.ref == 'refs/heads/main'
permissions:
contents: read
packages: write
steps:
- name: Checkout
uses: actions/checkout@v6
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Log in to GitHub Container Registry
uses: docker/login-action@v3
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Build and Push
uses: docker/build-push-action@v6
with:
context: .
push: true
tags: |
${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.event.inputs.tag }}
${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.sha }}
cache-from: type=gha
cache-to: type=gha,mode=max
platforms: linux/amd64
- name: Summary
run: |
echo "## ✅ Quick Fix Build Complete" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "### Images Published:" >> $GITHUB_STEP_SUMMARY
echo "- \`${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.event.inputs.tag }}\`" >> $GITHUB_STEP_SUMMARY
echo "- \`${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.sha }}\`" >> $GITHUB_STEP_SUMMARY
+192
View File
@@ -0,0 +1,192 @@
name: Unified Release
on:
workflow_dispatch:
inputs:
version:
description: 'Explicit version (e.g., 1.2.3). Leave blank to auto-increment.'
required: false
type: string
release_type:
description: 'Release type (used when version is blank)'
required: true
default: 'patch'
type: choice
options:
- patch
- minor
- major
env:
REGISTRY: ghcr.io
IMAGE_NAME: ${{ github.repository }}
jobs:
release:
runs-on: ubuntu-latest
permissions:
contents: write
packages: write
steps:
- name: Checkout
uses: actions/checkout@v6
with:
fetch-depth: 0
token: ${{ secrets.GITHUB_TOKEN }}
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Set up Helm
uses: azure/setup-helm@v4
- name: Configure Git
run: |
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"
- name: Determine Version
id: version
run: |
INPUT_VERSION="${{ github.event.inputs.version }}"
if [ -n "$INPUT_VERSION" ]; then
VERSION="$INPUT_VERSION"
else
# Auto-increment based on release_type
CURRENT=$(grep '^version:' chart/Chart.yaml | awk '{print $2}')
# Strip any pre-release suffix (e.g., 2.0.0-dev -> 2.0.0)
CURRENT=$(echo "$CURRENT" | sed 's/-.*//')
MAJOR=$(echo "$CURRENT" | cut -d. -f1)
MINOR=$(echo "$CURRENT" | cut -d. -f2)
PATCH=$(echo "$CURRENT" | cut -d. -f3)
case "${{ github.event.inputs.release_type }}" in
major) VERSION="$((MAJOR + 1)).0.0" ;;
minor) VERSION="${MAJOR}.$((MINOR + 1)).0" ;;
patch) VERSION="${MAJOR}.${MINOR}.$((PATCH + 1))" ;;
esac
fi
echo "version=${VERSION}" >> $GITHUB_OUTPUT
echo "tag=v${VERSION}" >> $GITHUB_OUTPUT
echo "Releasing version ${VERSION}"
- name: Update Chart Version
run: |
sed -i "s/^version: .*/version: ${{ steps.version.outputs.version }}/" chart/Chart.yaml
git add chart/Chart.yaml
git diff --quiet --staged || git commit -m "chore(release): ${{ steps.version.outputs.version }} [skip ci]"
- name: Create and Push Tag
run: |
git tag -a "${{ steps.version.outputs.tag }}" -m "Release ${{ steps.version.outputs.tag }}"
git push origin main
git push origin "${{ steps.version.outputs.tag }}"
- name: Log in to GitHub Container Registry
uses: docker/login-action@v3
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Build and Push Docker Image
uses: docker/build-push-action@v6
with:
context: .
push: true
tags: |
${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.version.outputs.tag }}
${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.version.outputs.version }}
${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest
cache-from: type=gha
cache-to: type=gha,mode=max
platforms: linux/amd64
- name: Publish Helm Chart to GitHub Pages
run: |
helm package chart/
CHART_TGZ="devcontainer-${{ steps.version.outputs.version }}.tgz"
# Set up gh-pages in a temporary directory
PAGES_DIR=$(mktemp -d)
if git ls-remote --heads origin gh-pages | grep -q gh-pages; then
# gh-pages exists — shallow clone just that branch
git clone --single-branch --branch gh-pages \
"https://x-access-token:${{ secrets.GITHUB_TOKEN }}@github.com/${{ github.repository }}.git" \
"$PAGES_DIR"
else
# First time — initialize gh-pages
git init "$PAGES_DIR"
git -C "$PAGES_DIR" checkout --orphan gh-pages
git -C "$PAGES_DIR" remote add origin \
"https://x-access-token:${{ secrets.GITHUB_TOKEN }}@github.com/${{ github.repository }}.git"
cat > "$PAGES_DIR/index.html" <<'HTMLEOF'
<!DOCTYPE html>
<html>
<head><title>Dev Container Helm Chart Repository</title></head>
<body>
<h1>Dev Container Helm Chart Repository</h1>
<p>Add this repository to Helm:</p>
<pre>helm repo add devcontainer https://cpfarhood.github.io/devcontainer</pre>
<p>Install the chart:</p>
<pre>helm install mydev devcontainer/devcontainer --set name=mydev</pre>
</body>
</html>
HTMLEOF
fi
git -C "$PAGES_DIR" config user.name "github-actions[bot]"
git -C "$PAGES_DIR" config user.email "github-actions[bot]@users.noreply.github.com"
# Copy chart package and rebuild index
cp "$CHART_TGZ" "$PAGES_DIR/"
if [ -f "$PAGES_DIR/index.yaml" ]; then
helm repo index "$PAGES_DIR" --url https://cpfarhood.github.io/devcontainer --merge "$PAGES_DIR/index.yaml"
else
helm repo index "$PAGES_DIR" --url https://cpfarhood.github.io/devcontainer
fi
# Commit and push
git -C "$PAGES_DIR" add .
git -C "$PAGES_DIR" commit -m "Publish chart ${{ steps.version.outputs.version }}"
git -C "$PAGES_DIR" push origin gh-pages
- name: Create GitHub Release
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
VERSION: ${{ steps.version.outputs.version }}
TAG: ${{ steps.version.outputs.tag }}
IMAGE: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.version.outputs.tag }}
run: |
PREV_TAG=$(git describe --tags --abbrev=0 HEAD~1 2>/dev/null || echo "")
if [ -z "$PREV_TAG" ]; then
COMMITS=$(git log --pretty=format:"- %s (%h)" HEAD)
else
COMMITS=$(git log --pretty=format:"- %s (%h)" "${PREV_TAG}..HEAD")
fi
cat > release-notes.md <<NOTESEOF
## Release ${VERSION}
### Changes
${COMMITS}
### Docker Image
\`\`\`bash
docker pull ${IMAGE}
\`\`\`
### Helm Chart
\`\`\`bash
helm repo add devcontainer https://cpfarhood.github.io/devcontainer
helm repo update
helm install mydev devcontainer/devcontainer --version ${VERSION} --set name=mydev
\`\`\`
NOTESEOF
sed -i 's/^ //' release-notes.md
gh release create "${TAG}" \
--title "Release ${TAG}" \
--notes-file release-notes.md
-51
View File
@@ -1,51 +0,0 @@
name: Release
on:
push:
tags:
- 'v*'
permissions:
contents: write
packages: write
jobs:
release:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v6
with:
fetch-depth: 0
- name: Generate Release Notes
id: notes
run: |
# Get the tag message or generate from commits
TAG_MESSAGE=$(git tag -l --format='%(contents)' ${{ github.ref_name }})
if [ -z "$TAG_MESSAGE" ]; then
# Generate from commit messages since last tag
PREV_TAG=$(git describe --tags --abbrev=0 ${{ github.ref_name }}^ 2>/dev/null || echo "")
if [ -z "$PREV_TAG" ]; then
COMMITS=$(git log --pretty=format:"- %s (%h)" ${{ github.ref_name }})
else
COMMITS=$(git log --pretty=format:"- %s (%h)" ${PREV_TAG}..${{ github.ref_name }})
fi
NOTES="## Changes\n\n${COMMITS}\n\n## Docker Image\n\n\`\`\`bash\ndocker pull ghcr.io/${{ github.repository }}:${{ github.ref_name }}\n\`\`\`"
else
NOTES="${TAG_MESSAGE}\n\n## Docker Image\n\n\`\`\`bash\ndocker pull ghcr.io/${{ github.repository }}:${{ github.ref_name }}\n\`\`\`"
fi
echo "notes<<EOF" >> $GITHUB_OUTPUT
echo -e "$NOTES" >> $GITHUB_OUTPUT
echo "EOF" >> $GITHUB_OUTPUT
- name: Create Release
uses: actions/create-release@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
tag_name: ${{ github.ref_name }}
release_name: Release ${{ github.ref_name }}
body: ${{ steps.notes.outputs.notes }}
draft: false
prerelease: false
-259
View File
@@ -1,259 +0,0 @@
# Release Process
This document describes how to create releases for this project.
## Semantic Versioning
We follow [Semantic Versioning 2.0.0](https://semver.org/):
- **MAJOR** version (v2.0.0): Incompatible API/breaking changes
- **MINOR** version (v1.1.0): New features, backwards compatible
- **PATCH** version (v1.0.1): Bug fixes, backwards compatible
## Creating a Release
### Method 1: Using GitHub CLI (Recommended)
```bash
# Ensure you're on main branch and up to date
git checkout main
git pull
# Create and push a tag
VERSION="v1.0.0" # Change this
git tag -a "$VERSION" -m "Release $VERSION
## What's New
- Feature 1
- Feature 2
- Bug fix 1
## Docker Image
\`\`\`bash
docker pull ghcr.io/cpfarhood/devcontainer:$VERSION
\`\`\`
"
git push origin "$VERSION"
# The GitHub Actions workflow will automatically:
# 1. Build the Docker image
# 2. Push to ghcr.io with multiple tags
# 3. Create a GitHub release with notes
```
### Method 2: Using Git Tags Only
```bash
git checkout main
git pull
# Create annotated tag
git tag -a v1.0.0 -m "Release v1.0.0"
# Push tag
git push origin v1.0.0
```
### Method 3: Using GitHub Web UI
1. Go to https://github.com/cpfarhood/devcontainer/releases
2. Click "Draft a new release"
3. Click "Choose a tag"
4. Type the new version (e.g., `v1.0.0`)
5. Click "Create new tag on publish"
6. Fill in the release title and description
7. Click "Publish release"
## What Happens Automatically
When you push a version tag (`v*`), GitHub Actions will:
1. **Build Docker image** with multiple tags:
- `ghcr.io/cpfarhood/devcontainer:v1.2.3` (exact version)
- `ghcr.io/cpfarhood/devcontainer:1.2` (minor version)
- `ghcr.io/cpfarhood/devcontainer:1` (major version)
- `ghcr.io/cpfarhood/devcontainer:latest` (if on default branch)
2. **Create GitHub Release** with:
- Auto-generated release notes from commits
- Docker pull command in the description
## Version Bump Guidelines
### Patch Release (v1.0.X)
- Bug fixes
- Documentation updates
- Minor dependency updates
- No new features
- No breaking changes
**Example:** v1.0.1
```bash
git tag -a v1.0.1 -m "Release v1.0.1 - Bug fixes"
git push origin v1.0.1
```
### Minor Release (v1.X.0)
- New features
- New optional configuration variables
- Enhancements to existing features
- Backwards compatible
- No breaking changes
**Example:** v1.1.0
```bash
git tag -a v1.1.0 -m "Release v1.1.0 - New Happy Coder features"
git push origin v1.1.0
```
### Major Release (vX.0.0)
- Breaking changes
- Required configuration changes
- Removal of deprecated features
- Incompatible API changes
**Example:** v2.0.0
```bash
git tag -a v2.0.0 -m "Release v2.0.0 - Breaking: New storage architecture"
git push origin v2.0.0
```
## Pre-releases
For alpha, beta, or release candidates:
```bash
# Alpha
git tag -a v1.1.0-alpha.1 -m "Release v1.1.0-alpha.1"
git push origin v1.1.0-alpha.1
# Beta
git tag -a v1.1.0-beta.1 -m "Release v1.1.0-beta.1"
git push origin v1.1.0-beta.1
# Release Candidate
git tag -a v1.1.0-rc.1 -m "Release v1.1.0-rc.1"
git push origin v1.1.0-rc.1
```
## Release Checklist
Before creating a release:
- [ ] All tests pass
- [ ] Documentation is up to date
- [ ] CHANGELOG.md is updated (if you maintain one)
- [ ] Version number follows semver
- [ ] On main/master branch
- [ ] All changes are committed
- [ ] Tag message includes release notes
## Docker Image Tags
Each release creates multiple Docker tags for flexibility:
| Git Tag | Docker Tags Created |
|---------|---------------------|
| v1.2.3 | `:v1.2.3`, `:1.2`, `:1`, `:latest` |
| v2.0.0 | `:v2.0.0`, `:2.0`, `:2`, `:latest` |
| v1.2.4-beta.1 | `:v1.2.4-beta.1`, `:1.2-beta` |
**Usage examples:**
```bash
# Specific version (recommended for production)
docker pull ghcr.io/cpfarhood/devcontainer:v1.2.3
# Minor version (gets patches automatically)
docker pull ghcr.io/cpfarhood/devcontainer:1.2
# Major version (gets minor updates and patches)
docker pull ghcr.io/cpfarhood/devcontainer:1
# Latest (always gets newest stable release)
docker pull ghcr.io/cpfarhood/devcontainer:latest
```
## Viewing Releases
- **GitHub Releases:** https://github.com/cpfarhood/devcontainer/releases
- **Docker Images:** https://github.com/cpfarhood/devcontainer/pkgs/container/devcontainer
- **Git Tags:** `git tag -l`
## Deleting a Release
If you need to delete a bad release:
```bash
# Delete local tag
git tag -d v1.0.0
# Delete remote tag
git push origin :refs/tags/v1.0.0
# Delete GitHub release (use web UI or gh CLI)
gh release delete v1.0.0
```
**Note:** Docker images pushed to ghcr.io cannot be easily deleted. It's better to create a new patch version.
## First Release
For the initial v1.0.0 release:
```bash
git checkout main
git pull
git tag -a v1.0.0 -m "Release v1.0.0 - Initial Release
## Features
- Antigravity IDE with web-based VNC access
- Happy Coder AI assistant integration
- Automatic GitHub repository cloning
- Persistent home directory with ReadWriteMany PVC
- Secure non-root execution (claude user, UID 1000)
- Support for private repositories with GitHub token
- HTTPRoute (Gateway API) support
- Multi-platform Docker images
- Comprehensive deployment documentation
## Docker Image
\`\`\`bash
docker pull ghcr.io/cpfarhood/devcontainer:v1.0.0
\`\`\`
## Deployment
See DEPLOYMENT.md for complete deployment instructions.
"
git push origin v1.0.0
```
## Example Release Workflow
```bash
# 1. Finish your feature/fix on a branch
git checkout feature/new-feature
git commit -m "feat: Add new feature"
git push
# 2. Create PR and merge to main
gh pr create
# ... get approval and merge ...
# 3. Pull latest main
git checkout main
git pull
# 4. Create release tag
git tag -a v1.1.0 -m "Release v1.1.0 - New feature"
git push origin v1.1.0
# 5. Wait for GitHub Actions
# - Check: https://github.com/cpfarhood/devcontainer/actions
# 6. Verify release
# - GitHub: https://github.com/cpfarhood/devcontainer/releases
# - Docker: docker pull ghcr.io/cpfarhood/devcontainer:v1.1.0
```
+14 -8
View File
@@ -1,5 +1,12 @@
{ {
"mcpServers": { "mcpServers": {
"github": {
"type": "http",
"url": "https://api.githubcopilot.com/mcp/",
"headers": {
"Authorization": "Bearer ${GITHUB_TOKEN}"
}
},
"kubernetes": { "kubernetes": {
"type": "sse", "type": "sse",
"url": "http://localhost:8080/sse" "url": "http://localhost:8080/sse"
@@ -8,14 +15,13 @@
"type": "sse", "type": "sse",
"url": "http://localhost:8081/sse" "url": "http://localhost:8081/sse"
}, },
"homeassistant": {
"type": "sse",
"url": "http://localhost:8087/sse"
},
"playwright": { "playwright": {
"type": "sse", "type": "sse",
"url": "http://playwright-mcp.playwright.svc.cluster.local:3000/sse" "url": "http://localhost:8086/sse"
} },
"helm": {
"type": "sse",
"url": "http://localhost:8012/sse"
}
} }
} }
+73 -30
View File
@@ -4,11 +4,13 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co
## Project Overview ## 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 - Web-based GUI IDE (VSCode/Antigravity) via VNC on port 5800
- Happy Coder AI assistant integration - Claude Code, 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 - Automatic GitHub repository cloning on startup
- Kubernetes-native deployment with persistent home storage - 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. 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 ```bash
make build # Build Docker image make build # Build Docker image
make build REGISTRY=ghcr.io/myuser IMAGE_TAG=v1.0 # Custom registry/tag 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 ### Running Locally
@@ -60,7 +62,6 @@ Container start
→ scripts/init-repo.sh → scripts/init-repo.sh
→ Configure git user & credentials → Configure git user & credentials
→ Clone GITHUB_REPO (if set) → Clone GITHUB_REPO (if set)
→ Start Happy Coder
→ Launch VSCode as user `user` in /workspace → Launch VSCode as user `user` in /workspace
``` ```
@@ -68,8 +69,8 @@ Container start
| File | Purpose | | File | Purpose |
|------|---------| |------|---------|
| `Dockerfile` | Image definition — installs Chrome, Node.js, VSCode, Happy Coder; creates non-root user (UID 1000) | | `Dockerfile` | Image definition — installs Chrome, VSCode, Helm, gh CLI, kubeseal, Claude Code, OpenCode, Crush; creates non-root user (UID 1000) |
| `scripts/init-repo.sh` | Configures git credentials, clones GitHub repo, starts Happy Coder background service | | `scripts/init-repo.sh` | Configures git credentials, clones GitHub repo |
| `scripts/startapp.sh` | Calls init-repo.sh then opens VSCode in the workspace | | `scripts/startapp.sh` | Calls init-repo.sh then opens VSCode in the workspace |
| `chart/` | Helm chart for Kubernetes deployment | | `chart/` | Helm chart for Kubernetes deployment |
| `chart/templates/deployment.yaml` | Deployment spec — main container + MCP sidecar containers | | `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/pvc.yaml` | PersistentVolumeClaim for user home |
| `chart/templates/service.yaml` | ClusterIP Service (VNC + optional SSH) | | `chart/templates/service.yaml` | ClusterIP Service (VNC + optional SSH) |
| `chart/values.yaml` | Default Helm values | | `chart/values.yaml` | Default Helm values |
| `.mcp.json` | MCP server connection config (Kubernetes, Flux, Playwright) | | `.mcp.json` | MCP server connection config (GitHub Copilot, Kubernetes, Flux, Helm, Fetch, Sequential Thinking, Playwright, pgtuner) |
| `Makefile` | Build/deploy automation | | `Makefile` | Build/deploy automation |
### MCP Sidecars ### MCP Sidecars
@@ -88,13 +89,23 @@ 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 | | `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 | | `flux-mcp` | `ghcr.io/controlplaneio-fluxcd/flux-operator-mcp` | v0.41.1 | 8081 | `http://localhost:8081/sse` | Enabled |
| `homeassistant-mcp` | `ghcr.io/homeassistant-ai/ha-mcp` | v6.7.1 | 8087 | `http://localhost:8087/sse` | Disabled | | `helm-mcp` | `ghcr.io/zekker6/mcp-helm` | v1.3.1 | 8012 | `http://localhost:8012/sse` | Enabled |
| `fetch-mcp` | `mcp/fetch` | latest | 8082 | `http://localhost:8082/sse` | Enabled |
| `sequentialthinking-mcp` | `mcp/sequentialthinking` | latest | 8083 | `http://localhost:8083/sse` | Enabled |
| `homeassistant-mcp` | `ghcr.io/homeassistant-ai/ha-mcp` | stable | 8087 | `http://localhost:8087/sse` | Disabled |
| `pgtuner-mcp` | `dog830228/pgtuner_mcp` | latest | 8085 | `http://localhost:8085/sse` | Disabled |
| `playwright-mcp` | `mcr.microsoft.com/playwright/mcp` | latest | 8086 | `http://localhost:8086/sse` | Enabled |
**Note:** **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 require `clusterAccess` != `none` to be deployed (they need RBAC permissions)
- Kubernetes and Flux sidecars inherit the pod's ServiceAccount RBAC permissions - Kubernetes and Flux sidecars inherit the pod's ServiceAccount RBAC permissions
- Helm sidecar enables browsing Helm repositories and chart metadata
- Fetch sidecar provides web content fetching capabilities and HTML to markdown conversion
- Sequential thinking sidecar enables structured thinking and problem-solving processes
- Home Assistant sidecar requires `HOMEASSISTANT_URL` and `HOMEASSISTANT_TOKEN` in the env secret - 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 #### Enabling/Disabling MCP Servers
@@ -102,30 +113,60 @@ To control MCP sidecars, set the `enabled` flag in your values override:
```yaml ```yaml
# Disable all MCP sidecars # Disable all MCP sidecars
mcpSidecars: mcp:
kubernetes: sidecars:
enabled: false kubernetes:
flux: enabled: false
enabled: false flux:
homeassistant: enabled: false
enabled: false helm:
enabled: false
fetch:
enabled: false
sequentialthinking:
enabled: false
homeassistant:
enabled: false
pgtuner:
enabled: false
playwright:
enabled: false
# Or selectively enable/disable # Or selectively enable/disable
mcpSidecars: mcp:
kubernetes: sidecars:
enabled: true # Keep Kubernetes MCP enabled kubernetes:
flux: enabled: true # Keep Kubernetes MCP enabled
enabled: false # Disable Flux MCP flux:
homeassistant: enabled: false # Disable Flux MCP
enabled: true # Enable Home Assistant MCP (requires secrets) helm:
enabled: true # Enable Helm MCP for chart browsing
fetch:
enabled: true # Enable Fetch MCP for web content fetching
sequentialthinking:
enabled: true # Enable Sequential Thinking MCP for problem-solving
homeassistant:
enabled: true # Enable Home Assistant MCP (requires secrets)
pgtuner:
enabled: true # Enable PostgreSQL tuner MCP (requires DATABASE_URI)
playwright:
enabled: true # Enable Playwright MCP for browser automation
``` ```
When deploying via Helm: When deploying via Helm:
```bash ```bash
# Using --set flag # Quick start (recommended)
helm install my-devcontainer ./chart --set mcpSidecars.kubernetes.enabled=false --set mcpSidecars.flux.enabled=false 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 helm install my-devcontainer ./chart -f custom-values.yaml
``` ```
@@ -147,20 +188,22 @@ helm install my-devcontainer ./chart -f custom-values.yaml
- `VNC_PASSWORD` — VNC web interface password - `VNC_PASSWORD` — VNC web interface password
- `DISPLAY_WIDTH` / `DISPLAY_HEIGHT` — VNC resolution - `DISPLAY_WIDTH` / `DISPLAY_HEIGHT` — VNC resolution
- `USER_ID` / `GROUP_ID` — Override UID/GID (default 1000) - `USER_ID` / `GROUP_ID` — Override UID/GID (default 1000)
- `HAPPY_SERVER_URL` / `HAPPY_WEBAPP_URL` — Custom Happy Coder endpoints - `WEB_FILE_MANAGER` — Set to `1` to enable the built-in web file manager (controlled via `fileManager.enabled` in Helm values)
- `HAPPY_HOME_DIR` / `HAPPY_EXPERIMENTAL` - `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 ### 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. - **`build-and-push.yaml`** — Builds and pushes to GHCR on every push to `main`, version tags (`v*`), and PRs. Tags: `latest` (main), semver, branch name, commit SHA.
- **`release.yaml`** — Creates a GitHub Release with docker pull instructions when a version tag is pushed. - **`release-unified.yaml`** — Manual release workflow: bumps chart version, builds Docker image, publishes Helm chart to GitHub Pages (`https://cpfarhood.github.io/devcontainer`), and creates GitHub Release.
- **`dependabot.yml`** — Weekly updates for GitHub Actions and Docker base image. - **`dependabot.yml`** — Weekly updates for GitHub Actions and Docker base image.
Image registry: `ghcr.io/cpfarhood/devcontainer` Image registry: `ghcr.io/cpfarhood/devcontainer`
Helm repo: `https://cpfarhood.github.io/devcontainer`
## Kubernetes Notes ## Kubernetes Notes
- Deployed via Helm chart (`chart/`), published as OCI artifact to GHCR, reconciled by Flux - Deployed via Helm chart (`chart/`), published to GitHub Pages Helm repo, reconciled by Flux
- Storage class is `ceph-filesystem` by default — change via `storage.className` in values - Storage class is `ceph-filesystem` by default — change via `storage.className` in values
- Resource limits: 14 CPU, 28Gi memory - Resource limits: 14 CPU, 28Gi memory
- Health checks (liveness/readiness probes) on port 5800 - Health checks (liveness/readiness probes) on port 5800
-12
View File
@@ -225,18 +225,6 @@ spec:
## Advanced Configurations ## Advanced Configurations
### Custom Happy Coder Endpoints
For self-hosted Happy instances:
```bash
helm install mydev ./chart \
--set name=mydev \
--set githubRepo=https://github.com/youruser/yourrepo \
--set happyServerUrl=https://your-happy-server.com \
--set happyWebappUrl=https://your-happy-webapp.com
```
### Custom Display Resolution ### Custom Display Resolution
```bash ```bash
+69 -11
View File
@@ -1,7 +1,7 @@
FROM jlesage/baseimage-gui:ubuntu-22.04-v4 FROM jlesage/baseimage-gui:ubuntu-22.04-v4
# Set environment variables # Set environment variables
ENV APP_NAME="Antigravity Dev Container" \ ENV APP_NAME="Dev Container" \
KEEP_APP_RUNNING=1 \ KEEP_APP_RUNNING=1 \
DISPLAY_WIDTH=1920 \ DISPLAY_WIDTH=1920 \
DISPLAY_HEIGHT=1080 \ DISPLAY_HEIGHT=1080 \
@@ -56,17 +56,56 @@ exec /usr/bin/google-chrome-stable \\\n\
"$@"\n' > /usr/local/bin/google-chrome && \ "$@"\n' > /usr/local/bin/google-chrome && \
chmod +x /usr/local/bin/google-chrome chmod +x /usr/local/bin/google-chrome
# Install Node.js (LTS version for Happy Coder) # Install Claude Code native binary (npm wrapper breaks remote control)
RUN curl -fsSL https://deb.nodesource.com/setup_lts.x | bash - && \ RUN curl -fsSL https://claude.ai/install.sh | bash && \
apt-get install -y nodejs && \ cp /root/.local/bin/claude /usr/local/bin/claude && \
rm -rf /root/.local/bin/claude && \
claude --version
# Disable Claude Code auto-updater (doesn't work inside Docker)
RUN mkdir -p /etc/skel/.claude && \
echo '{"env":{"DISABLE_AUTOUPDATER":"1"}}' > /etc/skel/.claude/settings.json
# Install OpenCode AI coding agent
RUN OPENCODE_VERSION=$(curl -sL https://api.github.com/repos/opencode-ai/opencode/releases/latest | jq -r '.tag_name') && \
curl -fsSL "https://github.com/opencode-ai/opencode/releases/download/${OPENCODE_VERSION}/opencode-linux-x86_64.tar.gz" | \
tar -xz -C /usr/local/bin opencode && \
chmod +x /usr/local/bin/opencode
# Install Crush AI coding agent (OpenCode successor by Charm)
RUN CRUSH_VERSION=$(curl -sL https://api.github.com/repos/charmbracelet/crush/releases/latest | jq -r '.tag_name' | sed 's/^v//') && \
curl -fsSL "https://github.com/charmbracelet/crush/releases/download/v${CRUSH_VERSION}/crush_${CRUSH_VERSION}_Linux_x86_64.tar.gz" -o /tmp/crush.tar.gz && \
tar -xzf /tmp/crush.tar.gz -C /tmp && \
mv /tmp/crush_${CRUSH_VERSION}_Linux_x86_64/crush /usr/local/bin/crush && \
chmod +x /usr/local/bin/crush && \
rm -rf /tmp/crush*
# Install Helm CLI for Kubernetes chart management
ARG HELM_VERSION=3.17.1
RUN curl -fsSL "https://get.helm.sh/helm-v${HELM_VERSION}-linux-amd64.tar.gz" | \
tar -xz --strip-components=1 -C /usr/local/bin linux-amd64/helm && \
chmod +x /usr/local/bin/helm
# Install GitHub CLI (gh) via official APT repo
RUN curl -fsSL https://cli.github.com/packages/githubcli-archive-keyring.gpg | dd of=/usr/share/keyrings/githubcli-archive-keyring.gpg && \
chmod go+r /usr/share/keyrings/githubcli-archive-keyring.gpg && \
echo "deb [arch=amd64 signed-by=/usr/share/keyrings/githubcli-archive-keyring.gpg] https://cli.github.com/packages stable main" > /etc/apt/sources.list.d/github-cli.list && \
apt-get update && \
apt-get install -y gh && \
rm -rf /var/lib/apt/lists/* rm -rf /var/lib/apt/lists/*
# Install Happy Coder and Claude Code globally # Install kubeseal CLI for Bitnami Sealed Secrets
RUN npm install -g happy-coder @anthropic-ai/claude-code RUN KUBESEAL_VERSION=$(curl -sL https://api.github.com/repos/bitnami-labs/sealed-secrets/releases/latest | jq -r '.tag_name' | sed 's/^v//') && \
curl -fsSL "https://github.com/bitnami-labs/sealed-secrets/releases/download/v${KUBESEAL_VERSION}/kubeseal-${KUBESEAL_VERSION}-linux-amd64.tar.gz" | \
tar -xz -C /usr/local/bin kubeseal && \
chmod +x /usr/local/bin/kubeseal
# Install VSCode # Install VSCode (using Microsoft's current recommended setup)
RUN wget -qO- https://packages.microsoft.com/keys/microsoft.asc | gpg --dearmor -o /usr/share/keyrings/packages.microsoft.gpg && \ RUN wget -qO- https://packages.microsoft.com/keys/microsoft.asc | gpg --dearmor > /tmp/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 && \ install -D -o root -g root -m 644 /tmp/microsoft.gpg /usr/share/keyrings/microsoft.gpg && \
rm -f /tmp/microsoft.gpg && \
printf 'Types: deb\nURIs: https://packages.microsoft.com/repos/code\nSuites: stable\nComponents: main\nArchitectures: amd64\nSigned-By: /usr/share/keyrings/microsoft.gpg\n' \
> /etc/apt/sources.list.d/vscode.sources && \
apt-get update && \ apt-get update && \
apt-get install -y code && \ apt-get install -y code && \
rm -rf /var/lib/apt/lists/* rm -rf /var/lib/apt/lists/*
@@ -77,10 +116,26 @@ RUN mkdir -p /etc/apt/keyrings && \
gpg --dearmor --yes -o /etc/apt/keyrings/antigravity-repo-key.gpg && \ gpg --dearmor --yes -o /etc/apt/keyrings/antigravity-repo-key.gpg && \
echo "deb [signed-by=/etc/apt/keyrings/antigravity-repo-key.gpg] https://us-central1-apt.pkg.dev/projects/antigravity-auto-updater-dev/ antigravity-debian main" \ 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 && \ > /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 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/* 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) # Install OpenSSH server (for SSH IDE mode)
RUN apt-get update && \ RUN apt-get update && \
apt-get install -y openssh-server && \ apt-get install -y openssh-server && \
@@ -103,6 +158,9 @@ RUN mkdir -p /workspace && \
# Copy startup scripts # Copy startup scripts
COPY --chmod=755 scripts/startapp.sh /startapp.sh COPY --chmod=755 scripts/startapp.sh /startapp.sh
COPY --chmod=755 scripts/init-repo.sh /usr/local/bin/init-repo 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 # 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-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 COPY --chmod=755 scripts/cont-init-sshd.sh /etc/cont-init.d/25-start-sshd.sh
@@ -119,4 +177,4 @@ ENV HOME=/config/userdata \
EXPOSE 5800 EXPOSE 5800
# Set app name for baseimage-gui # Set app name for baseimage-gui
RUN set-cont-env APP_NAME "Antigravity" RUN set-cont-env APP_NAME "Dev Container"
+7 -8
View File
@@ -2,7 +2,7 @@
# Variables # Variables
REGISTRY ?= ghcr.io/cpfarhood REGISTRY ?= ghcr.io/cpfarhood
IMAGE_NAME ?= antigravity IMAGE_NAME ?= devcontainer
IMAGE_TAG ?= latest IMAGE_TAG ?= latest
FULL_IMAGE = $(REGISTRY)/$(IMAGE_NAME):$(IMAGE_TAG) FULL_IMAGE = $(REGISTRY)/$(IMAGE_NAME):$(IMAGE_TAG)
@@ -26,18 +26,17 @@ run:
-e GITHUB_REPO="${GITHUB_REPO}" \ -e GITHUB_REPO="${GITHUB_REPO}" \
-e GITHUB_TOKEN="${GITHUB_TOKEN}" \ -e GITHUB_TOKEN="${GITHUB_TOKEN}" \
-e VNC_PASSWORD="${VNC_PASSWORD}" \ -e VNC_PASSWORD="${VNC_PASSWORD}" \
-e HAPPY_EXPERIMENTAL="true" \
-v $(PWD)/home:/home \ -v $(PWD)/home:/home \
-v $(PWD)/workspace:/workspace \ -v $(PWD)/workspace:/workspace \
--name antigravity \ --name devcontainer \
$(FULL_IMAGE) $(FULL_IMAGE)
@echo "Access at http://localhost:5800" @echo "Access at http://localhost:5800"
# Stop the running container # Stop the running container
stop: stop:
@echo "Stopping antigravity container..." @echo "Stopping devcontainer..."
docker stop antigravity || true docker stop devcontainer || true
docker rm antigravity || true docker rm devcontainer || true
# Clean up local volumes # Clean up local volumes
clean: stop clean: stop
@@ -81,7 +80,7 @@ helm-port-forward:
# Show help # Show help
help: help:
@echo "Antigravity Dev Container Makefile" @echo "Dev Container Makefile"
@echo "" @echo ""
@echo "Usage: make [target]" @echo "Usage: make [target]"
@echo "" @echo ""
@@ -101,7 +100,7 @@ help:
@echo "" @echo ""
@echo "Variables:" @echo "Variables:"
@echo " REGISTRY - Docker registry (default: ghcr.io/cpfarhood)" @echo " REGISTRY - Docker registry (default: ghcr.io/cpfarhood)"
@echo " IMAGE_NAME - Image name (default: antigravity)" @echo " IMAGE_NAME - Image name (default: devcontainer)"
@echo " IMAGE_TAG - Image tag (default: latest)" @echo " IMAGE_TAG - Image tag (default: latest)"
@echo " RELEASE_NAME - Helm release name (default: mydev)" @echo " RELEASE_NAME - Helm release name (default: mydev)"
@echo " NAMESPACE - Kubernetes namespace (default: default)" @echo " NAMESPACE - Kubernetes namespace (default: default)"
+152 -77
View File
@@ -1,17 +1,52 @@
# Antigravity Dev Container # Dev Container
![Build and Push](https://github.com/cpfarhood/devcontainer/actions/workflows/build-and-push.yaml/badge.svg) ![Build and Push](https://github.com/cpfarhood/devcontainer/actions/workflows/build-and-push.yaml/badge.svg)
A containerized cloud development environment with web-based GUI access, featuring: A containerized cloud development environment with web-based GUI access, featuring:
- **VSCode or Google Antigravity** via browser-based VNC (port 5800) - **VSCode or Google Antigravity** via browser-based VNC (port 5800)
- **SSH access** option (OpenSSH on port 22, additive with any IDE) - **SSH access** option (OpenSSH on port 22, additive with any IDE)
- **Happy Coder** AI assistant backed by Claude - **Claude Code**, **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 - **Automatic GitHub repo cloning** on startup
- **Persistent home directory** via ReadWriteMany PVC - **Persistent home directory** via ReadWriteMany PVC
- **Kubernetes-native** Helm chart deployment - **Kubernetes-native** Helm chart deployment
## Quick Start ## Quick Start
### Option A: Install from Helm Repo (Recommended)
```bash
# Add the Helm repository
helm repo add devcontainer https://cpfarhood.github.io/devcontainer
helm repo update
# Deploy with one command
helm install mydev devcontainer/devcontainer \
--set name=mydev \
--set githubRepo=https://github.com/youruser/yourrepo
```
### Option B: Install from Source
```bash
# Clone and customize the quickstart template
cp chart/values-quickstart.yaml my-values.yaml
# Edit my-values.yaml to set your name and repository
helm install mydev ./chart -f my-values.yaml
```
### Option C: One-Command from Source
```bash
helm install mydev ./chart \
--set name=mydev \
--set githubRepo=https://github.com/youruser/yourrepo
```
### Option C: Full Configuration
### 1. Create a secret ### 1. Create a secret
The secret is picked up automatically via `envFrom`. Keys recognised: The secret is picked up automatically via `envFrom`. Keys recognised:
@@ -22,8 +57,10 @@ The secret is picked up automatically via `envFrom`. Keys recognised:
| `VNC_PASSWORD` | Password for the VNC web UI | | `VNC_PASSWORD` | Password for the VNC web UI |
| `ANTHROPIC_API_KEY` | API key — alternative to browser-based Claude login | | `ANTHROPIC_API_KEY` | API key — alternative to browser-based Claude login |
| `SSH_AUTHORIZED_KEYS` | Public key(s) for SSH access (required when `ssh: true`) | | `SSH_AUTHORIZED_KEYS` | Public key(s) for SSH access (required when `ssh: true`) |
| `homeassistant-url` | Home Assistant URL (required when `mcpSidecars.homeassistant.enabled: true`) | | `HOMEASSISTANT_URL` | Home Assistant URL (required when `mcp.sidecars.homeassistant.enabled: true`) |
| `homeassistant-token` | Home Assistant long-lived access token (required when `mcpSidecars.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 ```bash
kubectl create secret generic devcontainer-mydev-secrets-env \ kubectl create secret generic devcontainer-mydev-secrets-env \
@@ -73,20 +110,33 @@ A Chrome browser window will open inside VNC for the Claude Max OAuth login. Cre
## Helm Chart Reference ## 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**: 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 ### Core values
| Value | Default | Description | | Value | Default | Description |
|-------|---------|-------------| |-------|---------|-------------|
| `name` | `""` | Instance name — used in all resource names (`devcontainer-{name}`) | | `name` | `""` | Instance name — used in all resource names (`devcontainer-{name}`) |
| `githubRepo` | `""` | Repository to clone into `/workspace` on startup | | `githubRepo` | `""` | Repository to clone into `/workspace` on startup |
| `ide` | `vscode` | IDE to launch — `vscode`, `antigravity`, or `none` (see below) | | `ide.type` | `vscode` | IDE to launch — `vscode`, `antigravity`, or `none` (see below) |
| `ssh` | `false` | Also start an OpenSSH server on port 22 (additive, any `ide`) | | `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.repository` | `ghcr.io/cpfarhood/devcontainer` | Container image |
| `image.tag` | `latest` | Image tag | | `image.tag` | `latest` | Image tag |
### IDE choice ### 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 | | Value | Port | Description |
|-------|------|-------------| |-------|------|-------------|
@@ -96,14 +146,14 @@ A Chrome browser window will open inside VNC for the Claude Max OAuth login. Cre
### SSH access ### SSH 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 ```bash
# SSH-only (no VNC) # 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 # 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: Add your public key to the env secret:
@@ -121,14 +171,23 @@ kubectl port-forward deployment/devcontainer-mydev 2222:22
ssh -p 2222 user@localhost ssh -p 2222 user@localhost
``` ```
### Happy Coder ### 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 | | Value | Default | Description |
|-------|---------|-------------| |-------|---------|-------------|
| `happyServerUrl` | `https://happy.farh.net` | Happy Coder server endpoint | | `fileManager.enabled` | `false` | Enable the web file manager |
| `happyWebappUrl` | `https://happy-coder.farh.net` | Happy Coder webapp URL | | `fileManager.allowedPaths` | `/workspace,/config` | Paths accessible by the file manager (`AUTO`, `ALL`, or comma-separated) |
| `happyHomeDir` | `/home/user/.happy` | Happy runtime state directory (persists on the home PVC) | | `fileManager.deniedPaths` | `""` | Paths to deny (takes precedence over allowed) |
| `happyExperimental` | `true` | Enable experimental Happy features |
```bash
# Enable the file manager
helm install mydev ./chart \
--set name=mydev \
--set githubRepo=https://github.com/youruser/yourrepo \
--set fileManager.enabled=true
```
### Kubernetes cluster access ### Kubernetes cluster access
@@ -158,29 +217,35 @@ The devcontainer includes MCP (Model Context Protocol) servers as sidecar contai
| Sidecar | Default | Purpose | | Sidecar | Default | Purpose |
|---------|---------|---------| |---------|---------|---------|
| `mcpSidecars.kubernetes.enabled` | `true` | Kubernetes API access via MCP | | `mcp.sidecars.kubernetes.enabled` | `true` | Kubernetes API access via MCP |
| `mcpSidecars.flux.enabled` | `true` | Flux GitOps operations via MCP | | `mcp.sidecars.flux.enabled` | `true` | Flux GitOps operations via MCP |
| `mcpSidecars.homeassistant.enabled` | `false` | Home Assistant smart home control 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:** **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 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`) - 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:** **Disable MCP sidecars:**
```bash ```bash
# Disable both sidecars # Disable multiple sidecars
helm install mydev ./chart \ helm install mydev ./chart \
--set name=mydev \ --set name=mydev \
--set githubRepo=https://github.com/youruser/yourrepo \ --set githubRepo=https://github.com/youruser/yourrepo \
--set mcpSidecars.kubernetes.enabled=false \ --set mcp.sidecars.kubernetes.enabled=false \
--set mcpSidecars.flux.enabled=false --set mcp.sidecars.flux.enabled=false \
--set mcp.sidecars.playwright.enabled=false
# Or selectively disable # Or selectively disable
helm install mydev ./chart \ helm install mydev ./chart \
--set name=mydev \ --set name=mydev \
--set githubRepo=https://github.com/youruser/yourrepo \ --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:** **Enable Home Assistant MCP:**
@@ -188,48 +253,75 @@ helm install mydev ./chart \
# Create secret with Home Assistant credentials # Create secret with Home Assistant credentials
kubectl create secret generic devcontainer-mydev-secrets-env \ kubectl create secret generic devcontainer-mydev-secrets-env \
--from-literal=GITHUB_TOKEN='ghp_...' \ --from-literal=GITHUB_TOKEN='ghp_...' \
--from-literal=homeassistant-url='http://homeassistant.local:8123' \ --from-literal=HOMEASSISTANT_URL='http://homeassistant.local:8123' \
--from-literal=homeassistant-token='your_long_lived_access_token' --from-literal=HOMEASSISTANT_TOKEN='your_long_lived_access_token'
# Deploy with Home Assistant MCP enabled # Deploy with Home Assistant MCP enabled
helm install mydev ./chart \ helm install mydev ./chart \
--set name=mydev \ --set name=mydev \
--set githubRepo=https://github.com/youruser/yourrepo \ --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:** **Custom MCP configuration:**
```yaml ```yaml
# values.yaml override # values.yaml override
mcpSidecars: mcp:
kubernetes: sidecars:
enabled: true kubernetes:
image: enabled: true
repository: quay.io/containers/kubernetes_mcp_server image:
tag: latest repository: quay.io/containers/kubernetes_mcp_server
port: 8080 tag: v0.0.57
resources: port: 8080
requests: resources:
memory: "64Mi" requests:
cpu: "50m" memory: "64Mi"
limits: cpu: "50m"
memory: "256Mi" limits:
cpu: "500m" memory: "256Mi"
flux: cpu: "500m"
enabled: false # Disabled in this example flux:
homeassistant: enabled: false # Disabled in this example
enabled: true homeassistant:
image: enabled: true
repository: ghcr.io/homeassistant-ai/ha-mcp image:
tag: v6.7.1 # Override the pinned version if needed repository: ghcr.io/homeassistant-ai/ha-mcp
port: 8087 tag: stable
resources: port: 8087
requests: pgtuner:
memory: "128Mi" enabled: true
cpu: "100m" image:
limits: repository: dog830228/pgtuner_mcp
memory: "512Mi" tag: latest
cpu: "500m" 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 ### Display and resources
@@ -238,9 +330,9 @@ mcpSidecars:
|-------|---------|-------------| |-------|---------|-------------|
| `display.width` | `1920` | VNC width (px) | | `display.width` | `1920` | VNC width (px) |
| `display.height` | `1080` | VNC height (px) | | `display.height` | `1080` | VNC height (px) |
| `secureConnection` | `0` | Set to `1` if TLS is not terminated upstream | | `display.secureConnection` | `0` | Set to `1` if TLS is not terminated upstream |
| `userId` | `1000` | UID for the app user | | `user.id` | `1000` | UID for the app user |
| `groupId` | `1000` | GID for the app user | | `user.groupId` | `1000` | GID for the app user |
| `storage.size` | `32Gi` | Home PVC size | | `storage.size` | `32Gi` | Home PVC size |
| `storage.className` | `ceph-filesystem` | StorageClass (must be ReadWriteMany) | | `storage.className` | `ceph-filesystem` | StorageClass (must be ReadWriteMany) |
| `shm.sizeLimit` | `2Gi` | `/dev/shm` size (memory-backed; used by Electron apps) | | `shm.sizeLimit` | `2Gi` | `/dev/shm` size (memory-backed; used by Electron apps) |
@@ -263,8 +355,6 @@ Container start
→ /startapp.sh (runs as app user, UID 1000) → /startapp.sh (runs as app user, UID 1000)
→ init-repo.sh → init-repo.sh
→ clone / pull GITHUB_REPO into /workspace/{repo} → 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=vscode: code --new-window --wait /workspace/{repo}
IDE=antigravity: antigravity --no-sandbox --user-data-dir ~/.config/antigravity ... /workspace/{repo} IDE=antigravity: antigravity --no-sandbox --user-data-dir ~/.config/antigravity ... /workspace/{repo}
IDE=none: sleep infinity IDE=none: sleep infinity
@@ -275,28 +365,13 @@ Container start
| Mount | Source | Persistence | | 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 | | `/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.
--- ---
## Troubleshooting ## Troubleshooting
### Happy Coder daemon not starting
```bash
# Check daemon status
happy daemon status
# Start manually (also clears any stale lock)
happy daemon start
# View daemon logs
ls ~/.happy/logs/
```
### Claude not authenticated ### Claude not authenticated
Browser-based OAuth login is the primary method (works inside VNC via the Chrome wrapper). If you prefer API key auth: Browser-based OAuth login is the primary method (works inside VNC via the Chrome wrapper). If you prefer API key auth:
@@ -362,4 +437,4 @@ The image is also built and pushed automatically by CI on every push to `main` a
## Credits ## Credits
- Base image: [jlesage/docker-baseimage-gui](https://github.com/jlesage/docker-baseimage-gui) - Base image: [jlesage/docker-baseimage-gui](https://github.com/jlesage/docker-baseimage-gui)
- AI assistant: [Happy Coder](https://happy.engineering) + [Claude](https://claude.ai) - AI assistant: [Claude](https://claude.ai)
-30
View File
@@ -52,30 +52,6 @@ Complete reference for all configurable values in the Antigravity Dev Container
- **Options:** `Always`, `IfNotPresent`, `Never` - **Options:** `Always`, `IfNotPresent`, `Never`
- **Description:** Image pull policy - **Description:** Image pull policy
## Happy Coder Configuration
### happyServerUrl
- **Type:** String
- **Default:** `https://happy.farh.net`
- **Description:** Happy Coder server endpoint
- **When to Change:** Self-hosted Happy instance
### happyWebappUrl
- **Type:** String
- **Default:** `https://happy-coder.farh.net`
- **Description:** Happy Coder webapp URL
- **When to Change:** Self-hosted Happy instance
### happyHomeDir
- **Type:** String
- **Default:** `/config/userdata/.happy`
- **Description:** Happy runtime state directory (persists on PVC)
### happyExperimental
- **Type:** String
- **Default:** `"true"`
- **Description:** Enable experimental Happy features
## Display Configuration ## Display Configuration
### display.width ### display.width
@@ -339,8 +315,6 @@ storage:
clusterAccess: readonly clusterAccess: readonly
happyServerUrl: https://happy.internal.company.com
happyWebappUrl: https://happy-app.internal.company.com
``` ```
### Smart Home Development Configuration ### Smart Home Development Configuration
@@ -431,10 +405,6 @@ These environment variables are set in the container based on chart values:
| `VNC_PASSWORD` | Secret: `vnc-password` | VNC access password | | `VNC_PASSWORD` | Secret: `vnc-password` | VNC access password |
| `ANTHROPIC_API_KEY` | Secret: `anthropic-api-key` | Claude API key | | `ANTHROPIC_API_KEY` | Secret: `anthropic-api-key` | Claude API key |
| `SSH_AUTHORIZED_KEYS` | Secret: `ssh-authorized-keys` | SSH public keys | | `SSH_AUTHORIZED_KEYS` | Secret: `ssh-authorized-keys` | SSH public keys |
| `HAPPY_SERVER_URL` | `happyServerUrl` | Happy server endpoint |
| `HAPPY_WEBAPP_URL` | `happyWebappUrl` | Happy webapp URL |
| `HAPPY_HOME_DIR` | `happyHomeDir` | Happy data directory |
| `HAPPY_EXPERIMENTAL` | `happyExperimental` | Experimental features |
| `DISPLAY_WIDTH` | `display.width` | VNC width | | `DISPLAY_WIDTH` | `display.width` | VNC width |
| `DISPLAY_HEIGHT` | `display.height` | VNC height | | `DISPLAY_HEIGHT` | `display.height` | VNC height |
| `SECURE_CONNECTION` | `secureConnection` | TLS termination | | `SECURE_CONNECTION` | `secureConnection` | TLS termination |
+9 -2
View File
@@ -1,6 +1,13 @@
apiVersion: v2 apiVersion: v2
name: devcontainer 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 type: application
version: 0.1.20 version: 2.2.2
appVersion: "latest" 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} 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 }} {{- printf "devcontainer-%s" .Values.name }}
{{- end }} {{- end }}
{{/* {{/*
PVC name: userhome-{name} PVC name: userhome-{name}
*/}} */}}
{{- define "antigravity.pvcName" -}} {{- define "devcontainer.pvcName" -}}
{{- printf "userhome-%s" .Values.name }} {{- printf "userhome-%s" .Values.name }}
{{- end }} {{- end }}
{{/* {{/*
Secret name for env vars, default to devcontainer-{name}-secrets-env 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) }} {{- .Values.envSecretName | default (printf "devcontainer-%s-secrets-env" .Values.name) }}
{{- end }} {{- end }}
{{/* {{/*
Common labels 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 app: devcontainer
instance: {{ .Values.name }} instance: {{ .Values.name }}
{{- end }} {{- 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 }}
+165 -50
View File
@@ -1,68 +1,92 @@
{{- if eq .Values.deploymentMode "persistent" }}
apiVersion: apps/v1 apiVersion: apps/v1
kind: Deployment kind: Deployment
metadata: metadata:
name: {{ include "antigravity.fullname" . }} name: {{ include "devcontainer.fullname" . }}
labels: labels:
{{- include "antigravity.labels" . | nindent 4 }} {{- include "devcontainer.labels" . | nindent 4 }}
spec: spec:
replicas: 1 replicas: 1
selector: selector:
matchLabels: matchLabels:
{{- include "antigravity.labels" . | nindent 6 }} {{- include "devcontainer.selectorLabels" . | nindent 6 }}
template: template:
metadata: metadata:
labels: labels:
{{- include "antigravity.labels" . | nindent 8 }} {{- include "devcontainer.labels" . | nindent 8 }}
spec: spec:
{{- if ne (.Values.clusterAccess | default "none") "none" }} {{- if ne (.Values.clusterAccess | default "none") "none" }}
serviceAccountName: {{ include "antigravity.fullname" . }} serviceAccountName: {{ include "devcontainer.fullname" . }}
{{- end }} {{- end }}
securityContext: securityContext:
fsGroup: 1000 fsGroup: 1000
fsGroupChangePolicy: "OnRootMismatch" fsGroupChangePolicy: "OnRootMismatch"
{{- if and .Values.ide.type (eq .Values.ide.type "antigravity") }}
initContainers:
- name: setup-userdata
image: busybox:1.37
command: ['sh', '-c']
args:
- |
echo "Setting up userdata directory..."
mkdir -p /config/userdata
chown 1000:1000 /config/userdata
chmod 755 /config/userdata
echo "Userdata directory setup complete"
volumeMounts:
- name: userhome
mountPath: /config
securityContext:
runAsUser: 0
runAsGroup: 0
{{- end }}
containers: containers:
- name: devcontainer - name: devcontainer
image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}" image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}"
imagePullPolicy: {{ .Values.image.pullPolicy }} imagePullPolicy: {{ .Values.image.pullPolicy }}
ports: ports:
{{- if ne (.Values.ide | default "vscode") "none" }} {{- if ne (.Values.ide.type | default "vscode") "none" }}
- containerPort: 5800 - containerPort: 5800
name: vnc-web name: vnc-web
protocol: TCP protocol: TCP
{{- end }} {{- end }}
{{- if .Values.ssh }} {{- if .Values.ssh.enabled }}
- containerPort: 22 - containerPort: 22
name: ssh name: ssh
protocol: TCP protocol: TCP
{{- end }} {{- end }}
env: env:
- name: IDE - name: IDE
value: {{ .Values.ide | default "vscode" | quote }} value: {{ .Values.ide.type | default "vscode" | quote }}
- name: SSH - name: SSH
value: {{ .Values.ssh | toString | quote }} value: {{ .Values.ssh.enabled | toString | quote }}
- name: USER_ID - name: USER_ID
value: {{ .Values.userId | quote }} value: {{ .Values.user.id | quote }}
- name: GROUP_ID - name: GROUP_ID
value: {{ .Values.groupId | quote }} value: {{ .Values.user.groupId | quote }}
- name: DISPLAY_WIDTH - name: DISPLAY_WIDTH
value: {{ .Values.display.width | quote }} value: {{ .Values.display.width | quote }}
- name: DISPLAY_HEIGHT - name: DISPLAY_HEIGHT
value: {{ .Values.display.height | quote }} value: {{ .Values.display.height | quote }}
- name: SECURE_CONNECTION - name: SECURE_CONNECTION
value: {{ .Values.secureConnection | quote }} value: {{ .Values.display.secureConnection | quote }}
- name: HAPPY_HOME_DIR {{- if .Values.fileManager.enabled }}
value: {{ .Values.happyHomeDir | quote }} - name: WEB_FILE_MANAGER
- name: HAPPY_EXPERIMENTAL value: "1"
value: {{ .Values.happyExperimental | quote }} - name: WEB_FILE_MANAGER_ALLOWED_PATHS
- name: HAPPY_SERVER_URL value: {{ .Values.fileManager.allowedPaths | quote }}
value: {{ .Values.happyServerUrl | quote }} {{- if .Values.fileManager.deniedPaths }}
- name: HAPPY_WEBAPP_URL - name: WEB_FILE_MANAGER_DENIED_PATHS
value: {{ .Values.happyWebappUrl | quote }} value: {{ .Values.fileManager.deniedPaths | quote }}
{{- end }}
{{- end }}
{{- if .Values.githubRepo }}
- name: GITHUB_REPO - name: GITHUB_REPO
value: {{ .Values.githubRepo | quote }} value: {{ .Values.githubRepo | quote }}
{{- end }}
envFrom: envFrom:
- secretRef: - secretRef:
name: {{ include "antigravity.envSecretName" . }} name: {{ include "devcontainer.envSecretName" . }}
optional: true optional: true
resources: resources:
{{- toYaml .Values.resources | nindent 12 }} {{- toYaml .Values.resources | nindent 12 }}
@@ -73,7 +97,7 @@ spec:
mountPath: /workspace mountPath: /workspace
- name: shm - name: shm
mountPath: /dev/shm mountPath: /dev/shm
{{- if ne (.Values.ide | default "vscode") "none" }} {{- if ne (.Values.ide.type | default "vscode") "none" }}
livenessProbe: livenessProbe:
httpGet: httpGet:
path: / path: /
@@ -86,7 +110,7 @@ spec:
port: 5800 port: 5800
initialDelaySeconds: 10 initialDelaySeconds: 10
periodSeconds: 5 periodSeconds: 5
{{- else if .Values.ssh }} {{- else if .Values.ssh.enabled }}
livenessProbe: livenessProbe:
tcpSocket: tcpSocket:
port: 22 port: 22
@@ -98,88 +122,178 @@ spec:
initialDelaySeconds: 5 initialDelaySeconds: 5
periodSeconds: 5 periodSeconds: 5
{{- end }} {{- 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 - 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: args:
- --port - --port
- {{ .Values.mcpSidecars.kubernetes.port | quote }} - {{ .Values.mcp.sidecars.kubernetes.port | quote }}
ports: ports:
- containerPort: {{ .Values.mcpSidecars.kubernetes.port }} - containerPort: {{ .Values.mcp.sidecars.kubernetes.port }}
name: k8s-mcp name: k8s-mcp
protocol: TCP protocol: TCP
livenessProbe: livenessProbe:
httpGet: httpGet:
path: /healthz path: /healthz
port: {{ .Values.mcpSidecars.kubernetes.port }} port: {{ .Values.mcp.sidecars.kubernetes.port }}
initialDelaySeconds: 10 initialDelaySeconds: 10
periodSeconds: 10 periodSeconds: 10
readinessProbe: readinessProbe:
httpGet: httpGet:
path: /healthz path: /healthz
port: {{ .Values.mcpSidecars.kubernetes.port }} port: {{ .Values.mcp.sidecars.kubernetes.port }}
initialDelaySeconds: 5 initialDelaySeconds: 5
periodSeconds: 5 periodSeconds: 5
resources: resources:
{{- toYaml .Values.mcpSidecars.kubernetes.resources | nindent 12 }} {{- toYaml .Values.mcp.sidecars.kubernetes.resources | nindent 12 }}
{{- end }} {{- 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 - 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: args:
- serve - serve
- --transport=sse - --transport=sse
- --port={{ .Values.mcpSidecars.flux.port }} - --port={{ .Values.mcp.sidecars.flux.port }}
ports: ports:
- containerPort: {{ .Values.mcpSidecars.flux.port }} - containerPort: {{ .Values.mcp.sidecars.flux.port }}
name: flux-mcp name: flux-mcp
protocol: TCP protocol: TCP
livenessProbe: livenessProbe:
tcpSocket: tcpSocket:
port: {{ .Values.mcpSidecars.flux.port }} port: {{ .Values.mcp.sidecars.flux.port }}
initialDelaySeconds: 10 initialDelaySeconds: 10
periodSeconds: 10 periodSeconds: 10
readinessProbe: readinessProbe:
tcpSocket: tcpSocket:
port: {{ .Values.mcpSidecars.flux.port }} port: {{ .Values.mcp.sidecars.flux.port }}
initialDelaySeconds: 5 initialDelaySeconds: 5
periodSeconds: 5 periodSeconds: 5
resources: resources:
{{- toYaml .Values.mcpSidecars.flux.resources | nindent 12 }} {{- toYaml .Values.mcp.sidecars.flux.resources | nindent 12 }}
{{- end }} {{- end }}
{{- if .Values.mcpSidecars.homeassistant.enabled }} {{- if .Values.mcp.sidecars.helm.enabled }}
- name: helm-mcp
image: "{{ .Values.mcp.sidecars.helm.image.repository }}:{{ .Values.mcp.sidecars.helm.image.tag }}"
args:
- -mode=sse
ports:
- containerPort: {{ .Values.mcp.sidecars.helm.port }}
name: helm-mcp
protocol: TCP
livenessProbe:
tcpSocket:
port: {{ .Values.mcp.sidecars.helm.port }}
initialDelaySeconds: 10
periodSeconds: 10
readinessProbe:
tcpSocket:
port: {{ .Values.mcp.sidecars.helm.port }}
initialDelaySeconds: 5
periodSeconds: 5
resources:
{{- toYaml .Values.mcp.sidecars.helm.resources | nindent 12 }}
{{- end }}
{{- if .Values.mcp.sidecars.homeassistant.enabled }}
- name: homeassistant-mcp - name: homeassistant-mcp
image: "{{ .Values.mcpSidecars.homeassistant.image.repository }}:{{ .Values.mcpSidecars.homeassistant.image.tag }}" image: "{{ .Values.mcp.sidecars.homeassistant.image.repository }}:{{ .Values.mcp.sidecars.homeassistant.image.tag }}"
imagePullPolicy: Always imagePullPolicy: IfNotPresent
command: ["fastmcp", "run", "fastmcp-sse.json"] command: ["fastmcp", "run", "--transport", "sse", "--host", "0.0.0.0", "--port", "{{ .Values.mcp.sidecars.homeassistant.port }}"]
ports: ports:
- name: homeassistant - name: homeassistant
containerPort: {{ .Values.mcpSidecars.homeassistant.port }} containerPort: {{ .Values.mcp.sidecars.homeassistant.port }}
env: env:
- name: HOMEASSISTANT_URL - name: HOMEASSISTANT_URL
valueFrom: valueFrom:
secretKeyRef: secretKeyRef:
name: {{ include "antigravity.envSecretName" . }} name: {{ include "devcontainer.envSecretName" . }}
key: homeassistant-url key: HOMEASSISTANT_URL
optional: true optional: true
- name: HOMEASSISTANT_TOKEN - name: HOMEASSISTANT_TOKEN
valueFrom: valueFrom:
secretKeyRef: secretKeyRef:
name: {{ include "antigravity.envSecretName" . }} name: {{ include "devcontainer.envSecretName" . }}
key: homeassistant-token key: HOMEASSISTANT_TOKEN
optional: true optional: true
livenessProbe: livenessProbe:
tcpSocket: tcpSocket:
port: {{ .Values.mcpSidecars.homeassistant.port }} port: {{ .Values.mcp.sidecars.homeassistant.port }}
initialDelaySeconds: 10 initialDelaySeconds: 10
periodSeconds: 10 periodSeconds: 10
readinessProbe: readinessProbe:
tcpSocket: tcpSocket:
port: {{ .Values.mcpSidecars.homeassistant.port }} port: {{ .Values.mcp.sidecars.homeassistant.port }}
initialDelaySeconds: 5 initialDelaySeconds: 5
periodSeconds: 5 periodSeconds: 5
resources: resources:
{{- toYaml .Values.mcpSidecars.homeassistant.resources | nindent 12 }} {{- toYaml .Values.mcp.sidecars.homeassistant.resources | nindent 12 }}
{{- end }}
{{- if .Values.mcp.sidecars.pgtuner.enabled }}
- name: pgtuner-mcp
image: "{{ .Values.mcp.sidecars.pgtuner.image.repository }}:{{ .Values.mcp.sidecars.pgtuner.image.tag }}"
imagePullPolicy: Always # pgtuner uses `latest` tag (no versioned releases available)
command: ["python", "-m", "pgtuner_mcp", "--mode", "sse", "--host", "0.0.0.0", "--port", "{{ .Values.mcp.sidecars.pgtuner.port }}"]
ports:
- name: pgtuner
containerPort: {{ .Values.mcp.sidecars.pgtuner.port }}
env:
- name: DATABASE_URI
valueFrom:
secretKeyRef:
name: {{ include "devcontainer.envSecretName" . }}
key: DATABASE_URI
optional: true
- name: PGTUNER_EXCLUDE_USERIDS
valueFrom:
secretKeyRef:
name: {{ include "devcontainer.envSecretName" . }}
key: PGTUNER_EXCLUDE_USERIDS
optional: true
livenessProbe:
tcpSocket:
port: {{ .Values.mcp.sidecars.pgtuner.port }}
initialDelaySeconds: 10
periodSeconds: 10
readinessProbe:
tcpSocket:
port: {{ .Values.mcp.sidecars.pgtuner.port }}
initialDelaySeconds: 5
periodSeconds: 5
resources:
{{- toYaml .Values.mcp.sidecars.pgtuner.resources | nindent 12 }}
{{- end }}
{{- if .Values.mcp.sidecars.playwright.enabled }}
- name: playwright-mcp
image: "{{ .Values.mcp.sidecars.playwright.image.repository }}:{{ .Values.mcp.sidecars.playwright.image.tag }}"
imagePullPolicy: IfNotPresent
command: ["node"]
args:
- cli.js
- --headless
- --browser
- chromium
- --no-sandbox
- --host
- 0.0.0.0
- --port
- {{ .Values.mcp.sidecars.playwright.port | quote }}
ports:
- name: playwright
containerPort: {{ .Values.mcp.sidecars.playwright.port }}
livenessProbe:
tcpSocket:
port: {{ .Values.mcp.sidecars.playwright.port }}
initialDelaySeconds: 15
periodSeconds: 10
readinessProbe:
tcpSocket:
port: {{ .Values.mcp.sidecars.playwright.port }}
initialDelaySeconds: 10
periodSeconds: 5
resources:
{{- toYaml .Values.mcp.sidecars.playwright.resources | nindent 12 }}
securityContext:
runAsUser: 1000
runAsGroup: 1000
{{- end }} {{- end }}
volumes: volumes:
- name: workspace - name: workspace
@@ -190,4 +304,5 @@ spec:
sizeLimit: {{ .Values.shm.sizeLimit }} sizeLimit: {{ .Values.shm.sizeLimit }}
- name: userhome - name: userhome
persistentVolumeClaim: 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 }}
+98
View File
@@ -0,0 +1,98 @@
{{- 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
# 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 apiVersion: v1
kind: PersistentVolumeClaim kind: PersistentVolumeClaim
metadata: metadata:
name: {{ include "antigravity.pvcName" . }} name: {{ include "devcontainer.pvcName" . }}
annotations:
helm.sh/resource-policy: keep
labels: labels:
{{- include "antigravity.labels" . | nindent 4 }} {{- include "devcontainer.labels" . | nindent 4 }}
spec: spec:
accessModes: accessModes:
- ReadWriteMany - ReadWriteMany
{{- if .Values.storage.className }}
storageClassName: {{ .Values.storage.className }} storageClassName: {{ .Values.storage.className }}
{{- end }}
resources: resources:
requests: requests:
storage: {{ .Values.storage.size }} storage: {{ .Values.storage.size }}
{{- end }}
+4 -2
View File
@@ -1,7 +1,8 @@
{{- if eq .Values.deploymentMode "persistent" }}
{{- $access := .Values.clusterAccess | default "none" }} {{- $access := .Values.clusterAccess | default "none" }}
{{- $name := include "antigravity.fullname" . }} {{- $name := include "devcontainer.fullname" . }}
{{- $ns := .Release.Namespace }} {{- $ns := .Release.Namespace }}
{{- $labels := include "antigravity.labels" . }} {{- $labels := include "devcontainer.labels" . }}
{{- if ne $access "none" }} {{- if ne $access "none" }}
--- ---
@@ -95,3 +96,4 @@ roleRef:
{{- end }} {{- end }}
{{- 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 apiVersion: v1
kind: Service kind: Service
metadata: metadata:
name: {{ include "antigravity.fullname" . }} name: {{ include "devcontainer.fullname" . }}
labels: labels:
{{- include "antigravity.labels" . | nindent 4 }} {{- include "devcontainer.labels" . | nindent 4 }}
spec: spec:
ports: ports:
{{- if ne (.Values.ide | default "vscode") "none" }} {{- if ne (.Values.ide.type | default "vscode") "none" }}
- port: 5800 - port: 5800
name: vnc-web name: vnc-web
protocol: TCP protocol: TCP
targetPort: vnc-web targetPort: vnc-web
{{- end }} {{- end }}
{{- if .Values.ssh }} {{- if .Values.ssh.enabled }}
- port: 22 - port: 22
name: ssh name: ssh
protocol: TCP protocol: TCP
targetPort: ssh targetPort: ssh
{{- end }} {{- end }}
selector: selector:
{{- include "antigravity.labels" . | nindent 4 }} {{- include "devcontainer.labels" . | nindent 4 }}
{{- end }}
+115
View File
@@ -0,0 +1,115 @@
# 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
# 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'
+331
View File
@@ -0,0 +1,331 @@
{
"$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"
},
"mcp": {
"type": "object",
"properties": {
"sidecars": {
"type": "object",
"properties": {
"kubernetes": {
"$ref": "#/$defs/mcpSidecar"
},
"flux": {
"$ref": "#/$defs/mcpSidecar"
},
"homeassistant": {
"$ref": "#/$defs/mcpSidecar"
},
"pgtuner": {
"$ref": "#/$defs/mcpSidecar"
},
"helm": {
"$ref": "#/$defs/mcpSidecar"
},
"playwright": {
"$ref": "#/$defs/mcpSidecar"
}
},
"additionalProperties": false
}
},
"required": ["sidecars"]
},
"envSecretName": {
"type": "string",
"description": "Custom environment secret name"
},
"resourceProfile": {
"type": "string",
"enum": ["auto", "small", "medium", "large", "xlarge"],
"description": "Resource profile preset"
}
},
"required": ["name"],
"$defs": {
"resourceSpec": {
"type": "object",
"properties": {
"memory": {
"type": "string",
"pattern": "^[0-9]+[KMGT]i$",
"description": "Memory resource specification"
},
"cpu": {
"type": "string",
"pattern": "^[0-9]+m?$",
"description": "CPU resource specification"
}
},
"required": ["memory", "cpu"]
},
"mcpSidecar": {
"type": "object",
"properties": {
"enabled": {
"type": "boolean",
"description": "Enable this MCP sidecar"
},
"image": {
"type": "object",
"properties": {
"repository": {
"type": "string"
},
"tag": {
"type": "string"
}
},
"required": ["repository", "tag"]
},
"port": {
"type": "integer",
"minimum": 1,
"maximum": 65535,
"description": "Port for the MCP sidecar"
},
"resources": {
"type": "object",
"properties": {
"requests": {
"$ref": "#/$defs/resourceSpec"
},
"limits": {
"$ref": "#/$defs/resourceSpec"
}
},
"required": ["requests", "limits"]
}
},
"required": ["enabled", "image", "port", "resources"]
}
}
}
+219 -68
View File
@@ -1,51 +1,68 @@
# =============================================================================
# BASIC CONFIGURATION
# =============================================================================
# Instance name — used to generate resource names (devcontainer-{name}, userhome-{name}) # Instance name — used to generate resource names (devcontainer-{name}, userhome-{name})
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: image:
repository: ghcr.io/cpfarhood/devcontainer repository: ghcr.io/cpfarhood/devcontainer
tag: latest tag: latest
pullPolicy: Always pullPolicy: Always
# GitHub repository to clone into /workspace # GitHub repository to clone into /workspace (ignored in dynamic mode - uses URL routing)
githubRepo: "" githubRepo: ""
# IDE to launch inside the container. # =============================================================================
# Options: # ACCESS & INTERFACE
# 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
# Start an OpenSSH server on port 22 in addition to the IDE. # IDE configuration
# Set SSH_AUTHORIZED_KEYS in the env secret to allow key-based login. ide:
ssh: false # Options: vscode | antigravity | none
type: vscode
# Happy Coder endpoints # SSH access configuration
happyServerUrl: "https://happy.farh.net" ssh:
happyWebappUrl: "https://happy-coder.farh.net" enabled: false
happyHomeDir: "/config/userdata/.happy"
happyExperimental: "true"
# 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: display:
width: "1920" width: "1920"
height: "1080" height: "1080"
secureConnection: "0" # Set to "1" when TLS is not terminated upstream
# Set to "0" when TLS is terminated at the gateway layer # User configuration
secureConnection: "0" user:
id: "1000"
groupId: "1000"
userId: "1000" # =============================================================================
groupId: "1000" # INFRASTRUCTURE & RESOURCES
# =============================================================================
# Storage configuration
storage: storage:
size: 32Gi size: 32Gi
className: ceph-filesystem className: "" # Empty string uses the cluster's default StorageClass (must support ReadWriteMany)
# 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
# Resource allocation
resources: resources:
requests: requests:
memory: "2Gi" memory: "2Gi"
@@ -54,57 +71,191 @@ resources:
memory: "8Gi" memory: "8Gi"
cpu: "4000m" cpu: "4000m"
# Kubernetes cluster access granted to the devcontainer pod via RBAC. # Shared memory for Electron apps (Chrome, Antigravity)
# Options: shm:
# none — no cluster access (default) sizeLimit: 2Gi
# readonlyns — get/list/watch all resources in the release namespace
# readwritens — full access to all resources in the release namespace # Kubernetes cluster access via RBAC
# readonly — get/list/watch all resources cluster-wide # Options: none | readonlyns | readwritens | readonly | readwrite
# readwrite — full access to all resources cluster-wide
clusterAccess: none clusterAccess: none
# Name of existing Secret containing env vars (GITHUB_TOKEN, VNC_PASSWORD, etc.) # =============================================================================
# Defaults to: devcontainer-{name}-secrets-env # INTEGRATIONS
envSecretName: "" # =============================================================================
# MCP server sidecars — run alongside the devcontainer to inherit pod RBAC. # MCP (Model Context Protocol) server sidecars
mcpSidecars: mcp:
kubernetes: sidecars:
# Kubernetes API access
kubernetes:
enabled: true
image:
repository: quay.io/containers/kubernetes_mcp_server
tag: v0.0.57
port: 8080
resources:
requests:
memory: "64Mi"
cpu: "50m"
limits:
memory: "256Mi"
cpu: "500m"
# Flux GitOps operations
flux:
enabled: true
image:
repository: ghcr.io/controlplaneio-fluxcd/flux-operator-mcp
tag: v0.41.1
port: 8081
resources:
requests:
memory: "64Mi"
cpu: "50m"
limits:
memory: "256Mi"
cpu: "500m"
# Helm chart browsing and management
helm:
enabled: true
image:
repository: ghcr.io/zekker6/mcp-helm
tag: v1.3.1
port: 8012
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: "6.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 enabled: true
replicas: 2 # High availability
image: image:
repository: quay.io/containers/kubernetes_mcp_server repository: ghcr.io/cpfarhood/devcontainer-routing-proxy
tag: v0.0.57 # Pinned version (Jan 27, 2025) with token exchange and field selector support tag: latest
port: 8080 pullPolicy: Always
resources: resources:
requests: requests:
memory: "64Mi" memory: "64Mi"
cpu: "50m" cpu: "100m"
limits: limits:
memory: "256Mi" memory: "256Mi"
cpu: "500m" cpu: "500m"
flux:
# Ingress configuration for dynamic mode
ingress:
enabled: true enabled: true
image: className: nginx
repository: ghcr.io/controlplaneio-fluxcd/flux-operator-mcp host: "" # Set this to your domain (e.g., devcontainer.farh.net)
tag: v0.41.1
port: 8081 # TLS configuration
resources: tls:
requests: enabled: true
memory: "64Mi" secretName: "" # Auto-generated if empty
cpu: "50m" issuer: "letsencrypt-prod" # cert-manager ClusterIssuer
limits:
memory: "256Mi" # Authentik forward auth configuration
cpu: "500m" authentik:
homeassistant: enabled: false # Set to true when Authentik is configured
enabled: false # Disabled by default, requires HOMEASSISTANT_URL and HOMEASSISTANT_TOKEN authUrl: "http://authentik.authentik.svc.cluster.local/outpost.goauthentik.io/auth/nginx"
image: signIn: "https://auth.example.com/outpost.goauthentik.io/start?rd=$escaped_request_uri"
repository: ghcr.io/homeassistant-ai/ha-mcp
tag: v6.7.1 # Pinned version (Feb 20, 2026) - latest stable release # =============================================================================
port: 8087 # ADVANCED CONFIGURATION
resources: # =============================================================================
requests:
memory: "64Mi" # Custom env secret name (defaults to: devcontainer-{name}-secrets-env)
cpu: "50m" envSecretName: ""
limits:
memory: "256Mi"
cpu: "500m"
+1 -1
View File
@@ -16,7 +16,7 @@
## MCP Sidecars ## MCP Sidecars
- **Kubernetes MCP** (v0.0.57, port 8080): Only deployed when enabled AND `clusterAccess` != `none` - **Kubernetes MCP** (v0.0.57, port 8080): Only deployed when enabled AND `clusterAccess` != `none`
- **Flux MCP** (v0.41.1, port 8081): Only deployed when enabled AND `clusterAccess` != `none` - **Flux MCP** (v0.41.1, port 8081): Only deployed when enabled AND `clusterAccess` != `none`
- **Home Assistant MCP** (v6.7.1, port 8087): Disabled by default, requires secrets: - **Home Assistant MCP** (6.7.1, port 8087): Disabled by default, requires secrets:
- `homeassistant-url`: Base URL like `http://homeassistant.local:8123` - `homeassistant-url`: Base URL like `http://homeassistant.local:8123`
- `homeassistant-token`: Long-lived access token - `homeassistant-token`: Long-lived access token
- **Playwright MCP**: External service, not a sidecar - **Playwright MCP**: External service, not a sidecar
+6 -2
View File
@@ -2,5 +2,9 @@
# Fix the app user (UID 1000) created by baseimage-gui at runtime. # Fix the app user (UID 1000) created by baseimage-gui at runtime.
# baseimage-gui sets shell=/sbin/nologin and home=/dev/null, which # baseimage-gui sets shell=/sbin/nologin and home=/dev/null, which
# prevents VSCode from opening terminals. # prevents VSCode from opening terminals.
usermod -s /bin/bash app if id app >/dev/null 2>&1; then
usermod -d /config/userdata app usermod -s /bin/bash app
usermod -d /config/userdata app
else
echo "WARNING: 'app' user not found, skipping usermod" >&2
fi
+9 -13
View File
@@ -1,5 +1,5 @@
#!/bin/bash #!/bin/bash
# Initialize repository and start Happy Coder # Initialize repository
set -e set -e
echo "=== Repository Initialization ===" echo "=== Repository Initialization ==="
@@ -22,6 +22,7 @@ if [ -n "$GITHUB_TOKEN" ]; then
# Create or update the credentials file # Create or update the credentials file
CREDENTIALS_FILE="/config/userdata/.git-credentials" CREDENTIALS_FILE="/config/userdata/.git-credentials"
mkdir -p "$(dirname "$CREDENTIALS_FILE")"
# Support multiple git hosting providers # Support multiple git hosting providers
# GitHub supports both oauth2 and token as username # GitHub supports both oauth2 and token as username
@@ -51,6 +52,7 @@ else
# Create an empty credentials file with proper permissions # Create an empty credentials file with proper permissions
CREDENTIALS_FILE="/config/userdata/.git-credentials" CREDENTIALS_FILE="/config/userdata/.git-credentials"
mkdir -p "$(dirname "$CREDENTIALS_FILE")"
touch "$CREDENTIALS_FILE" touch "$CREDENTIALS_FILE"
chmod 600 "$CREDENTIALS_FILE" chmod 600 "$CREDENTIALS_FILE"
@@ -102,18 +104,12 @@ chown -R "$RUN_UID:$RUN_GID" "$WORKSPACE_DIR"
mkdir -p "$HOME" mkdir -p "$HOME"
chown "$RUN_UID:$RUN_GID" "$HOME" chown "$RUN_UID:$RUN_GID" "$HOME"
# Start Happy Coder daemon. startapp.sh already runs as the app user (UID 1000), # Seed Claude Code settings if missing (disable auto-updater in Docker)
# so no sudo needed — Happy/Claude Code will find credentials in the correct home dir. if [ ! -f "$HOME/.claude/settings.json" ]; then
echo "Starting Happy Coder..." mkdir -p "$HOME/.claude"
echo '{"env":{"DISABLE_AUTOUPDATER":"1"}}' > "$HOME/.claude/settings.json"
# Remove stale lock file. HAPPY_HOME_DIR lives on the home PVC so it survives chown -R "$RUN_UID:$RUN_GID" "$HOME/.claude"
# pod restarts — without this cleanup the daemon refuses to start after a crash. fi
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 # Export workspace directory for startapp.sh
echo "$WORKSPACE_DIR" > /tmp/workspace-dir echo "$WORKSPACE_DIR" > /tmp/workspace-dir
+11 -2
View File
@@ -2,9 +2,15 @@
# Start application script for baseimage-gui # Start application script for baseimage-gui
set -e 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 /usr/local/bin/init-repo
# Get workspace directory # Get workspace directory
@@ -34,6 +40,9 @@ case "$IDE" in
exec sleep infinity exec sleep infinity
;; ;;
*) *)
if [ "$IDE" != "vscode" ]; then
echo "WARNING: Unknown IDE value '$IDE', defaulting to VSCode"
fi
echo "Opening VSCode in: $WORKSPACE_DIR" echo "Opening VSCode in: $WORKSPACE_DIR"
exec code --new-window --wait "$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
+243
View File
@@ -0,0 +1,243 @@
---
# 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"
# 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
+107
View File
@@ -0,0 +1,107 @@
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"
# 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