Compare commits
65 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 70e74ab2d2 | |||
| 673554b393 | |||
| 609752e7dc | |||
| fcd959ae1f | |||
| 149120ff6c | |||
| 50770b6e5f | |||
| 01d5ebbdb8 | |||
| d5338ab836 | |||
| a378c0f913 | |||
| c1f3fdbb90 | |||
| 60f96fc8da | |||
| a300e8e810 | |||
| b5ee7c84de | |||
| 02c4f864f7 | |||
| ea966fadab | |||
| f0e70438db | |||
| 0fe568a7d6 | |||
| 7235d2dc67 | |||
| 7940e80cf0 | |||
| f6cbec05f6 | |||
| 9175d48844 | |||
| cb60f2a428 | |||
| 1179897cba | |||
| 46dc486cb4 | |||
| 41ec70c7da | |||
| e3f751240a | |||
| e6c3b7f7bf | |||
| 41e270ec32 | |||
| 05b06d1d90 | |||
| 8736d5b500 | |||
| 713e5eebe6 | |||
| 276477e245 | |||
| 2136976b8e | |||
| e269e19f23 | |||
| 3109de7e2e | |||
| 2b9350c86d | |||
| 5d62842aec | |||
| 58719cf262 | |||
| c066aa49be | |||
| 204a673b3d | |||
| 04ed52bc8d | |||
| c670dd124f | |||
| 219af987ae | |||
| c70352dc41 | |||
| f689b27b78 | |||
| a978b505d0 | |||
| 69497b1ec6 | |||
| 698c5810a0 | |||
| 2582c1d824 | |||
| 6dd560f2ad | |||
| 19d47da079 | |||
| 12d3444cc5 | |||
| eeb995e1fc | |||
| d26b69c587 | |||
| da40d57e07 | |||
| e99ec65cd9 | |||
| 38e481484e | |||
| cc38a07168 | |||
| 5c3600a424 | |||
| 5565354127 | |||
| b69cd80cae | |||
| 3e46bf5ec1 | |||
| c8a7bbcd6e | |||
| adb2ee4817 | |||
| 3637a0a6fc |
@@ -0,0 +1,6 @@
|
|||||||
|
{
|
||||||
|
"enabledPlugins": {
|
||||||
|
"voltagent-dev-exp@voltagent-subagents": true,
|
||||||
|
"voltagent-lang@voltagent-subagents": true
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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)"
|
||||||
|
]
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,7 +19,6 @@
|
|||||||
- [ ] Built Docker image locally
|
- [ ] Built Docker image locally
|
||||||
- [ ] Tested container startup
|
- [ ] Tested container startup
|
||||||
- [ ] Tested repository cloning
|
- [ ] Tested repository cloning
|
||||||
- [ ] Tested Happy Coder integration
|
|
||||||
- [ ] Tested VNC web interface
|
- [ ] Tested VNC web interface
|
||||||
|
|
||||||
## Checklist
|
## Checklist
|
||||||
|
|||||||
@@ -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
|
||||||
@@ -16,13 +16,12 @@ 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])
|
if: >-
|
||||||
# to prevent racing with the release workflow's own Docker build
|
github.event_name != 'push'
|
||||||
if: github.event_name == 'workflow_dispatch' || github.event_name == 'pull_request' || github.actor != 'github-actions[bot]'
|
|| !contains(github.event.head_commit.message, '[skip ci]')
|
||||||
permissions:
|
permissions:
|
||||||
contents: read
|
contents: read
|
||||||
packages: write
|
packages: write
|
||||||
id-token: write
|
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
@@ -57,6 +56,7 @@ jobs:
|
|||||||
push: ${{ github.event_name != 'pull_request' }}
|
push: ${{ github.event_name != 'pull_request' }}
|
||||||
tags: ${{ steps.meta.outputs.tags }}
|
tags: ${{ steps.meta.outputs.tags }}
|
||||||
labels: ${{ steps.meta.outputs.labels }}
|
labels: ${{ steps.meta.outputs.labels }}
|
||||||
|
build-args: CACHE_BUST=${{ github.sha }}
|
||||||
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
|
||||||
|
|||||||
@@ -16,7 +16,9 @@ env:
|
|||||||
jobs:
|
jobs:
|
||||||
build:
|
build:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
if: github.ref == 'refs/heads/main'
|
||||||
permissions:
|
permissions:
|
||||||
|
contents: read
|
||||||
packages: write
|
packages: write
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
|
|||||||
@@ -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: |
|
||||||
@@ -99,61 +96,98 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
context: .
|
context: .
|
||||||
push: true
|
push: true
|
||||||
|
build-args: CACHE_BUST=${{ github.sha }}
|
||||||
|
cache-from: type=gha
|
||||||
|
cache-to: type=gha,mode=max
|
||||||
tags: |
|
tags: |
|
||||||
${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.version.outputs.tag }}
|
${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.version.outputs.tag }}
|
||||||
${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.version.outputs.version }}
|
${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.version.outputs.version }}
|
||||||
${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest
|
${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest
|
||||||
cache-from: type=gha
|
|
||||||
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 }}
|
||||||
|
VERSION: ${{ steps.version.outputs.version }}
|
||||||
|
TAG: ${{ steps.version.outputs.tag }}
|
||||||
|
IMAGE: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.version.outputs.tag }}
|
||||||
run: |
|
run: |
|
||||||
# Get commits since last tag
|
PREV_TAG=$(git describe --tags --abbrev=0 HEAD~1 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 <<NOTESEOF
|
||||||
## 🚀 Release ${{ steps.version.outputs.version }}
|
## Release ${VERSION}
|
||||||
|
|
||||||
### Changes
|
### Changes
|
||||||
${COMMITS}
|
${COMMITS}
|
||||||
|
|
||||||
### Docker Image
|
### Docker Image
|
||||||
\`\`\`bash
|
\`\`\`bash
|
||||||
docker pull ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.version.outputs.tag }}
|
docker pull ${IMAGE}
|
||||||
\`\`\`
|
\`\`\`
|
||||||
|
|
||||||
### 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 ${VERSION} --set name=mydev
|
||||||
\`\`\`
|
\`\`\`
|
||||||
EOF
|
NOTESEOF
|
||||||
|
sed -i 's/^ //' release-notes.md
|
||||||
|
|
||||||
echo "notes<<EOF" >> $GITHUB_OUTPUT
|
gh release create "${TAG}" \
|
||||||
cat release-notes.md >> $GITHUB_OUTPUT
|
--title "Release ${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
|
|
||||||
|
|||||||
@@ -18,19 +18,6 @@
|
|||||||
"playwright": {
|
"playwright": {
|
||||||
"type": "sse",
|
"type": "sse",
|
||||||
"url": "http://localhost:8086/sse"
|
"url": "http://localhost:8086/sse"
|
||||||
},
|
|
||||||
"pgtuner": {
|
|
||||||
"type": "sse",
|
|
||||||
"url": "http://localhost:8085/sse"
|
|
||||||
},
|
|
||||||
"fetch": {
|
|
||||||
"type": "sse",
|
|
||||||
"url": "http://localhost:8082/sse"
|
|
||||||
},
|
|
||||||
"sequentialthinking": {
|
|
||||||
"type": "sse",
|
|
||||||
"url": "http://localhost:8083/sse"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -6,7 +6,8 @@ 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, 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
|
||||||
@@ -60,7 +61,8 @@ Container start
|
|||||||
→ scripts/startapp.sh
|
→ scripts/startapp.sh
|
||||||
→ scripts/init-repo.sh
|
→ scripts/init-repo.sh
|
||||||
→ Configure git user & credentials
|
→ Configure git user & credentials
|
||||||
→ Clone GITHUB_REPO (if set)
|
→ Clone GITHUB_REPOS or GITHUB_REPO (if set)
|
||||||
|
→ Generate workspace.code-workspace for multi-repo setups
|
||||||
→ Launch VSCode as user `user` in /workspace
|
→ Launch VSCode as user `user` in /workspace
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -68,8 +70,8 @@ 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, VSCode, Helm, gh CLI, kubeseal, Claude Code, OpenCode, Crush, LSP servers (pyright, typescript-language-server, gopls, clangd, rust-analyzer, lua-language-server, jdtls, kotlin-language-server, intelephense); 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(s), generates multi-root workspace file |
|
||||||
| `scripts/startapp.sh` | Calls init-repo.sh then opens VSCode in the workspace |
|
| `scripts/startapp.sh` | Calls init-repo.sh then opens VSCode in the workspace |
|
||||||
| `chart/` | Helm chart for Kubernetes deployment |
|
| `chart/` | Helm chart for Kubernetes deployment |
|
||||||
| `chart/templates/deployment.yaml` | Deployment spec — main container + MCP sidecar containers |
|
| `chart/templates/deployment.yaml` | Deployment spec — main container + MCP sidecar containers |
|
||||||
@@ -77,7 +79,7 @@ Container start
|
|||||||
| `chart/templates/pvc.yaml` | PersistentVolumeClaim for user home |
|
| `chart/templates/pvc.yaml` | PersistentVolumeClaim for user home |
|
||||||
| `chart/templates/service.yaml` | ClusterIP Service (VNC + optional SSH) |
|
| `chart/templates/service.yaml` | ClusterIP Service (VNC + optional SSH) |
|
||||||
| `chart/values.yaml` | Default Helm values |
|
| `chart/values.yaml` | Default Helm values |
|
||||||
| `.mcp.json` | MCP server connection config (GitHub Copilot, Kubernetes, Flux, Fetch, Sequential Thinking, Playwright, pgtuner) |
|
| `.mcp.json` | MCP server connection config (GitHub Copilot, Kubernetes, Flux, Helm, Fetch, Sequential Thinking, Playwright, pgtuner) |
|
||||||
| `Makefile` | Build/deploy automation |
|
| `Makefile` | Build/deploy automation |
|
||||||
|
|
||||||
### MCP Sidecars
|
### MCP Sidecars
|
||||||
@@ -88,6 +90,7 @@ MCP (Model Context Protocol) servers run as sidecar containers in the pod, enabl
|
|||||||
|---------|-------|---------|------|----------|---------|
|
|---------|-------|---------|------|----------|---------|
|
||||||
| `kubernetes-mcp` | `quay.io/containers/kubernetes_mcp_server` | v0.0.57 | 8080 | `http://localhost:8080/sse` | Enabled |
|
| `kubernetes-mcp` | `quay.io/containers/kubernetes_mcp_server` | v0.0.57 | 8080 | `http://localhost:8080/sse` | Enabled |
|
||||||
| `flux-mcp` | `ghcr.io/controlplaneio-fluxcd/flux-operator-mcp` | v0.41.1 | 8081 | `http://localhost:8081/sse` | Enabled |
|
| `flux-mcp` | `ghcr.io/controlplaneio-fluxcd/flux-operator-mcp` | v0.41.1 | 8081 | `http://localhost:8081/sse` | Enabled |
|
||||||
|
| `helm-mcp` | `ghcr.io/zekker6/mcp-helm` | v1.3.1 | 8012 | `http://localhost:8012/sse` | Enabled |
|
||||||
| `fetch-mcp` | `mcp/fetch` | latest | 8082 | `http://localhost:8082/sse` | Enabled |
|
| `fetch-mcp` | `mcp/fetch` | latest | 8082 | `http://localhost:8082/sse` | Enabled |
|
||||||
| `sequentialthinking-mcp` | `mcp/sequentialthinking` | latest | 8083 | `http://localhost:8083/sse` | Enabled |
|
| `sequentialthinking-mcp` | `mcp/sequentialthinking` | latest | 8083 | `http://localhost:8083/sse` | Enabled |
|
||||||
| `homeassistant-mcp` | `ghcr.io/homeassistant-ai/ha-mcp` | stable | 8087 | `http://localhost:8087/sse` | Disabled |
|
| `homeassistant-mcp` | `ghcr.io/homeassistant-ai/ha-mcp` | stable | 8087 | `http://localhost:8087/sse` | Disabled |
|
||||||
@@ -98,6 +101,7 @@ MCP (Model Context Protocol) servers run as sidecar containers in the pod, enabl
|
|||||||
- GitHub MCP is accessed via the Copilot API (`https://api.githubcopilot.com/mcp/`), not as a sidecar
|
- GitHub MCP is accessed via the Copilot API (`https://api.githubcopilot.com/mcp/`), not as a sidecar
|
||||||
- Kubernetes and Flux sidecars require `clusterAccess` != `none` to be deployed (they need RBAC permissions)
|
- Kubernetes and Flux sidecars require `clusterAccess` != `none` to be deployed (they need RBAC permissions)
|
||||||
- Kubernetes and Flux sidecars inherit the pod's ServiceAccount RBAC permissions
|
- Kubernetes and Flux sidecars inherit the pod's ServiceAccount RBAC permissions
|
||||||
|
- Helm sidecar enables browsing Helm repositories and chart metadata
|
||||||
- Fetch sidecar provides web content fetching capabilities and HTML to markdown conversion
|
- Fetch sidecar provides web content fetching capabilities and HTML to markdown conversion
|
||||||
- Sequential thinking sidecar enables structured thinking and problem-solving processes
|
- Sequential thinking sidecar enables structured thinking and problem-solving processes
|
||||||
- Home Assistant sidecar requires `HOMEASSISTANT_URL` and `HOMEASSISTANT_TOKEN` in the env secret
|
- Home Assistant sidecar requires `HOMEASSISTANT_URL` and `HOMEASSISTANT_TOKEN` in the env secret
|
||||||
@@ -116,6 +120,8 @@ mcp:
|
|||||||
enabled: false
|
enabled: false
|
||||||
flux:
|
flux:
|
||||||
enabled: false
|
enabled: false
|
||||||
|
helm:
|
||||||
|
enabled: false
|
||||||
fetch:
|
fetch:
|
||||||
enabled: false
|
enabled: false
|
||||||
sequentialthinking:
|
sequentialthinking:
|
||||||
@@ -134,6 +140,8 @@ mcp:
|
|||||||
enabled: true # Keep Kubernetes MCP enabled
|
enabled: true # Keep Kubernetes MCP enabled
|
||||||
flux:
|
flux:
|
||||||
enabled: false # Disable Flux MCP
|
enabled: false # Disable Flux MCP
|
||||||
|
helm:
|
||||||
|
enabled: true # Enable Helm MCP for chart browsing
|
||||||
fetch:
|
fetch:
|
||||||
enabled: true # Enable Fetch MCP for web content fetching
|
enabled: true # Enable Fetch MCP for web content fetching
|
||||||
sequentialthinking:
|
sequentialthinking:
|
||||||
@@ -170,8 +178,9 @@ helm install my-devcontainer ./chart -f custom-values.yaml
|
|||||||
|
|
||||||
### Environment Variables
|
### Environment Variables
|
||||||
|
|
||||||
**Required:**
|
**Required (at least one):**
|
||||||
- `GITHUB_REPO` — URL of repository to clone into `/workspace`
|
- `GITHUB_REPO` — URL of a single repository to clone into `/workspace`
|
||||||
|
- `GITHUB_REPOS` — Comma-separated list of repository URLs to clone (takes precedence over `GITHUB_REPO`). When multiple repos are cloned, a `workspace.code-workspace` file is generated for multi-root IDE support.
|
||||||
|
|
||||||
**Optional:**
|
**Optional:**
|
||||||
- `GITHUB_TOKEN` — PAT for private repo access (automatically configures git credentials)
|
- `GITHUB_TOKEN` — PAT for private repo access (automatically configures git credentials)
|
||||||
@@ -181,19 +190,22 @@ helm install my-devcontainer ./chart -f custom-values.yaml
|
|||||||
- `VNC_PASSWORD` — VNC web interface password
|
- `VNC_PASSWORD` — VNC web interface password
|
||||||
- `DISPLAY_WIDTH` / `DISPLAY_HEIGHT` — VNC resolution
|
- `DISPLAY_WIDTH` / `DISPLAY_HEIGHT` — VNC resolution
|
||||||
- `USER_ID` / `GROUP_ID` — Override UID/GID (default 1000)
|
- `USER_ID` / `GROUP_ID` — Override UID/GID (default 1000)
|
||||||
- `HAPPY_SERVER_URL` / `HAPPY_WEBAPP_URL` — Custom Happy Coder endpoints
|
- `WEB_FILE_MANAGER` — Set to `1` to enable the built-in web file manager (controlled via `fileManager.enabled` in Helm values)
|
||||||
- `HAPPY_HOME_DIR` / `HAPPY_EXPERIMENTAL`
|
- `WEB_FILE_MANAGER_ALLOWED_PATHS` — Paths accessible by the file manager (default: `/workspace,/config`)
|
||||||
|
- `WEB_FILE_MANAGER_DENIED_PATHS` — Paths to deny access to (takes precedence over allowed)
|
||||||
|
|
||||||
### CI/CD
|
### CI/CD
|
||||||
|
|
||||||
- **`build-and-push.yaml`** — Builds and pushes to GHCR on every push to `main`, version tags (`v*`), and PRs. 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: 1–4 CPU, 2–8Gi memory
|
- Resource limits: 1–4 CPU, 2–8Gi memory
|
||||||
- Health checks (liveness/readiness probes) on port 5800
|
- Health checks (liveness/readiness probes) on port 5800
|
||||||
|
|||||||
@@ -225,18 +225,6 @@ spec:
|
|||||||
|
|
||||||
## Advanced Configurations
|
## Advanced Configurations
|
||||||
|
|
||||||
### Custom Happy Coder Endpoints
|
|
||||||
|
|
||||||
For self-hosted Happy instances:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
helm install mydev ./chart \
|
|
||||||
--set name=mydev \
|
|
||||||
--set githubRepo=https://github.com/youruser/yourrepo \
|
|
||||||
--set happyServerUrl=https://your-happy-server.com \
|
|
||||||
--set happyWebappUrl=https://your-happy-webapp.com
|
|
||||||
```
|
|
||||||
|
|
||||||
### Custom Display Resolution
|
### Custom Display Resolution
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
|
|||||||
+100
-8
@@ -1,5 +1,8 @@
|
|||||||
FROM jlesage/baseimage-gui:ubuntu-22.04-v4
|
FROM jlesage/baseimage-gui:ubuntu-22.04-v4
|
||||||
|
|
||||||
|
# Bust cache for all layers below (base image pull is still cached)
|
||||||
|
ARG CACHE_BUST
|
||||||
|
|
||||||
# Set environment variables
|
# Set environment variables
|
||||||
ENV APP_NAME="Dev Container" \
|
ENV APP_NAME="Dev Container" \
|
||||||
KEEP_APP_RUNNING=1 \
|
KEEP_APP_RUNNING=1 \
|
||||||
@@ -56,13 +59,22 @@ exec /usr/bin/google-chrome-stable \\\n\
|
|||||||
"$@"\n' > /usr/local/bin/google-chrome && \
|
"$@"\n' > /usr/local/bin/google-chrome && \
|
||||||
chmod +x /usr/local/bin/google-chrome
|
chmod +x /usr/local/bin/google-chrome
|
||||||
|
|
||||||
# Install Node.js (LTS version for Happy Coder)
|
# Install Node.js LTS via NodeSource
|
||||||
RUN curl -fsSL https://deb.nodesource.com/setup_lts.x | bash - && \
|
ARG NODE_MAJOR=22
|
||||||
|
RUN curl -fsSL https://deb.nodesource.com/setup_${NODE_MAJOR}.x | bash - && \
|
||||||
apt-get install -y nodejs && \
|
apt-get install -y nodejs && \
|
||||||
rm -rf /var/lib/apt/lists/*
|
rm -rf /var/lib/apt/lists/* && \
|
||||||
|
node --version && npm --version
|
||||||
|
|
||||||
# Install Happy Coder and Claude Code globally
|
# Install Claude Code native binary (npm wrapper breaks remote control)
|
||||||
RUN npm install -g happy-coder @anthropic-ai/claude-code
|
RUN curl -fsSL https://claude.ai/install.sh | bash && \
|
||||||
|
cp /root/.local/bin/claude /usr/local/bin/claude && \
|
||||||
|
rm -rf /root/.local/bin/claude && \
|
||||||
|
claude --version
|
||||||
|
|
||||||
|
# Disable Claude Code auto-updater (doesn't work inside Docker)
|
||||||
|
RUN mkdir -p /etc/skel/.claude && \
|
||||||
|
echo '{"env":{"DISABLE_AUTOUPDATER":"1"}}' > /etc/skel/.claude/settings.json
|
||||||
|
|
||||||
# Install OpenCode AI coding agent
|
# Install OpenCode AI coding agent
|
||||||
RUN OPENCODE_VERSION=$(curl -sL https://api.github.com/repos/opencode-ai/opencode/releases/latest | jq -r '.tag_name') && \
|
RUN OPENCODE_VERSION=$(curl -sL https://api.github.com/repos/opencode-ai/opencode/releases/latest | jq -r '.tag_name') && \
|
||||||
@@ -78,9 +90,89 @@ 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 VSCode
|
# Install Helm CLI for Kubernetes chart management
|
||||||
RUN wget -qO- https://packages.microsoft.com/keys/microsoft.asc | gpg --dearmor -o /usr/share/keyrings/packages.microsoft.gpg && \
|
ARG HELM_VERSION=3.17.1
|
||||||
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 && \
|
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 OpenTofu (open-source Terraform alternative)
|
||||||
|
ARG OPENTOFU_VERSION=1.11.5
|
||||||
|
RUN curl -fsSL "https://github.com/opentofu/opentofu/releases/download/v${OPENTOFU_VERSION}/tofu_${OPENTOFU_VERSION}_linux_amd64.zip" -o /tmp/tofu.zip && \
|
||||||
|
unzip -o /tmp/tofu.zip -d /usr/local/bin tofu && \
|
||||||
|
chmod +x /usr/local/bin/tofu && \
|
||||||
|
rm /tmp/tofu.zip
|
||||||
|
|
||||||
|
# Install GitHub CLI (gh) via official APT repo
|
||||||
|
RUN curl -fsSL https://cli.github.com/packages/githubcli-archive-keyring.gpg | dd of=/usr/share/keyrings/githubcli-archive-keyring.gpg && \
|
||||||
|
chmod go+r /usr/share/keyrings/githubcli-archive-keyring.gpg && \
|
||||||
|
echo "deb [arch=amd64 signed-by=/usr/share/keyrings/githubcli-archive-keyring.gpg] https://cli.github.com/packages stable main" > /etc/apt/sources.list.d/github-cli.list && \
|
||||||
|
apt-get update && \
|
||||||
|
apt-get install -y gh && \
|
||||||
|
rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
|
# Install kubeseal CLI for Bitnami Sealed Secrets
|
||||||
|
RUN KUBESEAL_VERSION=$(curl -sL https://api.github.com/repos/bitnami-labs/sealed-secrets/releases/latest | jq -r '.tag_name' | sed 's/^v//') && \
|
||||||
|
curl -fsSL "https://github.com/bitnami-labs/sealed-secrets/releases/download/v${KUBESEAL_VERSION}/kubeseal-${KUBESEAL_VERSION}-linux-amd64.tar.gz" | \
|
||||||
|
tar -xz -C /usr/local/bin kubeseal && \
|
||||||
|
chmod +x /usr/local/bin/kubeseal
|
||||||
|
|
||||||
|
# ── LSP servers for Claude Code language intelligence ──
|
||||||
|
|
||||||
|
# npm-based LSP servers: Python (pyright), TypeScript/JavaScript, PHP
|
||||||
|
RUN npm install -g pyright typescript-language-server typescript intelephense
|
||||||
|
|
||||||
|
# Install Go runtime and gopls LSP server
|
||||||
|
ARG GO_VERSION=1.23.6
|
||||||
|
RUN curl -fsSL "https://go.dev/dl/go${GO_VERSION}.linux-amd64.tar.gz" | tar -xz -C /usr/local && \
|
||||||
|
/usr/local/go/bin/go install golang.org/x/tools/gopls@latest && \
|
||||||
|
mv /root/go/bin/gopls /usr/local/bin/gopls && \
|
||||||
|
rm -rf /root/go
|
||||||
|
ENV PATH="/usr/local/go/bin:${PATH}"
|
||||||
|
|
||||||
|
# Install clangd LSP server (C/C++)
|
||||||
|
RUN apt-get update && \
|
||||||
|
apt-get install -y clangd && \
|
||||||
|
rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
|
# Install rust-analyzer LSP server (Rust) — standalone binary, no full toolchain needed
|
||||||
|
RUN RUST_ANALYZER_VERSION=$(curl -sL https://api.github.com/repos/rust-lang/rust-analyzer/releases/latest | jq -r '.tag_name') && \
|
||||||
|
curl -fsSL "https://github.com/rust-lang/rust-analyzer/releases/download/${RUST_ANALYZER_VERSION}/rust-analyzer-x86_64-unknown-linux-gnu.gz" | \
|
||||||
|
gunzip > /usr/local/bin/rust-analyzer && \
|
||||||
|
chmod +x /usr/local/bin/rust-analyzer
|
||||||
|
|
||||||
|
# Install lua-language-server (Lua)
|
||||||
|
RUN LUA_LS_VERSION=$(curl -sL https://api.github.com/repos/LuaLS/lua-language-server/releases/latest | jq -r '.tag_name') && \
|
||||||
|
mkdir -p /opt/lua-language-server && \
|
||||||
|
curl -fsSL "https://github.com/LuaLS/lua-language-server/releases/download/${LUA_LS_VERSION}/lua-language-server-${LUA_LS_VERSION}-linux-x64.tar.gz" | \
|
||||||
|
tar -xz -C /opt/lua-language-server && \
|
||||||
|
ln -s /opt/lua-language-server/bin/lua-language-server /usr/local/bin/lua-language-server
|
||||||
|
|
||||||
|
# Install JDK for Java/Kotlin LSP servers
|
||||||
|
RUN apt-get update && \
|
||||||
|
apt-get install -y openjdk-17-jdk-headless && \
|
||||||
|
rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
|
# Install kotlin-language-server
|
||||||
|
RUN KLS_VERSION=$(curl -sL https://api.github.com/repos/fwcd/kotlin-language-server/releases/latest | jq -r '.tag_name') && \
|
||||||
|
curl -fsSL "https://github.com/fwcd/kotlin-language-server/releases/download/${KLS_VERSION}/server.zip" -o /tmp/kls.zip && \
|
||||||
|
unzip -o /tmp/kls.zip -d /opt/kotlin-language-server && \
|
||||||
|
ln -s /opt/kotlin-language-server/server/bin/kotlin-language-server /usr/local/bin/kotlin-language-server && \
|
||||||
|
rm /tmp/kls.zip
|
||||||
|
|
||||||
|
# Install jdtls (Java LSP) — Eclipse JDT Language Server
|
||||||
|
RUN JDTLS_TARBALL=$(curl -sL https://download.eclipse.org/jdtls/snapshots/latest.txt) && \
|
||||||
|
mkdir -p /opt/jdtls && \
|
||||||
|
curl -fsSL "https://download.eclipse.org/jdtls/snapshots/${JDTLS_TARBALL}" | tar -xz -C /opt/jdtls && \
|
||||||
|
printf '#!/bin/bash\nexec java -Declipse.application=org.eclipse.jdt.ls.core.id1 -Dosgi.bundles.defaultStartLevel=4 -Declipse.product=org.eclipse.jdt.ls.core.product -jar /opt/jdtls/plugins/org.eclipse.equinox.launcher_*.jar -configuration /opt/jdtls/config_linux "$@"\n' > /usr/local/bin/jdtls && \
|
||||||
|
chmod +x /usr/local/bin/jdtls
|
||||||
|
|
||||||
|
# Install VSCode (using Microsoft's current recommended setup)
|
||||||
|
RUN wget -qO- https://packages.microsoft.com/keys/microsoft.asc | gpg --dearmor > /tmp/microsoft.gpg && \
|
||||||
|
install -D -o root -g root -m 644 /tmp/microsoft.gpg /usr/share/keyrings/microsoft.gpg && \
|
||||||
|
rm -f /tmp/microsoft.gpg && \
|
||||||
|
printf 'Types: deb\nURIs: https://packages.microsoft.com/repos/code\nSuites: stable\nComponents: main\nArchitectures: amd64\nSigned-By: /usr/share/keyrings/microsoft.gpg\n' \
|
||||||
|
> /etc/apt/sources.list.d/vscode.sources && \
|
||||||
apt-get update && \
|
apt-get update && \
|
||||||
apt-get install -y code && \
|
apt-get install -y code && \
|
||||||
rm -rf /var/lib/apt/lists/*
|
rm -rf /var/lib/apt/lists/*
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
# Variables
|
# Variables
|
||||||
REGISTRY ?= ghcr.io/cpfarhood
|
REGISTRY ?= ghcr.io/cpfarhood
|
||||||
IMAGE_NAME ?= antigravity
|
IMAGE_NAME ?= devcontainer
|
||||||
IMAGE_TAG ?= latest
|
IMAGE_TAG ?= latest
|
||||||
FULL_IMAGE = $(REGISTRY)/$(IMAGE_NAME):$(IMAGE_TAG)
|
FULL_IMAGE = $(REGISTRY)/$(IMAGE_NAME):$(IMAGE_TAG)
|
||||||
|
|
||||||
@@ -26,18 +26,17 @@ run:
|
|||||||
-e GITHUB_REPO="${GITHUB_REPO}" \
|
-e GITHUB_REPO="${GITHUB_REPO}" \
|
||||||
-e GITHUB_TOKEN="${GITHUB_TOKEN}" \
|
-e GITHUB_TOKEN="${GITHUB_TOKEN}" \
|
||||||
-e VNC_PASSWORD="${VNC_PASSWORD}" \
|
-e VNC_PASSWORD="${VNC_PASSWORD}" \
|
||||||
-e HAPPY_EXPERIMENTAL="true" \
|
|
||||||
-v $(PWD)/home:/home \
|
-v $(PWD)/home:/home \
|
||||||
-v $(PWD)/workspace:/workspace \
|
-v $(PWD)/workspace:/workspace \
|
||||||
--name antigravity \
|
--name devcontainer \
|
||||||
$(FULL_IMAGE)
|
$(FULL_IMAGE)
|
||||||
@echo "Access at http://localhost:5800"
|
@echo "Access at http://localhost:5800"
|
||||||
|
|
||||||
# Stop the running container
|
# Stop the running container
|
||||||
stop:
|
stop:
|
||||||
@echo "Stopping antigravity container..."
|
@echo "Stopping devcontainer..."
|
||||||
docker stop antigravity || true
|
docker stop devcontainer || true
|
||||||
docker rm antigravity || true
|
docker rm devcontainer || true
|
||||||
|
|
||||||
# Clean up local volumes
|
# Clean up local volumes
|
||||||
clean: stop
|
clean: stop
|
||||||
@@ -81,7 +80,7 @@ helm-port-forward:
|
|||||||
|
|
||||||
# Show help
|
# Show help
|
||||||
help:
|
help:
|
||||||
@echo "Antigravity Dev Container Makefile"
|
@echo "Dev Container Makefile"
|
||||||
@echo ""
|
@echo ""
|
||||||
@echo "Usage: make [target]"
|
@echo "Usage: make [target]"
|
||||||
@echo ""
|
@echo ""
|
||||||
@@ -101,7 +100,7 @@ help:
|
|||||||
@echo ""
|
@echo ""
|
||||||
@echo "Variables:"
|
@echo "Variables:"
|
||||||
@echo " REGISTRY - Docker registry (default: ghcr.io/cpfarhood)"
|
@echo " REGISTRY - Docker registry (default: ghcr.io/cpfarhood)"
|
||||||
@echo " IMAGE_NAME - Image name (default: antigravity)"
|
@echo " IMAGE_NAME - Image name (default: devcontainer)"
|
||||||
@echo " IMAGE_TAG - Image tag (default: latest)"
|
@echo " IMAGE_TAG - Image tag (default: latest)"
|
||||||
@echo " RELEASE_NAME - Helm release name (default: mydev)"
|
@echo " RELEASE_NAME - Helm release name (default: mydev)"
|
||||||
@echo " NAMESPACE - Kubernetes namespace (default: default)"
|
@echo " NAMESPACE - Kubernetes namespace (default: default)"
|
||||||
|
|||||||
@@ -5,30 +5,39 @@
|
|||||||
A containerized cloud development environment with web-based GUI access, featuring:
|
A containerized cloud development environment with web-based GUI access, featuring:
|
||||||
- **VSCode or Google Antigravity** via browser-based VNC (port 5800)
|
- **VSCode or Google Antigravity** via browser-based VNC (port 5800)
|
||||||
- **SSH access** option (OpenSSH on port 22, additive with any IDE)
|
- **SSH access** option (OpenSSH on port 22, additive with any IDE)
|
||||||
- **Claude Code**, **Happy Coder**, **OpenCode**, and **Crush** AI coding agents (terminal-based)
|
- **Claude Code**, **OpenCode**, and **Crush** AI coding agents (terminal-based)
|
||||||
|
- **Built-in web file manager** for uploading/downloading files via the VNC web interface
|
||||||
|
- **Helm CLI** included for Kubernetes chart development and deployment
|
||||||
- **Automatic GitHub repo cloning** on startup
|
- **Automatic GitHub repo cloning** on startup
|
||||||
- **Persistent home directory** via ReadWriteMany PVC
|
- **Persistent home directory** via ReadWriteMany PVC
|
||||||
- **Kubernetes-native** Helm chart deployment
|
- **Kubernetes-native** Helm chart deployment
|
||||||
|
|
||||||
## Quick Start
|
## Quick Start
|
||||||
|
|
||||||
### Option A: 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 \
|
||||||
@@ -105,7 +114,7 @@ The Helm chart uses a logical organization with these main sections:
|
|||||||
- **Basic Configuration**: name, image, githubRepo
|
- **Basic Configuration**: name, image, githubRepo
|
||||||
- **Access & Interface**: IDE, SSH, display, user settings
|
- **Access & Interface**: IDE, SSH, display, user settings
|
||||||
- **Infrastructure**: storage, resources, cluster access
|
- **Infrastructure**: storage, resources, cluster access
|
||||||
- **Integrations**: Happy Coder, MCP sidecars
|
- **Integrations**: MCP sidecars
|
||||||
- **Smart Defaults**: auto-detection and profiles
|
- **Smart Defaults**: auto-detection and profiles
|
||||||
|
|
||||||
📖 **Documentation**:
|
📖 **Documentation**:
|
||||||
@@ -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,14 +171,23 @@ kubectl port-forward deployment/devcontainer-mydev 2222:22
|
|||||||
ssh -p 2222 user@localhost
|
ssh -p 2222 user@localhost
|
||||||
```
|
```
|
||||||
|
|
||||||
### Happy Coder
|
### Web file manager
|
||||||
|
|
||||||
|
The base image includes a built-in web file manager for uploading and downloading files through the VNC web interface (port 5800). No additional sidecar is needed.
|
||||||
|
|
||||||
| Value | Default | Description |
|
| Value | Default | Description |
|
||||||
|-------|---------|-------------|
|
|-------|---------|-------------|
|
||||||
| `happy.serverUrl` | `https://happy.farh.net` | Happy Coder server endpoint |
|
| `fileManager.enabled` | `false` | Enable the web file manager |
|
||||||
| `happy.webappUrl` | `https://happy-coder.farh.net` | Happy Coder webapp URL |
|
| `fileManager.allowedPaths` | `/workspace,/config` | Paths accessible by the file manager (`AUTO`, `ALL`, or comma-separated) |
|
||||||
| `happy.homeDir` | `/config/userdata/.happy` | Happy runtime state directory (persists on the home PVC) |
|
| `fileManager.deniedPaths` | `""` | Paths to deny (takes precedence over allowed) |
|
||||||
| `happy.experimental` | `true` | Enable experimental Happy features |
|
|
||||||
|
```bash
|
||||||
|
# Enable the file manager
|
||||||
|
helm install mydev ./chart \
|
||||||
|
--set name=mydev \
|
||||||
|
--set githubRepo=https://github.com/youruser/yourrepo \
|
||||||
|
--set fileManager.enabled=true
|
||||||
|
```
|
||||||
|
|
||||||
### Kubernetes cluster access
|
### Kubernetes cluster access
|
||||||
|
|
||||||
@@ -349,30 +368,10 @@ Container start
|
|||||||
| `/config` | ReadWriteMany PVC (`userhome-{name}`) | Survives pod restarts — stores Claude credentials, dotfiles, git config |
|
| `/config` | ReadWriteMany PVC (`userhome-{name}`) | Survives pod restarts — stores Claude credentials, dotfiles, git config |
|
||||||
| `/workspace` | `emptyDir` | Ephemeral — repo is re-cloned on each pod start |
|
| `/workspace` | `emptyDir` | Ephemeral — repo is re-cloned on each pod start |
|
||||||
|
|
||||||
Happy Coder's runtime state (`HAPPY_HOME_DIR`) is kept in `/config/userdata/.happy` on the persistent home PVC, so auth credentials and settings survive pod restarts when manually started.
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Troubleshooting
|
## Troubleshooting
|
||||||
|
|
||||||
### Happy Coder (manual startup)
|
|
||||||
|
|
||||||
Happy daemon is not started automatically. Launch it manually when needed:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Start Happy Coder daemon manually
|
|
||||||
happy daemon start
|
|
||||||
|
|
||||||
# Check daemon status
|
|
||||||
happy daemon status
|
|
||||||
|
|
||||||
# View daemon logs
|
|
||||||
ls ~/.happy/logs/
|
|
||||||
|
|
||||||
# Stop daemon if needed
|
|
||||||
happy daemon stop
|
|
||||||
```
|
|
||||||
|
|
||||||
### Claude not authenticated
|
### Claude not authenticated
|
||||||
|
|
||||||
Browser-based OAuth login is the primary method (works inside VNC via the Chrome wrapper). If you prefer API key auth:
|
Browser-based OAuth login is the primary method (works inside VNC via the Chrome wrapper). If you prefer API key auth:
|
||||||
@@ -438,4 +437,4 @@ The image is also built and pushed automatically by CI on every push to `main` a
|
|||||||
## Credits
|
## Credits
|
||||||
|
|
||||||
- Base image: [jlesage/docker-baseimage-gui](https://github.com/jlesage/docker-baseimage-gui)
|
- Base image: [jlesage/docker-baseimage-gui](https://github.com/jlesage/docker-baseimage-gui)
|
||||||
- AI assistant: [Happy Coder](https://happy.engineering) + [Claude](https://claude.ai)
|
- AI assistant: [Claude](https://claude.ai)
|
||||||
|
|||||||
@@ -52,30 +52,6 @@ Complete reference for all configurable values in the Antigravity Dev Container
|
|||||||
- **Options:** `Always`, `IfNotPresent`, `Never`
|
- **Options:** `Always`, `IfNotPresent`, `Never`
|
||||||
- **Description:** Image pull policy
|
- **Description:** Image pull policy
|
||||||
|
|
||||||
## Happy Coder Configuration
|
|
||||||
|
|
||||||
### happyServerUrl
|
|
||||||
- **Type:** String
|
|
||||||
- **Default:** `https://happy.farh.net`
|
|
||||||
- **Description:** Happy Coder server endpoint
|
|
||||||
- **When to Change:** Self-hosted Happy instance
|
|
||||||
|
|
||||||
### happyWebappUrl
|
|
||||||
- **Type:** String
|
|
||||||
- **Default:** `https://happy-coder.farh.net`
|
|
||||||
- **Description:** Happy Coder webapp URL
|
|
||||||
- **When to Change:** Self-hosted Happy instance
|
|
||||||
|
|
||||||
### happyHomeDir
|
|
||||||
- **Type:** String
|
|
||||||
- **Default:** `/config/userdata/.happy`
|
|
||||||
- **Description:** Happy runtime state directory (persists on PVC)
|
|
||||||
|
|
||||||
### happyExperimental
|
|
||||||
- **Type:** String
|
|
||||||
- **Default:** `"true"`
|
|
||||||
- **Description:** Enable experimental Happy features
|
|
||||||
|
|
||||||
## Display Configuration
|
## Display Configuration
|
||||||
|
|
||||||
### display.width
|
### display.width
|
||||||
@@ -339,8 +315,6 @@ storage:
|
|||||||
|
|
||||||
clusterAccess: readonly
|
clusterAccess: readonly
|
||||||
|
|
||||||
happyServerUrl: https://happy.internal.company.com
|
|
||||||
happyWebappUrl: https://happy-app.internal.company.com
|
|
||||||
```
|
```
|
||||||
|
|
||||||
### Smart Home Development Configuration
|
### Smart Home Development Configuration
|
||||||
@@ -431,10 +405,6 @@ These environment variables are set in the container based on chart values:
|
|||||||
| `VNC_PASSWORD` | Secret: `vnc-password` | VNC access password |
|
| `VNC_PASSWORD` | Secret: `vnc-password` | VNC access password |
|
||||||
| `ANTHROPIC_API_KEY` | Secret: `anthropic-api-key` | Claude API key |
|
| `ANTHROPIC_API_KEY` | Secret: `anthropic-api-key` | Claude API key |
|
||||||
| `SSH_AUTHORIZED_KEYS` | Secret: `ssh-authorized-keys` | SSH public keys |
|
| `SSH_AUTHORIZED_KEYS` | Secret: `ssh-authorized-keys` | SSH public keys |
|
||||||
| `HAPPY_SERVER_URL` | `happyServerUrl` | Happy server endpoint |
|
|
||||||
| `HAPPY_WEBAPP_URL` | `happyWebappUrl` | Happy webapp URL |
|
|
||||||
| `HAPPY_HOME_DIR` | `happyHomeDir` | Happy data directory |
|
|
||||||
| `HAPPY_EXPERIMENTAL` | `happyExperimental` | Experimental features |
|
|
||||||
| `DISPLAY_WIDTH` | `display.width` | VNC width |
|
| `DISPLAY_WIDTH` | `display.width` | VNC width |
|
||||||
| `DISPLAY_HEIGHT` | `display.height` | VNC height |
|
| `DISPLAY_HEIGHT` | `display.height` | VNC height |
|
||||||
| `SECURE_CONNECTION` | `secureConnection` | TLS termination |
|
| `SECURE_CONNECTION` | `secureConnection` | TLS termination |
|
||||||
|
|||||||
+6
-1
@@ -2,5 +2,10 @@ 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
|
||||||
type: application
|
type: application
|
||||||
version: 0.4.13
|
version: 2.6.0
|
||||||
appVersion: "latest"
|
appVersion: "latest"
|
||||||
|
keywords:
|
||||||
|
- development
|
||||||
|
- devcontainer
|
||||||
|
- vscode
|
||||||
|
- ai
|
||||||
|
|||||||
@@ -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 }}
|
||||||
@@ -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 }}
|
||||||
|
|
||||||
{{/*
|
{{/*
|
||||||
|
|||||||
@@ -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,16 +69,24 @@ 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 }}
|
||||||
- name: HAPPY_HOME_DIR
|
{{- if .Values.fileManager.enabled }}
|
||||||
value: {{ .Values.happy.homeDir | quote }}
|
- name: WEB_FILE_MANAGER
|
||||||
- name: HAPPY_EXPERIMENTAL
|
value: "1"
|
||||||
value: {{ .Values.happy.experimental | quote }}
|
- name: WEB_FILE_MANAGER_ALLOWED_PATHS
|
||||||
- name: HAPPY_SERVER_URL
|
value: {{ .Values.fileManager.allowedPaths | quote }}
|
||||||
value: {{ .Values.happy.serverUrl | quote }}
|
{{- if .Values.fileManager.deniedPaths }}
|
||||||
- name: HAPPY_WEBAPP_URL
|
- name: WEB_FILE_MANAGER_DENIED_PATHS
|
||||||
value: {{ .Values.happy.webappUrl | quote }}
|
value: {{ .Values.fileManager.deniedPaths | quote }}
|
||||||
|
{{- end }}
|
||||||
|
{{- end }}
|
||||||
|
{{- if .Values.githubRepo }}
|
||||||
- name: GITHUB_REPO
|
- name: GITHUB_REPO
|
||||||
value: {{ .Values.githubRepo | quote }}
|
value: {{ .Values.githubRepo | quote }}
|
||||||
|
{{- end }}
|
||||||
|
{{- if .Values.githubRepos }}
|
||||||
|
- name: GITHUB_REPOS
|
||||||
|
value: {{ join "," .Values.githubRepos | quote }}
|
||||||
|
{{- end }}
|
||||||
envFrom:
|
envFrom:
|
||||||
- secretRef:
|
- secretRef:
|
||||||
name: {{ include "devcontainer.envSecretName" . }}
|
name: {{ include "devcontainer.envSecretName" . }}
|
||||||
@@ -166,10 +174,32 @@ 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.helm.enabled }}
|
||||||
|
- name: helm-mcp
|
||||||
|
image: "{{ .Values.mcp.sidecars.helm.image.repository }}:{{ .Values.mcp.sidecars.helm.image.tag }}"
|
||||||
|
args:
|
||||||
|
- -mode=sse
|
||||||
|
ports:
|
||||||
|
- containerPort: {{ .Values.mcp.sidecars.helm.port }}
|
||||||
|
name: helm-mcp
|
||||||
|
protocol: TCP
|
||||||
|
livenessProbe:
|
||||||
|
tcpSocket:
|
||||||
|
port: {{ .Values.mcp.sidecars.helm.port }}
|
||||||
|
initialDelaySeconds: 10
|
||||||
|
periodSeconds: 10
|
||||||
|
readinessProbe:
|
||||||
|
tcpSocket:
|
||||||
|
port: {{ .Values.mcp.sidecars.helm.port }}
|
||||||
|
initialDelaySeconds: 5
|
||||||
|
periodSeconds: 5
|
||||||
|
resources:
|
||||||
|
{{- toYaml .Values.mcp.sidecars.helm.resources | nindent 12 }}
|
||||||
|
{{- end }}
|
||||||
{{- if .Values.mcp.sidecars.homeassistant.enabled }}
|
{{- 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 +233,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 +267,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
|
||||||
|
|||||||
@@ -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 }}
|
||||||
|
|||||||
@@ -8,6 +8,11 @@ name: mydev
|
|||||||
# GitHub repository to clone (required)
|
# GitHub repository to clone (required)
|
||||||
githubRepo: https://github.com/youruser/yourrepo
|
githubRepo: https://github.com/youruser/yourrepo
|
||||||
|
|
||||||
|
# Multiple repositories (optional, takes precedence over githubRepo)
|
||||||
|
# githubRepos:
|
||||||
|
# - https://github.com/youruser/repo1
|
||||||
|
# - https://github.com/youruser/repo2
|
||||||
|
|
||||||
# IDE choice (optional - defaults to vscode)
|
# IDE choice (optional - defaults to vscode)
|
||||||
# Options: vscode | antigravity | none
|
# Options: vscode | antigravity | none
|
||||||
ide:
|
ide:
|
||||||
|
|||||||
+28
-28
@@ -34,8 +34,30 @@
|
|||||||
},
|
},
|
||||||
"githubRepo": {
|
"githubRepo": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"description": "GitHub repository URL to clone",
|
"description": "GitHub repository URL to clone into /workspace"
|
||||||
"pattern": "^https://github\\.com/.+/.+$"
|
},
|
||||||
|
"githubRepos": {
|
||||||
|
"type": "array",
|
||||||
|
"items": { "type": "string" },
|
||||||
|
"description": "Multiple GitHub repository URLs to clone (takes precedence over githubRepo)"
|
||||||
|
},
|
||||||
|
"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"]
|
||||||
},
|
},
|
||||||
"ide": {
|
"ide": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
@@ -108,7 +130,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",
|
||||||
@@ -138,31 +160,6 @@
|
|||||||
"enum": ["none", "readonlyns", "readwritens", "readonly", "readwrite"],
|
"enum": ["none", "readonlyns", "readwritens", "readonly", "readwrite"],
|
||||||
"description": "Kubernetes cluster access level"
|
"description": "Kubernetes cluster access level"
|
||||||
},
|
},
|
||||||
"happy": {
|
|
||||||
"type": "object",
|
|
||||||
"properties": {
|
|
||||||
"serverUrl": {
|
|
||||||
"type": "string",
|
|
||||||
"format": "uri",
|
|
||||||
"description": "Happy Coder server URL"
|
|
||||||
},
|
|
||||||
"webappUrl": {
|
|
||||||
"type": "string",
|
|
||||||
"format": "uri",
|
|
||||||
"description": "Happy Coder webapp URL"
|
|
||||||
},
|
|
||||||
"homeDir": {
|
|
||||||
"type": "string",
|
|
||||||
"description": "Happy Coder home directory"
|
|
||||||
},
|
|
||||||
"experimental": {
|
|
||||||
"type": "string",
|
|
||||||
"enum": ["true", "false"],
|
|
||||||
"description": "Enable experimental Happy features"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"required": ["serverUrl", "webappUrl", "homeDir", "experimental"]
|
|
||||||
},
|
|
||||||
"mcp": {
|
"mcp": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
@@ -181,6 +178,9 @@
|
|||||||
"pgtuner": {
|
"pgtuner": {
|
||||||
"$ref": "#/$defs/mcpSidecar"
|
"$ref": "#/$defs/mcpSidecar"
|
||||||
},
|
},
|
||||||
|
"helm": {
|
||||||
|
"$ref": "#/$defs/mcpSidecar"
|
||||||
|
},
|
||||||
"playwright": {
|
"playwright": {
|
||||||
"$ref": "#/$defs/mcpSidecar"
|
"$ref": "#/$defs/mcpSidecar"
|
||||||
}
|
}
|
||||||
|
|||||||
+34
-10
@@ -14,6 +14,13 @@ image:
|
|||||||
# GitHub repository to clone into /workspace
|
# GitHub repository to clone into /workspace
|
||||||
githubRepo: ""
|
githubRepo: ""
|
||||||
|
|
||||||
|
# Multiple GitHub repositories to clone into /workspace (takes precedence over githubRepo)
|
||||||
|
# Example:
|
||||||
|
# githubRepos:
|
||||||
|
# - https://github.com/user/repo1
|
||||||
|
# - https://github.com/user/repo2
|
||||||
|
githubRepos: []
|
||||||
|
|
||||||
# =============================================================================
|
# =============================================================================
|
||||||
# ACCESS & INTERFACE
|
# ACCESS & INTERFACE
|
||||||
# =============================================================================
|
# =============================================================================
|
||||||
@@ -27,6 +34,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 +62,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:
|
||||||
@@ -68,13 +85,6 @@ clusterAccess: none
|
|||||||
# INTEGRATIONS
|
# INTEGRATIONS
|
||||||
# =============================================================================
|
# =============================================================================
|
||||||
|
|
||||||
# Happy Coder AI assistant configuration
|
|
||||||
happy:
|
|
||||||
serverUrl: "https://happy.farh.net"
|
|
||||||
webappUrl: "https://happy-coder.farh.net"
|
|
||||||
homeDir: "/config/userdata/.happy"
|
|
||||||
experimental: "true"
|
|
||||||
|
|
||||||
# MCP (Model Context Protocol) server sidecars
|
# MCP (Model Context Protocol) server sidecars
|
||||||
mcp:
|
mcp:
|
||||||
sidecars:
|
sidecars:
|
||||||
@@ -109,13 +119,27 @@ mcp:
|
|||||||
cpu: "500m"
|
cpu: "500m"
|
||||||
|
|
||||||
|
|
||||||
|
# Helm chart browsing and management
|
||||||
|
helm:
|
||||||
|
enabled: false
|
||||||
|
image:
|
||||||
|
repository: ghcr.io/zekker6/mcp-helm
|
||||||
|
tag: v1.3.1
|
||||||
|
port: 8012
|
||||||
|
resources:
|
||||||
|
requests:
|
||||||
|
memory: "64Mi"
|
||||||
|
cpu: "50m"
|
||||||
|
limits:
|
||||||
|
memory: "256Mi"
|
||||||
|
cpu: "500m"
|
||||||
|
|
||||||
# Home Assistant smart home control
|
# 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: "6.7.1"
|
||||||
port: 8087
|
port: 8087
|
||||||
resources:
|
resources:
|
||||||
requests:
|
requests:
|
||||||
@@ -145,7 +169,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:
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
+81
-28
@@ -4,6 +4,13 @@ set -e
|
|||||||
|
|
||||||
echo "=== Repository Initialization ==="
|
echo "=== Repository Initialization ==="
|
||||||
|
|
||||||
|
# Ensure home directory exists on the PVC before any git operations
|
||||||
|
# (git config --global writes to $HOME/.gitconfig, which fails on a fresh volume)
|
||||||
|
RUN_UID="${USER_ID:-1000}"
|
||||||
|
RUN_GID="${GROUP_ID:-1000}"
|
||||||
|
mkdir -p "$HOME"
|
||||||
|
chown "$RUN_UID:$RUN_GID" "$HOME"
|
||||||
|
|
||||||
# Set up basic git configuration
|
# Set up basic git configuration
|
||||||
echo "Configuring git user settings..."
|
echo "Configuring git user settings..."
|
||||||
# Use environment variables if provided, otherwise use defaults
|
# Use environment variables if provided, otherwise use defaults
|
||||||
@@ -22,6 +29,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 +59,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"
|
||||||
|
|
||||||
@@ -60,48 +69,92 @@ else
|
|||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Check if GITHUB_REPO is set
|
# Build list of repositories to clone
|
||||||
if [ -z "$GITHUB_REPO" ]; then
|
REPOS=()
|
||||||
echo "GITHUB_REPO not set, skipping repository clone"
|
if [ -n "$GITHUB_REPOS" ]; then
|
||||||
|
# GITHUB_REPOS is a comma-separated list (takes precedence over GITHUB_REPO)
|
||||||
|
IFS=',' read -ra RAW_REPOS <<< "$GITHUB_REPOS"
|
||||||
|
for repo in "${RAW_REPOS[@]}"; do
|
||||||
|
repo="$(echo "$repo" | xargs)" # trim whitespace
|
||||||
|
[ -n "$repo" ] && REPOS+=("$repo")
|
||||||
|
done
|
||||||
|
elif [ -n "$GITHUB_REPO" ]; then
|
||||||
|
REPOS+=("$GITHUB_REPO")
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ ${#REPOS[@]} -eq 0 ]; then
|
||||||
|
echo "No repositories configured, skipping clone"
|
||||||
WORKSPACE_DIR="/workspace/default"
|
WORKSPACE_DIR="/workspace/default"
|
||||||
mkdir -p "$WORKSPACE_DIR"
|
mkdir -p "$WORKSPACE_DIR"
|
||||||
else
|
else
|
||||||
# Parse repo name from URL
|
CLONED_DIRS=()
|
||||||
REPO_NAME=$(basename "$GITHUB_REPO" .git)
|
for REPO_URL in "${REPOS[@]}"; do
|
||||||
WORKSPACE_DIR="/workspace/$REPO_NAME"
|
REPO_NAME=$(basename "$REPO_URL" .git)
|
||||||
|
REPO_DIR="/workspace/$REPO_NAME"
|
||||||
|
|
||||||
echo "Repository: $GITHUB_REPO"
|
echo "Repository: $REPO_URL"
|
||||||
echo "Target directory: $WORKSPACE_DIR"
|
echo "Target directory: $REPO_DIR"
|
||||||
|
|
||||||
# Check if repo already exists
|
if [ -d "$REPO_DIR/.git" ]; then
|
||||||
if [ -d "$WORKSPACE_DIR/.git" ]; then
|
echo "Repository already exists, pulling latest changes..."
|
||||||
echo "Repository already exists, pulling latest changes..."
|
cd "$REPO_DIR"
|
||||||
cd "$WORKSPACE_DIR"
|
git pull || echo "Pull failed, continuing anyway..."
|
||||||
git pull || echo "Pull failed, continuing anyway..."
|
|
||||||
else
|
|
||||||
echo "Cloning repository..."
|
|
||||||
mkdir -p "$(dirname "$WORKSPACE_DIR")"
|
|
||||||
|
|
||||||
# Clone with token if provided
|
|
||||||
if [ -n "$GITHUB_TOKEN" ]; then
|
|
||||||
# Replace https://github.com/ with https://oauth2:token@github.com/
|
|
||||||
CLONE_URL=$(echo "$GITHUB_REPO" | sed "s|https://github.com/|https://oauth2:${GITHUB_TOKEN}@github.com/|")
|
|
||||||
git clone "$CLONE_URL" "$WORKSPACE_DIR"
|
|
||||||
else
|
else
|
||||||
git clone "$GITHUB_REPO" "$WORKSPACE_DIR"
|
echo "Cloning repository..."
|
||||||
|
mkdir -p "$(dirname "$REPO_DIR")"
|
||||||
|
|
||||||
|
if [ -n "$GITHUB_TOKEN" ]; then
|
||||||
|
CLONE_URL=$(echo "$REPO_URL" | sed "s|https://github.com/|https://oauth2:${GITHUB_TOKEN}@github.com/|")
|
||||||
|
git clone "$CLONE_URL" "$REPO_DIR"
|
||||||
|
else
|
||||||
|
git clone "$REPO_URL" "$REPO_DIR"
|
||||||
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
CLONED_DIRS+=("$REPO_DIR")
|
||||||
|
done
|
||||||
|
|
||||||
|
if [ ${#CLONED_DIRS[@]} -eq 1 ]; then
|
||||||
|
# Single repo — open directory directly (same as legacy behavior)
|
||||||
|
WORKSPACE_DIR="${CLONED_DIRS[0]}"
|
||||||
|
else
|
||||||
|
# Multiple repos — generate a multi-root workspace file
|
||||||
|
WS_FILE="/workspace/workspace.code-workspace"
|
||||||
|
printf '{\n "folders": [\n' > "$WS_FILE"
|
||||||
|
for i in "${!CLONED_DIRS[@]}"; do
|
||||||
|
printf ' {"path": "%s"}' "${CLONED_DIRS[$i]}" >> "$WS_FILE"
|
||||||
|
if [ "$i" -lt $(( ${#CLONED_DIRS[@]} - 1 )) ]; then
|
||||||
|
printf ',\n' >> "$WS_FILE"
|
||||||
|
else
|
||||||
|
printf '\n' >> "$WS_FILE"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
printf ' ],\n "settings": {}\n}\n' >> "$WS_FILE"
|
||||||
|
WORKSPACE_DIR="$WS_FILE"
|
||||||
|
echo "Generated multi-root workspace: $WS_FILE"
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Set ownership using numeric IDs (username may not exist yet in baseimage-gui)
|
# Set ownership using numeric IDs (username may not exist yet in baseimage-gui)
|
||||||
RUN_UID="${USER_ID:-1000}"
|
RUN_UID="${USER_ID:-1000}"
|
||||||
RUN_GID="${GROUP_ID:-1000}"
|
RUN_GID="${GROUP_ID:-1000}"
|
||||||
chown -R "$RUN_UID:$RUN_GID" "$WORKSPACE_DIR"
|
for dir in "${CLONED_DIRS[@]}"; do
|
||||||
|
chown -R "$RUN_UID:$RUN_GID" "$dir"
|
||||||
# Ensure home directory exists on the PVC (may be absent on a fresh volume)
|
done
|
||||||
mkdir -p "$HOME"
|
if [ -n "$WS_FILE" ] && [ -f "$WS_FILE" ]; then
|
||||||
chown "$RUN_UID:$RUN_GID" "$HOME"
|
chown "$RUN_UID:$RUN_GID" "$WS_FILE"
|
||||||
|
fi
|
||||||
|
# Ensure default workspace dir ownership if no repos were cloned
|
||||||
|
if [ ${#REPOS[@]} -eq 0 ]; then
|
||||||
|
chown -R "$RUN_UID:$RUN_GID" "$WORKSPACE_DIR"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Seed Claude Code settings if missing (disable auto-updater in Docker)
|
||||||
|
if [ ! -f "$HOME/.claude/settings.json" ]; then
|
||||||
|
mkdir -p "$HOME/.claude"
|
||||||
|
echo '{"env":{"DISABLE_AUTOUPDATER":"1"}}' > "$HOME/.claude/settings.json"
|
||||||
|
chown -R "$RUN_UID:$RUN_GID" "$HOME/.claude"
|
||||||
|
fi
|
||||||
|
|
||||||
# Export workspace directory for startapp.sh
|
# Export workspace directory for startapp.sh
|
||||||
echo "$WORKSPACE_DIR" > /tmp/workspace-dir
|
echo "$WORKSPACE_DIR" > /tmp/workspace-dir
|
||||||
|
|||||||
@@ -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"
|
||||||
;;
|
;;
|
||||||
|
|||||||
Reference in New Issue
Block a user