Compare commits

...

6 Commits

Author SHA1 Message Date
DevContainer User da40d57e07 fix: overhaul release pipeline — 5 issues resolved
1. version input now optional — auto-increment from release_type works
2. replaced deprecated actions/create-release@v1 with gh release create
3. race condition fixed — release commit uses [skip ci], removed fragile
   github.actor guard from build-and-push.yaml
4. simplified gh-pages publishing — uses clean temp dir + shallow clone
   instead of convoluted git worktree fallback
5. version parsing strips pre-release suffixes (e.g., 2.0.0-dev → 2.0.0)

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

Co-Authored-By: Claude <noreply@anthropic.com>
Co-Authored-By: Happy <yesreply@happy.engineering>
2026-02-25 13:53:58 +00:00
DevContainer User e99ec65cd9 docs: update all references from OCI registry to GitHub Pages Helm repo
Update CLAUDE.md, README.md, and workflows README to reference the new
GitHub Pages Helm repository at https://cpfarhood.github.io/devcontainer
instead of the old OCI registry at oci://ghcr.io/cpfarhood/charts.

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

Co-Authored-By: Claude <noreply@anthropic.com>
Co-Authored-By: Happy <yesreply@happy.engineering>
2026-02-25 13:38:03 +00:00
DevContainer User 38e481484e feat: switch Helm chart publishing from OCI registry to GitHub Pages
Replaces OCI push (oci://ghcr.io/cpfarhood/charts) with GitHub Pages
Helm repository at https://cpfarhood.github.io/devcontainer. The release
workflow now packages the chart, maintains an index.yaml on the gh-pages
branch, and auto-creates the branch on first run.

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

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

Co-Authored-By: Claude <noreply@anthropic.com>
Co-Authored-By: Happy <yesreply@happy.engineering>
2026-02-25 13:34:56 +00:00
DevContainer User 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
19 changed files with 248 additions and 100 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)"
]
}
} }
+3 -4
View File
@@ -15,9 +15,8 @@ Use this for all version releases:
- ✅ Updates chart version - ✅ Updates chart version
- ✅ Creates git tag - ✅ Creates git tag
- ✅ Builds Docker image with all proper tags - ✅ Builds Docker image with all proper tags
- ✅ Publishes Helm chart to GHCR - ✅ Publishes Helm chart to GitHub Pages (`https://cpfarhood.github.io/devcontainer`)
- ✅ Creates GitHub Release with changelog - ✅ Creates GitHub Release with changelog
- ✅ No more `[skip ci]` blocking builds!
### 2️⃣ For Quick Fixes → **Quick Fix Build** ### 2️⃣ For Quick Fixes → **Quick Fix Build**
Use this for emergency fixes without version changes: Use this for emergency fixes without version changes:
@@ -30,8 +29,8 @@ Use this for emergency fixes without version changes:
### 3️⃣ Automatic CI → **Build and Push** ### 3️⃣ Automatic CI → **Build and Push**
Runs automatically on: Runs automatically on:
- Pushes to `main` (builds and pushes; skipped for release commits via `[skip ci]`)
- Pull requests (builds but doesn't push) - Pull requests (builds but doesn't push)
- Tags starting with `v*` (builds and pushes)
- Manual trigger available - Manual trigger available
## Workflow Files ## Workflow Files
@@ -90,5 +89,5 @@ gh run watch
### After (Simple! 🎉) ### After (Simple! 🎉)
- **3 total workflows** (down from 6+) - **3 total workflows** (down from 6+)
- **1 button** for complete releases - **1 button** for complete releases
- **No more `[skip ci]`** blocking builds - Release builds its own Docker image — `[skip ci]` on the version commit prevents duplicate CI builds
- **Clear separation** of concerns - **Clear separation** of concerns
-3
View File
@@ -16,9 +16,6 @@ env:
jobs: jobs:
build-and-push: build-and-push:
runs-on: ubuntu-latest runs-on: ubuntu-latest
# Skip builds triggered by release-unified.yaml commits (github-actions[bot])
# to prevent racing with the release workflow's own Docker build
if: github.event_name == 'workflow_dispatch' || github.event_name == 'pull_request' || github.actor != 'github-actions[bot]'
permissions: permissions:
contents: read contents: read
packages: write packages: write
+76 -46
View File
@@ -4,11 +4,11 @@ on:
workflow_dispatch: workflow_dispatch:
inputs: inputs:
version: version:
description: 'Version to release (e.g., 0.1.25)' description: 'Explicit version (e.g., 1.2.3). Leave blank to auto-increment.'
required: true required: false
type: string type: string
release_type: release_type:
description: 'Release type' description: 'Release type (used when version is blank)'
required: true required: true
default: 'patch' default: 'patch'
type: choice type: choice
@@ -49,37 +49,34 @@ jobs:
- name: Determine Version - name: Determine Version
id: version id: version
run: | run: |
if [ "${{ github.event.inputs.version }}" != "" ]; then INPUT_VERSION="${{ github.event.inputs.version }}"
VERSION="${{ github.event.inputs.version }}" if [ -n "$INPUT_VERSION" ]; then
VERSION="$INPUT_VERSION"
else else
# Auto-determine next version based on release type # Auto-increment based on release_type
CURRENT=$(grep '^version:' chart/Chart.yaml | awk '{print $2}') CURRENT=$(grep '^version:' chart/Chart.yaml | awk '{print $2}')
MAJOR=$(echo $CURRENT | cut -d. -f1) # Strip any pre-release suffix (e.g., 2.0.0-dev -> 2.0.0)
MINOR=$(echo $CURRENT | cut -d. -f2) CURRENT=$(echo "$CURRENT" | sed 's/-.*//')
PATCH=$(echo $CURRENT | cut -d. -f3) MAJOR=$(echo "$CURRENT" | cut -d. -f1)
MINOR=$(echo "$CURRENT" | cut -d. -f2)
PATCH=$(echo "$CURRENT" | cut -d. -f3)
case "${{ github.event.inputs.release_type }}" in case "${{ github.event.inputs.release_type }}" in
major) major) VERSION="$((MAJOR + 1)).0.0" ;;
VERSION="$((MAJOR + 1)).0.0" minor) VERSION="${MAJOR}.$((MINOR + 1)).0" ;;
;; patch) VERSION="${MAJOR}.${MINOR}.$((PATCH + 1))" ;;
minor)
VERSION="${MAJOR}.$((MINOR + 1)).0"
;;
patch)
VERSION="${MAJOR}.${MINOR}.$((PATCH + 1))"
;;
esac esac
fi fi
echo "version=${VERSION}" >> $GITHUB_OUTPUT echo "version=${VERSION}" >> $GITHUB_OUTPUT
echo "tag=v${VERSION}" >> $GITHUB_OUTPUT echo "tag=v${VERSION}" >> $GITHUB_OUTPUT
echo "🚀 Releasing version ${VERSION}" echo "Releasing version ${VERSION}"
- name: Update Chart Version - name: Update Chart Version
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 diff --quiet --staged || git commit -m "chore: release version ${{ steps.version.outputs.version }}" git diff --quiet --staged || git commit -m "chore(release): ${{ steps.version.outputs.version }} [skip ci]"
- name: Create and Push Tag - name: Create and Push Tag
run: | run: |
@@ -107,27 +104,69 @@ jobs:
cache-to: type=gha,mode=max cache-to: type=gha,mode=max
platforms: linux/amd64 platforms: linux/amd64
- name: Package Helm Chart - name: Publish Helm Chart to GitHub Pages
run: | run: |
helm registry login ghcr.io \
--username ${{ github.actor }} \
--password ${{ secrets.GITHUB_TOKEN }}
helm package chart/ helm package chart/
helm push devcontainer-${{ steps.version.outputs.version }}.tgz oci://ghcr.io/cpfarhood/charts CHART_TGZ="devcontainer-${{ steps.version.outputs.version }}.tgz"
- name: Generate Release Notes # Set up gh-pages in a temporary directory
id: notes PAGES_DIR=$(mktemp -d)
if git ls-remote --heads origin gh-pages | grep -q gh-pages; then
# gh-pages exists — shallow clone just that branch
git clone --single-branch --branch gh-pages \
"https://x-access-token:${{ secrets.GITHUB_TOKEN }}@github.com/${{ github.repository }}.git" \
"$PAGES_DIR"
else
# First time — initialize gh-pages
git init "$PAGES_DIR"
git -C "$PAGES_DIR" checkout --orphan gh-pages
git -C "$PAGES_DIR" remote add origin \
"https://x-access-token:${{ secrets.GITHUB_TOKEN }}@github.com/${{ github.repository }}.git"
cat > "$PAGES_DIR/index.html" <<'HTMLEOF'
<!DOCTYPE html>
<html>
<head><title>Dev Container Helm Chart Repository</title></head>
<body>
<h1>Dev Container Helm Chart Repository</h1>
<p>Add this repository to Helm:</p>
<pre>helm repo add devcontainer https://cpfarhood.github.io/devcontainer</pre>
<p>Install the chart:</p>
<pre>helm install mydev devcontainer/devcontainer --set name=mydev</pre>
</body>
</html>
HTMLEOF
fi
git -C "$PAGES_DIR" config user.name "github-actions[bot]"
git -C "$PAGES_DIR" config user.email "github-actions[bot]@users.noreply.github.com"
# Copy chart package and rebuild index
cp "$CHART_TGZ" "$PAGES_DIR/"
if [ -f "$PAGES_DIR/index.yaml" ]; then
helm repo index "$PAGES_DIR" --url https://cpfarhood.github.io/devcontainer --merge "$PAGES_DIR/index.yaml"
else
helm repo index "$PAGES_DIR" --url https://cpfarhood.github.io/devcontainer
fi
# Commit and push
git -C "$PAGES_DIR" add .
git -C "$PAGES_DIR" commit -m "Publish chart ${{ steps.version.outputs.version }}"
git -C "$PAGES_DIR" push origin gh-pages
- name: Create GitHub Release
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: | run: |
# Get commits since last tag # Build release notes
PREV_TAG=$(git describe --tags --abbrev=0 HEAD^ 2>/dev/null || echo "") PREV_TAG=$(git describe --tags --abbrev=0 HEAD^ 2>/dev/null || echo "")
if [ -z "$PREV_TAG" ]; then if [ -z "$PREV_TAG" ]; then
COMMITS=$(git log --pretty=format:"- %s (%h)" HEAD) COMMITS=$(git log --pretty=format:"- %s (%h)" HEAD)
else else
COMMITS=$(git log --pretty=format:"- %s (%h)" ${PREV_TAG}..HEAD) COMMITS=$(git log --pretty=format:"- %s (%h)" "${PREV_TAG}..HEAD")
fi fi
cat << EOF > release-notes.md cat > release-notes.md <<EOF
## 🚀 Release ${{ steps.version.outputs.version }} ## Release ${{ steps.version.outputs.version }}
### Changes ### Changes
${COMMITS} ${COMMITS}
@@ -139,21 +178,12 @@ jobs:
### Helm Chart ### Helm Chart
\`\`\`bash \`\`\`bash
helm install devcontainer oci://ghcr.io/cpfarhood/charts/devcontainer --version ${{ steps.version.outputs.version }} helm repo add devcontainer https://cpfarhood.github.io/devcontainer
helm repo update
helm install mydev devcontainer/devcontainer --version ${{ steps.version.outputs.version }} --set name=mydev
\`\`\` \`\`\`
EOF EOF
echo "notes<<EOF" >> $GITHUB_OUTPUT gh release create "${{ steps.version.outputs.tag }}" \
cat release-notes.md >> $GITHUB_OUTPUT --title "Release ${{ steps.version.outputs.tag }}" \
echo "EOF" >> $GITHUB_OUTPUT --notes-file release-notes.md
- name: Create GitHub Release
uses: actions/create-release@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
tag_name: ${{ steps.version.outputs.tag }}
release_name: Release ${{ steps.version.outputs.tag }}
body: ${{ steps.notes.outputs.notes }}
draft: false
prerelease: false
-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"
} }
} }
} }
+9 -3
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,17 +184,22 @@ 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
- **`build-and-push.yaml`** — Builds and pushes to GHCR on every push to `main`, version tags (`v*`), and PRs. For version tags, also creates GitHub Release with Helm chart after Docker build completes. Tags: `latest` (main), semver, branch name, commit SHA. - **`build-and-push.yaml`** — Builds and pushes to GHCR on every push to `main`, version tags (`v*`), and PRs. Tags: `latest` (main), semver, branch name, commit SHA.
- **`release-unified.yaml`** — Manual release workflow: bumps chart version, builds Docker image, publishes Helm chart to GitHub Pages (`https://cpfarhood.github.io/devcontainer`), and creates GitHub Release.
- **`dependabot.yml`** — Weekly updates for GitHub Actions and Docker base image. - **`dependabot.yml`** — Weekly updates for GitHub Actions and Docker base image.
Image registry: `ghcr.io/cpfarhood/devcontainer` Image registry: `ghcr.io/cpfarhood/devcontainer`
Helm repo: `https://cpfarhood.github.io/devcontainer`
## Kubernetes Notes ## Kubernetes Notes
- Deployed via Helm chart (`chart/`), published as OCI artifact to GHCR, reconciled by Flux - Deployed via Helm chart (`chart/`), published to GitHub Pages Helm repo, reconciled by Flux
- Storage class is `ceph-filesystem` by default — change via `storage.className` in values - Storage class is `ceph-filesystem` by default — change via `storage.className` in values
- Resource limits: 14 CPU, 28Gi memory - Resource limits: 14 CPU, 28Gi memory
- Health checks (liveness/readiness probes) on port 5800 - Health checks (liveness/readiness probes) on port 5800
+6
View File
@@ -78,6 +78,12 @@ RUN CRUSH_VERSION=$(curl -sL https://api.github.com/repos/charmbracelet/crush/re
chmod +x /usr/local/bin/crush && \ chmod +x /usr/local/bin/crush && \
rm -rf /tmp/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 && \
echo "deb [arch=amd64 signed-by=/usr/share/keyrings/packages.microsoft.gpg] https://packages.microsoft.com/repos/code stable main" > /etc/apt/sources.list.d/vscode.list && \ echo "deb [arch=amd64 signed-by=/usr/share/keyrings/packages.microsoft.gpg] https://packages.microsoft.com/repos/code stable main" > /etc/apt/sources.list.d/vscode.list && \
+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)"
+38 -10
View File
@@ -6,29 +6,38 @@ 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
## Quick Start ## Quick Start
### Option A: Quickstart (Recommended) ### Option A: Install from Helm Repo (Recommended)
For 80% of users, use the simplified quickstart values:
```bash ```bash
# Copy and customize the quickstart template # Add the Helm repository
helm repo add devcontainer https://cpfarhood.github.io/devcontainer
helm repo update
# Deploy with one command
helm install mydev devcontainer/devcontainer \
--set name=mydev \
--set githubRepo=https://github.com/youruser/yourrepo
```
### Option B: Install from Source
```bash
# Clone and customize the quickstart template
cp chart/values-quickstart.yaml my-values.yaml cp chart/values-quickstart.yaml my-values.yaml
# Edit my-values.yaml to set your name and repository
# Edit my-values.yaml to set your name and repository:
# name: mydev
# githubRepo: https://github.com/youruser/yourrepo
# Deploy with minimal configuration
helm install mydev ./chart -f my-values.yaml helm install mydev ./chart -f my-values.yaml
``` ```
### Option B: One-Command Deploy ### Option C: One-Command from Source
```bash ```bash
helm install mydev ./chart \ helm install mydev ./chart \
@@ -121,6 +130,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 +171,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 |
+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 }}
{{/* {{/*
+15 -5
View File
@@ -8,7 +8,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 +23,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 +69,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
@@ -169,7 +179,7 @@ spec:
{{- 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
@@ -203,7 +213,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
@@ -237,7 +247,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
+4
View File
@@ -2,12 +2,16 @@ 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 }}
+3 -5
View File
@@ -35,7 +35,7 @@
"githubRepo": { "githubRepo": {
"type": "string", "type": "string",
"description": "GitHub repository URL to clone", "description": "GitHub repository URL to clone",
"pattern": "^https://github\\.com/.+/.+$" "pattern": "^https?://.+/.+/.+$"
}, },
"ide": { "ide": {
"type": "object", "type": "object",
@@ -108,7 +108,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",
@@ -143,12 +143,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": {
@@ -161,7 +159,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",
+15 -5
View File
@@ -27,6 +27,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 +55,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 +80,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"
@@ -115,7 +125,7 @@ mcp:
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:
@@ -145,7 +155,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:
+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"
+3
View File
@@ -34,6 +34,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"
;; ;;