Compare commits

..

18 Commits

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

Validated: helm template renders cleanly for both modes.

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

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

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

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

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

This enables immediate testing of serverless features without manual builds.

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Also adds resourceProfile to the schema as it was missing.

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

Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: Happy <yesreply@happy.engineering>
2026-02-23 18:57:41 -05:00
35 changed files with 2206 additions and 116 deletions
+6
View File
@@ -0,0 +1,6 @@
{
"enabledPlugins": {
"voltagent-dev-exp@voltagent-subagents": true,
"voltagent-lang@voltagent-subagents": true
}
}
+9 -2
View File
@@ -4,6 +4,13 @@
"flux", "flux",
"playwright", "playwright",
"github", "github",
"pgtuner" "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)"
]
}
} }
+48
View File
@@ -4,6 +4,7 @@ on:
push: push:
branches: branches:
- main - main
- 'feature/serverless-*' # Build development images for serverless features
pull_request: pull_request:
branches: branches:
- main - main
@@ -49,6 +50,8 @@ jobs:
type=ref,event=pr type=ref,event=pr
type=sha,prefix=sha- type=sha,prefix=sha-
type=raw,value=latest,enable={{is_default_branch}} type=raw,value=latest,enable={{is_default_branch}}
# Development tags for serverless features
type=raw,value=2.0.0-dev,enable=${{ github.ref == 'refs/heads/feature/serverless-2.0.0' }}
- name: Build and push Docker image - name: Build and push Docker image
uses: docker/build-push-action@v6 uses: docker/build-push-action@v6
@@ -60,3 +63,48 @@ jobs:
cache-from: type=gha cache-from: type=gha
cache-to: type=gha,mode=max cache-to: type=gha,mode=max
platforms: linux/amd64 platforms: linux/amd64
# Build routing proxy image for serverless features
build-routing-proxy:
runs-on: ubuntu-latest
# Only build routing proxy for serverless feature branches
if: github.ref == 'refs/heads/feature/serverless-2.0.0' && github.event_name != 'pull_request'
permissions:
contents: read
packages: write
id-token: write
steps:
- name: Checkout repository
uses: actions/checkout@v6
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Log in to GitHub Container Registry
uses: docker/login-action@v3
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Extract metadata for routing proxy
id: meta-proxy
uses: docker/metadata-action@v5
with:
images: ${{ env.REGISTRY }}/cpfarhood/devcontainer-routing-proxy
tags: |
type=raw,value=latest
type=raw,value=2.0.0-dev
type=sha,prefix=sha-
- name: Build and push routing proxy image
uses: docker/build-push-action@v6
with:
context: ./serverless/routing-proxy
push: true
tags: ${{ steps.meta-proxy.outputs.tags }}
labels: ${{ steps.meta-proxy.outputs.labels }}
cache-from: type=gha
cache-to: type=gha,mode=max
platforms: linux/amd64
+1 -1
View File
@@ -79,7 +79,7 @@ jobs:
run: | run: |
sed -i "s/^version: .*/version: ${{ steps.version.outputs.version }}/" chart/Chart.yaml sed -i "s/^version: .*/version: ${{ steps.version.outputs.version }}/" chart/Chart.yaml
git add chart/Chart.yaml git add chart/Chart.yaml
git commit -m "chore: release version ${{ steps.version.outputs.version }}" git diff --quiet --staged || git commit -m "chore: release version ${{ steps.version.outputs.version }}"
- name: Create and Push Tag - name: Create and Push Tag
run: | run: |
-8
View File
@@ -22,14 +22,6 @@
"pgtuner": { "pgtuner": {
"type": "sse", "type": "sse",
"url": "http://localhost:8085/sse" "url": "http://localhost:8085/sse"
},
"fetch": {
"type": "sse",
"url": "http://localhost:8082/sse"
},
"sequentialthinking": {
"type": "sse",
"url": "http://localhost:8083/sse"
} }
} }
} }
+5 -1
View File
@@ -7,6 +7,7 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co
The Dev Container 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
- Claude Code, Happy Coder, OpenCode, and Crush AI coding agents (terminal-based) - Claude Code, Happy Coder, OpenCode, and Crush AI coding agents (terminal-based)
- Built-in web file manager for uploading/downloading files (optional, via `fileManager.enabled`)
- Automatic GitHub repository cloning on startup - 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 - MCP (Model Context Protocol) sidecars for AI assistant integrations
@@ -68,7 +69,7 @@ Container start
| File | Purpose | | File | Purpose |
|------|---------| |------|---------|
| `Dockerfile` | Image definition — installs Chrome, Node.js, VSCode, Claude Code, Happy Coder, OpenCode, Crush; creates non-root user (UID 1000) | | `Dockerfile` | Image definition — installs Chrome, Node.js, VSCode, Helm, Claude Code, Happy Coder, OpenCode, Crush; creates non-root user (UID 1000) |
| `scripts/init-repo.sh` | Configures git credentials, clones GitHub repo | | `scripts/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 |
@@ -183,6 +184,9 @@ helm install my-devcontainer ./chart -f custom-values.yaml
- `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 - `HAPPY_SERVER_URL` / `HAPPY_WEBAPP_URL` — Custom Happy Coder endpoints
- `HAPPY_HOME_DIR` / `HAPPY_EXPERIMENTAL` - `HAPPY_HOME_DIR` / `HAPPY_EXPERIMENTAL`
- `WEB_FILE_MANAGER` — Set to `1` to enable the built-in web file manager (controlled via `fileManager.enabled` in Helm values)
- `WEB_FILE_MANAGER_ALLOWED_PATHS` — Paths accessible by the file manager (default: `/workspace,/config`)
- `WEB_FILE_MANAGER_DENIED_PATHS` — Paths to deny access to (takes precedence over allowed)
### CI/CD ### CI/CD
+14 -3
View File
@@ -72,9 +72,17 @@ RUN OPENCODE_VERSION=$(curl -sL https://api.github.com/repos/opencode-ai/opencod
# Install Crush AI coding agent (OpenCode successor by Charm) # 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//') && \ 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" | \ 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 -xz --strip-components=1 -C /usr/local/bin "crush_${CRUSH_VERSION}_Linux_x86_64/crush" && \ tar -xzf /tmp/crush.tar.gz -C /tmp && \
chmod +x /usr/local/bin/crush mv /tmp/crush_${CRUSH_VERSION}_Linux_x86_64/crush /usr/local/bin/crush && \
chmod +x /usr/local/bin/crush && \
rm -rf /tmp/crush*
# Install Helm CLI for Kubernetes chart management
ARG HELM_VERSION=3.17.1
RUN curl -fsSL "https://get.helm.sh/helm-v${HELM_VERSION}-linux-amd64.tar.gz" | \
tar -xz --strip-components=1 -C /usr/local/bin linux-amd64/helm && \
chmod +x /usr/local/bin/helm
# Install VSCode # Install VSCode
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 -o /usr/share/keyrings/packages.microsoft.gpg && \
@@ -131,6 +139,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
+7 -7
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)
@@ -29,15 +29,15 @@ run:
-e HAPPY_EXPERIMENTAL="true" \ -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 +81,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 +101,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)"
+21
View File
@@ -6,6 +6,8 @@ A containerized cloud development environment with web-based GUI access, featuri
- **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)
- **Claude Code**, **Happy Coder**, **OpenCode**, and **Crush** AI coding agents (terminal-based) - **Claude Code**, **Happy Coder**, **OpenCode**, and **Crush** AI coding agents (terminal-based)
- **Built-in web file manager** for uploading/downloading files via the VNC web interface
- **Helm CLI** included for Kubernetes chart development and deployment
- **Automatic GitHub repo cloning** on startup - **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
@@ -121,6 +123,7 @@ The Helm chart uses a logical organization with these main sections:
| `githubRepo` | `""` | Repository to clone into `/workspace` on startup | | `githubRepo` | `""` | Repository to clone into `/workspace` on startup |
| `ide.type` | `vscode` | IDE to launch — `vscode`, `antigravity`, or `none` (see below) | | `ide.type` | `vscode` | IDE to launch — `vscode`, `antigravity`, or `none` (see below) |
| `ssh.enabled` | `false` | Also start an OpenSSH server on port 22 (additive, any IDE) | | `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 |
@@ -161,6 +164,24 @@ kubectl port-forward deployment/devcontainer-mydev 2222:22
ssh -p 2222 user@localhost ssh -p 2222 user@localhost
``` ```
### Web file manager
The base image includes a built-in web file manager for uploading and downloading files through the VNC web interface (port 5800). No additional sidecar is needed.
| Value | Default | Description |
|-------|---------|-------------|
| `fileManager.enabled` | `false` | Enable the web file manager |
| `fileManager.allowedPaths` | `/workspace,/config` | Paths accessible by the file manager (`AUTO`, `ALL`, or comma-separated) |
| `fileManager.deniedPaths` | `""` | Paths to deny (takes precedence over allowed) |
```bash
# Enable the file manager
helm install mydev ./chart \
--set name=mydev \
--set githubRepo=https://github.com/youruser/yourrepo \
--set fileManager.enabled=true
```
### Happy Coder ### Happy Coder
| Value | Default | Description | | Value | Default | Description |
+9 -2
View File
@@ -1,6 +1,13 @@
apiVersion: v2 apiVersion: v2
name: devcontainer name: devcontainer
description: Dev Container with AI coding agents and MCP sidecars description: Dev Container with AI coding agents and MCP sidecars - supports persistent and dynamic deployment modes
type: application type: application
version: 0.4.7 version: 2.0.0-dev
appVersion: "latest" appVersion: "latest"
keywords:
- development
- devcontainer
- vscode
- ai
- knative
- serverless
+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 }}
+15
View File
@@ -2,6 +2,9 @@
Resource name prefix: devcontainer-{name} Resource name prefix: devcontainer-{name}
*/}} */}}
{{- define "devcontainer.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 }}
@@ -25,6 +28,18 @@ Common labels
{{- define "devcontainer.labels" -}} {{- define "devcontainer.labels" -}}
app: devcontainer app: devcontainer
instance: {{ .Values.name }} instance: {{ .Values.name }}
app.kubernetes.io/name: devcontainer
app.kubernetes.io/instance: {{ .Values.name }}
app.kubernetes.io/managed-by: {{ .Release.Service }}
helm.sh/chart: {{ .Chart.Name }}-{{ .Chart.Version | replace "+" "_" }}
{{- end }}
{{/*
Selector labels keep narrow since changing these requires recreating the Deployment
*/}}
{{- define "devcontainer.selectorLabels" -}}
app: devcontainer
instance: {{ .Values.name }}
{{- end }} {{- end }}
{{/* {{/*
+17 -47
View File
@@ -1,3 +1,4 @@
{{- if eq .Values.deploymentMode "persistent" }}
apiVersion: apps/v1 apiVersion: apps/v1
kind: Deployment kind: Deployment
metadata: metadata:
@@ -8,7 +9,7 @@ spec:
replicas: 1 replicas: 1
selector: selector:
matchLabels: matchLabels:
{{- include "devcontainer.labels" . | nindent 6 }} {{- include "devcontainer.selectorLabels" . | nindent 6 }}
template: template:
metadata: metadata:
labels: labels:
@@ -23,7 +24,7 @@ spec:
{{- if and .Values.ide.type (eq .Values.ide.type "antigravity") }} {{- if and .Values.ide.type (eq .Values.ide.type "antigravity") }}
initContainers: initContainers:
- name: setup-userdata - name: setup-userdata
image: busybox:latest image: busybox:1.37
command: ['sh', '-c'] command: ['sh', '-c']
args: args:
- | - |
@@ -69,6 +70,16 @@ spec:
value: {{ .Values.display.height | quote }} value: {{ .Values.display.height | quote }}
- name: SECURE_CONNECTION - name: SECURE_CONNECTION
value: {{ .Values.display.secureConnection | quote }} value: {{ .Values.display.secureConnection | quote }}
{{- if .Values.fileManager.enabled }}
- name: WEB_FILE_MANAGER
value: "1"
- name: WEB_FILE_MANAGER_ALLOWED_PATHS
value: {{ .Values.fileManager.allowedPaths | quote }}
{{- if .Values.fileManager.deniedPaths }}
- name: WEB_FILE_MANAGER_DENIED_PATHS
value: {{ .Values.fileManager.deniedPaths | quote }}
{{- end }}
{{- end }}
- name: HAPPY_HOME_DIR - name: HAPPY_HOME_DIR
value: {{ .Values.happy.homeDir | quote }} value: {{ .Values.happy.homeDir | quote }}
- name: HAPPY_EXPERIMENTAL - name: HAPPY_EXPERIMENTAL
@@ -166,52 +177,10 @@ spec:
resources: resources:
{{- toYaml .Values.mcp.sidecars.flux.resources | nindent 12 }} {{- toYaml .Values.mcp.sidecars.flux.resources | nindent 12 }}
{{- end }} {{- end }}
{{- if .Values.mcp.sidecars.fetch.enabled }}
- name: fetch-mcp
image: "{{ .Values.mcp.sidecars.fetch.image.repository }}:{{ .Values.mcp.sidecars.fetch.image.tag }}"
imagePullPolicy: Always
command: ["fastmcp", "run", "--transport", "sse", "--host", "0.0.0.0", "--port", "{{ .Values.mcp.sidecars.fetch.port }}"]
ports:
- name: fetch
containerPort: {{ .Values.mcp.sidecars.fetch.port }}
livenessProbe:
tcpSocket:
port: {{ .Values.mcp.sidecars.fetch.port }}
initialDelaySeconds: 10
periodSeconds: 10
readinessProbe:
tcpSocket:
port: {{ .Values.mcp.sidecars.fetch.port }}
initialDelaySeconds: 5
periodSeconds: 5
resources:
{{- toYaml .Values.mcp.sidecars.fetch.resources | nindent 12 }}
{{- end }}
{{- if .Values.mcp.sidecars.sequentialthinking.enabled }}
- name: sequentialthinking-mcp
image: "{{ .Values.mcp.sidecars.sequentialthinking.image.repository }}:{{ .Values.mcp.sidecars.sequentialthinking.image.tag }}"
imagePullPolicy: Always
command: ["fastmcp", "run", "--transport", "sse", "--host", "0.0.0.0", "--port", "{{ .Values.mcp.sidecars.sequentialthinking.port }}"]
ports:
- name: sequentialthinking
containerPort: {{ .Values.mcp.sidecars.sequentialthinking.port }}
livenessProbe:
tcpSocket:
port: {{ .Values.mcp.sidecars.sequentialthinking.port }}
initialDelaySeconds: 10
periodSeconds: 10
readinessProbe:
tcpSocket:
port: {{ .Values.mcp.sidecars.sequentialthinking.port }}
initialDelaySeconds: 5
periodSeconds: 5
resources:
{{- toYaml .Values.mcp.sidecars.sequentialthinking.resources | nindent 12 }}
{{- end }}
{{- if .Values.mcp.sidecars.homeassistant.enabled }} {{- if .Values.mcp.sidecars.homeassistant.enabled }}
- name: homeassistant-mcp - name: homeassistant-mcp
image: "{{ .Values.mcp.sidecars.homeassistant.image.repository }}:{{ .Values.mcp.sidecars.homeassistant.image.tag }}" image: "{{ .Values.mcp.sidecars.homeassistant.image.repository }}:{{ .Values.mcp.sidecars.homeassistant.image.tag }}"
imagePullPolicy: Always imagePullPolicy: IfNotPresent
command: ["fastmcp", "run", "--transport", "sse", "--host", "0.0.0.0", "--port", "{{ .Values.mcp.sidecars.homeassistant.port }}"] command: ["fastmcp", "run", "--transport", "sse", "--host", "0.0.0.0", "--port", "{{ .Values.mcp.sidecars.homeassistant.port }}"]
ports: ports:
- name: homeassistant - name: homeassistant
@@ -245,7 +214,7 @@ spec:
{{- if .Values.mcp.sidecars.pgtuner.enabled }} {{- if .Values.mcp.sidecars.pgtuner.enabled }}
- name: pgtuner-mcp - name: pgtuner-mcp
image: "{{ .Values.mcp.sidecars.pgtuner.image.repository }}:{{ .Values.mcp.sidecars.pgtuner.image.tag }}" image: "{{ .Values.mcp.sidecars.pgtuner.image.repository }}:{{ .Values.mcp.sidecars.pgtuner.image.tag }}"
imagePullPolicy: Always 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 }}"] command: ["python", "-m", "pgtuner_mcp", "--mode", "sse", "--host", "0.0.0.0", "--port", "{{ .Values.mcp.sidecars.pgtuner.port }}"]
ports: ports:
- name: pgtuner - name: pgtuner
@@ -279,7 +248,7 @@ spec:
{{- if .Values.mcp.sidecars.playwright.enabled }} {{- if .Values.mcp.sidecars.playwright.enabled }}
- name: playwright-mcp - name: playwright-mcp
image: "{{ .Values.mcp.sidecars.playwright.image.repository }}:{{ .Values.mcp.sidecars.playwright.image.tag }}" image: "{{ .Values.mcp.sidecars.playwright.image.repository }}:{{ .Values.mcp.sidecars.playwright.image.tag }}"
imagePullPolicy: Always imagePullPolicy: IfNotPresent
command: ["node"] command: ["node"]
args: args:
- cli.js - cli.js
@@ -320,3 +289,4 @@ spec:
- name: userhome - name: userhome
persistentVolumeClaim: persistentVolumeClaim:
claimName: {{ include "devcontainer.pvcName" . }} claimName: {{ include "devcontainer.pvcName" . }}
{{- end }}
+68
View File
@@ -0,0 +1,68 @@
{{- if and (eq .Values.deploymentMode "dynamic") .Values.dynamic.ingress.enabled .Values.dynamic.ingress.host }}
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: {{ include "devcontainer.fullname" . }}-dynamic
labels:
{{- include "devcontainer.labels" . | nindent 4 }}
app.kubernetes.io/component: dynamic-ingress
annotations:
{{- if .Values.dynamic.ingress.className }}
kubernetes.io/ingress.class: {{ .Values.dynamic.ingress.className }}
{{- end }}
# SSL configuration
{{- if .Values.dynamic.ingress.tls.enabled }}
cert-manager.io/cluster-issuer: {{ .Values.dynamic.ingress.tls.issuer | quote }}
nginx.ingress.kubernetes.io/ssl-redirect: "true"
nginx.ingress.kubernetes.io/force-ssl-redirect: "true"
{{- end }}
# Authentik forward auth (if enabled)
{{- if .Values.dynamic.ingress.authentik.enabled }}
nginx.ingress.kubernetes.io/auth-url: {{ .Values.dynamic.ingress.authentik.authUrl | quote }}
nginx.ingress.kubernetes.io/auth-signin: {{ .Values.dynamic.ingress.authentik.signIn | quote }}
nginx.ingress.kubernetes.io/auth-response-headers: "X-Authentik-Username,X-Authentik-Groups,X-Authentik-Email,X-Authentik-Name"
nginx.ingress.kubernetes.io/auth-snippet: |
proxy_set_header X-Forwarded-Host $http_host;
{{- end }}
# WebSocket support for VNC connections
nginx.ingress.kubernetes.io/proxy-read-timeout: "3600"
nginx.ingress.kubernetes.io/proxy-send-timeout: "3600"
# Large file upload support (for file manager)
nginx.ingress.kubernetes.io/client-max-body-size: "100m"
nginx.ingress.kubernetes.io/proxy-body-size: "100m"
# Custom server snippet for GitHub repo logging
nginx.ingress.kubernetes.io/server-snippet: |
location ~ ^/github/([^/]+/[^/]+) {
# Log the GitHub repo being accessed
access_log /var/log/nginx/devcontainer-access.log combined;
# Set additional headers for audit/monitoring
proxy_set_header X-GitHub-Repo-Requested https://github.com/$1;
proxy_set_header X-Request-Timestamp $time_iso8601;
proxy_set_header X-Client-IP $remote_addr;
}
spec:
{{- if .Values.dynamic.ingress.tls.enabled }}
tls:
- hosts:
- {{ .Values.dynamic.ingress.host }}
secretName: {{ .Values.dynamic.ingress.tls.secretName | default (printf "%s-tls" (include "devcontainer.fullname" .)) }}
{{- end }}
rules:
- host: {{ .Values.dynamic.ingress.host }}
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: {{ include "devcontainer.fullname" . }}-routing-proxy
port:
number: 80
{{- end }}
+111
View File
@@ -0,0 +1,111 @@
{{- if eq .Values.deploymentMode "dynamic" }}
apiVersion: serving.knative.dev/v1
kind: Service
metadata:
name: {{ include "devcontainer.fullname" . }}
labels:
{{- include "devcontainer.labels" . | nindent 4 }}
annotations:
# Knative scaling annotations
autoscaling.knative.dev/minScale: {{ .Values.dynamic.knative.minScale | quote }}
autoscaling.knative.dev/maxScale: {{ .Values.dynamic.knative.maxScale | quote }}
autoscaling.knative.dev/target: {{ .Values.dynamic.knative.target | quote }}
autoscaling.knative.dev/scale-to-zero-grace-period: {{ .Values.dynamic.knative.scaleToZeroGracePeriod | quote }}
spec:
template:
metadata:
labels:
{{- include "devcontainer.labels" . | nindent 8 }}
annotations:
# Container configuration
autoscaling.knative.dev/targetPort: "5800"
serving.knative.dev/timeoutSeconds: {{ .Values.dynamic.knative.timeoutSeconds | quote }}
# Scaling configuration
autoscaling.knative.dev/class: "kpa.autoscaling.knative.dev"
autoscaling.knative.dev/metric: "concurrency"
spec:
# Container startup timeout
timeoutSeconds: {{ .Values.dynamic.knative.timeoutSeconds }}
containers:
- name: devcontainer
image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}"
imagePullPolicy: {{ .Values.image.pullPolicy }}
ports:
- containerPort: 5800
name: vnc-web
env:
# Dynamic mode flags
- name: SERVERLESS_MODE
value: "true"
- name: DYNAMIC_GITHUB_ROUTING
value: "true"
- name: DEPLOYMENT_MODE
value: "dynamic"
# Standard configuration
- name: IDE
value: {{ .Values.ide.type | default "vscode" | quote }}
- name: USER_ID
value: {{ .Values.user.id | quote }}
- name: GROUP_ID
value: {{ .Values.user.groupId | quote }}
- name: DISPLAY_WIDTH
value: {{ .Values.display.width | quote }}
- name: DISPLAY_HEIGHT
value: {{ .Values.display.height | quote }}
- name: SECURE_CONNECTION
value: {{ .Values.display.secureConnection | quote }}
# File manager (always enabled in dynamic mode for easy file transfer)
- name: WEB_FILE_MANAGER
value: "1"
- name: WEB_FILE_MANAGER_ALLOWED_PATHS
value: "/workspace,/tmp" # No persistent /config in dynamic mode
# Happy Coder (ephemeral in dynamic mode)
- name: HAPPY_HOME_DIR
value: "/tmp/.happy"
- name: HAPPY_EXPERIMENTAL
value: {{ .Values.happy.experimental | quote }}
{{- if .Values.happy.serverUrl }}
- name: HAPPY_SERVER_URL
value: {{ .Values.happy.serverUrl | quote }}
{{- end }}
{{- if .Values.happy.webappUrl }}
- name: HAPPY_WEBAPP_URL
value: {{ .Values.happy.webappUrl | quote }}
{{- end }}
# Secret environment variables
envFrom:
- secretRef:
name: {{ include "devcontainer.envSecretName" . }}
optional: true
resources:
{{- toYaml .Values.dynamic.knative.resources | nindent 10 }}
volumeMounts:
- name: tmp-home
mountPath: /config
- name: shm
mountPath: /dev/shm
# Health probes (adjusted for dynamic mode startup time)
readinessProbe:
httpGet:
path: /
port: 5800
initialDelaySeconds: 60
periodSeconds: 10
timeoutSeconds: 5
failureThreshold: 10
livenessProbe:
httpGet:
path: /
port: 5800
initialDelaySeconds: 120
periodSeconds: 30
timeoutSeconds: 10
failureThreshold: 3
volumes:
- name: tmp-home
emptyDir: {} # Ephemeral - each instance gets fresh home
- name: shm
emptyDir:
medium: Memory
sizeLimit: {{ .Values.shm.sizeLimit }}
{{- end }}
+6
View File
@@ -1,13 +1,19 @@
{{- if eq .Values.deploymentMode "persistent" }}
apiVersion: v1 apiVersion: v1
kind: PersistentVolumeClaim kind: PersistentVolumeClaim
metadata: metadata:
name: {{ include "devcontainer.pvcName" . }} name: {{ include "devcontainer.pvcName" . }}
annotations:
helm.sh/resource-policy: keep
labels: labels:
{{- include "devcontainer.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 }}
+2
View File
@@ -1,3 +1,4 @@
{{- if eq .Values.deploymentMode "persistent" }}
{{- $access := .Values.clusterAccess | default "none" }} {{- $access := .Values.clusterAccess | default "none" }}
{{- $name := include "devcontainer.fullname" . }} {{- $name := include "devcontainer.fullname" . }}
{{- $ns := .Release.Namespace }} {{- $ns := .Release.Namespace }}
@@ -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 }}
+2
View File
@@ -1,3 +1,4 @@
{{- if eq .Values.deploymentMode "persistent" }}
apiVersion: v1 apiVersion: v1
kind: Service kind: Service
metadata: metadata:
@@ -20,3 +21,4 @@ spec:
{{- end }} {{- end }}
selector: selector:
{{- include "devcontainer.labels" . | nindent 4 }} {{- include "devcontainer.labels" . | nindent 4 }}
{{- end }}
+122
View File
@@ -0,0 +1,122 @@
# Example values for dynamic (serverless) deployment mode
# Copy this file and customize for your environment:
# cp values-dynamic.yaml my-dynamic-values.yaml
# =============================================================================
# BASIC CONFIGURATION
# =============================================================================
name: "mydev" # REQUIRED: Instance name
deploymentMode: dynamic # Use serverless/dynamic mode
# Container images
image:
repository: ghcr.io/cpfarhood/devcontainer
tag: "2.0.0-dev"
pullPolicy: Always
# githubRepo is ignored in dynamic mode - repos are specified via URL routing
# =============================================================================
# ACCESS & INTERFACE
# =============================================================================
ide:
type: vscode # vscode | antigravity | none
# SSH not supported in dynamic mode (ephemeral containers)
ssh:
enabled: false
# File manager automatically enabled in dynamic mode for file transfer
fileManager:
enabled: true
# =============================================================================
# DYNAMIC MODE CONFIGURATION
# =============================================================================
dynamic:
# Knative Service auto-scaling configuration
knative:
minScale: 0 # Scale to zero when not in use
maxScale: 10 # Maximum concurrent instances
target: 1 # Requests per instance (1 = perfect isolation)
scaleToZeroGracePeriod: "5m" # Keep instances warm for 5 minutes
timeoutSeconds: 600 # 10 minutes for repo cloning + IDE startup
# Resources per container instance
resources:
requests:
memory: "1Gi"
cpu: "500m"
limits:
memory: "4Gi"
cpu: "2000m"
# Routing proxy (extracts GitHub repo from URL path)
routingProxy:
enabled: true
replicas: 2 # High availability
image:
repository: ghcr.io/cpfarhood/devcontainer-routing-proxy
tag: latest
pullPolicy: Always
# Ingress configuration
ingress:
enabled: true
className: nginx
host: "devcontainer.example.com" # REQUIRED: Set your domain
# SSL with cert-manager
tls:
enabled: true
# secretName: "" # Auto-generated if empty
issuer: "letsencrypt-prod"
# Authentik forward auth (configure after Authentik setup)
authentik:
enabled: false # Set to true when ready
authUrl: "http://authentik.authentik.svc.cluster.local/outpost.goauthentik.io/auth/nginx"
signIn: "https://auth.example.com/outpost.goauthentik.io/start?rd=$escaped_request_uri"
# =============================================================================
# STANDARD CONFIGURATION (applies to both modes)
# =============================================================================
# Display settings
display:
width: "1920"
height: "1080"
secureConnection: "0"
# User configuration
user:
id: "1000"
groupId: "1000"
# Resource allocation (container shared memory)
shm:
sizeLimit: 2Gi
# Happy Coder (ephemeral in dynamic mode)
happy:
serverUrl: ""
webappUrl: ""
homeDir: "/tmp/.happy" # Ephemeral location in dynamic mode
experimental: "true"
# MCP sidecars are not supported in dynamic mode (Knative limitation)
mcp:
sidecars:
kubernetes:
enabled: false
flux:
enabled: false
homeassistant:
enabled: false
pgtuner:
enabled: false
playwright:
enabled: false
+102 -7
View File
@@ -4,6 +4,7 @@
"title": "Dev Container Helm Chart Values Schema", "title": "Dev Container Helm Chart Values Schema",
"description": "Schema for validating values.yaml in the Dev Container Helm chart", "description": "Schema for validating values.yaml in the Dev Container Helm chart",
"type": "object", "type": "object",
"additionalProperties": true,
"properties": { "properties": {
"name": { "name": {
"type": "string", "type": "string",
@@ -31,10 +32,101 @@
}, },
"required": ["repository", "tag"] "required": ["repository", "tag"]
}, },
"deploymentMode": {
"type": "string",
"enum": ["persistent", "dynamic"],
"description": "Deployment mode: persistent (PVC-based) or dynamic (Knative serverless)"
},
"githubRepo": { "githubRepo": {
"type": "string", "type": "string",
"description": "GitHub repository URL to clone", "description": "GitHub repository URL to clone (required in persistent mode, ignored in dynamic mode)"
"pattern": "^https://github\\.com/.+/.+$" },
"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": { "ide": {
"type": "object", "type": "object",
@@ -107,7 +199,7 @@
"description": "Storage class name (must support ReadWriteMany)" "description": "Storage class name (must support ReadWriteMany)"
} }
}, },
"required": ["size", "className"] "required": ["size"]
}, },
"resources": { "resources": {
"type": "object", "type": "object",
@@ -142,12 +234,10 @@
"properties": { "properties": {
"serverUrl": { "serverUrl": {
"type": "string", "type": "string",
"format": "uri",
"description": "Happy Coder server URL" "description": "Happy Coder server URL"
}, },
"webappUrl": { "webappUrl": {
"type": "string", "type": "string",
"format": "uri",
"description": "Happy Coder webapp URL" "description": "Happy Coder webapp URL"
}, },
"homeDir": { "homeDir": {
@@ -160,7 +250,7 @@
"description": "Enable experimental Happy features" "description": "Enable experimental Happy features"
} }
}, },
"required": ["serverUrl", "webappUrl", "homeDir", "experimental"] "required": ["homeDir", "experimental"]
}, },
"mcp": { "mcp": {
"type": "object", "type": "object",
@@ -192,6 +282,11 @@
"envSecretName": { "envSecretName": {
"type": "string", "type": "string",
"description": "Custom environment secret name" "description": "Custom environment secret name"
},
"resourceProfile": {
"type": "string",
"enum": ["auto", "small", "medium", "large", "xlarge"],
"description": "Resource profile preset"
} }
}, },
"required": ["name"], "required": ["name"],
@@ -253,4 +348,4 @@
"required": ["enabled", "image", "port", "resources"] "required": ["enabled", "image", "port", "resources"]
} }
} }
} }
+83 -35
View File
@@ -5,13 +5,18 @@
# 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 # Container image configuration
image: image:
repository: ghcr.io/cpfarhood/devcontainer repository: ghcr.io/cpfarhood/devcontainer
tag: latest tag: 2.0.0-dev
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: ""
# ============================================================================= # =============================================================================
@@ -27,6 +32,16 @@ ide:
ssh: ssh:
enabled: false enabled: false
# Web file manager — built-in upload/download via the VNC web interface (port 5800)
# Uses the base image's WEB_FILE_MANAGER feature (no extra sidecar needed)
fileManager:
enabled: false
# Paths the file manager can access (default: AUTO = mapped volumes)
# Options: AUTO | ALL | comma-separated list of paths
allowedPaths: "/workspace,/config"
# Paths to deny (takes precedence over allowedPaths)
deniedPaths: ""
# VNC display settings # VNC display settings
display: display:
width: "1920" width: "1920"
@@ -45,7 +60,7 @@ user:
# Storage configuration # Storage configuration
storage: storage:
size: 32Gi size: 32Gi
className: ceph-filesystem className: "" # Empty string uses the cluster's default StorageClass (must support ReadWriteMany)
# Resource allocation # Resource allocation
resources: resources:
@@ -70,8 +85,8 @@ clusterAccess: none
# Happy Coder AI assistant configuration # Happy Coder AI assistant configuration
happy: happy:
serverUrl: "https://happy.farh.net" serverUrl: ""
webappUrl: "https://happy-coder.farh.net" webappUrl: ""
homeDir: "/config/userdata/.happy" homeDir: "/config/userdata/.happy"
experimental: "true" experimental: "true"
@@ -108,42 +123,14 @@ mcp:
memory: "256Mi" memory: "256Mi"
cpu: "500m" cpu: "500m"
# Web content fetching capabilities
fetch:
enabled: true
image:
repository: mcp/fetch
tag: latest
port: 8082
resources:
requests:
memory: "64Mi"
cpu: "50m"
limits:
memory: "256Mi"
cpu: "500m"
# Sequential thinking and problem-solving
sequentialthinking:
enabled: true
image:
repository: mcp/sequentialthinking
tag: latest
port: 8083
resources:
requests:
memory: "64Mi"
cpu: "50m"
limits:
memory: "256Mi"
cpu: "500m"
# Home Assistant smart home control # Home Assistant smart home control
homeassistant: homeassistant:
enabled: false # Requires HOMEASSISTANT_URL and HOMEASSISTANT_TOKEN enabled: false # Requires HOMEASSISTANT_URL and HOMEASSISTANT_TOKEN
image: image:
repository: ghcr.io/homeassistant-ai/ha-mcp repository: ghcr.io/homeassistant-ai/ha-mcp
tag: stable tag: v6.7.1
port: 8087 port: 8087
resources: resources:
requests: requests:
@@ -173,7 +160,7 @@ mcp:
enabled: true enabled: true
image: image:
repository: mcr.microsoft.com/playwright/mcp repository: mcr.microsoft.com/playwright/mcp
tag: latest tag: v0.0.68
port: 8086 port: 8086
resources: resources:
requests: requests:
@@ -198,6 +185,67 @@ autoDetect:
# Override specific values above to customize # Override specific values above to customize
resourceProfile: auto # auto | small | medium | large | xlarge resourceProfile: auto # auto | small | medium | large | xlarge
# =============================================================================
# DYNAMIC MODE CONFIGURATION (deploymentMode: dynamic)
# =============================================================================
# Dynamic mode uses Knative Services and routing proxy for serverless operation
dynamic:
# Knative Service configuration
knative:
# Scaling configuration
minScale: 0 # Scale to zero when not in use
maxScale: 10 # Maximum number of concurrent instances
target: 1 # Requests per instance (isolation = 1 request per pod)
scaleToZeroGracePeriod: "5m" # Keep instances warm for 5 minutes
# Container startup timeout (repo cloning + IDE startup)
timeoutSeconds: 600 # 10 minutes
# Resource configuration (per instance)
resources:
requests:
memory: "1Gi"
cpu: "500m"
limits:
memory: "4Gi"
cpu: "2000m"
# Routing proxy configuration (extracts GitHub repo from URL)
routingProxy:
enabled: true
replicas: 2 # High availability
image:
repository: ghcr.io/cpfarhood/devcontainer-routing-proxy
tag: 2.0.0-dev
pullPolicy: Always
resources:
requests:
memory: "64Mi"
cpu: "100m"
limits:
memory: "256Mi"
cpu: "500m"
# Ingress configuration for dynamic mode
ingress:
enabled: true
className: nginx
host: "" # Set this to your domain (e.g., devcontainer.farh.net)
# TLS configuration
tls:
enabled: true
secretName: "" # Auto-generated if empty
issuer: "letsencrypt-prod" # cert-manager ClusterIssuer
# Authentik forward auth configuration
authentik:
enabled: false # Set to true when Authentik is configured
authUrl: "http://authentik.authentik.svc.cluster.local/outpost.goauthentik.io/auth/nginx"
signIn: "https://auth.example.com/outpost.goauthentik.io/start?rd=$escaped_request_uri"
# ============================================================================= # =============================================================================
# ADVANCED CONFIGURATION # ADVANCED CONFIGURATION
# ============================================================================= # =============================================================================
+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
+2
View File
@@ -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"
+10 -1
View File
@@ -4,7 +4,13 @@ set -e
echo "=== Starting Dev Container ===" echo "=== Starting Dev Container ==="
# Initialize repository # 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
+248
View File
@@ -0,0 +1,248 @@
---
# Namespace for serverless components
apiVersion: v1
kind: Namespace
metadata:
name: devcontainers
labels:
app.kubernetes.io/name: devcontainer
app.kubernetes.io/component: serverless
---
# Secret for GitHub tokens, VNC passwords, etc.
apiVersion: v1
kind: Secret
metadata:
name: devcontainer-serverless-secrets
namespace: devcontainers
type: Opaque
stringData:
# Update these values as needed
GITHUB_TOKEN: ""
VNC_PASSWORD: "changeme"
ANTHROPIC_API_KEY: ""
GIT_USER_NAME: "DevContainer User"
GIT_USER_EMAIL: "devcontainer@example.com"
---
# Routing proxy deployment (handles GitHub repo extraction)
apiVersion: apps/v1
kind: Deployment
metadata:
name: devcontainer-routing-proxy
namespace: devcontainers
labels:
app.kubernetes.io/name: devcontainer
app.kubernetes.io/component: routing-proxy
spec:
replicas: 2 # High availability
selector:
matchLabels:
app.kubernetes.io/name: devcontainer
app.kubernetes.io/component: routing-proxy
template:
metadata:
labels:
app.kubernetes.io/name: devcontainer
app.kubernetes.io/component: routing-proxy
spec:
containers:
- name: routing-proxy
image: ghcr.io/cpfarhood/devcontainer-routing-proxy:latest
ports:
- containerPort: 8080
name: http
env:
- name: DEVCONTAINER_SERVICE_URL
value: "devcontainer-serverless.devcontainers.svc.cluster.local"
resources:
requests:
memory: "64Mi"
cpu: "100m"
limits:
memory: "256Mi"
cpu: "500m"
livenessProbe:
httpGet:
path: /health
port: 8080
initialDelaySeconds: 5
periodSeconds: 10
readinessProbe:
httpGet:
path: /health
port: 8080
initialDelaySeconds: 2
periodSeconds: 5
---
# Service for routing proxy
apiVersion: v1
kind: Service
metadata:
name: devcontainer-routing-proxy
namespace: devcontainers
labels:
app.kubernetes.io/name: devcontainer
app.kubernetes.io/component: routing-proxy
spec:
type: ClusterIP
ports:
- port: 80
targetPort: 8080
name: http
selector:
app.kubernetes.io/name: devcontainer
app.kubernetes.io/component: routing-proxy
---
# Knative Service (auto-scaling devcontainer instances)
apiVersion: serving.knative.dev/v1
kind: Service
metadata:
name: devcontainer-serverless
namespace: devcontainers
annotations:
# Scale to zero when not in use (saves resources)
autoscaling.knative.dev/minScale: "0"
autoscaling.knative.dev/maxScale: "10"
# Keep instances warm for 5 minutes after last request
autoscaling.knative.dev/scale-to-zero-grace-period: "5m"
# Target 1 concurrent request per pod (ensures isolation)
autoscaling.knative.dev/target: "1"
# Custom domain (optional - configure after Authentik setup)
# serving.knative.dev/domain: "devcontainer.farh.net"
spec:
template:
metadata:
annotations:
# Container port for VNC web interface
autoscaling.knative.dev/targetPort: "5800"
# Timeout for cold starts (dev containers need time to initialize)
serving.knative.dev/timeoutSeconds: "600" # 10 minutes for repo cloning
# Resource allocation per instance
autoscaling.knative.dev/class: "kpa.autoscaling.knative.dev"
autoscaling.knative.dev/metric: "concurrency"
spec:
# Give containers more time to start (repo cloning + IDE launch)
timeoutSeconds: 600 # 10 minutes
containers:
- name: devcontainer
image: ghcr.io/cpfarhood/devcontainer:latest
ports:
- containerPort: 5800
name: vnc-web
env:
# Flag to indicate serverless mode
- name: SERVERLESS_MODE
value: "true"
- name: DYNAMIC_GITHUB_ROUTING
value: "true"
- name: IDE
value: "vscode"
- name: DISPLAY_WIDTH
value: "1920"
- name: DISPLAY_HEIGHT
value: "1080"
- name: SECURE_CONNECTION
value: "0"
- name: USER_ID
value: "1000"
- name: GROUP_ID
value: "1000"
# Enable file manager for easy upload/download
- name: WEB_FILE_MANAGER
value: "1"
- name: WEB_FILE_MANAGER_ALLOWED_PATHS
value: "/workspace,/config"
# Happy Coder config (ephemeral in serverless mode)
- name: HAPPY_HOME_DIR
value: "/tmp/.happy"
- name: HAPPY_EXPERIMENTAL
value: "true"
# Use secrets for sensitive data
envFrom:
- secretRef:
name: devcontainer-serverless-secrets
optional: false
resources:
requests:
memory: "1Gi"
cpu: "500m"
limits:
memory: "4Gi"
cpu: "2000m"
volumeMounts:
- name: tmp-home
mountPath: /config
- name: shm
mountPath: /dev/shm
# Readiness probe - VNC must be ready
readinessProbe:
httpGet:
path: /
port: 5800
initialDelaySeconds: 60
periodSeconds: 10
timeoutSeconds: 5
failureThreshold: 10
# Liveness probe - ensure container stays healthy
livenessProbe:
httpGet:
path: /
port: 5800
initialDelaySeconds: 120
periodSeconds: 30
timeoutSeconds: 10
failureThreshold: 3
volumes:
- name: tmp-home
emptyDir: {} # Ephemeral - each instance gets fresh home
- name: shm
emptyDir:
medium: Memory
sizeLimit: 2Gi
---
# Ingress for the routing proxy (will be secured by Authentik)
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: devcontainer-serverless-ingress
namespace: devcontainers
annotations:
# Authentik forward auth annotations
nginx.ingress.kubernetes.io/auth-url: http://authentik.authentik.svc.cluster.local/outpost.goauthentik.io/auth/nginx
nginx.ingress.kubernetes.io/auth-signin: https://auth.farh.net/outpost.goauthentik.io/start?rd=$escaped_request_uri
nginx.ingress.kubernetes.io/auth-response-headers: X-Authentik-Username,X-Authentik-Groups,X-Authentik-Email,X-Authentik-Name
nginx.ingress.kubernetes.io/auth-snippet: |
proxy_set_header X-Forwarded-Host $http_host;
# SSL and general settings
cert-manager.io/cluster-issuer: "letsencrypt-prod"
nginx.ingress.kubernetes.io/ssl-redirect: "true"
nginx.ingress.kubernetes.io/force-ssl-redirect: "true"
# WebSocket support for VNC
nginx.ingress.kubernetes.io/proxy-read-timeout: "3600"
nginx.ingress.kubernetes.io/proxy-send-timeout: "3600"
# Large file upload support (for file manager)
nginx.ingress.kubernetes.io/client-max-body-size: "100m"
nginx.ingress.kubernetes.io/proxy-body-size: "100m"
spec:
tls:
- hosts:
- devcontainer.farh.net
secretName: devcontainer-serverless-tls
rules:
- host: devcontainer.farh.net
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: devcontainer-routing-proxy
port:
number: 80
+112
View File
@@ -0,0 +1,112 @@
apiVersion: serving.knative.dev/v1
kind: Service
metadata:
name: devcontainer-serverless
namespace: devcontainers
annotations:
# Scale to zero when not in use (saves resources)
autoscaling.knative.dev/minScale: "0"
autoscaling.knative.dev/maxScale: "10"
# Keep instances warm for 5 minutes after last request
autoscaling.knative.dev/scale-to-zero-grace-period: "5m"
# Target 1 concurrent request per pod (ensures isolation)
autoscaling.knative.dev/target: "1"
spec:
template:
metadata:
annotations:
# Container port for VNC web interface
autoscaling.knative.dev/targetPort: "5800"
# Timeout for cold starts (dev containers need time to initialize)
serving.knative.dev/timeoutSeconds: "300"
spec:
# Give containers more time to start (repo cloning + IDE launch)
timeoutSeconds: 300
containers:
- name: devcontainer
image: ghcr.io/cpfarhood/devcontainer:latest
ports:
- containerPort: 5800
name: vnc-web
env:
# Dynamic repo extraction will be handled by a startup script
- name: DYNAMIC_GITHUB_ROUTING
value: "true"
- name: IDE
value: "vscode"
- name: DISPLAY_WIDTH
value: "1920"
- name: DISPLAY_HEIGHT
value: "1080"
- name: SECURE_CONNECTION
value: "0"
- name: USER_ID
value: "1000"
- name: GROUP_ID
value: "1000"
# Enable file manager for easy upload/download
- name: WEB_FILE_MANAGER
value: "1"
- name: WEB_FILE_MANAGER_ALLOWED_PATHS
value: "/workspace,/config"
# Happy Coder config
- name: HAPPY_HOME_DIR
value: "/config/userdata/.happy"
- name: HAPPY_EXPERIMENTAL
value: "true"
# Use secrets for sensitive data
envFrom:
- secretRef:
name: devcontainer-serverless-secrets
optional: true
resources:
requests:
memory: "1Gi"
cpu: "500m"
limits:
memory: "4Gi"
cpu: "2000m"
volumeMounts:
- name: userhome
mountPath: /config
- name: shm
mountPath: /dev/shm
# Readiness probe - VNC must be ready
readinessProbe:
httpGet:
path: /
port: 5800
initialDelaySeconds: 30
periodSeconds: 5
timeoutSeconds: 3
# Liveness probe - ensure container stays healthy
livenessProbe:
httpGet:
path: /
port: 5800
initialDelaySeconds: 60
periodSeconds: 10
timeoutSeconds: 5
volumes:
- name: userhome
emptyDir: {} # Ephemeral - each instance gets fresh home
- name: shm
emptyDir:
medium: Memory
sizeLimit: 2Gi
---
# Secret template for GitHub tokens, VNC passwords, etc.
apiVersion: v1
kind: Secret
metadata:
name: devcontainer-serverless-secrets
namespace: devcontainers
type: Opaque
data:
# Base64 encoded values - update as needed
# echo -n "your-github-token" | base64
GITHUB_TOKEN: ""
# echo -n "your-vnc-password" | base64
VNC_PASSWORD: ""
# echo -n "your-anthropic-key" | base64
ANTHROPIC_API_KEY: ""
+16
View File
@@ -0,0 +1,16 @@
# Lightweight routing proxy for dynamic GitHub repo routing
FROM nginx:1.27-alpine
# Install envsubst for template rendering
RUN apk add --no-cache gettext
# Copy nginx configuration template
COPY nginx.conf.template /etc/nginx/nginx.conf.template
COPY entrypoint.sh /entrypoint.sh
RUN chmod +x /entrypoint.sh
EXPOSE 8080
ENTRYPOINT ["/entrypoint.sh"]
CMD ["nginx", "-g", "daemon off;"]
+16
View File
@@ -0,0 +1,16 @@
#!/bin/sh
# Set default values for environment variables
DEVCONTAINER_SERVICE_URL=${DEVCONTAINER_SERVICE_URL:-"devcontainer-serverless.devcontainers.svc.cluster.local"}
# Create temp directories
mkdir -p /tmp/client_temp /tmp/proxy_temp /tmp/fastcgi_temp /tmp/uwsgi_temp /tmp/scgi_temp
# Substitute environment variables in nginx config
envsubst '$DEVCONTAINER_SERVICE_URL' < /etc/nginx/nginx.conf.template > /etc/nginx/nginx.conf
echo "Starting routing proxy..."
echo "Routing to: $DEVCONTAINER_SERVICE_URL"
# Start nginx
exec "$@"
@@ -0,0 +1,124 @@
worker_processes auto;
error_log /var/log/nginx/error.log warn;
pid /tmp/nginx.pid;
events {
worker_connections 1024;
}
http {
include /etc/nginx/mime.types;
default_type application/octet-stream;
# Logging format
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for" '
'repo="$github_repo" user="$authentik_user"';
access_log /var/log/nginx/access.log main;
# Basic settings
sendfile on;
tcp_nopush on;
tcp_nodelay on;
keepalive_timeout 65;
types_hash_max_size 2048;
client_max_body_size 100M; # Allow large file uploads via file manager
# Temp directories (writable in container)
client_body_temp_path /tmp/client_temp;
proxy_temp_path /tmp/proxy_temp;
fastcgi_temp_path /tmp/fastcgi_temp;
uwsgi_temp_path /tmp/uwsgi_temp;
scgi_temp_path /tmp/scgi_temp;
# Upstream Knative service (will be resolved by Knative networking)
upstream devcontainer_serverless {
server ${DEVCONTAINER_SERVICE_URL};
}
# Map to extract GitHub repo from URL path
map $request_uri $github_repo {
~^/github/([^/]+/[^/]+)(/.*)?$ https://github.com/$1;
default "";
}
# Extract Authentik user info from headers (set by Authentik forward auth)
map $http_x_authentik_username $authentik_user {
default $http_x_authentik_username;
}
server {
listen 8080;
server_name _;
# Health check endpoint
location /health {
access_log off;
return 200 "OK\n";
add_header Content-Type text/plain;
}
# GitHub repo routing
location ~ ^/github/([^/]+/[^/]+)(/.*)?$ {
# Validate the repo format
if ($github_repo = "") {
return 400 "Invalid GitHub repository format. Use: /github/owner/repo\n";
}
# Log the routing decision
access_log /var/log/nginx/routing.log main;
# Set headers for the devcontainer
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
# Custom headers for dynamic repo routing
proxy_set_header X-GitHub-Repo $github_repo;
proxy_set_header X-Authentik-User $authentik_user;
proxy_set_header X-Request-Path $request_uri;
# Preserve Authentik auth headers
proxy_set_header X-Authentik-Username $http_x_authentik_username;
proxy_set_header X-Authentik-Email $http_x_authentik_email;
proxy_set_header X-Authentik-Name $http_x_authentik_name;
proxy_set_header X-Authentik-Groups $http_x_authentik_groups;
# Proxy settings for long-running connections (VNC)
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $connection_upgrade;
proxy_read_timeout 86400; # 24 hours
proxy_send_timeout 86400;
proxy_connect_timeout 30;
# Buffer settings for file uploads
proxy_buffering off;
proxy_request_buffering off;
# Forward to the devcontainer
proxy_pass http://devcontainer_serverless$2;
}
# Root path - show available repositories or redirect to auth
location = / {
return 200 "DevContainer Serverless\nUsage: /github/{owner}/{repo}\nExample: /github/microsoft/vscode\n";
add_header Content-Type text/plain;
}
# Anything else
location / {
return 404 "Not found. Use /github/{owner}/{repo} to access repositories.\n";
add_header Content-Type text/plain;
}
}
# WebSocket upgrade handling
map $http_upgrade $connection_upgrade {
default upgrade;
'' close;
}
}
+124
View File
@@ -0,0 +1,124 @@
#!/bin/bash
# Dynamic GitHub repository initialization for serverless mode
# This script extracts the GitHub repo from HTTP headers set by the routing proxy
set -e
log() {
echo "[$(date '+%Y-%m-%d %H:%M:%S')] DYNAMIC-INIT: $*" >&2
}
log "Starting dynamic repository initialization..."
# In serverless mode, we expect the routing proxy to have set these environment variables
# from the HTTP headers. If running standalone, fallback to GITHUB_REPO env var.
if [[ "$SERVERLESS_MODE" == "true" ]]; then
log "Serverless mode detected"
# The routing proxy should have set these via HTTP headers -> env vars
# Check if we have the GitHub repo from the X-GitHub-Repo header
if [[ -n "$HTTP_X_GITHUB_REPO" ]]; then
GITHUB_REPO="$HTTP_X_GITHUB_REPO"
log "Using GitHub repo from header: $GITHUB_REPO"
elif [[ -n "$X_GITHUB_REPO" ]]; then
GITHUB_REPO="$X_GITHUB_REPO"
log "Using GitHub repo from X-GitHub-Repo: $GITHUB_REPO"
else
# Try to extract from a file written by an init container or sidecar
if [[ -f "/tmp/github-repo" ]]; then
GITHUB_REPO=$(cat /tmp/github-repo)
log "Using GitHub repo from file: $GITHUB_REPO"
else
log "ERROR: No GitHub repository specified in serverless mode"
log "Expected HTTP_X_GITHUB_REPO or X_GITHUB_REPO header from routing proxy"
exit 1
fi
fi
# Extract user info if available
if [[ -n "$HTTP_X_AUTHENTIK_USERNAME" ]]; then
export GIT_USER_NAME="${HTTP_X_AUTHENTIK_NAME:-$HTTP_X_AUTHENTIK_USERNAME}"
export GIT_USER_EMAIL="${HTTP_X_AUTHENTIK_EMAIL:-${HTTP_X_AUTHENTIK_USERNAME}@devcontainer.local}"
log "Using Authentik user: $GIT_USER_NAME <$GIT_USER_EMAIL>"
fi
else
log "Traditional mode - using GITHUB_REPO environment variable"
if [[ -z "$GITHUB_REPO" ]]; then
log "ERROR: GITHUB_REPO environment variable is required"
exit 1
fi
fi
# Validate the GitHub repo URL
if [[ ! "$GITHUB_REPO" =~ ^https://github\.com/[^/]+/[^/]+/?$ ]]; then
log "ERROR: Invalid GitHub repository URL: $GITHUB_REPO"
log "Expected format: https://github.com/owner/repo"
exit 1
fi
# Extract owner and repo name for workspace directory
REPO_OWNER=$(echo "$GITHUB_REPO" | sed 's|https://github.com/\([^/]*\)/.*|\1|')
REPO_NAME=$(echo "$GITHUB_REPO" | sed 's|https://github.com/[^/]*/\([^/]*\)/?|\1|')
WORKSPACE_DIR="/workspace/${REPO_OWNER}-${REPO_NAME}"
log "Repository: $GITHUB_REPO"
log "Owner: $REPO_OWNER"
log "Name: $REPO_NAME"
log "Workspace: $WORKSPACE_DIR"
# Configure git user (use defaults if not set via Authentik)
GIT_USER_NAME="${GIT_USER_NAME:-DevContainer User}"
GIT_USER_EMAIL="${GIT_USER_EMAIL:-devcontainer@example.com}"
log "Configuring git user: $GIT_USER_NAME <$GIT_USER_EMAIL>"
git config --global user.name "$GIT_USER_NAME"
git config --global user.email "$GIT_USER_EMAIL"
# Configure git credentials if GitHub token is available
if [[ -n "$GITHUB_TOKEN" ]]; then
log "Configuring GitHub credentials..."
git config --global credential.helper store
echo "https://oauth2:${GITHUB_TOKEN}@github.com" > ~/.git-credentials
chmod 600 ~/.git-credentials
else
log "No GitHub token provided - using public access only"
fi
# Create workspace directory
mkdir -p "$(dirname "$WORKSPACE_DIR")"
cd "$(dirname "$WORKSPACE_DIR")"
# Clone the repository
if [[ -d "$WORKSPACE_DIR" ]]; then
log "Repository directory exists, pulling latest changes..."
cd "$WORKSPACE_DIR"
git pull --ff-only || {
log "WARNING: Could not fast-forward, repository may have diverged"
log "Continuing with existing state..."
}
else
log "Cloning repository..."
git clone "$GITHUB_REPO" "$WORKSPACE_DIR" || {
log "ERROR: Failed to clone repository $GITHUB_REPO"
log "This may be a private repository or the URL may be incorrect"
exit 1
}
cd "$WORKSPACE_DIR"
fi
# Set the workspace directory for the IDE
export WORKSPACE_DIR
log "Repository initialization complete!"
log "Workspace directory: $WORKSPACE_DIR"
# Change to the workspace directory so the IDE opens in the right place
cd "$WORKSPACE_DIR"
# Export variables for the parent script
export GITHUB_REPO
export WORKSPACE_DIR
export REPO_OWNER
export REPO_NAME
+86
View File
@@ -0,0 +1,86 @@
#!/bin/bash
# Serverless-aware startup script for devcontainer
# This replaces the standard /startapp.sh when in serverless mode
set -e
log() {
echo "[$(date '+%Y-%m-%d %H:%M:%S')] SERVERLESS-START: $*" >&2
}
log "Starting serverless devcontainer..."
log "Mode: ${SERVERLESS_MODE:-traditional}"
log "IDE: ${IDE:-vscode}"
# Wait for HTTP headers to be available (in case of init container pattern)
# In Knative, the headers should be available immediately as env vars
sleep 2
# Check if we're in serverless mode with dynamic routing
if [[ "$SERVERLESS_MODE" == "true" && "$DYNAMIC_GITHUB_ROUTING" == "true" ]]; then
log "Dynamic GitHub routing enabled"
# In Knative, HTTP headers become environment variables with HTTP_ prefix
# But we also check for the unprefixed versions set by proxies
AVAILABLE_VARS=$(env | grep -E "(GITHUB|AUTHENTIK|X_)" | sort)
if [[ -n "$AVAILABLE_VARS" ]]; then
log "Available routing variables:"
echo "$AVAILABLE_VARS" | while read -r var; do
log " $var"
done
else
log "No routing variables found, checking for alternatives..."
# Check if there's a file with the repo info
if [[ -f "/tmp/github-repo" ]]; then
export GITHUB_REPO=$(cat /tmp/github-repo)
log "Found repo file: $GITHUB_REPO"
else
log "ERROR: No GitHub repository information available"
log "Expected routing headers or /tmp/github-repo file"
exit 1
fi
fi
# Use the dynamic initialization script
source /usr/local/bin/dynamic-init-repo
else
log "Using standard initialization..."
# Use the standard initialization
source /usr/local/bin/init-repo
fi
# At this point, WORKSPACE_DIR should be set by the init script
WORKSPACE_DIR="${WORKSPACE_DIR:-/workspace}"
log "Working directory: $WORKSPACE_DIR"
# Ensure we're in the workspace directory
cd "$WORKSPACE_DIR"
# Launch the appropriate IDE based on the IDE environment variable
case "${IDE:-vscode}" in
"vscode")
log "Starting VSCode..."
exec code --new-window --wait "$WORKSPACE_DIR"
;;
"antigravity")
log "Starting Antigravity..."
exec antigravity \
--no-sandbox \
--user-data-dir ~/.config/antigravity \
--disable-dev-shm-usage \
--disable-gpu \
--disable-features=VizDisplayCompositor \
--new-window \
"$WORKSPACE_DIR"
;;
"none")
log "No IDE requested, keeping container alive..."
exec sleep infinity
;;
*)
log "ERROR: Unknown IDE type: $IDE"
log "Valid options: vscode, antigravity, none"
exit 1
;;
esac