Compare commits
24 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 |
@@ -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
|
||||||
|
|||||||
@@ -56,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
|
||||||
|
|||||||
@@ -96,12 +96,13 @@ 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: Publish Helm Chart to GitHub Pages
|
- name: Publish Helm Chart to GitHub Pages
|
||||||
|
|||||||
@@ -18,10 +18,6 @@
|
|||||||
"playwright": {
|
"playwright": {
|
||||||
"type": "sse",
|
"type": "sse",
|
||||||
"url": "http://localhost:8086/sse"
|
"url": "http://localhost:8086/sse"
|
||||||
},
|
|
||||||
"helm": {
|
|
||||||
"type": "sse",
|
|
||||||
"url": "http://localhost:8088/sse"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -6,7 +6,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, OpenCode, and Crush AI coding agents (terminal-based)
|
||||||
- Built-in web file manager for uploading/downloading files (optional, via `fileManager.enabled`)
|
- 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
|
||||||
@@ -61,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
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -69,8 +70,8 @@ Container start
|
|||||||
|
|
||||||
| File | Purpose |
|
| File | Purpose |
|
||||||
|------|---------|
|
|------|---------|
|
||||||
| `Dockerfile` | Image definition — installs Chrome, Node.js, VSCode, Helm, gh CLI, kubeseal, 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 |
|
||||||
@@ -89,7 +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 | 8088 | `http://localhost:8088/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 |
|
||||||
@@ -177,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)
|
||||||
@@ -188,8 +190,6 @@ 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
|
|
||||||
- `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` — 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_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)
|
- `WEB_FILE_MANAGER_DENIED_PATHS` — Paths to deny access to (takes precedence over allowed)
|
||||||
|
|||||||
@@ -225,18 +225,6 @@ spec:
|
|||||||
|
|
||||||
## Advanced Configurations
|
## Advanced Configurations
|
||||||
|
|
||||||
### Custom Happy Coder Endpoints
|
|
||||||
|
|
||||||
For self-hosted Happy instances:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
helm install mydev ./chart \
|
|
||||||
--set name=mydev \
|
|
||||||
--set githubRepo=https://github.com/youruser/yourrepo \
|
|
||||||
--set happyServerUrl=https://your-happy-server.com \
|
|
||||||
--set happyWebappUrl=https://your-happy-webapp.com
|
|
||||||
```
|
|
||||||
|
|
||||||
### Custom Display Resolution
|
### Custom Display Resolution
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
|
|||||||
+69
-9
@@ -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,12 @@ 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 (required by 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 globally via npm (stable, rarely changes)
|
|
||||||
RUN npm install -g happy-coder
|
|
||||||
|
|
||||||
# Install Claude Code native binary (npm wrapper breaks remote control)
|
# Install Claude Code native binary (npm wrapper breaks remote control)
|
||||||
RUN curl -fsSL https://claude.ai/install.sh | bash && \
|
RUN curl -fsSL https://claude.ai/install.sh | bash && \
|
||||||
@@ -70,6 +72,10 @@ RUN curl -fsSL https://claude.ai/install.sh | bash && \
|
|||||||
rm -rf /root/.local/bin/claude && \
|
rm -rf /root/.local/bin/claude && \
|
||||||
claude --version
|
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') && \
|
||||||
curl -fsSL "https://github.com/opencode-ai/opencode/releases/download/${OPENCODE_VERSION}/opencode-linux-x86_64.tar.gz" | \
|
curl -fsSL "https://github.com/opencode-ai/opencode/releases/download/${OPENCODE_VERSION}/opencode-linux-x86_64.tar.gz" | \
|
||||||
@@ -90,6 +96,13 @@ 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 && \
|
tar -xz --strip-components=1 -C /usr/local/bin linux-amd64/helm && \
|
||||||
chmod +x /usr/local/bin/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
|
# 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 && \
|
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 && \
|
chmod go+r /usr/share/keyrings/githubcli-archive-keyring.gpg && \
|
||||||
@@ -104,6 +117,56 @@ RUN KUBESEAL_VERSION=$(curl -sL https://api.github.com/repos/bitnami-labs/sealed
|
|||||||
tar -xz -C /usr/local/bin kubeseal && \
|
tar -xz -C /usr/local/bin kubeseal && \
|
||||||
chmod +x /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)
|
# Install VSCode (using Microsoft's current recommended setup)
|
||||||
RUN wget -qO- https://packages.microsoft.com/keys/microsoft.asc | gpg --dearmor > /tmp/microsoft.gpg && \
|
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 && \
|
install -D -o root -g root -m 644 /tmp/microsoft.gpg /usr/share/keyrings/microsoft.gpg && \
|
||||||
@@ -162,9 +225,6 @@ RUN mkdir -p /workspace && \
|
|||||||
# Copy startup scripts
|
# Copy startup scripts
|
||||||
COPY --chmod=755 scripts/startapp.sh /startapp.sh
|
COPY --chmod=755 scripts/startapp.sh /startapp.sh
|
||||||
COPY --chmod=755 scripts/init-repo.sh /usr/local/bin/init-repo
|
COPY --chmod=755 scripts/init-repo.sh /usr/local/bin/init-repo
|
||||||
# Copy serverless scripts (conditional execution)
|
|
||||||
COPY --chmod=755 serverless/scripts/dynamic-init-repo.sh /usr/local/bin/dynamic-init-repo
|
|
||||||
COPY --chmod=755 serverless/scripts/serverless-startapp.sh /usr/local/bin/serverless-startapp
|
|
||||||
# Fix app user shell after baseimage-gui creates it at runtime
|
# Fix app user shell after baseimage-gui creates it at runtime
|
||||||
COPY --chmod=755 scripts/cont-init-user.sh /etc/cont-init.d/20-fix-user-shell.sh
|
COPY --chmod=755 scripts/cont-init-user.sh /etc/cont-init.d/20-fix-user-shell.sh
|
||||||
COPY --chmod=755 scripts/cont-init-sshd.sh /etc/cont-init.d/25-start-sshd.sh
|
COPY --chmod=755 scripts/cont-init-sshd.sh /etc/cont-init.d/25-start-sshd.sh
|
||||||
|
|||||||
@@ -26,7 +26,6 @@ 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 devcontainer \
|
--name devcontainer \
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
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
|
- **Built-in web file manager** for uploading/downloading files via the VNC web interface
|
||||||
- **Helm CLI** included for Kubernetes chart development and deployment
|
- **Helm CLI** included for Kubernetes chart development and deployment
|
||||||
- **Automatic GitHub repo cloning** on startup
|
- **Automatic GitHub repo cloning** on startup
|
||||||
@@ -114,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**:
|
||||||
@@ -189,15 +189,6 @@ helm install mydev ./chart \
|
|||||||
--set fileManager.enabled=true
|
--set fileManager.enabled=true
|
||||||
```
|
```
|
||||||
|
|
||||||
### Happy Coder
|
|
||||||
|
|
||||||
| Value | Default | Description |
|
|
||||||
|-------|---------|-------------|
|
|
||||||
| `happy.serverUrl` | `https://happy.farh.net` | Happy Coder server endpoint |
|
|
||||||
| `happy.webappUrl` | `https://happy-coder.farh.net` | Happy Coder webapp URL |
|
|
||||||
| `happy.homeDir` | `/config/userdata/.happy` | Happy runtime state directory (persists on the home PVC) |
|
|
||||||
| `happy.experimental` | `true` | Enable experimental Happy features |
|
|
||||||
|
|
||||||
### Kubernetes cluster access
|
### Kubernetes cluster access
|
||||||
|
|
||||||
The `clusterAccess` value provisions a ServiceAccount, Role/ClusterRole, and binding so the devcontainer pod can interact with the Kubernetes API. The default is `none` — no RBAC resources are created.
|
The `clusterAccess` value provisions a ServiceAccount, Role/ClusterRole, and binding so the devcontainer pod can interact with the Kubernetes API. The default is `none` — no RBAC resources are created.
|
||||||
@@ -377,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:
|
||||||
@@ -466,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 |
|
||||||
|
|||||||
+2
-4
@@ -1,13 +1,11 @@
|
|||||||
apiVersion: v2
|
apiVersion: v2
|
||||||
name: devcontainer
|
name: devcontainer
|
||||||
description: Dev Container with AI coding agents and MCP sidecars - supports persistent and dynamic deployment modes
|
description: Dev Container with AI coding agents and MCP sidecars
|
||||||
type: application
|
type: application
|
||||||
version: 2.1.1
|
version: 2.6.0
|
||||||
appVersion: "latest"
|
appVersion: "latest"
|
||||||
keywords:
|
keywords:
|
||||||
- development
|
- development
|
||||||
- devcontainer
|
- devcontainer
|
||||||
- vscode
|
- vscode
|
||||||
- ai
|
- ai
|
||||||
- knative
|
|
||||||
- serverless
|
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
{{- if eq .Values.deploymentMode "persistent" }}
|
|
||||||
apiVersion: apps/v1
|
apiVersion: apps/v1
|
||||||
kind: Deployment
|
kind: Deployment
|
||||||
metadata:
|
metadata:
|
||||||
@@ -80,18 +79,14 @@ spec:
|
|||||||
value: {{ .Values.fileManager.deniedPaths | quote }}
|
value: {{ .Values.fileManager.deniedPaths | quote }}
|
||||||
{{- end }}
|
{{- end }}
|
||||||
{{- end }}
|
{{- end }}
|
||||||
- name: HAPPY_HOME_DIR
|
|
||||||
value: {{ .Values.happy.homeDir | quote }}
|
|
||||||
- name: HAPPY_EXPERIMENTAL
|
|
||||||
value: {{ .Values.happy.experimental | quote }}
|
|
||||||
- name: HAPPY_SERVER_URL
|
|
||||||
value: {{ .Values.happy.serverUrl | quote }}
|
|
||||||
- name: HAPPY_WEBAPP_URL
|
|
||||||
value: {{ .Values.happy.webappUrl | quote }}
|
|
||||||
{{- if .Values.githubRepo }}
|
{{- if .Values.githubRepo }}
|
||||||
- name: GITHUB_REPO
|
- name: GITHUB_REPO
|
||||||
value: {{ .Values.githubRepo | quote }}
|
value: {{ .Values.githubRepo | quote }}
|
||||||
{{- end }}
|
{{- 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" . }}
|
||||||
@@ -184,7 +179,6 @@ spec:
|
|||||||
image: "{{ .Values.mcp.sidecars.helm.image.repository }}:{{ .Values.mcp.sidecars.helm.image.tag }}"
|
image: "{{ .Values.mcp.sidecars.helm.image.repository }}:{{ .Values.mcp.sidecars.helm.image.tag }}"
|
||||||
args:
|
args:
|
||||||
- -mode=sse
|
- -mode=sse
|
||||||
- -port={{ .Values.mcp.sidecars.helm.port }}
|
|
||||||
ports:
|
ports:
|
||||||
- containerPort: {{ .Values.mcp.sidecars.helm.port }}
|
- containerPort: {{ .Values.mcp.sidecars.helm.port }}
|
||||||
name: helm-mcp
|
name: helm-mcp
|
||||||
@@ -314,4 +308,3 @@ spec:
|
|||||||
- name: userhome
|
- name: userhome
|
||||||
persistentVolumeClaim:
|
persistentVolumeClaim:
|
||||||
claimName: {{ include "devcontainer.pvcName" . }}
|
claimName: {{ include "devcontainer.pvcName" . }}
|
||||||
{{- end }}
|
|
||||||
|
|||||||
@@ -1,68 +0,0 @@
|
|||||||
{{- if and (eq .Values.deploymentMode "dynamic") .Values.dynamic.ingress.enabled .Values.dynamic.ingress.host }}
|
|
||||||
apiVersion: networking.k8s.io/v1
|
|
||||||
kind: Ingress
|
|
||||||
metadata:
|
|
||||||
name: {{ include "devcontainer.fullname" . }}-dynamic
|
|
||||||
labels:
|
|
||||||
{{- include "devcontainer.labels" . | nindent 4 }}
|
|
||||||
app.kubernetes.io/component: dynamic-ingress
|
|
||||||
annotations:
|
|
||||||
{{- if .Values.dynamic.ingress.className }}
|
|
||||||
kubernetes.io/ingress.class: {{ .Values.dynamic.ingress.className }}
|
|
||||||
{{- end }}
|
|
||||||
|
|
||||||
# SSL configuration
|
|
||||||
{{- if .Values.dynamic.ingress.tls.enabled }}
|
|
||||||
cert-manager.io/cluster-issuer: {{ .Values.dynamic.ingress.tls.issuer | quote }}
|
|
||||||
nginx.ingress.kubernetes.io/ssl-redirect: "true"
|
|
||||||
nginx.ingress.kubernetes.io/force-ssl-redirect: "true"
|
|
||||||
{{- end }}
|
|
||||||
|
|
||||||
# Authentik forward auth (if enabled)
|
|
||||||
{{- if .Values.dynamic.ingress.authentik.enabled }}
|
|
||||||
nginx.ingress.kubernetes.io/auth-url: {{ .Values.dynamic.ingress.authentik.authUrl | quote }}
|
|
||||||
nginx.ingress.kubernetes.io/auth-signin: {{ .Values.dynamic.ingress.authentik.signIn | quote }}
|
|
||||||
nginx.ingress.kubernetes.io/auth-response-headers: "X-Authentik-Username,X-Authentik-Groups,X-Authentik-Email,X-Authentik-Name"
|
|
||||||
nginx.ingress.kubernetes.io/auth-snippet: |
|
|
||||||
proxy_set_header X-Forwarded-Host $http_host;
|
|
||||||
{{- end }}
|
|
||||||
|
|
||||||
# WebSocket support for VNC connections
|
|
||||||
nginx.ingress.kubernetes.io/proxy-read-timeout: "3600"
|
|
||||||
nginx.ingress.kubernetes.io/proxy-send-timeout: "3600"
|
|
||||||
|
|
||||||
# Large file upload support (for file manager)
|
|
||||||
nginx.ingress.kubernetes.io/client-max-body-size: "100m"
|
|
||||||
nginx.ingress.kubernetes.io/proxy-body-size: "100m"
|
|
||||||
|
|
||||||
# Custom server snippet for GitHub repo logging
|
|
||||||
nginx.ingress.kubernetes.io/server-snippet: |
|
|
||||||
location ~ ^/github/([^/]+/[^/]+) {
|
|
||||||
# Log the GitHub repo being accessed
|
|
||||||
access_log /var/log/nginx/devcontainer-access.log combined;
|
|
||||||
|
|
||||||
# Set additional headers for audit/monitoring
|
|
||||||
proxy_set_header X-GitHub-Repo-Requested https://github.com/$1;
|
|
||||||
proxy_set_header X-Request-Timestamp $time_iso8601;
|
|
||||||
proxy_set_header X-Client-IP $remote_addr;
|
|
||||||
}
|
|
||||||
|
|
||||||
spec:
|
|
||||||
{{- if .Values.dynamic.ingress.tls.enabled }}
|
|
||||||
tls:
|
|
||||||
- hosts:
|
|
||||||
- {{ .Values.dynamic.ingress.host }}
|
|
||||||
secretName: {{ .Values.dynamic.ingress.tls.secretName | default (printf "%s-tls" (include "devcontainer.fullname" .)) }}
|
|
||||||
{{- end }}
|
|
||||||
rules:
|
|
||||||
- host: {{ .Values.dynamic.ingress.host }}
|
|
||||||
http:
|
|
||||||
paths:
|
|
||||||
- path: /
|
|
||||||
pathType: Prefix
|
|
||||||
backend:
|
|
||||||
service:
|
|
||||||
name: {{ include "devcontainer.fullname" . }}-routing-proxy
|
|
||||||
port:
|
|
||||||
number: 80
|
|
||||||
{{- end }}
|
|
||||||
@@ -1,111 +0,0 @@
|
|||||||
{{- if eq .Values.deploymentMode "dynamic" }}
|
|
||||||
apiVersion: serving.knative.dev/v1
|
|
||||||
kind: Service
|
|
||||||
metadata:
|
|
||||||
name: {{ include "devcontainer.fullname" . }}
|
|
||||||
labels:
|
|
||||||
{{- include "devcontainer.labels" . | nindent 4 }}
|
|
||||||
annotations:
|
|
||||||
# Knative scaling annotations
|
|
||||||
autoscaling.knative.dev/minScale: {{ .Values.dynamic.knative.minScale | quote }}
|
|
||||||
autoscaling.knative.dev/maxScale: {{ .Values.dynamic.knative.maxScale | quote }}
|
|
||||||
autoscaling.knative.dev/target: {{ .Values.dynamic.knative.target | quote }}
|
|
||||||
autoscaling.knative.dev/scale-to-zero-grace-period: {{ .Values.dynamic.knative.scaleToZeroGracePeriod | quote }}
|
|
||||||
spec:
|
|
||||||
template:
|
|
||||||
metadata:
|
|
||||||
labels:
|
|
||||||
{{- include "devcontainer.labels" . | nindent 8 }}
|
|
||||||
annotations:
|
|
||||||
# Container configuration
|
|
||||||
autoscaling.knative.dev/targetPort: "5800"
|
|
||||||
serving.knative.dev/timeoutSeconds: {{ .Values.dynamic.knative.timeoutSeconds | quote }}
|
|
||||||
# Scaling configuration
|
|
||||||
autoscaling.knative.dev/class: "kpa.autoscaling.knative.dev"
|
|
||||||
autoscaling.knative.dev/metric: "concurrency"
|
|
||||||
spec:
|
|
||||||
# Container startup timeout
|
|
||||||
timeoutSeconds: {{ .Values.dynamic.knative.timeoutSeconds }}
|
|
||||||
containers:
|
|
||||||
- name: devcontainer
|
|
||||||
image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}"
|
|
||||||
imagePullPolicy: {{ .Values.image.pullPolicy }}
|
|
||||||
ports:
|
|
||||||
- containerPort: 5800
|
|
||||||
name: vnc-web
|
|
||||||
env:
|
|
||||||
# Dynamic mode flags
|
|
||||||
- name: SERVERLESS_MODE
|
|
||||||
value: "true"
|
|
||||||
- name: DYNAMIC_GITHUB_ROUTING
|
|
||||||
value: "true"
|
|
||||||
- name: DEPLOYMENT_MODE
|
|
||||||
value: "dynamic"
|
|
||||||
# Standard configuration
|
|
||||||
- name: IDE
|
|
||||||
value: {{ .Values.ide.type | default "vscode" | quote }}
|
|
||||||
- name: USER_ID
|
|
||||||
value: {{ .Values.user.id | quote }}
|
|
||||||
- name: GROUP_ID
|
|
||||||
value: {{ .Values.user.groupId | quote }}
|
|
||||||
- name: DISPLAY_WIDTH
|
|
||||||
value: {{ .Values.display.width | quote }}
|
|
||||||
- name: DISPLAY_HEIGHT
|
|
||||||
value: {{ .Values.display.height | quote }}
|
|
||||||
- name: SECURE_CONNECTION
|
|
||||||
value: {{ .Values.display.secureConnection | quote }}
|
|
||||||
# File manager (always enabled in dynamic mode for easy file transfer)
|
|
||||||
- name: WEB_FILE_MANAGER
|
|
||||||
value: "1"
|
|
||||||
- name: WEB_FILE_MANAGER_ALLOWED_PATHS
|
|
||||||
value: "/workspace,/tmp" # No persistent /config in dynamic mode
|
|
||||||
# Happy Coder (ephemeral in dynamic mode)
|
|
||||||
- name: HAPPY_HOME_DIR
|
|
||||||
value: "/tmp/.happy"
|
|
||||||
- name: HAPPY_EXPERIMENTAL
|
|
||||||
value: {{ .Values.happy.experimental | quote }}
|
|
||||||
{{- if .Values.happy.serverUrl }}
|
|
||||||
- name: HAPPY_SERVER_URL
|
|
||||||
value: {{ .Values.happy.serverUrl | quote }}
|
|
||||||
{{- end }}
|
|
||||||
{{- if .Values.happy.webappUrl }}
|
|
||||||
- name: HAPPY_WEBAPP_URL
|
|
||||||
value: {{ .Values.happy.webappUrl | quote }}
|
|
||||||
{{- end }}
|
|
||||||
# Secret environment variables
|
|
||||||
envFrom:
|
|
||||||
- secretRef:
|
|
||||||
name: {{ include "devcontainer.envSecretName" . }}
|
|
||||||
optional: true
|
|
||||||
resources:
|
|
||||||
{{- toYaml .Values.dynamic.knative.resources | nindent 10 }}
|
|
||||||
volumeMounts:
|
|
||||||
- name: tmp-home
|
|
||||||
mountPath: /config
|
|
||||||
- name: shm
|
|
||||||
mountPath: /dev/shm
|
|
||||||
# Health probes (adjusted for dynamic mode startup time)
|
|
||||||
readinessProbe:
|
|
||||||
httpGet:
|
|
||||||
path: /
|
|
||||||
port: 5800
|
|
||||||
initialDelaySeconds: 60
|
|
||||||
periodSeconds: 10
|
|
||||||
timeoutSeconds: 5
|
|
||||||
failureThreshold: 10
|
|
||||||
livenessProbe:
|
|
||||||
httpGet:
|
|
||||||
path: /
|
|
||||||
port: 5800
|
|
||||||
initialDelaySeconds: 120
|
|
||||||
periodSeconds: 30
|
|
||||||
timeoutSeconds: 10
|
|
||||||
failureThreshold: 3
|
|
||||||
volumes:
|
|
||||||
- name: tmp-home
|
|
||||||
emptyDir: {} # Ephemeral - each instance gets fresh home
|
|
||||||
- name: shm
|
|
||||||
emptyDir:
|
|
||||||
medium: Memory
|
|
||||||
sizeLimit: {{ .Values.shm.sizeLimit }}
|
|
||||||
{{- end }}
|
|
||||||
@@ -1,4 +1,3 @@
|
|||||||
{{- if eq .Values.deploymentMode "persistent" }}
|
|
||||||
apiVersion: v1
|
apiVersion: v1
|
||||||
kind: PersistentVolumeClaim
|
kind: PersistentVolumeClaim
|
||||||
metadata:
|
metadata:
|
||||||
@@ -16,4 +15,3 @@ spec:
|
|||||||
resources:
|
resources:
|
||||||
requests:
|
requests:
|
||||||
storage: {{ .Values.storage.size }}
|
storage: {{ .Values.storage.size }}
|
||||||
{{- end }}
|
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
{{- if eq .Values.deploymentMode "persistent" }}
|
|
||||||
{{- $access := .Values.clusterAccess | default "none" }}
|
{{- $access := .Values.clusterAccess | default "none" }}
|
||||||
{{- $name := include "devcontainer.fullname" . }}
|
{{- $name := include "devcontainer.fullname" . }}
|
||||||
{{- $ns := .Release.Namespace }}
|
{{- $ns := .Release.Namespace }}
|
||||||
@@ -96,4 +95,3 @@ roleRef:
|
|||||||
{{- end }}
|
{{- end }}
|
||||||
|
|
||||||
{{- end }}
|
{{- end }}
|
||||||
{{- end }}
|
|
||||||
|
|||||||
@@ -1,66 +0,0 @@
|
|||||||
{{- if and (eq .Values.deploymentMode "dynamic") .Values.dynamic.routingProxy.enabled }}
|
|
||||||
---
|
|
||||||
# Routing proxy deployment for dynamic GitHub repo extraction
|
|
||||||
apiVersion: apps/v1
|
|
||||||
kind: Deployment
|
|
||||||
metadata:
|
|
||||||
name: {{ include "devcontainer.fullname" . }}-routing-proxy
|
|
||||||
labels:
|
|
||||||
{{- include "devcontainer.labels" . | nindent 4 }}
|
|
||||||
app.kubernetes.io/component: routing-proxy
|
|
||||||
spec:
|
|
||||||
replicas: {{ .Values.dynamic.routingProxy.replicas }}
|
|
||||||
selector:
|
|
||||||
matchLabels:
|
|
||||||
{{- include "devcontainer.selectorLabels" . | nindent 6 }}
|
|
||||||
app.kubernetes.io/component: routing-proxy
|
|
||||||
template:
|
|
||||||
metadata:
|
|
||||||
labels:
|
|
||||||
{{- include "devcontainer.labels" . | nindent 8 }}
|
|
||||||
app.kubernetes.io/component: routing-proxy
|
|
||||||
spec:
|
|
||||||
containers:
|
|
||||||
- name: routing-proxy
|
|
||||||
image: "{{ .Values.dynamic.routingProxy.image.repository }}:{{ .Values.dynamic.routingProxy.image.tag }}"
|
|
||||||
imagePullPolicy: {{ .Values.dynamic.routingProxy.image.pullPolicy }}
|
|
||||||
ports:
|
|
||||||
- containerPort: 8080
|
|
||||||
name: http
|
|
||||||
env:
|
|
||||||
- name: DEVCONTAINER_SERVICE_URL
|
|
||||||
value: "{{ include "devcontainer.fullname" . }}.{{ .Release.Namespace }}.svc.cluster.local"
|
|
||||||
resources:
|
|
||||||
{{- toYaml .Values.dynamic.routingProxy.resources | nindent 10 }}
|
|
||||||
livenessProbe:
|
|
||||||
httpGet:
|
|
||||||
path: /health
|
|
||||||
port: 8080
|
|
||||||
initialDelaySeconds: 5
|
|
||||||
periodSeconds: 10
|
|
||||||
readinessProbe:
|
|
||||||
httpGet:
|
|
||||||
path: /health
|
|
||||||
port: 8080
|
|
||||||
initialDelaySeconds: 2
|
|
||||||
periodSeconds: 5
|
|
||||||
|
|
||||||
---
|
|
||||||
# Service for routing proxy
|
|
||||||
apiVersion: v1
|
|
||||||
kind: Service
|
|
||||||
metadata:
|
|
||||||
name: {{ include "devcontainer.fullname" . }}-routing-proxy
|
|
||||||
labels:
|
|
||||||
{{- include "devcontainer.labels" . | nindent 4 }}
|
|
||||||
app.kubernetes.io/component: routing-proxy
|
|
||||||
spec:
|
|
||||||
type: ClusterIP
|
|
||||||
ports:
|
|
||||||
- port: 80
|
|
||||||
targetPort: 8080
|
|
||||||
name: http
|
|
||||||
selector:
|
|
||||||
{{- include "devcontainer.selectorLabels" . | nindent 4 }}
|
|
||||||
app.kubernetes.io/component: routing-proxy
|
|
||||||
{{- end }}
|
|
||||||
@@ -1,4 +1,3 @@
|
|||||||
{{- if eq .Values.deploymentMode "persistent" }}
|
|
||||||
apiVersion: v1
|
apiVersion: v1
|
||||||
kind: Service
|
kind: Service
|
||||||
metadata:
|
metadata:
|
||||||
@@ -21,4 +20,3 @@ spec:
|
|||||||
{{- end }}
|
{{- end }}
|
||||||
selector:
|
selector:
|
||||||
{{- include "devcontainer.labels" . | nindent 4 }}
|
{{- include "devcontainer.labels" . | nindent 4 }}
|
||||||
{{- end }}
|
|
||||||
|
|||||||
@@ -1,122 +0,0 @@
|
|||||||
# Example values for dynamic (serverless) deployment mode
|
|
||||||
# Copy this file and customize for your environment:
|
|
||||||
# cp values-dynamic.yaml my-dynamic-values.yaml
|
|
||||||
|
|
||||||
# =============================================================================
|
|
||||||
# BASIC CONFIGURATION
|
|
||||||
# =============================================================================
|
|
||||||
|
|
||||||
name: "mydev" # REQUIRED: Instance name
|
|
||||||
deploymentMode: dynamic # Use serverless/dynamic mode
|
|
||||||
|
|
||||||
# Container images
|
|
||||||
image:
|
|
||||||
repository: ghcr.io/cpfarhood/devcontainer
|
|
||||||
tag: "2.0.0-dev"
|
|
||||||
pullPolicy: Always
|
|
||||||
|
|
||||||
# githubRepo is ignored in dynamic mode - repos are specified via URL routing
|
|
||||||
|
|
||||||
# =============================================================================
|
|
||||||
# ACCESS & INTERFACE
|
|
||||||
# =============================================================================
|
|
||||||
|
|
||||||
ide:
|
|
||||||
type: vscode # vscode | antigravity | none
|
|
||||||
|
|
||||||
# SSH not supported in dynamic mode (ephemeral containers)
|
|
||||||
ssh:
|
|
||||||
enabled: false
|
|
||||||
|
|
||||||
# File manager automatically enabled in dynamic mode for file transfer
|
|
||||||
fileManager:
|
|
||||||
enabled: true
|
|
||||||
|
|
||||||
# =============================================================================
|
|
||||||
# DYNAMIC MODE CONFIGURATION
|
|
||||||
# =============================================================================
|
|
||||||
|
|
||||||
dynamic:
|
|
||||||
# Knative Service auto-scaling configuration
|
|
||||||
knative:
|
|
||||||
minScale: 0 # Scale to zero when not in use
|
|
||||||
maxScale: 10 # Maximum concurrent instances
|
|
||||||
target: 1 # Requests per instance (1 = perfect isolation)
|
|
||||||
scaleToZeroGracePeriod: "5m" # Keep instances warm for 5 minutes
|
|
||||||
timeoutSeconds: 600 # 10 minutes for repo cloning + IDE startup
|
|
||||||
|
|
||||||
# Resources per container instance
|
|
||||||
resources:
|
|
||||||
requests:
|
|
||||||
memory: "1Gi"
|
|
||||||
cpu: "500m"
|
|
||||||
limits:
|
|
||||||
memory: "4Gi"
|
|
||||||
cpu: "2000m"
|
|
||||||
|
|
||||||
# Routing proxy (extracts GitHub repo from URL path)
|
|
||||||
routingProxy:
|
|
||||||
enabled: true
|
|
||||||
replicas: 2 # High availability
|
|
||||||
image:
|
|
||||||
repository: ghcr.io/cpfarhood/devcontainer-routing-proxy
|
|
||||||
tag: latest
|
|
||||||
pullPolicy: Always
|
|
||||||
|
|
||||||
# Ingress configuration
|
|
||||||
ingress:
|
|
||||||
enabled: true
|
|
||||||
className: nginx
|
|
||||||
host: "devcontainer.example.com" # REQUIRED: Set your domain
|
|
||||||
|
|
||||||
# SSL with cert-manager
|
|
||||||
tls:
|
|
||||||
enabled: true
|
|
||||||
# secretName: "" # Auto-generated if empty
|
|
||||||
issuer: "letsencrypt-prod"
|
|
||||||
|
|
||||||
# Authentik forward auth (configure after Authentik setup)
|
|
||||||
authentik:
|
|
||||||
enabled: false # Set to true when ready
|
|
||||||
authUrl: "http://authentik.authentik.svc.cluster.local/outpost.goauthentik.io/auth/nginx"
|
|
||||||
signIn: "https://auth.example.com/outpost.goauthentik.io/start?rd=$escaped_request_uri"
|
|
||||||
|
|
||||||
# =============================================================================
|
|
||||||
# STANDARD CONFIGURATION (applies to both modes)
|
|
||||||
# =============================================================================
|
|
||||||
|
|
||||||
# Display settings
|
|
||||||
display:
|
|
||||||
width: "1920"
|
|
||||||
height: "1080"
|
|
||||||
secureConnection: "0"
|
|
||||||
|
|
||||||
# User configuration
|
|
||||||
user:
|
|
||||||
id: "1000"
|
|
||||||
groupId: "1000"
|
|
||||||
|
|
||||||
# Resource allocation (container shared memory)
|
|
||||||
shm:
|
|
||||||
sizeLimit: 2Gi
|
|
||||||
|
|
||||||
# Happy Coder (ephemeral in dynamic mode)
|
|
||||||
happy:
|
|
||||||
serverUrl: ""
|
|
||||||
webappUrl: ""
|
|
||||||
homeDir: "/tmp/.happy" # Ephemeral location in dynamic mode
|
|
||||||
experimental: "true"
|
|
||||||
|
|
||||||
# MCP sidecars are not supported in dynamic mode (Knative limitation)
|
|
||||||
mcp:
|
|
||||||
sidecars:
|
|
||||||
kubernetes:
|
|
||||||
enabled: false
|
|
||||||
flux:
|
|
||||||
enabled: false
|
|
||||||
homeassistant:
|
|
||||||
enabled: false
|
|
||||||
pgtuner:
|
|
||||||
enabled: false
|
|
||||||
playwright:
|
|
||||||
enabled: false
|
|
||||||
@@ -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:
|
||||||
|
|||||||
@@ -32,14 +32,14 @@
|
|||||||
},
|
},
|
||||||
"required": ["repository", "tag"]
|
"required": ["repository", "tag"]
|
||||||
},
|
},
|
||||||
"deploymentMode": {
|
|
||||||
"type": "string",
|
|
||||||
"enum": ["persistent", "dynamic"],
|
|
||||||
"description": "Deployment mode: persistent (PVC-based) or dynamic (Knative serverless)"
|
|
||||||
},
|
|
||||||
"githubRepo": {
|
"githubRepo": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"description": "GitHub repository URL to clone (required in persistent mode, ignored in dynamic mode)"
|
"description": "GitHub repository URL to clone into /workspace"
|
||||||
|
},
|
||||||
|
"githubRepos": {
|
||||||
|
"type": "array",
|
||||||
|
"items": { "type": "string" },
|
||||||
|
"description": "Multiple GitHub repository URLs to clone (takes precedence over githubRepo)"
|
||||||
},
|
},
|
||||||
"fileManager": {
|
"fileManager": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
@@ -59,75 +59,6 @@
|
|||||||
},
|
},
|
||||||
"required": ["enabled"]
|
"required": ["enabled"]
|
||||||
},
|
},
|
||||||
"dynamic": {
|
|
||||||
"type": "object",
|
|
||||||
"description": "Configuration for dynamic (serverless) deployment mode",
|
|
||||||
"properties": {
|
|
||||||
"knative": {
|
|
||||||
"type": "object",
|
|
||||||
"properties": {
|
|
||||||
"minScale": { "type": "integer", "minimum": 0 },
|
|
||||||
"maxScale": { "type": "integer", "minimum": 1 },
|
|
||||||
"target": { "type": "integer", "minimum": 1 },
|
|
||||||
"scaleToZeroGracePeriod": { "type": "string" },
|
|
||||||
"timeoutSeconds": { "type": "integer", "minimum": 60 },
|
|
||||||
"resources": {
|
|
||||||
"type": "object",
|
|
||||||
"properties": {
|
|
||||||
"requests": { "$ref": "#/$defs/resourceSpec" },
|
|
||||||
"limits": { "$ref": "#/$defs/resourceSpec" }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"routingProxy": {
|
|
||||||
"type": "object",
|
|
||||||
"properties": {
|
|
||||||
"enabled": { "type": "boolean" },
|
|
||||||
"replicas": { "type": "integer", "minimum": 1 },
|
|
||||||
"image": {
|
|
||||||
"type": "object",
|
|
||||||
"properties": {
|
|
||||||
"repository": { "type": "string" },
|
|
||||||
"tag": { "type": "string" },
|
|
||||||
"pullPolicy": { "type": "string", "enum": ["Always", "IfNotPresent", "Never"] }
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"resources": {
|
|
||||||
"type": "object",
|
|
||||||
"properties": {
|
|
||||||
"requests": { "$ref": "#/$defs/resourceSpec" },
|
|
||||||
"limits": { "$ref": "#/$defs/resourceSpec" }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"ingress": {
|
|
||||||
"type": "object",
|
|
||||||
"properties": {
|
|
||||||
"enabled": { "type": "boolean" },
|
|
||||||
"className": { "type": "string" },
|
|
||||||
"host": { "type": "string" },
|
|
||||||
"tls": {
|
|
||||||
"type": "object",
|
|
||||||
"properties": {
|
|
||||||
"enabled": { "type": "boolean" },
|
|
||||||
"secretName": { "type": "string" },
|
|
||||||
"issuer": { "type": "string" }
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"authentik": {
|
|
||||||
"type": "object",
|
|
||||||
"properties": {
|
|
||||||
"enabled": { "type": "boolean" },
|
|
||||||
"authUrl": { "type": "string" },
|
|
||||||
"signIn": { "type": "string" }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"ide": {
|
"ide": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
@@ -229,29 +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",
|
|
||||||
"description": "Happy Coder server URL"
|
|
||||||
},
|
|
||||||
"webappUrl": {
|
|
||||||
"type": "string",
|
|
||||||
"description": "Happy Coder webapp URL"
|
|
||||||
},
|
|
||||||
"homeDir": {
|
|
||||||
"type": "string",
|
|
||||||
"description": "Happy Coder home directory"
|
|
||||||
},
|
|
||||||
"experimental": {
|
|
||||||
"type": "string",
|
|
||||||
"enum": ["true", "false"],
|
|
||||||
"description": "Enable experimental Happy features"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"required": ["homeDir", "experimental"]
|
|
||||||
},
|
|
||||||
"mcp": {
|
"mcp": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
|
|||||||
+11
-77
@@ -5,20 +5,22 @@
|
|||||||
# Instance name — used to generate resource names (devcontainer-{name}, userhome-{name})
|
# Instance name — used to generate resource names (devcontainer-{name}, userhome-{name})
|
||||||
name: ""
|
name: ""
|
||||||
|
|
||||||
# Deployment mode controls the infrastructure pattern
|
|
||||||
# - persistent: Traditional model with PVC storage, single long-lived deployment
|
|
||||||
# - dynamic: Serverless model with Knative, auto-scaling from 0, dynamic GitHub routing
|
|
||||||
deploymentMode: persistent # persistent | dynamic
|
|
||||||
|
|
||||||
# Container image configuration
|
# Container image configuration
|
||||||
image:
|
image:
|
||||||
repository: ghcr.io/cpfarhood/devcontainer
|
repository: ghcr.io/cpfarhood/devcontainer
|
||||||
tag: latest
|
tag: latest
|
||||||
pullPolicy: Always
|
pullPolicy: Always
|
||||||
|
|
||||||
# GitHub repository to clone into /workspace (ignored in dynamic mode - uses URL routing)
|
# 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
|
||||||
# =============================================================================
|
# =============================================================================
|
||||||
@@ -83,13 +85,6 @@ clusterAccess: none
|
|||||||
# INTEGRATIONS
|
# INTEGRATIONS
|
||||||
# =============================================================================
|
# =============================================================================
|
||||||
|
|
||||||
# Happy Coder AI assistant configuration
|
|
||||||
happy:
|
|
||||||
serverUrl: ""
|
|
||||||
webappUrl: ""
|
|
||||||
homeDir: "/config/userdata/.happy"
|
|
||||||
experimental: "true"
|
|
||||||
|
|
||||||
# MCP (Model Context Protocol) server sidecars
|
# MCP (Model Context Protocol) server sidecars
|
||||||
mcp:
|
mcp:
|
||||||
sidecars:
|
sidecars:
|
||||||
@@ -126,11 +121,11 @@ mcp:
|
|||||||
|
|
||||||
# Helm chart browsing and management
|
# Helm chart browsing and management
|
||||||
helm:
|
helm:
|
||||||
enabled: true
|
enabled: false
|
||||||
image:
|
image:
|
||||||
repository: ghcr.io/zekker6/mcp-helm
|
repository: ghcr.io/zekker6/mcp-helm
|
||||||
tag: v1.3.1
|
tag: v1.3.1
|
||||||
port: 8088
|
port: 8012
|
||||||
resources:
|
resources:
|
||||||
requests:
|
requests:
|
||||||
memory: "64Mi"
|
memory: "64Mi"
|
||||||
@@ -144,7 +139,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: v6.7.1
|
tag: "6.7.1"
|
||||||
port: 8087
|
port: 8087
|
||||||
resources:
|
resources:
|
||||||
requests:
|
requests:
|
||||||
@@ -199,67 +194,6 @@ autoDetect:
|
|||||||
# Override specific values above to customize
|
# Override specific values above to customize
|
||||||
resourceProfile: auto # auto | small | medium | large | xlarge
|
resourceProfile: auto # auto | small | medium | large | xlarge
|
||||||
|
|
||||||
# =============================================================================
|
|
||||||
# DYNAMIC MODE CONFIGURATION (deploymentMode: dynamic)
|
|
||||||
# =============================================================================
|
|
||||||
|
|
||||||
# Dynamic mode uses Knative Services and routing proxy for serverless operation
|
|
||||||
dynamic:
|
|
||||||
# Knative Service configuration
|
|
||||||
knative:
|
|
||||||
# Scaling configuration
|
|
||||||
minScale: 0 # Scale to zero when not in use
|
|
||||||
maxScale: 10 # Maximum number of concurrent instances
|
|
||||||
target: 1 # Requests per instance (isolation = 1 request per pod)
|
|
||||||
scaleToZeroGracePeriod: "5m" # Keep instances warm for 5 minutes
|
|
||||||
|
|
||||||
# Container startup timeout (repo cloning + IDE startup)
|
|
||||||
timeoutSeconds: 600 # 10 minutes
|
|
||||||
|
|
||||||
# Resource configuration (per instance)
|
|
||||||
resources:
|
|
||||||
requests:
|
|
||||||
memory: "1Gi"
|
|
||||||
cpu: "500m"
|
|
||||||
limits:
|
|
||||||
memory: "4Gi"
|
|
||||||
cpu: "2000m"
|
|
||||||
|
|
||||||
# Routing proxy configuration (extracts GitHub repo from URL)
|
|
||||||
routingProxy:
|
|
||||||
enabled: true
|
|
||||||
replicas: 2 # High availability
|
|
||||||
image:
|
|
||||||
repository: ghcr.io/cpfarhood/devcontainer-routing-proxy
|
|
||||||
tag: latest
|
|
||||||
pullPolicy: Always
|
|
||||||
|
|
||||||
resources:
|
|
||||||
requests:
|
|
||||||
memory: "64Mi"
|
|
||||||
cpu: "100m"
|
|
||||||
limits:
|
|
||||||
memory: "256Mi"
|
|
||||||
cpu: "500m"
|
|
||||||
|
|
||||||
# Ingress configuration for dynamic mode
|
|
||||||
ingress:
|
|
||||||
enabled: true
|
|
||||||
className: nginx
|
|
||||||
host: "" # Set this to your domain (e.g., devcontainer.farh.net)
|
|
||||||
|
|
||||||
# TLS configuration
|
|
||||||
tls:
|
|
||||||
enabled: true
|
|
||||||
secretName: "" # Auto-generated if empty
|
|
||||||
issuer: "letsencrypt-prod" # cert-manager ClusterIssuer
|
|
||||||
|
|
||||||
# Authentik forward auth configuration
|
|
||||||
authentik:
|
|
||||||
enabled: false # Set to true when Authentik is configured
|
|
||||||
authUrl: "http://authentik.authentik.svc.cluster.local/outpost.goauthentik.io/auth/nginx"
|
|
||||||
signIn: "https://auth.example.com/outpost.goauthentik.io/start?rd=$escaped_request_uri"
|
|
||||||
|
|
||||||
# =============================================================================
|
# =============================================================================
|
||||||
# ADVANCED CONFIGURATION
|
# ADVANCED CONFIGURATION
|
||||||
# =============================================================================
|
# =============================================================================
|
||||||
|
|||||||
+79
-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
|
||||||
@@ -62,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
|
||||||
|
|||||||
+1
-7
@@ -4,13 +4,7 @@ set -e
|
|||||||
|
|
||||||
echo "=== Starting Dev Container ==="
|
echo "=== Starting Dev Container ==="
|
||||||
|
|
||||||
# Check if we're in serverless mode
|
# Initialize repository
|
||||||
if [[ "$SERVERLESS_MODE" == "true" ]]; then
|
|
||||||
echo "Serverless mode detected, using serverless startup script..."
|
|
||||||
exec /usr/local/bin/serverless-startapp
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Traditional mode - initialize repository
|
|
||||||
/usr/local/bin/init-repo
|
/usr/local/bin/init-repo
|
||||||
|
|
||||||
# Get workspace directory
|
# Get workspace directory
|
||||||
|
|||||||
@@ -1,173 +0,0 @@
|
|||||||
# DevContainer Serverless 2.0 Makefile
|
|
||||||
|
|
||||||
# Configuration
|
|
||||||
REGISTRY ?= ghcr.io/cpfarhood
|
|
||||||
ROUTING_PROXY_IMAGE := $(REGISTRY)/devcontainer-routing-proxy
|
|
||||||
DEVCONTAINER_IMAGE := $(REGISTRY)/devcontainer
|
|
||||||
VERSION ?= 2.0.0-alpha
|
|
||||||
NAMESPACE := devcontainers
|
|
||||||
|
|
||||||
# Knative service name
|
|
||||||
KN_SERVICE := devcontainer-serverless
|
|
||||||
|
|
||||||
.PHONY: help build push deploy test clean
|
|
||||||
|
|
||||||
help: ## Display this help message
|
|
||||||
@echo "DevContainer Serverless 2.0"
|
|
||||||
@echo ""
|
|
||||||
@echo "Available targets:"
|
|
||||||
@awk 'BEGIN {FS = ":.*?## "} /^[a-zA-Z_-]+:.*?## / {printf " %-15s %s\n", $$1, $$2}' $(MAKEFILE_LIST)
|
|
||||||
|
|
||||||
# Build targets
|
|
||||||
build-routing-proxy: ## Build the routing proxy image
|
|
||||||
@echo "Building routing proxy image..."
|
|
||||||
cd routing-proxy && docker build -t $(ROUTING_PROXY_IMAGE):$(VERSION) .
|
|
||||||
docker tag $(ROUTING_PROXY_IMAGE):$(VERSION) $(ROUTING_PROXY_IMAGE):latest
|
|
||||||
|
|
||||||
build-devcontainer: ## Build the main devcontainer image (from parent directory)
|
|
||||||
@echo "Building devcontainer image..."
|
|
||||||
cd .. && docker build -t $(DEVCONTAINER_IMAGE):$(VERSION) .
|
|
||||||
docker tag $(DEVCONTAINER_IMAGE):$(VERSION) $(DEVCONTAINER_IMAGE):latest
|
|
||||||
|
|
||||||
build: build-routing-proxy build-devcontainer ## Build all images
|
|
||||||
|
|
||||||
# Push targets
|
|
||||||
push-routing-proxy: build-routing-proxy ## Push routing proxy image
|
|
||||||
@echo "Pushing routing proxy image..."
|
|
||||||
docker push $(ROUTING_PROXY_IMAGE):$(VERSION)
|
|
||||||
docker push $(ROUTING_PROXY_IMAGE):latest
|
|
||||||
|
|
||||||
push-devcontainer: build-devcontainer ## Push devcontainer image
|
|
||||||
@echo "Pushing devcontainer image..."
|
|
||||||
docker push $(DEVCONTAINER_IMAGE):$(VERSION)
|
|
||||||
docker push $(DEVCONTAINER_IMAGE):latest
|
|
||||||
|
|
||||||
push: push-routing-proxy push-devcontainer ## Push all images
|
|
||||||
|
|
||||||
# Deployment targets
|
|
||||||
create-namespace: ## Create the devcontainers namespace
|
|
||||||
@echo "Creating namespace..."
|
|
||||||
kubectl create namespace $(NAMESPACE) --dry-run=client -o yaml | kubectl apply -f -
|
|
||||||
|
|
||||||
deploy-secrets: create-namespace ## Deploy secrets (update values first!)
|
|
||||||
@echo "Deploying secrets..."
|
|
||||||
@echo "WARNING: Update the secret values in deployment.yaml first!"
|
|
||||||
kubectl apply -f deployment.yaml
|
|
||||||
@echo "Don't forget to update the secret with real values:"
|
|
||||||
@echo "kubectl edit secret devcontainer-serverless-secrets -n $(NAMESPACE)"
|
|
||||||
|
|
||||||
deploy-components: create-namespace ## Deploy routing proxy and Knative service
|
|
||||||
@echo "Deploying serverless components..."
|
|
||||||
kubectl apply -f deployment.yaml
|
|
||||||
|
|
||||||
deploy: deploy-secrets deploy-components ## Deploy everything
|
|
||||||
|
|
||||||
# Configuration targets
|
|
||||||
configure-authentik: ## Apply Authentik configuration
|
|
||||||
@echo "Applying Authentik configuration..."
|
|
||||||
kubectl apply -f authentik-config.yaml
|
|
||||||
@echo "Complete the setup in Authentik web UI:"
|
|
||||||
@echo "1. Create Forward Auth Provider"
|
|
||||||
@echo "2. Create Application"
|
|
||||||
@echo "3. Create Outpost"
|
|
||||||
|
|
||||||
# Testing targets
|
|
||||||
test-routing-proxy: ## Test routing proxy locally
|
|
||||||
@echo "Testing routing proxy..."
|
|
||||||
@echo "Starting local test..."
|
|
||||||
cd routing-proxy && docker run --rm -d --name devcontainer-routing-test \
|
|
||||||
-p 8080:8080 \
|
|
||||||
-e DEVCONTAINER_SERVICE_URL=httpbin.org \
|
|
||||||
$(ROUTING_PROXY_IMAGE):latest
|
|
||||||
@echo "Testing GitHub repo extraction..."
|
|
||||||
sleep 2
|
|
||||||
curl -v "http://localhost:8080/github/microsoft/vscode" || true
|
|
||||||
docker stop devcontainer-routing-test
|
|
||||||
@echo "Test complete!"
|
|
||||||
|
|
||||||
test-knative: ## Test Knative service deployment
|
|
||||||
@echo "Testing Knative service..."
|
|
||||||
kubectl get ksvc $(KN_SERVICE) -n $(NAMESPACE)
|
|
||||||
kubectl describe ksvc $(KN_SERVICE) -n $(NAMESPACE)
|
|
||||||
|
|
||||||
test: test-routing-proxy test-knative ## Run all tests
|
|
||||||
|
|
||||||
# Status and debugging targets
|
|
||||||
status: ## Show status of all components
|
|
||||||
@echo "=== Namespace ==="
|
|
||||||
kubectl get ns $(NAMESPACE) || echo "Namespace not found"
|
|
||||||
@echo ""
|
|
||||||
@echo "=== Routing Proxy ==="
|
|
||||||
kubectl get deployment devcontainer-routing-proxy -n $(NAMESPACE) || echo "Routing proxy not found"
|
|
||||||
@echo ""
|
|
||||||
@echo "=== Knative Service ==="
|
|
||||||
kubectl get ksvc $(KN_SERVICE) -n $(NAMESPACE) || echo "Knative service not found"
|
|
||||||
@echo ""
|
|
||||||
@echo "=== Pods ==="
|
|
||||||
kubectl get pods -n $(NAMESPACE)
|
|
||||||
@echo ""
|
|
||||||
@echo "=== Ingress ==="
|
|
||||||
kubectl get ingress -n $(NAMESPACE)
|
|
||||||
|
|
||||||
logs-routing-proxy: ## Show routing proxy logs
|
|
||||||
kubectl logs -n $(NAMESPACE) deployment/devcontainer-routing-proxy -f
|
|
||||||
|
|
||||||
logs-knative: ## Show Knative service logs
|
|
||||||
kubectl logs -n $(NAMESPACE) -l serving.knative.dev/service=$(KN_SERVICE) -f
|
|
||||||
|
|
||||||
# Cleanup targets
|
|
||||||
clean-pods: ## Delete all pods in the namespace
|
|
||||||
kubectl delete pods --all -n $(NAMESPACE)
|
|
||||||
|
|
||||||
clean-deployment: ## Delete the serverless deployment
|
|
||||||
kubectl delete -f deployment.yaml --ignore-not-found
|
|
||||||
|
|
||||||
clean-namespace: ## Delete the entire namespace
|
|
||||||
kubectl delete namespace $(NAMESPACE) --ignore-not-found
|
|
||||||
|
|
||||||
clean: clean-deployment ## Clean up deployment
|
|
||||||
|
|
||||||
# Development targets
|
|
||||||
dev-setup: ## Set up development environment
|
|
||||||
@echo "Setting up development environment..."
|
|
||||||
@echo "Prerequisites:"
|
|
||||||
@echo "- Kubernetes cluster with Knative Serving"
|
|
||||||
@echo "- kubectl configured"
|
|
||||||
@echo "- Docker for building images"
|
|
||||||
@echo ""
|
|
||||||
@echo "Run 'make build deploy' to get started"
|
|
||||||
|
|
||||||
scale-to-zero: ## Force Knative service to scale to zero
|
|
||||||
@echo "Scaling Knative service to zero..."
|
|
||||||
kubectl patch ksvc $(KN_SERVICE) -n $(NAMESPACE) --type='merge' -p='{"spec":{"template":{"metadata":{"annotations":{"autoscaling.knative.dev/minScale":"0"}}}}}'
|
|
||||||
|
|
||||||
scale-up: ## Trigger a scale-up of the Knative service
|
|
||||||
@echo "Triggering scale-up..."
|
|
||||||
curl -H "X-GitHub-Repo: https://github.com/microsoft/vscode" \
|
|
||||||
"http://devcontainer-routing-proxy.$(NAMESPACE).svc.cluster.local/github/microsoft/vscode" || \
|
|
||||||
kubectl run curl --rm -i --restart=Never --image=curlimages/curl -- \
|
|
||||||
-H "X-GitHub-Repo: https://github.com/microsoft/vscode" \
|
|
||||||
"http://devcontainer-routing-proxy.$(NAMESPACE).svc.cluster.local/github/microsoft/vscode"
|
|
||||||
|
|
||||||
# Documentation targets
|
|
||||||
docs: ## Generate documentation
|
|
||||||
@echo "Documentation files:"
|
|
||||||
@echo "- README.md: Main documentation"
|
|
||||||
@echo "- deployment.yaml: Kubernetes manifests"
|
|
||||||
@echo "- authentik-config.yaml: Authentik configuration"
|
|
||||||
@echo ""
|
|
||||||
@echo "View online documentation at: https://github.com/cpfarhood/devcontainer/tree/feature/serverless-2.0.0/serverless"
|
|
||||||
|
|
||||||
# Version management
|
|
||||||
version: ## Show current version
|
|
||||||
@echo "Version: $(VERSION)"
|
|
||||||
@echo "Registry: $(REGISTRY)"
|
|
||||||
@echo "Images:"
|
|
||||||
@echo " - $(ROUTING_PROXY_IMAGE):$(VERSION)"
|
|
||||||
@echo " - $(DEVCONTAINER_IMAGE):$(VERSION)"
|
|
||||||
|
|
||||||
# Quick development workflow
|
|
||||||
dev: build deploy status ## Quick development: build, deploy, show status
|
|
||||||
|
|
||||||
# Production deployment workflow
|
|
||||||
prod: build push deploy configure-authentik status ## Production deployment workflow
|
|
||||||
@@ -1,376 +0,0 @@
|
|||||||
# DevContainer Serverless 2.0
|
|
||||||
|
|
||||||
A serverless, auto-scaling development container platform with dynamic GitHub repository routing, secured by Authentik authentication.
|
|
||||||
|
|
||||||
## Architecture Overview
|
|
||||||
|
|
||||||
```
|
|
||||||
User Request: https://devcontainer.farh.net/github/microsoft/vscode
|
|
||||||
↓
|
|
||||||
Authentik (Authentication & Authorization)
|
|
||||||
↓ (authenticated request with user headers)
|
|
||||||
NGINX Ingress (SSL termination, rate limiting)
|
|
||||||
↓
|
|
||||||
Routing Proxy (extracts GitHub repo from URL, adds headers)
|
|
||||||
↓ (with X-GitHub-Repo header)
|
|
||||||
Knative Service (devcontainer-serverless)
|
|
||||||
↓ (auto-scales from 0 to N instances)
|
|
||||||
Dev Container Instances (ephemeral, repo-specific)
|
|
||||||
```
|
|
||||||
|
|
||||||
### Key Features
|
|
||||||
|
|
||||||
- 🚀 **Scale to Zero**: Containers automatically scale down to zero when not in use
|
|
||||||
- 🔐 **Authentik Integration**: Full authentication and authorization via Authentik
|
|
||||||
- 🐙 **Dynamic GitHub Routing**: Access any repo via `/github/{owner}/{repo}`
|
|
||||||
- ⚡ **Fast Cold Start**: Optimized startup for quick repository access
|
|
||||||
- 📁 **Built-in File Manager**: Upload/download files via web interface
|
|
||||||
- 🛠️ **Multiple IDEs**: VSCode, Antigravity, or headless mode
|
|
||||||
- 🎯 **Per-User Isolation**: Each request gets its own container instance
|
|
||||||
|
|
||||||
## Quick Start
|
|
||||||
|
|
||||||
### Prerequisites
|
|
||||||
|
|
||||||
- Kubernetes cluster with Knative Serving installed
|
|
||||||
- Authentik deployed and configured
|
|
||||||
- NGINX Ingress Controller
|
|
||||||
- cert-manager for SSL certificates
|
|
||||||
|
|
||||||
### 1. Deploy the Serverless Components
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Create namespace and deploy all components
|
|
||||||
kubectl apply -f serverless/deployment.yaml
|
|
||||||
|
|
||||||
# Build and push the routing proxy image
|
|
||||||
cd serverless/routing-proxy
|
|
||||||
docker build -t ghcr.io/cpfarhood/devcontainer-routing-proxy:latest .
|
|
||||||
docker push ghcr.io/cpfarhood/devcontainer-routing-proxy:latest
|
|
||||||
```
|
|
||||||
|
|
||||||
### 2. Configure Authentik
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Apply Authentik configuration
|
|
||||||
kubectl apply -f serverless/authentik-config.yaml
|
|
||||||
|
|
||||||
# Configure the application via Authentik web UI:
|
|
||||||
# 1. Go to Applications > Providers > Create
|
|
||||||
# 2. Type: Forward Auth (single application)
|
|
||||||
# 3. Name: devcontainer-forward-auth-provider
|
|
||||||
# 4. External host: https://devcontainer.farh.net
|
|
||||||
# 5. Create the Application pointing to this provider
|
|
||||||
```
|
|
||||||
|
|
||||||
### 3. Update DNS and SSL
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Point devcontainer.farh.net to your ingress controller
|
|
||||||
# The cert-manager will automatically provision SSL certificates
|
|
||||||
```
|
|
||||||
|
|
||||||
### 4. Test the Deployment
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Visit in browser (will redirect to Authentik for login)
|
|
||||||
https://devcontainer.farh.net/github/microsoft/vscode
|
|
||||||
|
|
||||||
# Check pod scaling
|
|
||||||
kubectl get pods -n devcontainers -w
|
|
||||||
|
|
||||||
# View logs
|
|
||||||
kubectl logs -n devcontainers deployment/devcontainer-routing-proxy -f
|
|
||||||
kubectl logs -n devcontainers -l serving.knative.dev/service=devcontainer-serverless -f
|
|
||||||
```
|
|
||||||
|
|
||||||
## Usage
|
|
||||||
|
|
||||||
### URL Format
|
|
||||||
|
|
||||||
```
|
|
||||||
https://devcontainer.farh.net/github/{owner}/{repo}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Examples
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Microsoft VSCode
|
|
||||||
https://devcontainer.farh.net/github/microsoft/vscode
|
|
||||||
|
|
||||||
# Kubernetes
|
|
||||||
https://devcontainer.farh.net/github/kubernetes/kubernetes
|
|
||||||
|
|
||||||
# Your private repo (requires GitHub token)
|
|
||||||
https://devcontainer.farh.net/github/yourorg/private-repo
|
|
||||||
```
|
|
||||||
|
|
||||||
### Authentication Flow
|
|
||||||
|
|
||||||
1. User visits `https://devcontainer.farh.net/github/owner/repo`
|
|
||||||
2. NGINX Ingress checks with Authentik for authentication
|
|
||||||
3. If not authenticated, redirects to Authentik login
|
|
||||||
4. After successful login, request proceeds with user headers
|
|
||||||
5. Routing proxy extracts repository from URL
|
|
||||||
6. Knative spins up (or reuses) a container instance
|
|
||||||
7. Container clones the specified repository and starts IDE
|
|
||||||
|
|
||||||
### File Upload/Download
|
|
||||||
|
|
||||||
Each container includes a built-in file manager accessible via the VNC web interface:
|
|
||||||
|
|
||||||
1. Connect to your dev container via the browser
|
|
||||||
2. Look for the file manager icon in the VNC toolbar
|
|
||||||
3. Upload/download files directly through the web interface
|
|
||||||
|
|
||||||
## Configuration
|
|
||||||
|
|
||||||
### Environment Variables (Secret)
|
|
||||||
|
|
||||||
Update the secret in `serverless/deployment.yaml`:
|
|
||||||
|
|
||||||
```yaml
|
|
||||||
stringData:
|
|
||||||
GITHUB_TOKEN: "ghp_your_github_token" # For private repositories
|
|
||||||
VNC_PASSWORD: "your_secure_password" # VNC access password
|
|
||||||
ANTHROPIC_API_KEY: "sk-ant-your_key" # Claude API key
|
|
||||||
GIT_USER_NAME: "Your Name" # Git commit author
|
|
||||||
GIT_USER_EMAIL: "your.email@example.com" # Git commit email
|
|
||||||
```
|
|
||||||
|
|
||||||
### Scaling Configuration
|
|
||||||
|
|
||||||
Modify the Knative Service annotations in `deployment.yaml`:
|
|
||||||
|
|
||||||
```yaml
|
|
||||||
annotations:
|
|
||||||
autoscaling.knative.dev/minScale: "0" # Scale to zero
|
|
||||||
autoscaling.knative.dev/maxScale: "20" # Max instances
|
|
||||||
autoscaling.knative.dev/target: "1" # 1 request per pod
|
|
||||||
autoscaling.knative.dev/scale-to-zero-grace-period: "10m"
|
|
||||||
```
|
|
||||||
|
|
||||||
### Resource Limits
|
|
||||||
|
|
||||||
Adjust per-instance resources:
|
|
||||||
|
|
||||||
```yaml
|
|
||||||
resources:
|
|
||||||
requests:
|
|
||||||
memory: "1Gi"
|
|
||||||
cpu: "500m"
|
|
||||||
limits:
|
|
||||||
memory: "8Gi" # More memory for large repos
|
|
||||||
cpu: "4000m" # More CPU for compilation tasks
|
|
||||||
```
|
|
||||||
|
|
||||||
### IDE Selection
|
|
||||||
|
|
||||||
Set the default IDE via environment variable:
|
|
||||||
|
|
||||||
```yaml
|
|
||||||
env:
|
|
||||||
- name: IDE
|
|
||||||
value: "vscode" # Options: vscode, antigravity, none
|
|
||||||
```
|
|
||||||
|
|
||||||
## Monitoring and Observability
|
|
||||||
|
|
||||||
### Health Checks
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Routing proxy health
|
|
||||||
curl http://devcontainer-routing-proxy.devcontainers.svc.cluster.local/health
|
|
||||||
|
|
||||||
# Knative service status
|
|
||||||
kn service describe devcontainer-serverless -n devcontainers
|
|
||||||
|
|
||||||
# Check container logs
|
|
||||||
kubectl logs -n devcontainers -l serving.knative.dev/service=devcontainer-serverless -f
|
|
||||||
```
|
|
||||||
|
|
||||||
### Metrics
|
|
||||||
|
|
||||||
The setup includes Prometheus integration:
|
|
||||||
|
|
||||||
- **Authentik metrics**: User authentication events
|
|
||||||
- **Knative metrics**: Container scaling, cold starts, request latency
|
|
||||||
- **NGINX metrics**: Request rates, response times
|
|
||||||
- **Container metrics**: Resource usage per repository
|
|
||||||
|
|
||||||
### Grafana Dashboards
|
|
||||||
|
|
||||||
Import the provided dashboard for monitoring:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# TODO: Create Grafana dashboard JSON
|
|
||||||
```
|
|
||||||
|
|
||||||
## Security Considerations
|
|
||||||
|
|
||||||
### Network Policies
|
|
||||||
|
|
||||||
```yaml
|
|
||||||
# Restrict networking between components
|
|
||||||
apiVersion: networking.k8s.io/v1
|
|
||||||
kind: NetworkPolicy
|
|
||||||
metadata:
|
|
||||||
name: devcontainer-serverless-network-policy
|
|
||||||
namespace: devcontainers
|
|
||||||
spec:
|
|
||||||
podSelector:
|
|
||||||
matchLabels:
|
|
||||||
serving.knative.dev/service: devcontainer-serverless
|
|
||||||
policyTypes:
|
|
||||||
- Ingress
|
|
||||||
- Egress
|
|
||||||
ingress:
|
|
||||||
- from:
|
|
||||||
- podSelector:
|
|
||||||
matchLabels:
|
|
||||||
app.kubernetes.io/component: routing-proxy
|
|
||||||
ports:
|
|
||||||
- protocol: TCP
|
|
||||||
port: 5800
|
|
||||||
egress:
|
|
||||||
- to: [] # Allow all outbound (needed for git clone, package installs)
|
|
||||||
ports:
|
|
||||||
- protocol: TCP
|
|
||||||
port: 443
|
|
||||||
- protocol: TCP
|
|
||||||
port: 80
|
|
||||||
```
|
|
||||||
|
|
||||||
### Repository Access Control
|
|
||||||
|
|
||||||
Configure Authentik policies to control repository access:
|
|
||||||
|
|
||||||
```python
|
|
||||||
# Example Authentik expression policy
|
|
||||||
github_repo = request.http_request.headers.get('X-GitHub-Repo', '')
|
|
||||||
user_groups = [g.name for g in request.user.ak_groups.all()]
|
|
||||||
|
|
||||||
# Allow admins access to everything
|
|
||||||
if 'admins' in user_groups:
|
|
||||||
return True
|
|
||||||
|
|
||||||
# Allow developers access to public repos and specific private repos
|
|
||||||
if 'developers' in user_groups:
|
|
||||||
# Add logic for private repository access based on user attributes
|
|
||||||
if 'private-repo-access' in user.ak_attributes:
|
|
||||||
allowed_repos = user.ak_attributes['private-repo-access']
|
|
||||||
return github_repo in allowed_repos
|
|
||||||
return True # Public repos only
|
|
||||||
|
|
||||||
return False
|
|
||||||
```
|
|
||||||
|
|
||||||
## Troubleshooting
|
|
||||||
|
|
||||||
### Common Issues
|
|
||||||
|
|
||||||
1. **Container won't start**
|
|
||||||
```bash
|
|
||||||
# Check Knative service status
|
|
||||||
kn service describe devcontainer-serverless -n devcontainers
|
|
||||||
|
|
||||||
# Check pod events
|
|
||||||
kubectl describe pod -n devcontainers -l serving.knative.dev/service=devcontainer-serverless
|
|
||||||
```
|
|
||||||
|
|
||||||
2. **Repository clone fails**
|
|
||||||
```bash
|
|
||||||
# Check GitHub token in secret
|
|
||||||
kubectl get secret devcontainer-serverless-secrets -n devcontainers -o yaml
|
|
||||||
|
|
||||||
# Check container logs for git errors
|
|
||||||
kubectl logs -n devcontainers -l serving.knative.dev/service=devcontainer-serverless --tail=100
|
|
||||||
```
|
|
||||||
|
|
||||||
3. **Authentik authentication loop**
|
|
||||||
```bash
|
|
||||||
# Check Authentik outpost logs
|
|
||||||
kubectl logs -n authentik -l app.kubernetes.io/name=authentik
|
|
||||||
|
|
||||||
# Verify ingress annotations
|
|
||||||
kubectl describe ingress devcontainer-serverless-ingress -n devcontainers
|
|
||||||
```
|
|
||||||
|
|
||||||
4. **Slow cold starts**
|
|
||||||
```bash
|
|
||||||
# Check container startup time
|
|
||||||
kubectl logs -n devcontainers -l serving.knative.dev/service=devcontainer-serverless --timestamps
|
|
||||||
|
|
||||||
# Consider increasing timeout
|
|
||||||
# serving.knative.dev/timeoutSeconds: "900" # 15 minutes
|
|
||||||
```
|
|
||||||
|
|
||||||
### Performance Tuning
|
|
||||||
|
|
||||||
1. **Reduce cold start time**:
|
|
||||||
- Use minimal base image layers
|
|
||||||
- Pre-install common development tools
|
|
||||||
- Optimize git clone (shallow clone for large repos)
|
|
||||||
|
|
||||||
2. **Resource optimization**:
|
|
||||||
- Set appropriate resource requests/limits
|
|
||||||
- Use `autoscaling.knative.dev/target-utilization-percentage`
|
|
||||||
- Consider persistent volumes for frequently accessed repos
|
|
||||||
|
|
||||||
3. **Network optimization**:
|
|
||||||
- Use private container registry for faster image pulls
|
|
||||||
- Configure image pull policies appropriately
|
|
||||||
- Consider using a git cache proxy
|
|
||||||
|
|
||||||
## Development
|
|
||||||
|
|
||||||
### Building the Routing Proxy
|
|
||||||
|
|
||||||
```bash
|
|
||||||
cd serverless/routing-proxy
|
|
||||||
docker build -t ghcr.io/cpfarhood/devcontainer-routing-proxy:v2.0.0 .
|
|
||||||
docker push ghcr.io/cpfarhood/devcontainer-routing-proxy:v2.0.0
|
|
||||||
```
|
|
||||||
|
|
||||||
### Testing Locally
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Run the routing proxy locally
|
|
||||||
cd serverless/routing-proxy
|
|
||||||
docker run -p 8080:8080 \
|
|
||||||
-e DEVCONTAINER_SERVICE_URL=host.docker.internal:5800 \
|
|
||||||
ghcr.io/cpfarhood/devcontainer-routing-proxy:latest
|
|
||||||
|
|
||||||
# Test routing
|
|
||||||
curl -H "X-GitHub-Repo: https://github.com/microsoft/vscode" \
|
|
||||||
http://localhost:8080/github/microsoft/vscode
|
|
||||||
```
|
|
||||||
|
|
||||||
### Contributing
|
|
||||||
|
|
||||||
1. Create feature branch from `feature/serverless-2.0.0`
|
|
||||||
2. Make changes to serverless components
|
|
||||||
3. Test with local Knative setup
|
|
||||||
4. Submit pull request
|
|
||||||
|
|
||||||
## Migration from 1.x
|
|
||||||
|
|
||||||
The serverless 2.0 architecture is a complete redesign. Migration steps:
|
|
||||||
|
|
||||||
1. **Backup existing data**: Export user configs, git credentials
|
|
||||||
2. **Deploy 2.0 components**: Following the quick start guide
|
|
||||||
3. **Migrate users**: Update Authentik with existing user accounts
|
|
||||||
4. **Test extensively**: Verify repository access and functionality
|
|
||||||
5. **Switch DNS**: Point domain to new infrastructure
|
|
||||||
6. **Cleanup 1.x**: Remove old Helm deployments
|
|
||||||
|
|
||||||
## Roadmap
|
|
||||||
|
|
||||||
- [ ] GitLab support (`/gitlab/group/project`)
|
|
||||||
- [ ] Bitbucket support
|
|
||||||
- [ ] Repository templates and scaffolding
|
|
||||||
- [ ] Collaborative editing features
|
|
||||||
- [ ] IDE plugins and extensions management
|
|
||||||
- [ ] Resource quotas per user/group
|
|
||||||
- [ ] Repository caching and optimization
|
|
||||||
- [ ] Integration with CI/CD pipelines
|
|
||||||
@@ -1,168 +0,0 @@
|
|||||||
# Authentik configuration for DevContainer serverless auth
|
|
||||||
# This assumes Authentik is already deployed in the 'authentik' namespace
|
|
||||||
|
|
||||||
---
|
|
||||||
# Application definition for DevContainer Serverless
|
|
||||||
apiVersion: v1
|
|
||||||
kind: ConfigMap
|
|
||||||
metadata:
|
|
||||||
name: authentik-devcontainer-app-config
|
|
||||||
namespace: authentik
|
|
||||||
data:
|
|
||||||
# This will be applied via Authentik API or web interface
|
|
||||||
application.yaml: |
|
|
||||||
name: DevContainer Serverless
|
|
||||||
slug: devcontainer-serverless
|
|
||||||
provider: devcontainer-forward-auth-provider
|
|
||||||
launch_url: https://devcontainer.farh.net/
|
|
||||||
open_in_new_tab: true
|
|
||||||
meta_description: "Serverless development containers with dynamic GitHub repository routing"
|
|
||||||
meta_publisher: "DevContainer Team"
|
|
||||||
policy_engine_mode: "all"
|
|
||||||
group: "Development Tools"
|
|
||||||
|
|
||||||
---
|
|
||||||
# Forward Auth Provider configuration
|
|
||||||
apiVersion: v1
|
|
||||||
kind: ConfigMap
|
|
||||||
metadata:
|
|
||||||
name: authentik-devcontainer-provider-config
|
|
||||||
namespace: authentik
|
|
||||||
data:
|
|
||||||
provider.yaml: |
|
|
||||||
name: devcontainer-forward-auth-provider
|
|
||||||
authorization_flow: default-authorization-flow # Use your default flow
|
|
||||||
external_host: https://devcontainer.farh.net
|
|
||||||
|
|
||||||
# Advanced settings
|
|
||||||
token_validity: hours=24 # Long-lived sessions for dev work
|
|
||||||
|
|
||||||
# Headers to forward to the application
|
|
||||||
# These will be available as HTTP_* environment variables in containers
|
|
||||||
property_mappings:
|
|
||||||
- "authentik_core.x-authentik-username"
|
|
||||||
- "authentik_core.x-authentik-email"
|
|
||||||
- "authentik_core.x-authentik-name"
|
|
||||||
- "authentik_core.x-authentik-groups"
|
|
||||||
|
|
||||||
---
|
|
||||||
# Outpost configuration for forward auth
|
|
||||||
apiVersion: v1
|
|
||||||
kind: ConfigMap
|
|
||||||
metadata:
|
|
||||||
name: authentik-devcontainer-outpost-config
|
|
||||||
namespace: authentik
|
|
||||||
data:
|
|
||||||
outpost.yaml: |
|
|
||||||
name: devcontainer-forward-auth-outpost
|
|
||||||
type: proxy
|
|
||||||
providers:
|
|
||||||
- devcontainer-forward-auth-provider
|
|
||||||
|
|
||||||
# Outpost configuration
|
|
||||||
config:
|
|
||||||
authentik_host: https://auth.farh.net
|
|
||||||
authentik_host_insecure: false
|
|
||||||
authentik_host_browser: https://auth.farh.net
|
|
||||||
|
|
||||||
# Log level for debugging
|
|
||||||
log_level: info
|
|
||||||
|
|
||||||
# Cookie settings
|
|
||||||
cookie_domain: .farh.net
|
|
||||||
cookie_secure: true
|
|
||||||
|
|
||||||
# NGINX ingress integration
|
|
||||||
external_host: https://devcontainer.farh.net
|
|
||||||
internal_host: http://authentik.authentik.svc.cluster.local
|
|
||||||
|
|
||||||
# Forward auth specific settings
|
|
||||||
mode: forward_single
|
|
||||||
skip_path_regex: "^/(health|metrics)$" # Skip auth for health checks
|
|
||||||
|
|
||||||
---
|
|
||||||
# Example NGINX Ingress annotations for reference
|
|
||||||
# (These go in the main ingress resource)
|
|
||||||
apiVersion: v1
|
|
||||||
kind: ConfigMap
|
|
||||||
metadata:
|
|
||||||
name: authentik-nginx-annotations
|
|
||||||
namespace: devcontainers
|
|
||||||
data:
|
|
||||||
annotations.yaml: |
|
|
||||||
# Forward auth configuration
|
|
||||||
nginx.ingress.kubernetes.io/auth-url: http://authentik.authentik.svc.cluster.local/outpost.goauthentik.io/auth/nginx
|
|
||||||
nginx.ingress.kubernetes.io/auth-signin: https://auth.farh.net/outpost.goauthentik.io/start?rd=$escaped_request_uri
|
|
||||||
nginx.ingress.kubernetes.io/auth-response-headers: X-Authentik-Username,X-Authentik-Groups,X-Authentik-Email,X-Authentik-Name
|
|
||||||
nginx.ingress.kubernetes.io/auth-snippet: |
|
|
||||||
proxy_set_header X-Forwarded-Host $http_host;
|
|
||||||
|
|
||||||
# Additional headers for the application
|
|
||||||
nginx.ingress.kubernetes.io/server-snippet: |
|
|
||||||
location ~ ^/github/([^/]+/[^/]+) {
|
|
||||||
# Log the GitHub repo being accessed
|
|
||||||
access_log /var/log/nginx/devcontainer-access.log combined;
|
|
||||||
|
|
||||||
# Set additional headers for audit/monitoring
|
|
||||||
proxy_set_header X-GitHub-Repo-Requested https://github.com/$1;
|
|
||||||
proxy_set_header X-Request-Timestamp $time_iso8601;
|
|
||||||
proxy_set_header X-Client-IP $remote_addr;
|
|
||||||
}
|
|
||||||
|
|
||||||
---
|
|
||||||
# Policy for controlling access (optional - can be configured via Authentik UI)
|
|
||||||
apiVersion: v1
|
|
||||||
kind: ConfigMap
|
|
||||||
metadata:
|
|
||||||
name: authentik-devcontainer-policies
|
|
||||||
namespace: authentik
|
|
||||||
data:
|
|
||||||
# Example group-based access policy
|
|
||||||
group-access-policy.yaml: |
|
|
||||||
name: DevContainer Access Policy
|
|
||||||
policy_type: group_membership
|
|
||||||
groups:
|
|
||||||
- developers
|
|
||||||
- devops
|
|
||||||
- admins
|
|
||||||
|
|
||||||
# Example expression policy for advanced access control
|
|
||||||
repo-access-policy.yaml: |
|
|
||||||
name: Repository Access Policy
|
|
||||||
policy_type: expression
|
|
||||||
expression: |
|
|
||||||
# Allow access to public repositories for all authenticated users
|
|
||||||
# Require specific groups for private repositories
|
|
||||||
|
|
||||||
github_repo = request.http_request.headers.get('X-GitHub-Repo', '')
|
|
||||||
|
|
||||||
# Check if user has access to private repositories
|
|
||||||
if 'private-repo-access' in user.ak_groups.values_list('name', flat=True):
|
|
||||||
return True
|
|
||||||
|
|
||||||
# For now, allow all authenticated users to access any repository
|
|
||||||
# You can customize this based on your needs
|
|
||||||
return True
|
|
||||||
|
|
||||||
---
|
|
||||||
# Service Monitor for Prometheus (optional)
|
|
||||||
apiVersion: v1
|
|
||||||
kind: ConfigMap
|
|
||||||
metadata:
|
|
||||||
name: authentik-devcontainer-monitoring
|
|
||||||
namespace: authentik
|
|
||||||
data:
|
|
||||||
servicemonitor.yaml: |
|
|
||||||
apiVersion: monitoring.coreos.com/v1
|
|
||||||
kind: ServiceMonitor
|
|
||||||
metadata:
|
|
||||||
name: devcontainer-authentik
|
|
||||||
namespace: authentik
|
|
||||||
spec:
|
|
||||||
selector:
|
|
||||||
matchLabels:
|
|
||||||
app.kubernetes.io/name: authentik
|
|
||||||
endpoints:
|
|
||||||
- port: http
|
|
||||||
interval: 30s
|
|
||||||
path: /metrics
|
|
||||||
@@ -1,248 +0,0 @@
|
|||||||
---
|
|
||||||
# Namespace for serverless components
|
|
||||||
apiVersion: v1
|
|
||||||
kind: Namespace
|
|
||||||
metadata:
|
|
||||||
name: devcontainers
|
|
||||||
labels:
|
|
||||||
app.kubernetes.io/name: devcontainer
|
|
||||||
app.kubernetes.io/component: serverless
|
|
||||||
|
|
||||||
---
|
|
||||||
# Secret for GitHub tokens, VNC passwords, etc.
|
|
||||||
apiVersion: v1
|
|
||||||
kind: Secret
|
|
||||||
metadata:
|
|
||||||
name: devcontainer-serverless-secrets
|
|
||||||
namespace: devcontainers
|
|
||||||
type: Opaque
|
|
||||||
stringData:
|
|
||||||
# Update these values as needed
|
|
||||||
GITHUB_TOKEN: ""
|
|
||||||
VNC_PASSWORD: "changeme"
|
|
||||||
ANTHROPIC_API_KEY: ""
|
|
||||||
GIT_USER_NAME: "DevContainer User"
|
|
||||||
GIT_USER_EMAIL: "devcontainer@example.com"
|
|
||||||
|
|
||||||
---
|
|
||||||
# Routing proxy deployment (handles GitHub repo extraction)
|
|
||||||
apiVersion: apps/v1
|
|
||||||
kind: Deployment
|
|
||||||
metadata:
|
|
||||||
name: devcontainer-routing-proxy
|
|
||||||
namespace: devcontainers
|
|
||||||
labels:
|
|
||||||
app.kubernetes.io/name: devcontainer
|
|
||||||
app.kubernetes.io/component: routing-proxy
|
|
||||||
spec:
|
|
||||||
replicas: 2 # High availability
|
|
||||||
selector:
|
|
||||||
matchLabels:
|
|
||||||
app.kubernetes.io/name: devcontainer
|
|
||||||
app.kubernetes.io/component: routing-proxy
|
|
||||||
template:
|
|
||||||
metadata:
|
|
||||||
labels:
|
|
||||||
app.kubernetes.io/name: devcontainer
|
|
||||||
app.kubernetes.io/component: routing-proxy
|
|
||||||
spec:
|
|
||||||
containers:
|
|
||||||
- name: routing-proxy
|
|
||||||
image: ghcr.io/cpfarhood/devcontainer-routing-proxy:latest
|
|
||||||
ports:
|
|
||||||
- containerPort: 8080
|
|
||||||
name: http
|
|
||||||
env:
|
|
||||||
- name: DEVCONTAINER_SERVICE_URL
|
|
||||||
value: "devcontainer-serverless.devcontainers.svc.cluster.local"
|
|
||||||
resources:
|
|
||||||
requests:
|
|
||||||
memory: "64Mi"
|
|
||||||
cpu: "100m"
|
|
||||||
limits:
|
|
||||||
memory: "256Mi"
|
|
||||||
cpu: "500m"
|
|
||||||
livenessProbe:
|
|
||||||
httpGet:
|
|
||||||
path: /health
|
|
||||||
port: 8080
|
|
||||||
initialDelaySeconds: 5
|
|
||||||
periodSeconds: 10
|
|
||||||
readinessProbe:
|
|
||||||
httpGet:
|
|
||||||
path: /health
|
|
||||||
port: 8080
|
|
||||||
initialDelaySeconds: 2
|
|
||||||
periodSeconds: 5
|
|
||||||
|
|
||||||
---
|
|
||||||
# Service for routing proxy
|
|
||||||
apiVersion: v1
|
|
||||||
kind: Service
|
|
||||||
metadata:
|
|
||||||
name: devcontainer-routing-proxy
|
|
||||||
namespace: devcontainers
|
|
||||||
labels:
|
|
||||||
app.kubernetes.io/name: devcontainer
|
|
||||||
app.kubernetes.io/component: routing-proxy
|
|
||||||
spec:
|
|
||||||
type: ClusterIP
|
|
||||||
ports:
|
|
||||||
- port: 80
|
|
||||||
targetPort: 8080
|
|
||||||
name: http
|
|
||||||
selector:
|
|
||||||
app.kubernetes.io/name: devcontainer
|
|
||||||
app.kubernetes.io/component: routing-proxy
|
|
||||||
|
|
||||||
---
|
|
||||||
# Knative Service (auto-scaling devcontainer instances)
|
|
||||||
apiVersion: serving.knative.dev/v1
|
|
||||||
kind: Service
|
|
||||||
metadata:
|
|
||||||
name: devcontainer-serverless
|
|
||||||
namespace: devcontainers
|
|
||||||
annotations:
|
|
||||||
# Scale to zero when not in use (saves resources)
|
|
||||||
autoscaling.knative.dev/minScale: "0"
|
|
||||||
autoscaling.knative.dev/maxScale: "10"
|
|
||||||
# Keep instances warm for 5 minutes after last request
|
|
||||||
autoscaling.knative.dev/scale-to-zero-grace-period: "5m"
|
|
||||||
# Target 1 concurrent request per pod (ensures isolation)
|
|
||||||
autoscaling.knative.dev/target: "1"
|
|
||||||
# Custom domain (optional - configure after Authentik setup)
|
|
||||||
# serving.knative.dev/domain: "devcontainer.farh.net"
|
|
||||||
spec:
|
|
||||||
template:
|
|
||||||
metadata:
|
|
||||||
annotations:
|
|
||||||
# Container port for VNC web interface
|
|
||||||
autoscaling.knative.dev/targetPort: "5800"
|
|
||||||
# Timeout for cold starts (dev containers need time to initialize)
|
|
||||||
serving.knative.dev/timeoutSeconds: "600" # 10 minutes for repo cloning
|
|
||||||
# Resource allocation per instance
|
|
||||||
autoscaling.knative.dev/class: "kpa.autoscaling.knative.dev"
|
|
||||||
autoscaling.knative.dev/metric: "concurrency"
|
|
||||||
spec:
|
|
||||||
# Give containers more time to start (repo cloning + IDE launch)
|
|
||||||
timeoutSeconds: 600 # 10 minutes
|
|
||||||
containers:
|
|
||||||
- name: devcontainer
|
|
||||||
image: ghcr.io/cpfarhood/devcontainer:latest
|
|
||||||
ports:
|
|
||||||
- containerPort: 5800
|
|
||||||
name: vnc-web
|
|
||||||
env:
|
|
||||||
# Flag to indicate serverless mode
|
|
||||||
- name: SERVERLESS_MODE
|
|
||||||
value: "true"
|
|
||||||
- name: DYNAMIC_GITHUB_ROUTING
|
|
||||||
value: "true"
|
|
||||||
- name: IDE
|
|
||||||
value: "vscode"
|
|
||||||
- name: DISPLAY_WIDTH
|
|
||||||
value: "1920"
|
|
||||||
- name: DISPLAY_HEIGHT
|
|
||||||
value: "1080"
|
|
||||||
- name: SECURE_CONNECTION
|
|
||||||
value: "0"
|
|
||||||
- name: USER_ID
|
|
||||||
value: "1000"
|
|
||||||
- name: GROUP_ID
|
|
||||||
value: "1000"
|
|
||||||
# Enable file manager for easy upload/download
|
|
||||||
- name: WEB_FILE_MANAGER
|
|
||||||
value: "1"
|
|
||||||
- name: WEB_FILE_MANAGER_ALLOWED_PATHS
|
|
||||||
value: "/workspace,/config"
|
|
||||||
# Happy Coder config (ephemeral in serverless mode)
|
|
||||||
- name: HAPPY_HOME_DIR
|
|
||||||
value: "/tmp/.happy"
|
|
||||||
- name: HAPPY_EXPERIMENTAL
|
|
||||||
value: "true"
|
|
||||||
# Use secrets for sensitive data
|
|
||||||
envFrom:
|
|
||||||
- secretRef:
|
|
||||||
name: devcontainer-serverless-secrets
|
|
||||||
optional: false
|
|
||||||
resources:
|
|
||||||
requests:
|
|
||||||
memory: "1Gi"
|
|
||||||
cpu: "500m"
|
|
||||||
limits:
|
|
||||||
memory: "4Gi"
|
|
||||||
cpu: "2000m"
|
|
||||||
volumeMounts:
|
|
||||||
- name: tmp-home
|
|
||||||
mountPath: /config
|
|
||||||
- name: shm
|
|
||||||
mountPath: /dev/shm
|
|
||||||
# Readiness probe - VNC must be ready
|
|
||||||
readinessProbe:
|
|
||||||
httpGet:
|
|
||||||
path: /
|
|
||||||
port: 5800
|
|
||||||
initialDelaySeconds: 60
|
|
||||||
periodSeconds: 10
|
|
||||||
timeoutSeconds: 5
|
|
||||||
failureThreshold: 10
|
|
||||||
# Liveness probe - ensure container stays healthy
|
|
||||||
livenessProbe:
|
|
||||||
httpGet:
|
|
||||||
path: /
|
|
||||||
port: 5800
|
|
||||||
initialDelaySeconds: 120
|
|
||||||
periodSeconds: 30
|
|
||||||
timeoutSeconds: 10
|
|
||||||
failureThreshold: 3
|
|
||||||
volumes:
|
|
||||||
- name: tmp-home
|
|
||||||
emptyDir: {} # Ephemeral - each instance gets fresh home
|
|
||||||
- name: shm
|
|
||||||
emptyDir:
|
|
||||||
medium: Memory
|
|
||||||
sizeLimit: 2Gi
|
|
||||||
|
|
||||||
---
|
|
||||||
# Ingress for the routing proxy (will be secured by Authentik)
|
|
||||||
apiVersion: networking.k8s.io/v1
|
|
||||||
kind: Ingress
|
|
||||||
metadata:
|
|
||||||
name: devcontainer-serverless-ingress
|
|
||||||
namespace: devcontainers
|
|
||||||
annotations:
|
|
||||||
# Authentik forward auth annotations
|
|
||||||
nginx.ingress.kubernetes.io/auth-url: http://authentik.authentik.svc.cluster.local/outpost.goauthentik.io/auth/nginx
|
|
||||||
nginx.ingress.kubernetes.io/auth-signin: https://auth.farh.net/outpost.goauthentik.io/start?rd=$escaped_request_uri
|
|
||||||
nginx.ingress.kubernetes.io/auth-response-headers: X-Authentik-Username,X-Authentik-Groups,X-Authentik-Email,X-Authentik-Name
|
|
||||||
nginx.ingress.kubernetes.io/auth-snippet: |
|
|
||||||
proxy_set_header X-Forwarded-Host $http_host;
|
|
||||||
|
|
||||||
# SSL and general settings
|
|
||||||
cert-manager.io/cluster-issuer: "letsencrypt-prod"
|
|
||||||
nginx.ingress.kubernetes.io/ssl-redirect: "true"
|
|
||||||
nginx.ingress.kubernetes.io/force-ssl-redirect: "true"
|
|
||||||
|
|
||||||
# WebSocket support for VNC
|
|
||||||
nginx.ingress.kubernetes.io/proxy-read-timeout: "3600"
|
|
||||||
nginx.ingress.kubernetes.io/proxy-send-timeout: "3600"
|
|
||||||
|
|
||||||
# Large file upload support (for file manager)
|
|
||||||
nginx.ingress.kubernetes.io/client-max-body-size: "100m"
|
|
||||||
nginx.ingress.kubernetes.io/proxy-body-size: "100m"
|
|
||||||
spec:
|
|
||||||
tls:
|
|
||||||
- hosts:
|
|
||||||
- devcontainer.farh.net
|
|
||||||
secretName: devcontainer-serverless-tls
|
|
||||||
rules:
|
|
||||||
- host: devcontainer.farh.net
|
|
||||||
http:
|
|
||||||
paths:
|
|
||||||
- path: /
|
|
||||||
pathType: Prefix
|
|
||||||
backend:
|
|
||||||
service:
|
|
||||||
name: devcontainer-routing-proxy
|
|
||||||
port:
|
|
||||||
number: 80
|
|
||||||
@@ -1,112 +0,0 @@
|
|||||||
apiVersion: serving.knative.dev/v1
|
|
||||||
kind: Service
|
|
||||||
metadata:
|
|
||||||
name: devcontainer-serverless
|
|
||||||
namespace: devcontainers
|
|
||||||
annotations:
|
|
||||||
# Scale to zero when not in use (saves resources)
|
|
||||||
autoscaling.knative.dev/minScale: "0"
|
|
||||||
autoscaling.knative.dev/maxScale: "10"
|
|
||||||
# Keep instances warm for 5 minutes after last request
|
|
||||||
autoscaling.knative.dev/scale-to-zero-grace-period: "5m"
|
|
||||||
# Target 1 concurrent request per pod (ensures isolation)
|
|
||||||
autoscaling.knative.dev/target: "1"
|
|
||||||
spec:
|
|
||||||
template:
|
|
||||||
metadata:
|
|
||||||
annotations:
|
|
||||||
# Container port for VNC web interface
|
|
||||||
autoscaling.knative.dev/targetPort: "5800"
|
|
||||||
# Timeout for cold starts (dev containers need time to initialize)
|
|
||||||
serving.knative.dev/timeoutSeconds: "300"
|
|
||||||
spec:
|
|
||||||
# Give containers more time to start (repo cloning + IDE launch)
|
|
||||||
timeoutSeconds: 300
|
|
||||||
containers:
|
|
||||||
- name: devcontainer
|
|
||||||
image: ghcr.io/cpfarhood/devcontainer:latest
|
|
||||||
ports:
|
|
||||||
- containerPort: 5800
|
|
||||||
name: vnc-web
|
|
||||||
env:
|
|
||||||
# Dynamic repo extraction will be handled by a startup script
|
|
||||||
- name: DYNAMIC_GITHUB_ROUTING
|
|
||||||
value: "true"
|
|
||||||
- name: IDE
|
|
||||||
value: "vscode"
|
|
||||||
- name: DISPLAY_WIDTH
|
|
||||||
value: "1920"
|
|
||||||
- name: DISPLAY_HEIGHT
|
|
||||||
value: "1080"
|
|
||||||
- name: SECURE_CONNECTION
|
|
||||||
value: "0"
|
|
||||||
- name: USER_ID
|
|
||||||
value: "1000"
|
|
||||||
- name: GROUP_ID
|
|
||||||
value: "1000"
|
|
||||||
# Enable file manager for easy upload/download
|
|
||||||
- name: WEB_FILE_MANAGER
|
|
||||||
value: "1"
|
|
||||||
- name: WEB_FILE_MANAGER_ALLOWED_PATHS
|
|
||||||
value: "/workspace,/config"
|
|
||||||
# Happy Coder config
|
|
||||||
- name: HAPPY_HOME_DIR
|
|
||||||
value: "/config/userdata/.happy"
|
|
||||||
- name: HAPPY_EXPERIMENTAL
|
|
||||||
value: "true"
|
|
||||||
# Use secrets for sensitive data
|
|
||||||
envFrom:
|
|
||||||
- secretRef:
|
|
||||||
name: devcontainer-serverless-secrets
|
|
||||||
optional: true
|
|
||||||
resources:
|
|
||||||
requests:
|
|
||||||
memory: "1Gi"
|
|
||||||
cpu: "500m"
|
|
||||||
limits:
|
|
||||||
memory: "4Gi"
|
|
||||||
cpu: "2000m"
|
|
||||||
volumeMounts:
|
|
||||||
- name: userhome
|
|
||||||
mountPath: /config
|
|
||||||
- name: shm
|
|
||||||
mountPath: /dev/shm
|
|
||||||
# Readiness probe - VNC must be ready
|
|
||||||
readinessProbe:
|
|
||||||
httpGet:
|
|
||||||
path: /
|
|
||||||
port: 5800
|
|
||||||
initialDelaySeconds: 30
|
|
||||||
periodSeconds: 5
|
|
||||||
timeoutSeconds: 3
|
|
||||||
# Liveness probe - ensure container stays healthy
|
|
||||||
livenessProbe:
|
|
||||||
httpGet:
|
|
||||||
path: /
|
|
||||||
port: 5800
|
|
||||||
initialDelaySeconds: 60
|
|
||||||
periodSeconds: 10
|
|
||||||
timeoutSeconds: 5
|
|
||||||
volumes:
|
|
||||||
- name: userhome
|
|
||||||
emptyDir: {} # Ephemeral - each instance gets fresh home
|
|
||||||
- name: shm
|
|
||||||
emptyDir:
|
|
||||||
medium: Memory
|
|
||||||
sizeLimit: 2Gi
|
|
||||||
---
|
|
||||||
# Secret template for GitHub tokens, VNC passwords, etc.
|
|
||||||
apiVersion: v1
|
|
||||||
kind: Secret
|
|
||||||
metadata:
|
|
||||||
name: devcontainer-serverless-secrets
|
|
||||||
namespace: devcontainers
|
|
||||||
type: Opaque
|
|
||||||
data:
|
|
||||||
# Base64 encoded values - update as needed
|
|
||||||
# echo -n "your-github-token" | base64
|
|
||||||
GITHUB_TOKEN: ""
|
|
||||||
# echo -n "your-vnc-password" | base64
|
|
||||||
VNC_PASSWORD: ""
|
|
||||||
# echo -n "your-anthropic-key" | base64
|
|
||||||
ANTHROPIC_API_KEY: ""
|
|
||||||
@@ -1,16 +0,0 @@
|
|||||||
# Lightweight routing proxy for dynamic GitHub repo routing
|
|
||||||
FROM nginx:1.27-alpine
|
|
||||||
|
|
||||||
# Install envsubst for template rendering
|
|
||||||
RUN apk add --no-cache gettext
|
|
||||||
|
|
||||||
# Copy nginx configuration template
|
|
||||||
COPY nginx.conf.template /etc/nginx/nginx.conf.template
|
|
||||||
COPY entrypoint.sh /entrypoint.sh
|
|
||||||
|
|
||||||
RUN chmod +x /entrypoint.sh
|
|
||||||
|
|
||||||
EXPOSE 8080
|
|
||||||
|
|
||||||
ENTRYPOINT ["/entrypoint.sh"]
|
|
||||||
CMD ["nginx", "-g", "daemon off;"]
|
|
||||||
@@ -1,16 +0,0 @@
|
|||||||
#!/bin/sh
|
|
||||||
|
|
||||||
# Set default values for environment variables
|
|
||||||
DEVCONTAINER_SERVICE_URL=${DEVCONTAINER_SERVICE_URL:-"devcontainer-serverless.devcontainers.svc.cluster.local"}
|
|
||||||
|
|
||||||
# Create temp directories
|
|
||||||
mkdir -p /tmp/client_temp /tmp/proxy_temp /tmp/fastcgi_temp /tmp/uwsgi_temp /tmp/scgi_temp
|
|
||||||
|
|
||||||
# Substitute environment variables in nginx config
|
|
||||||
envsubst '$DEVCONTAINER_SERVICE_URL' < /etc/nginx/nginx.conf.template > /etc/nginx/nginx.conf
|
|
||||||
|
|
||||||
echo "Starting routing proxy..."
|
|
||||||
echo "Routing to: $DEVCONTAINER_SERVICE_URL"
|
|
||||||
|
|
||||||
# Start nginx
|
|
||||||
exec "$@"
|
|
||||||
@@ -1,124 +0,0 @@
|
|||||||
worker_processes auto;
|
|
||||||
error_log /var/log/nginx/error.log warn;
|
|
||||||
pid /tmp/nginx.pid;
|
|
||||||
|
|
||||||
events {
|
|
||||||
worker_connections 1024;
|
|
||||||
}
|
|
||||||
|
|
||||||
http {
|
|
||||||
include /etc/nginx/mime.types;
|
|
||||||
default_type application/octet-stream;
|
|
||||||
|
|
||||||
# Logging format
|
|
||||||
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
|
|
||||||
'$status $body_bytes_sent "$http_referer" '
|
|
||||||
'"$http_user_agent" "$http_x_forwarded_for" '
|
|
||||||
'repo="$github_repo" user="$authentik_user"';
|
|
||||||
|
|
||||||
access_log /var/log/nginx/access.log main;
|
|
||||||
|
|
||||||
# Basic settings
|
|
||||||
sendfile on;
|
|
||||||
tcp_nopush on;
|
|
||||||
tcp_nodelay on;
|
|
||||||
keepalive_timeout 65;
|
|
||||||
types_hash_max_size 2048;
|
|
||||||
client_max_body_size 100M; # Allow large file uploads via file manager
|
|
||||||
|
|
||||||
# Temp directories (writable in container)
|
|
||||||
client_body_temp_path /tmp/client_temp;
|
|
||||||
proxy_temp_path /tmp/proxy_temp;
|
|
||||||
fastcgi_temp_path /tmp/fastcgi_temp;
|
|
||||||
uwsgi_temp_path /tmp/uwsgi_temp;
|
|
||||||
scgi_temp_path /tmp/scgi_temp;
|
|
||||||
|
|
||||||
# Upstream Knative service (will be resolved by Knative networking)
|
|
||||||
upstream devcontainer_serverless {
|
|
||||||
server ${DEVCONTAINER_SERVICE_URL};
|
|
||||||
}
|
|
||||||
|
|
||||||
# Map to extract GitHub repo from URL path
|
|
||||||
map $request_uri $github_repo {
|
|
||||||
~^/github/([^/]+/[^/]+)(/.*)?$ https://github.com/$1;
|
|
||||||
default "";
|
|
||||||
}
|
|
||||||
|
|
||||||
# Extract Authentik user info from headers (set by Authentik forward auth)
|
|
||||||
map $http_x_authentik_username $authentik_user {
|
|
||||||
default $http_x_authentik_username;
|
|
||||||
}
|
|
||||||
|
|
||||||
server {
|
|
||||||
listen 8080;
|
|
||||||
server_name _;
|
|
||||||
|
|
||||||
# Health check endpoint
|
|
||||||
location /health {
|
|
||||||
access_log off;
|
|
||||||
return 200 "OK\n";
|
|
||||||
add_header Content-Type text/plain;
|
|
||||||
}
|
|
||||||
|
|
||||||
# GitHub repo routing
|
|
||||||
location ~ ^/github/([^/]+/[^/]+)(/.*)?$ {
|
|
||||||
# Validate the repo format
|
|
||||||
if ($github_repo = "") {
|
|
||||||
return 400 "Invalid GitHub repository format. Use: /github/owner/repo\n";
|
|
||||||
}
|
|
||||||
|
|
||||||
# Log the routing decision
|
|
||||||
access_log /var/log/nginx/routing.log main;
|
|
||||||
|
|
||||||
# Set headers for the devcontainer
|
|
||||||
proxy_set_header Host $host;
|
|
||||||
proxy_set_header X-Real-IP $remote_addr;
|
|
||||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
|
||||||
proxy_set_header X-Forwarded-Proto $scheme;
|
|
||||||
|
|
||||||
# Custom headers for dynamic repo routing
|
|
||||||
proxy_set_header X-GitHub-Repo $github_repo;
|
|
||||||
proxy_set_header X-Authentik-User $authentik_user;
|
|
||||||
proxy_set_header X-Request-Path $request_uri;
|
|
||||||
|
|
||||||
# Preserve Authentik auth headers
|
|
||||||
proxy_set_header X-Authentik-Username $http_x_authentik_username;
|
|
||||||
proxy_set_header X-Authentik-Email $http_x_authentik_email;
|
|
||||||
proxy_set_header X-Authentik-Name $http_x_authentik_name;
|
|
||||||
proxy_set_header X-Authentik-Groups $http_x_authentik_groups;
|
|
||||||
|
|
||||||
# Proxy settings for long-running connections (VNC)
|
|
||||||
proxy_http_version 1.1;
|
|
||||||
proxy_set_header Upgrade $http_upgrade;
|
|
||||||
proxy_set_header Connection $connection_upgrade;
|
|
||||||
proxy_read_timeout 86400; # 24 hours
|
|
||||||
proxy_send_timeout 86400;
|
|
||||||
proxy_connect_timeout 30;
|
|
||||||
|
|
||||||
# Buffer settings for file uploads
|
|
||||||
proxy_buffering off;
|
|
||||||
proxy_request_buffering off;
|
|
||||||
|
|
||||||
# Forward to the devcontainer
|
|
||||||
proxy_pass http://devcontainer_serverless$2;
|
|
||||||
}
|
|
||||||
|
|
||||||
# Root path - show available repositories or redirect to auth
|
|
||||||
location = / {
|
|
||||||
return 200 "DevContainer Serverless\nUsage: /github/{owner}/{repo}\nExample: /github/microsoft/vscode\n";
|
|
||||||
add_header Content-Type text/plain;
|
|
||||||
}
|
|
||||||
|
|
||||||
# Anything else
|
|
||||||
location / {
|
|
||||||
return 404 "Not found. Use /github/{owner}/{repo} to access repositories.\n";
|
|
||||||
add_header Content-Type text/plain;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
# WebSocket upgrade handling
|
|
||||||
map $http_upgrade $connection_upgrade {
|
|
||||||
default upgrade;
|
|
||||||
'' close;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,124 +0,0 @@
|
|||||||
#!/bin/bash
|
|
||||||
|
|
||||||
# Dynamic GitHub repository initialization for serverless mode
|
|
||||||
# This script extracts the GitHub repo from HTTP headers set by the routing proxy
|
|
||||||
|
|
||||||
set -e
|
|
||||||
|
|
||||||
log() {
|
|
||||||
echo "[$(date '+%Y-%m-%d %H:%M:%S')] DYNAMIC-INIT: $*" >&2
|
|
||||||
}
|
|
||||||
|
|
||||||
log "Starting dynamic repository initialization..."
|
|
||||||
|
|
||||||
# In serverless mode, we expect the routing proxy to have set these environment variables
|
|
||||||
# from the HTTP headers. If running standalone, fallback to GITHUB_REPO env var.
|
|
||||||
|
|
||||||
if [[ "$SERVERLESS_MODE" == "true" ]]; then
|
|
||||||
log "Serverless mode detected"
|
|
||||||
|
|
||||||
# The routing proxy should have set these via HTTP headers -> env vars
|
|
||||||
# Check if we have the GitHub repo from the X-GitHub-Repo header
|
|
||||||
if [[ -n "$HTTP_X_GITHUB_REPO" ]]; then
|
|
||||||
GITHUB_REPO="$HTTP_X_GITHUB_REPO"
|
|
||||||
log "Using GitHub repo from header: $GITHUB_REPO"
|
|
||||||
elif [[ -n "$X_GITHUB_REPO" ]]; then
|
|
||||||
GITHUB_REPO="$X_GITHUB_REPO"
|
|
||||||
log "Using GitHub repo from X-GitHub-Repo: $GITHUB_REPO"
|
|
||||||
else
|
|
||||||
# Try to extract from a file written by an init container or sidecar
|
|
||||||
if [[ -f "/tmp/github-repo" ]]; then
|
|
||||||
GITHUB_REPO=$(cat /tmp/github-repo)
|
|
||||||
log "Using GitHub repo from file: $GITHUB_REPO"
|
|
||||||
else
|
|
||||||
log "ERROR: No GitHub repository specified in serverless mode"
|
|
||||||
log "Expected HTTP_X_GITHUB_REPO or X_GITHUB_REPO header from routing proxy"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Extract user info if available
|
|
||||||
if [[ -n "$HTTP_X_AUTHENTIK_USERNAME" ]]; then
|
|
||||||
export GIT_USER_NAME="${HTTP_X_AUTHENTIK_NAME:-$HTTP_X_AUTHENTIK_USERNAME}"
|
|
||||||
export GIT_USER_EMAIL="${HTTP_X_AUTHENTIK_EMAIL:-${HTTP_X_AUTHENTIK_USERNAME}@devcontainer.local}"
|
|
||||||
log "Using Authentik user: $GIT_USER_NAME <$GIT_USER_EMAIL>"
|
|
||||||
fi
|
|
||||||
else
|
|
||||||
log "Traditional mode - using GITHUB_REPO environment variable"
|
|
||||||
if [[ -z "$GITHUB_REPO" ]]; then
|
|
||||||
log "ERROR: GITHUB_REPO environment variable is required"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Validate the GitHub repo URL
|
|
||||||
if [[ ! "$GITHUB_REPO" =~ ^https://github\.com/[^/]+/[^/]+/?$ ]]; then
|
|
||||||
log "ERROR: Invalid GitHub repository URL: $GITHUB_REPO"
|
|
||||||
log "Expected format: https://github.com/owner/repo"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Extract owner and repo name for workspace directory
|
|
||||||
REPO_OWNER=$(echo "$GITHUB_REPO" | sed 's|https://github.com/\([^/]*\)/.*|\1|')
|
|
||||||
REPO_NAME=$(echo "$GITHUB_REPO" | sed 's|https://github.com/[^/]*/\([^/]*\)/?|\1|')
|
|
||||||
WORKSPACE_DIR="/workspace/${REPO_OWNER}-${REPO_NAME}"
|
|
||||||
|
|
||||||
log "Repository: $GITHUB_REPO"
|
|
||||||
log "Owner: $REPO_OWNER"
|
|
||||||
log "Name: $REPO_NAME"
|
|
||||||
log "Workspace: $WORKSPACE_DIR"
|
|
||||||
|
|
||||||
# Configure git user (use defaults if not set via Authentik)
|
|
||||||
GIT_USER_NAME="${GIT_USER_NAME:-DevContainer User}"
|
|
||||||
GIT_USER_EMAIL="${GIT_USER_EMAIL:-devcontainer@example.com}"
|
|
||||||
|
|
||||||
log "Configuring git user: $GIT_USER_NAME <$GIT_USER_EMAIL>"
|
|
||||||
git config --global user.name "$GIT_USER_NAME"
|
|
||||||
git config --global user.email "$GIT_USER_EMAIL"
|
|
||||||
|
|
||||||
# Configure git credentials if GitHub token is available
|
|
||||||
if [[ -n "$GITHUB_TOKEN" ]]; then
|
|
||||||
log "Configuring GitHub credentials..."
|
|
||||||
git config --global credential.helper store
|
|
||||||
echo "https://oauth2:${GITHUB_TOKEN}@github.com" > ~/.git-credentials
|
|
||||||
chmod 600 ~/.git-credentials
|
|
||||||
else
|
|
||||||
log "No GitHub token provided - using public access only"
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Create workspace directory
|
|
||||||
mkdir -p "$(dirname "$WORKSPACE_DIR")"
|
|
||||||
cd "$(dirname "$WORKSPACE_DIR")"
|
|
||||||
|
|
||||||
# Clone the repository
|
|
||||||
if [[ -d "$WORKSPACE_DIR" ]]; then
|
|
||||||
log "Repository directory exists, pulling latest changes..."
|
|
||||||
cd "$WORKSPACE_DIR"
|
|
||||||
git pull --ff-only || {
|
|
||||||
log "WARNING: Could not fast-forward, repository may have diverged"
|
|
||||||
log "Continuing with existing state..."
|
|
||||||
}
|
|
||||||
else
|
|
||||||
log "Cloning repository..."
|
|
||||||
git clone "$GITHUB_REPO" "$WORKSPACE_DIR" || {
|
|
||||||
log "ERROR: Failed to clone repository $GITHUB_REPO"
|
|
||||||
log "This may be a private repository or the URL may be incorrect"
|
|
||||||
exit 1
|
|
||||||
}
|
|
||||||
cd "$WORKSPACE_DIR"
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Set the workspace directory for the IDE
|
|
||||||
export WORKSPACE_DIR
|
|
||||||
|
|
||||||
log "Repository initialization complete!"
|
|
||||||
log "Workspace directory: $WORKSPACE_DIR"
|
|
||||||
|
|
||||||
# Change to the workspace directory so the IDE opens in the right place
|
|
||||||
cd "$WORKSPACE_DIR"
|
|
||||||
|
|
||||||
# Export variables for the parent script
|
|
||||||
export GITHUB_REPO
|
|
||||||
export WORKSPACE_DIR
|
|
||||||
export REPO_OWNER
|
|
||||||
export REPO_NAME
|
|
||||||
@@ -1,86 +0,0 @@
|
|||||||
#!/bin/bash
|
|
||||||
|
|
||||||
# Serverless-aware startup script for devcontainer
|
|
||||||
# This replaces the standard /startapp.sh when in serverless mode
|
|
||||||
|
|
||||||
set -e
|
|
||||||
|
|
||||||
log() {
|
|
||||||
echo "[$(date '+%Y-%m-%d %H:%M:%S')] SERVERLESS-START: $*" >&2
|
|
||||||
}
|
|
||||||
|
|
||||||
log "Starting serverless devcontainer..."
|
|
||||||
log "Mode: ${SERVERLESS_MODE:-traditional}"
|
|
||||||
log "IDE: ${IDE:-vscode}"
|
|
||||||
|
|
||||||
# Wait for HTTP headers to be available (in case of init container pattern)
|
|
||||||
# In Knative, the headers should be available immediately as env vars
|
|
||||||
sleep 2
|
|
||||||
|
|
||||||
# Check if we're in serverless mode with dynamic routing
|
|
||||||
if [[ "$SERVERLESS_MODE" == "true" && "$DYNAMIC_GITHUB_ROUTING" == "true" ]]; then
|
|
||||||
log "Dynamic GitHub routing enabled"
|
|
||||||
|
|
||||||
# In Knative, HTTP headers become environment variables with HTTP_ prefix
|
|
||||||
# But we also check for the unprefixed versions set by proxies
|
|
||||||
AVAILABLE_VARS=$(env | grep -E "(GITHUB|AUTHENTIK|X_)" | sort)
|
|
||||||
if [[ -n "$AVAILABLE_VARS" ]]; then
|
|
||||||
log "Available routing variables:"
|
|
||||||
echo "$AVAILABLE_VARS" | while read -r var; do
|
|
||||||
log " $var"
|
|
||||||
done
|
|
||||||
else
|
|
||||||
log "No routing variables found, checking for alternatives..."
|
|
||||||
# Check if there's a file with the repo info
|
|
||||||
if [[ -f "/tmp/github-repo" ]]; then
|
|
||||||
export GITHUB_REPO=$(cat /tmp/github-repo)
|
|
||||||
log "Found repo file: $GITHUB_REPO"
|
|
||||||
else
|
|
||||||
log "ERROR: No GitHub repository information available"
|
|
||||||
log "Expected routing headers or /tmp/github-repo file"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Use the dynamic initialization script
|
|
||||||
source /usr/local/bin/dynamic-init-repo
|
|
||||||
else
|
|
||||||
log "Using standard initialization..."
|
|
||||||
# Use the standard initialization
|
|
||||||
source /usr/local/bin/init-repo
|
|
||||||
fi
|
|
||||||
|
|
||||||
# At this point, WORKSPACE_DIR should be set by the init script
|
|
||||||
WORKSPACE_DIR="${WORKSPACE_DIR:-/workspace}"
|
|
||||||
log "Working directory: $WORKSPACE_DIR"
|
|
||||||
|
|
||||||
# Ensure we're in the workspace directory
|
|
||||||
cd "$WORKSPACE_DIR"
|
|
||||||
|
|
||||||
# Launch the appropriate IDE based on the IDE environment variable
|
|
||||||
case "${IDE:-vscode}" in
|
|
||||||
"vscode")
|
|
||||||
log "Starting VSCode..."
|
|
||||||
exec code --new-window --wait "$WORKSPACE_DIR"
|
|
||||||
;;
|
|
||||||
"antigravity")
|
|
||||||
log "Starting Antigravity..."
|
|
||||||
exec antigravity \
|
|
||||||
--no-sandbox \
|
|
||||||
--user-data-dir ~/.config/antigravity \
|
|
||||||
--disable-dev-shm-usage \
|
|
||||||
--disable-gpu \
|
|
||||||
--disable-features=VizDisplayCompositor \
|
|
||||||
--new-window \
|
|
||||||
"$WORKSPACE_DIR"
|
|
||||||
;;
|
|
||||||
"none")
|
|
||||||
log "No IDE requested, keeping container alive..."
|
|
||||||
exec sleep infinity
|
|
||||||
;;
|
|
||||||
*)
|
|
||||||
log "ERROR: Unknown IDE type: $IDE"
|
|
||||||
log "Valid options: vscode, antigravity, none"
|
|
||||||
exit 1
|
|
||||||
;;
|
|
||||||
esac
|
|
||||||
Reference in New Issue
Block a user