From 60f96fc8da283611b5b49e89f5e89dede823694a Mon Sep 17 00:00:00 2001 From: DevContainer User Date: Tue, 3 Mar 2026 16:42:53 +0000 Subject: [PATCH] feat: add multi-repo cloning, remove dynamic/serverless mode Add githubRepos list field for cloning multiple repositories into a single dev container with multi-root workspace file generation. Remove the unused dynamic deployment mode (Knative, routing proxy, serverless scripts) to simplify the chart to persistent-only. Fix release workflow cache-to setting that violated the no-cache policy. Co-Authored-By: Claude Opus 4.6 --- .github/workflows/release-unified.yaml | 1 - CLAUDE.md | 10 +- Dockerfile | 3 - chart/Chart.yaml | 4 +- chart/templates/deployment.yaml | 6 +- chart/templates/dynamic-ingress.yaml | 68 ---- chart/templates/knative-service.yaml | 98 ----- chart/templates/pvc.yaml | 2 - chart/templates/rbac.yaml | 2 - chart/templates/routing-proxy.yaml | 66 ---- chart/templates/service.yaml | 2 - chart/values-dynamic.yaml | 115 ------ chart/values-quickstart.yaml | 5 + chart/values.schema.json | 81 +--- chart/values.yaml | 75 +--- scripts/init-repo.sh | 90 +++-- scripts/startapp.sh | 8 +- serverless/Makefile | 173 --------- serverless/README.md | 376 ------------------- serverless/authentik-config.yaml | 168 --------- serverless/deployment.yaml | 243 ------------ serverless/knative-service.yaml | 107 ------ serverless/routing-proxy/Dockerfile | 16 - serverless/routing-proxy/entrypoint.sh | 16 - serverless/routing-proxy/nginx.conf.template | 124 ------ serverless/scripts/dynamic-init-repo.sh | 124 ------ serverless/scripts/serverless-startapp.sh | 86 ----- 27 files changed, 97 insertions(+), 1972 deletions(-) delete mode 100644 chart/templates/dynamic-ingress.yaml delete mode 100644 chart/templates/knative-service.yaml delete mode 100644 chart/templates/routing-proxy.yaml delete mode 100644 chart/values-dynamic.yaml delete mode 100644 serverless/Makefile delete mode 100644 serverless/README.md delete mode 100644 serverless/authentik-config.yaml delete mode 100644 serverless/deployment.yaml delete mode 100644 serverless/knative-service.yaml delete mode 100644 serverless/routing-proxy/Dockerfile delete mode 100644 serverless/routing-proxy/entrypoint.sh delete mode 100644 serverless/routing-proxy/nginx.conf.template delete mode 100644 serverless/scripts/dynamic-init-repo.sh delete mode 100644 serverless/scripts/serverless-startapp.sh diff --git a/.github/workflows/release-unified.yaml b/.github/workflows/release-unified.yaml index 8dc40c1..0b4d85f 100644 --- a/.github/workflows/release-unified.yaml +++ b/.github/workflows/release-unified.yaml @@ -101,7 +101,6 @@ jobs: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.version.outputs.tag }} ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.version.outputs.version }} ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest - cache-to: type=gha,mode=max platforms: linux/amd64 - name: Publish Helm Chart to GitHub Pages diff --git a/CLAUDE.md b/CLAUDE.md index 794bf28..567050f 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -61,7 +61,8 @@ Container start → scripts/startapp.sh → scripts/init-repo.sh → 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 ``` @@ -70,7 +71,7 @@ Container start | File | Purpose | |------|---------| | `Dockerfile` | Image definition — installs Chrome, VSCode, Helm, gh CLI, kubeseal, Claude Code, OpenCode, Crush; creates non-root user (UID 1000) | -| `scripts/init-repo.sh` | Configures git credentials, clones GitHub repo | +| `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 | | `chart/` | Helm chart for Kubernetes deployment | | `chart/templates/deployment.yaml` | Deployment spec — main container + MCP sidecar containers | @@ -177,8 +178,9 @@ helm install my-devcontainer ./chart -f custom-values.yaml ### Environment Variables -**Required:** -- `GITHUB_REPO` — URL of repository to clone into `/workspace` +**Required (at least one):** +- `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:** - `GITHUB_TOKEN` — PAT for private repo access (automatically configures git credentials) diff --git a/Dockerfile b/Dockerfile index 1782a90..1d0f6f2 100644 --- a/Dockerfile +++ b/Dockerfile @@ -165,9 +165,6 @@ RUN mkdir -p /workspace && \ # Copy startup scripts COPY --chmod=755 scripts/startapp.sh /startapp.sh 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 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 diff --git a/chart/Chart.yaml b/chart/Chart.yaml index beebbbe..60d409f 100644 --- a/chart/Chart.yaml +++ b/chart/Chart.yaml @@ -1,6 +1,6 @@ apiVersion: v2 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 version: 2.3.0 appVersion: "latest" @@ -9,5 +9,3 @@ keywords: - devcontainer - vscode - ai - - knative - - serverless diff --git a/chart/templates/deployment.yaml b/chart/templates/deployment.yaml index f4a22ad..6b5d06c 100644 --- a/chart/templates/deployment.yaml +++ b/chart/templates/deployment.yaml @@ -1,4 +1,3 @@ -{{- if eq .Values.deploymentMode "persistent" }} apiVersion: apps/v1 kind: Deployment metadata: @@ -84,6 +83,10 @@ spec: - name: GITHUB_REPO value: {{ .Values.githubRepo | quote }} {{- end }} + {{- if .Values.githubRepos }} + - name: GITHUB_REPOS + value: {{ join "," .Values.githubRepos | quote }} + {{- end }} envFrom: - secretRef: name: {{ include "devcontainer.envSecretName" . }} @@ -305,4 +308,3 @@ spec: - name: userhome persistentVolumeClaim: claimName: {{ include "devcontainer.pvcName" . }} -{{- end }} diff --git a/chart/templates/dynamic-ingress.yaml b/chart/templates/dynamic-ingress.yaml deleted file mode 100644 index b995d9f..0000000 --- a/chart/templates/dynamic-ingress.yaml +++ /dev/null @@ -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 }} \ No newline at end of file diff --git a/chart/templates/knative-service.yaml b/chart/templates/knative-service.yaml deleted file mode 100644 index 30e4b8b..0000000 --- a/chart/templates/knative-service.yaml +++ /dev/null @@ -1,98 +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 - # 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 }} \ No newline at end of file diff --git a/chart/templates/pvc.yaml b/chart/templates/pvc.yaml index 49aea39..b79659a 100644 --- a/chart/templates/pvc.yaml +++ b/chart/templates/pvc.yaml @@ -1,4 +1,3 @@ -{{- if eq .Values.deploymentMode "persistent" }} apiVersion: v1 kind: PersistentVolumeClaim metadata: @@ -16,4 +15,3 @@ spec: resources: requests: storage: {{ .Values.storage.size }} -{{- end }} diff --git a/chart/templates/rbac.yaml b/chart/templates/rbac.yaml index baf7e44..2e792bf 100644 --- a/chart/templates/rbac.yaml +++ b/chart/templates/rbac.yaml @@ -1,4 +1,3 @@ -{{- if eq .Values.deploymentMode "persistent" }} {{- $access := .Values.clusterAccess | default "none" }} {{- $name := include "devcontainer.fullname" . }} {{- $ns := .Release.Namespace }} @@ -96,4 +95,3 @@ roleRef: {{- end }} {{- end }} -{{- end }} diff --git a/chart/templates/routing-proxy.yaml b/chart/templates/routing-proxy.yaml deleted file mode 100644 index 42bee23..0000000 --- a/chart/templates/routing-proxy.yaml +++ /dev/null @@ -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 }} \ No newline at end of file diff --git a/chart/templates/service.yaml b/chart/templates/service.yaml index 04ee2a7..42b3d2d 100644 --- a/chart/templates/service.yaml +++ b/chart/templates/service.yaml @@ -1,4 +1,3 @@ -{{- if eq .Values.deploymentMode "persistent" }} apiVersion: v1 kind: Service metadata: @@ -21,4 +20,3 @@ spec: {{- end }} selector: {{- include "devcontainer.labels" . | nindent 4 }} -{{- end }} diff --git a/chart/values-dynamic.yaml b/chart/values-dynamic.yaml deleted file mode 100644 index 5d56803..0000000 --- a/chart/values-dynamic.yaml +++ /dev/null @@ -1,115 +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 - -# 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 \ No newline at end of file diff --git a/chart/values-quickstart.yaml b/chart/values-quickstart.yaml index 922cf06..a87e2a6 100644 --- a/chart/values-quickstart.yaml +++ b/chart/values-quickstart.yaml @@ -8,6 +8,11 @@ name: mydev # GitHub repository to clone (required) 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) # Options: vscode | antigravity | none ide: diff --git a/chart/values.schema.json b/chart/values.schema.json index e7f1fc2..1e301ed 100644 --- a/chart/values.schema.json +++ b/chart/values.schema.json @@ -32,14 +32,14 @@ }, "required": ["repository", "tag"] }, - "deploymentMode": { - "type": "string", - "enum": ["persistent", "dynamic"], - "description": "Deployment mode: persistent (PVC-based) or dynamic (Knative serverless)" - }, "githubRepo": { "type": "string", - "description": "GitHub repository URL to clone (required in persistent mode, ignored in dynamic mode)" + "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": { "type": "object", @@ -59,75 +59,6 @@ }, "required": ["enabled"] }, - "dynamic": { - "type": "object", - "description": "Configuration for dynamic (serverless) deployment mode", - "properties": { - "knative": { - "type": "object", - "properties": { - "minScale": { "type": "integer", "minimum": 0 }, - "maxScale": { "type": "integer", "minimum": 1 }, - "target": { "type": "integer", "minimum": 1 }, - "scaleToZeroGracePeriod": { "type": "string" }, - "timeoutSeconds": { "type": "integer", "minimum": 60 }, - "resources": { - "type": "object", - "properties": { - "requests": { "$ref": "#/$defs/resourceSpec" }, - "limits": { "$ref": "#/$defs/resourceSpec" } - } - } - } - }, - "routingProxy": { - "type": "object", - "properties": { - "enabled": { "type": "boolean" }, - "replicas": { "type": "integer", "minimum": 1 }, - "image": { - "type": "object", - "properties": { - "repository": { "type": "string" }, - "tag": { "type": "string" }, - "pullPolicy": { "type": "string", "enum": ["Always", "IfNotPresent", "Never"] } - } - }, - "resources": { - "type": "object", - "properties": { - "requests": { "$ref": "#/$defs/resourceSpec" }, - "limits": { "$ref": "#/$defs/resourceSpec" } - } - } - } - }, - "ingress": { - "type": "object", - "properties": { - "enabled": { "type": "boolean" }, - "className": { "type": "string" }, - "host": { "type": "string" }, - "tls": { - "type": "object", - "properties": { - "enabled": { "type": "boolean" }, - "secretName": { "type": "string" }, - "issuer": { "type": "string" } - } - }, - "authentik": { - "type": "object", - "properties": { - "enabled": { "type": "boolean" }, - "authUrl": { "type": "string" }, - "signIn": { "type": "string" } - } - } - } - } - } - }, "ide": { "type": "object", "properties": { diff --git a/chart/values.yaml b/chart/values.yaml index d299d7e..b9b3c39 100644 --- a/chart/values.yaml +++ b/chart/values.yaml @@ -5,20 +5,22 @@ # Instance name — used to generate resource names (devcontainer-{name}, userhome-{name}) name: "" -# Deployment mode controls the infrastructure pattern -# - persistent: Traditional model with PVC storage, single long-lived deployment -# - dynamic: Serverless model with Knative, auto-scaling from 0, dynamic GitHub routing -deploymentMode: persistent # persistent | dynamic - # Container image configuration image: repository: ghcr.io/cpfarhood/devcontainer tag: latest pullPolicy: Always -# GitHub repository to clone into /workspace (ignored in dynamic mode - uses URL routing) +# GitHub repository to clone into /workspace 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 # ============================================================================= @@ -192,67 +194,6 @@ autoDetect: # Override specific values above to customize resourceProfile: auto # auto | small | medium | large | xlarge -# ============================================================================= -# DYNAMIC MODE CONFIGURATION (deploymentMode: dynamic) -# ============================================================================= - -# Dynamic mode uses Knative Services and routing proxy for serverless operation -dynamic: - # Knative Service configuration - knative: - # Scaling configuration - minScale: 0 # Scale to zero when not in use - maxScale: 10 # Maximum number of concurrent instances - target: 1 # Requests per instance (isolation = 1 request per pod) - scaleToZeroGracePeriod: "5m" # Keep instances warm for 5 minutes - - # Container startup timeout (repo cloning + IDE startup) - timeoutSeconds: 600 # 10 minutes - - # Resource configuration (per instance) - resources: - requests: - memory: "1Gi" - cpu: "500m" - limits: - memory: "4Gi" - cpu: "2000m" - - # Routing proxy configuration (extracts GitHub repo from URL) - routingProxy: - enabled: true - 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 # ============================================================================= diff --git a/scripts/init-repo.sh b/scripts/init-repo.sh index 9842614..34f44cb 100644 --- a/scripts/init-repo.sh +++ b/scripts/init-repo.sh @@ -62,43 +62,85 @@ else fi fi -# Check if GITHUB_REPO is set -if [ -z "$GITHUB_REPO" ]; then - echo "GITHUB_REPO not set, skipping repository clone" +# Build list of repositories to clone +REPOS=() +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" mkdir -p "$WORKSPACE_DIR" else - # Parse repo name from URL - REPO_NAME=$(basename "$GITHUB_REPO" .git) - WORKSPACE_DIR="/workspace/$REPO_NAME" + CLONED_DIRS=() + for REPO_URL in "${REPOS[@]}"; do + REPO_NAME=$(basename "$REPO_URL" .git) + REPO_DIR="/workspace/$REPO_NAME" - echo "Repository: $GITHUB_REPO" - echo "Target directory: $WORKSPACE_DIR" + echo "Repository: $REPO_URL" + echo "Target directory: $REPO_DIR" - # Check if repo already exists - if [ -d "$WORKSPACE_DIR/.git" ]; then - echo "Repository already exists, pulling latest changes..." - cd "$WORKSPACE_DIR" - 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" + if [ -d "$REPO_DIR/.git" ]; then + echo "Repository already exists, pulling latest changes..." + cd "$REPO_DIR" + git pull || echo "Pull failed, continuing anyway..." 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 + + 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 # Set ownership using numeric IDs (username may not exist yet in baseimage-gui) RUN_UID="${USER_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" +done +if [ -n "$WS_FILE" ] && [ -f "$WS_FILE" ]; then + 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 # Ensure home directory exists on the PVC (may be absent on a fresh volume) mkdir -p "$HOME" diff --git a/scripts/startapp.sh b/scripts/startapp.sh index 36d29c2..bfae2e6 100644 --- a/scripts/startapp.sh +++ b/scripts/startapp.sh @@ -4,13 +4,7 @@ set -e echo "=== Starting Dev Container ===" -# Check if we're in serverless mode -if [[ "$SERVERLESS_MODE" == "true" ]]; then - echo "Serverless mode detected, using serverless startup script..." - exec /usr/local/bin/serverless-startapp -fi - -# Traditional mode - initialize repository +# Initialize repository /usr/local/bin/init-repo # Get workspace directory diff --git a/serverless/Makefile b/serverless/Makefile deleted file mode 100644 index 1dd757c..0000000 --- a/serverless/Makefile +++ /dev/null @@ -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 \ No newline at end of file diff --git a/serverless/README.md b/serverless/README.md deleted file mode 100644 index 88f7786..0000000 --- a/serverless/README.md +++ /dev/null @@ -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 \ No newline at end of file diff --git a/serverless/authentik-config.yaml b/serverless/authentik-config.yaml deleted file mode 100644 index 1c40281..0000000 --- a/serverless/authentik-config.yaml +++ /dev/null @@ -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 \ No newline at end of file diff --git a/serverless/deployment.yaml b/serverless/deployment.yaml deleted file mode 100644 index 1b7b95c..0000000 --- a/serverless/deployment.yaml +++ /dev/null @@ -1,243 +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" - # 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 \ No newline at end of file diff --git a/serverless/knative-service.yaml b/serverless/knative-service.yaml deleted file mode 100644 index b2f2560..0000000 --- a/serverless/knative-service.yaml +++ /dev/null @@ -1,107 +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" - # 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: "" \ No newline at end of file diff --git a/serverless/routing-proxy/Dockerfile b/serverless/routing-proxy/Dockerfile deleted file mode 100644 index ebfe3fc..0000000 --- a/serverless/routing-proxy/Dockerfile +++ /dev/null @@ -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;"] \ No newline at end of file diff --git a/serverless/routing-proxy/entrypoint.sh b/serverless/routing-proxy/entrypoint.sh deleted file mode 100644 index 10ecb24..0000000 --- a/serverless/routing-proxy/entrypoint.sh +++ /dev/null @@ -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 "$@" \ No newline at end of file diff --git a/serverless/routing-proxy/nginx.conf.template b/serverless/routing-proxy/nginx.conf.template deleted file mode 100644 index 0a8b569..0000000 --- a/serverless/routing-proxy/nginx.conf.template +++ /dev/null @@ -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; - } -} \ No newline at end of file diff --git a/serverless/scripts/dynamic-init-repo.sh b/serverless/scripts/dynamic-init-repo.sh deleted file mode 100644 index 0b0dd70..0000000 --- a/serverless/scripts/dynamic-init-repo.sh +++ /dev/null @@ -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 \ No newline at end of file diff --git a/serverless/scripts/serverless-startapp.sh b/serverless/scripts/serverless-startapp.sh deleted file mode 100644 index 3cc03a1..0000000 --- a/serverless/scripts/serverless-startapp.sh +++ /dev/null @@ -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 \ No newline at end of file