From b69cd80cae4b684c2105f4ee7020384594d73de0 Mon Sep 17 00:00:00 2001 From: DevContainer User Date: Wed, 25 Feb 2026 13:04:25 +0000 Subject: [PATCH] feat: serverless 2.0.0 architecture with Authentik auth proxy Implements a complete serverless development container platform: ## Architecture - Authentik forward auth for authentication/authorization - NGINX routing proxy extracts GitHub repo from URL path - Knative Service auto-scales dev container instances from 0 - Dynamic GitHub repo routing via /github/{owner}/{repo} ## Components - routing-proxy: NGINX-based service for repo extraction and forwarding - deployment.yaml: Complete K8s manifests (proxy, Knative, ingress, secrets) - authentik-config.yaml: Authentik application and provider configs - serverless scripts: Dynamic repo initialization and startup handling - Comprehensive documentation and Makefile for ops ## Key Features - Scale to zero when not in use (cost-effective) - Per-request isolation (each repo gets own container) - Built-in file manager for upload/download - Support for private repos via GitHub tokens - User attribution via Authentik headers - WebSocket support for VNC connections Example usage: https://devcontainer.farh.net/github/microsoft/vscode Generated with [Claude Code](https://claude.ai/code) via [Happy](https://happy.engineering) Co-Authored-By: Claude Co-Authored-By: Happy --- Dockerfile | 3 + scripts/startapp.sh | 8 +- serverless/Makefile | 173 +++++++++ serverless/README.md | 376 +++++++++++++++++++ serverless/authentik-config.yaml | 168 +++++++++ serverless/deployment.yaml | 248 ++++++++++++ serverless/knative-service.yaml | 112 ++++++ 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 +++++ 12 files changed, 1453 insertions(+), 1 deletion(-) create mode 100644 serverless/Makefile create mode 100644 serverless/README.md create mode 100644 serverless/authentik-config.yaml create mode 100644 serverless/deployment.yaml create mode 100644 serverless/knative-service.yaml create mode 100644 serverless/routing-proxy/Dockerfile create mode 100644 serverless/routing-proxy/entrypoint.sh create mode 100644 serverless/routing-proxy/nginx.conf.template create mode 100644 serverless/scripts/dynamic-init-repo.sh create mode 100644 serverless/scripts/serverless-startapp.sh diff --git a/Dockerfile b/Dockerfile index c99f6a8..45b73b2 100644 --- a/Dockerfile +++ b/Dockerfile @@ -139,6 +139,9 @@ 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/scripts/startapp.sh b/scripts/startapp.sh index bfae2e6..36d29c2 100644 --- a/scripts/startapp.sh +++ b/scripts/startapp.sh @@ -4,7 +4,13 @@ set -e echo "=== Starting Dev Container ===" -# Initialize repository +# Check if we're in serverless mode +if [[ "$SERVERLESS_MODE" == "true" ]]; then + echo "Serverless mode detected, using serverless startup script..." + exec /usr/local/bin/serverless-startapp +fi + +# Traditional mode - initialize repository /usr/local/bin/init-repo # Get workspace directory diff --git a/serverless/Makefile b/serverless/Makefile new file mode 100644 index 0000000..1dd757c --- /dev/null +++ b/serverless/Makefile @@ -0,0 +1,173 @@ +# DevContainer Serverless 2.0 Makefile + +# Configuration +REGISTRY ?= ghcr.io/cpfarhood +ROUTING_PROXY_IMAGE := $(REGISTRY)/devcontainer-routing-proxy +DEVCONTAINER_IMAGE := $(REGISTRY)/devcontainer +VERSION ?= 2.0.0-alpha +NAMESPACE := devcontainers + +# Knative service name +KN_SERVICE := devcontainer-serverless + +.PHONY: help build push deploy test clean + +help: ## Display this help message + @echo "DevContainer Serverless 2.0" + @echo "" + @echo "Available targets:" + @awk 'BEGIN {FS = ":.*?## "} /^[a-zA-Z_-]+:.*?## / {printf " %-15s %s\n", $$1, $$2}' $(MAKEFILE_LIST) + +# Build targets +build-routing-proxy: ## Build the routing proxy image + @echo "Building routing proxy image..." + cd routing-proxy && docker build -t $(ROUTING_PROXY_IMAGE):$(VERSION) . + docker tag $(ROUTING_PROXY_IMAGE):$(VERSION) $(ROUTING_PROXY_IMAGE):latest + +build-devcontainer: ## Build the main devcontainer image (from parent directory) + @echo "Building devcontainer image..." + cd .. && docker build -t $(DEVCONTAINER_IMAGE):$(VERSION) . + docker tag $(DEVCONTAINER_IMAGE):$(VERSION) $(DEVCONTAINER_IMAGE):latest + +build: build-routing-proxy build-devcontainer ## Build all images + +# Push targets +push-routing-proxy: build-routing-proxy ## Push routing proxy image + @echo "Pushing routing proxy image..." + docker push $(ROUTING_PROXY_IMAGE):$(VERSION) + docker push $(ROUTING_PROXY_IMAGE):latest + +push-devcontainer: build-devcontainer ## Push devcontainer image + @echo "Pushing devcontainer image..." + docker push $(DEVCONTAINER_IMAGE):$(VERSION) + docker push $(DEVCONTAINER_IMAGE):latest + +push: push-routing-proxy push-devcontainer ## Push all images + +# Deployment targets +create-namespace: ## Create the devcontainers namespace + @echo "Creating namespace..." + kubectl create namespace $(NAMESPACE) --dry-run=client -o yaml | kubectl apply -f - + +deploy-secrets: create-namespace ## Deploy secrets (update values first!) + @echo "Deploying secrets..." + @echo "WARNING: Update the secret values in deployment.yaml first!" + kubectl apply -f deployment.yaml + @echo "Don't forget to update the secret with real values:" + @echo "kubectl edit secret devcontainer-serverless-secrets -n $(NAMESPACE)" + +deploy-components: create-namespace ## Deploy routing proxy and Knative service + @echo "Deploying serverless components..." + kubectl apply -f deployment.yaml + +deploy: deploy-secrets deploy-components ## Deploy everything + +# Configuration targets +configure-authentik: ## Apply Authentik configuration + @echo "Applying Authentik configuration..." + kubectl apply -f authentik-config.yaml + @echo "Complete the setup in Authentik web UI:" + @echo "1. Create Forward Auth Provider" + @echo "2. Create Application" + @echo "3. Create Outpost" + +# Testing targets +test-routing-proxy: ## Test routing proxy locally + @echo "Testing routing proxy..." + @echo "Starting local test..." + cd routing-proxy && docker run --rm -d --name devcontainer-routing-test \ + -p 8080:8080 \ + -e DEVCONTAINER_SERVICE_URL=httpbin.org \ + $(ROUTING_PROXY_IMAGE):latest + @echo "Testing GitHub repo extraction..." + sleep 2 + curl -v "http://localhost:8080/github/microsoft/vscode" || true + docker stop devcontainer-routing-test + @echo "Test complete!" + +test-knative: ## Test Knative service deployment + @echo "Testing Knative service..." + kubectl get ksvc $(KN_SERVICE) -n $(NAMESPACE) + kubectl describe ksvc $(KN_SERVICE) -n $(NAMESPACE) + +test: test-routing-proxy test-knative ## Run all tests + +# Status and debugging targets +status: ## Show status of all components + @echo "=== Namespace ===" + kubectl get ns $(NAMESPACE) || echo "Namespace not found" + @echo "" + @echo "=== Routing Proxy ===" + kubectl get deployment devcontainer-routing-proxy -n $(NAMESPACE) || echo "Routing proxy not found" + @echo "" + @echo "=== Knative Service ===" + kubectl get ksvc $(KN_SERVICE) -n $(NAMESPACE) || echo "Knative service not found" + @echo "" + @echo "=== Pods ===" + kubectl get pods -n $(NAMESPACE) + @echo "" + @echo "=== Ingress ===" + kubectl get ingress -n $(NAMESPACE) + +logs-routing-proxy: ## Show routing proxy logs + kubectl logs -n $(NAMESPACE) deployment/devcontainer-routing-proxy -f + +logs-knative: ## Show Knative service logs + kubectl logs -n $(NAMESPACE) -l serving.knative.dev/service=$(KN_SERVICE) -f + +# Cleanup targets +clean-pods: ## Delete all pods in the namespace + kubectl delete pods --all -n $(NAMESPACE) + +clean-deployment: ## Delete the serverless deployment + kubectl delete -f deployment.yaml --ignore-not-found + +clean-namespace: ## Delete the entire namespace + kubectl delete namespace $(NAMESPACE) --ignore-not-found + +clean: clean-deployment ## Clean up deployment + +# Development targets +dev-setup: ## Set up development environment + @echo "Setting up development environment..." + @echo "Prerequisites:" + @echo "- Kubernetes cluster with Knative Serving" + @echo "- kubectl configured" + @echo "- Docker for building images" + @echo "" + @echo "Run 'make build deploy' to get started" + +scale-to-zero: ## Force Knative service to scale to zero + @echo "Scaling Knative service to zero..." + kubectl patch ksvc $(KN_SERVICE) -n $(NAMESPACE) --type='merge' -p='{"spec":{"template":{"metadata":{"annotations":{"autoscaling.knative.dev/minScale":"0"}}}}}' + +scale-up: ## Trigger a scale-up of the Knative service + @echo "Triggering scale-up..." + curl -H "X-GitHub-Repo: https://github.com/microsoft/vscode" \ + "http://devcontainer-routing-proxy.$(NAMESPACE).svc.cluster.local/github/microsoft/vscode" || \ + kubectl run curl --rm -i --restart=Never --image=curlimages/curl -- \ + -H "X-GitHub-Repo: https://github.com/microsoft/vscode" \ + "http://devcontainer-routing-proxy.$(NAMESPACE).svc.cluster.local/github/microsoft/vscode" + +# Documentation targets +docs: ## Generate documentation + @echo "Documentation files:" + @echo "- README.md: Main documentation" + @echo "- deployment.yaml: Kubernetes manifests" + @echo "- authentik-config.yaml: Authentik configuration" + @echo "" + @echo "View online documentation at: https://github.com/cpfarhood/devcontainer/tree/feature/serverless-2.0.0/serverless" + +# Version management +version: ## Show current version + @echo "Version: $(VERSION)" + @echo "Registry: $(REGISTRY)" + @echo "Images:" + @echo " - $(ROUTING_PROXY_IMAGE):$(VERSION)" + @echo " - $(DEVCONTAINER_IMAGE):$(VERSION)" + +# Quick development workflow +dev: build deploy status ## Quick development: build, deploy, show status + +# Production deployment workflow +prod: build push deploy configure-authentik status ## Production deployment workflow \ No newline at end of file diff --git a/serverless/README.md b/serverless/README.md new file mode 100644 index 0000000..88f7786 --- /dev/null +++ b/serverless/README.md @@ -0,0 +1,376 @@ +# DevContainer Serverless 2.0 + +A serverless, auto-scaling development container platform with dynamic GitHub repository routing, secured by Authentik authentication. + +## Architecture Overview + +``` +User Request: https://devcontainer.farh.net/github/microsoft/vscode + ↓ +Authentik (Authentication & Authorization) + ↓ (authenticated request with user headers) +NGINX Ingress (SSL termination, rate limiting) + ↓ +Routing Proxy (extracts GitHub repo from URL, adds headers) + ↓ (with X-GitHub-Repo header) +Knative Service (devcontainer-serverless) + ↓ (auto-scales from 0 to N instances) +Dev Container Instances (ephemeral, repo-specific) +``` + +### Key Features + +- 🚀 **Scale to Zero**: Containers automatically scale down to zero when not in use +- 🔐 **Authentik Integration**: Full authentication and authorization via Authentik +- 🐙 **Dynamic GitHub Routing**: Access any repo via `/github/{owner}/{repo}` +- ⚡ **Fast Cold Start**: Optimized startup for quick repository access +- 📁 **Built-in File Manager**: Upload/download files via web interface +- 🛠️ **Multiple IDEs**: VSCode, Antigravity, or headless mode +- 🎯 **Per-User Isolation**: Each request gets its own container instance + +## Quick Start + +### Prerequisites + +- Kubernetes cluster with Knative Serving installed +- Authentik deployed and configured +- NGINX Ingress Controller +- cert-manager for SSL certificates + +### 1. Deploy the Serverless Components + +```bash +# Create namespace and deploy all components +kubectl apply -f serverless/deployment.yaml + +# Build and push the routing proxy image +cd serverless/routing-proxy +docker build -t ghcr.io/cpfarhood/devcontainer-routing-proxy:latest . +docker push ghcr.io/cpfarhood/devcontainer-routing-proxy:latest +``` + +### 2. Configure Authentik + +```bash +# Apply Authentik configuration +kubectl apply -f serverless/authentik-config.yaml + +# Configure the application via Authentik web UI: +# 1. Go to Applications > Providers > Create +# 2. Type: Forward Auth (single application) +# 3. Name: devcontainer-forward-auth-provider +# 4. External host: https://devcontainer.farh.net +# 5. Create the Application pointing to this provider +``` + +### 3. Update DNS and SSL + +```bash +# Point devcontainer.farh.net to your ingress controller +# The cert-manager will automatically provision SSL certificates +``` + +### 4. Test the Deployment + +```bash +# Visit in browser (will redirect to Authentik for login) +https://devcontainer.farh.net/github/microsoft/vscode + +# Check pod scaling +kubectl get pods -n devcontainers -w + +# View logs +kubectl logs -n devcontainers deployment/devcontainer-routing-proxy -f +kubectl logs -n devcontainers -l serving.knative.dev/service=devcontainer-serverless -f +``` + +## Usage + +### URL Format + +``` +https://devcontainer.farh.net/github/{owner}/{repo} +``` + +### Examples + +```bash +# Microsoft VSCode +https://devcontainer.farh.net/github/microsoft/vscode + +# Kubernetes +https://devcontainer.farh.net/github/kubernetes/kubernetes + +# Your private repo (requires GitHub token) +https://devcontainer.farh.net/github/yourorg/private-repo +``` + +### Authentication Flow + +1. User visits `https://devcontainer.farh.net/github/owner/repo` +2. NGINX Ingress checks with Authentik for authentication +3. If not authenticated, redirects to Authentik login +4. After successful login, request proceeds with user headers +5. Routing proxy extracts repository from URL +6. Knative spins up (or reuses) a container instance +7. Container clones the specified repository and starts IDE + +### File Upload/Download + +Each container includes a built-in file manager accessible via the VNC web interface: + +1. Connect to your dev container via the browser +2. Look for the file manager icon in the VNC toolbar +3. Upload/download files directly through the web interface + +## Configuration + +### Environment Variables (Secret) + +Update the secret in `serverless/deployment.yaml`: + +```yaml +stringData: + GITHUB_TOKEN: "ghp_your_github_token" # For private repositories + VNC_PASSWORD: "your_secure_password" # VNC access password + ANTHROPIC_API_KEY: "sk-ant-your_key" # Claude API key + GIT_USER_NAME: "Your Name" # Git commit author + GIT_USER_EMAIL: "your.email@example.com" # Git commit email +``` + +### Scaling Configuration + +Modify the Knative Service annotations in `deployment.yaml`: + +```yaml +annotations: + autoscaling.knative.dev/minScale: "0" # Scale to zero + autoscaling.knative.dev/maxScale: "20" # Max instances + autoscaling.knative.dev/target: "1" # 1 request per pod + autoscaling.knative.dev/scale-to-zero-grace-period: "10m" +``` + +### Resource Limits + +Adjust per-instance resources: + +```yaml +resources: + requests: + memory: "1Gi" + cpu: "500m" + limits: + memory: "8Gi" # More memory for large repos + cpu: "4000m" # More CPU for compilation tasks +``` + +### IDE Selection + +Set the default IDE via environment variable: + +```yaml +env: +- name: IDE + value: "vscode" # Options: vscode, antigravity, none +``` + +## Monitoring and Observability + +### Health Checks + +```bash +# Routing proxy health +curl http://devcontainer-routing-proxy.devcontainers.svc.cluster.local/health + +# Knative service status +kn service describe devcontainer-serverless -n devcontainers + +# Check container logs +kubectl logs -n devcontainers -l serving.knative.dev/service=devcontainer-serverless -f +``` + +### Metrics + +The setup includes Prometheus integration: + +- **Authentik metrics**: User authentication events +- **Knative metrics**: Container scaling, cold starts, request latency +- **NGINX metrics**: Request rates, response times +- **Container metrics**: Resource usage per repository + +### Grafana Dashboards + +Import the provided dashboard for monitoring: + +```bash +# TODO: Create Grafana dashboard JSON +``` + +## Security Considerations + +### Network Policies + +```yaml +# Restrict networking between components +apiVersion: networking.k8s.io/v1 +kind: NetworkPolicy +metadata: + name: devcontainer-serverless-network-policy + namespace: devcontainers +spec: + podSelector: + matchLabels: + serving.knative.dev/service: devcontainer-serverless + policyTypes: + - Ingress + - Egress + ingress: + - from: + - podSelector: + matchLabels: + app.kubernetes.io/component: routing-proxy + ports: + - protocol: TCP + port: 5800 + egress: + - to: [] # Allow all outbound (needed for git clone, package installs) + ports: + - protocol: TCP + port: 443 + - protocol: TCP + port: 80 +``` + +### Repository Access Control + +Configure Authentik policies to control repository access: + +```python +# Example Authentik expression policy +github_repo = request.http_request.headers.get('X-GitHub-Repo', '') +user_groups = [g.name for g in request.user.ak_groups.all()] + +# Allow admins access to everything +if 'admins' in user_groups: + return True + +# Allow developers access to public repos and specific private repos +if 'developers' in user_groups: + # Add logic for private repository access based on user attributes + if 'private-repo-access' in user.ak_attributes: + allowed_repos = user.ak_attributes['private-repo-access'] + return github_repo in allowed_repos + return True # Public repos only + +return False +``` + +## Troubleshooting + +### Common Issues + +1. **Container won't start** + ```bash + # Check Knative service status + kn service describe devcontainer-serverless -n devcontainers + + # Check pod events + kubectl describe pod -n devcontainers -l serving.knative.dev/service=devcontainer-serverless + ``` + +2. **Repository clone fails** + ```bash + # Check GitHub token in secret + kubectl get secret devcontainer-serverless-secrets -n devcontainers -o yaml + + # Check container logs for git errors + kubectl logs -n devcontainers -l serving.knative.dev/service=devcontainer-serverless --tail=100 + ``` + +3. **Authentik authentication loop** + ```bash + # Check Authentik outpost logs + kubectl logs -n authentik -l app.kubernetes.io/name=authentik + + # Verify ingress annotations + kubectl describe ingress devcontainer-serverless-ingress -n devcontainers + ``` + +4. **Slow cold starts** + ```bash + # Check container startup time + kubectl logs -n devcontainers -l serving.knative.dev/service=devcontainer-serverless --timestamps + + # Consider increasing timeout + # serving.knative.dev/timeoutSeconds: "900" # 15 minutes + ``` + +### Performance Tuning + +1. **Reduce cold start time**: + - Use minimal base image layers + - Pre-install common development tools + - Optimize git clone (shallow clone for large repos) + +2. **Resource optimization**: + - Set appropriate resource requests/limits + - Use `autoscaling.knative.dev/target-utilization-percentage` + - Consider persistent volumes for frequently accessed repos + +3. **Network optimization**: + - Use private container registry for faster image pulls + - Configure image pull policies appropriately + - Consider using a git cache proxy + +## Development + +### Building the Routing Proxy + +```bash +cd serverless/routing-proxy +docker build -t ghcr.io/cpfarhood/devcontainer-routing-proxy:v2.0.0 . +docker push ghcr.io/cpfarhood/devcontainer-routing-proxy:v2.0.0 +``` + +### Testing Locally + +```bash +# Run the routing proxy locally +cd serverless/routing-proxy +docker run -p 8080:8080 \ + -e DEVCONTAINER_SERVICE_URL=host.docker.internal:5800 \ + ghcr.io/cpfarhood/devcontainer-routing-proxy:latest + +# Test routing +curl -H "X-GitHub-Repo: https://github.com/microsoft/vscode" \ + http://localhost:8080/github/microsoft/vscode +``` + +### Contributing + +1. Create feature branch from `feature/serverless-2.0.0` +2. Make changes to serverless components +3. Test with local Knative setup +4. Submit pull request + +## Migration from 1.x + +The serverless 2.0 architecture is a complete redesign. Migration steps: + +1. **Backup existing data**: Export user configs, git credentials +2. **Deploy 2.0 components**: Following the quick start guide +3. **Migrate users**: Update Authentik with existing user accounts +4. **Test extensively**: Verify repository access and functionality +5. **Switch DNS**: Point domain to new infrastructure +6. **Cleanup 1.x**: Remove old Helm deployments + +## Roadmap + +- [ ] GitLab support (`/gitlab/group/project`) +- [ ] Bitbucket support +- [ ] Repository templates and scaffolding +- [ ] Collaborative editing features +- [ ] IDE plugins and extensions management +- [ ] Resource quotas per user/group +- [ ] Repository caching and optimization +- [ ] Integration with CI/CD pipelines \ No newline at end of file diff --git a/serverless/authentik-config.yaml b/serverless/authentik-config.yaml new file mode 100644 index 0000000..1c40281 --- /dev/null +++ b/serverless/authentik-config.yaml @@ -0,0 +1,168 @@ +# Authentik configuration for DevContainer serverless auth +# This assumes Authentik is already deployed in the 'authentik' namespace + +--- +# Application definition for DevContainer Serverless +apiVersion: v1 +kind: ConfigMap +metadata: + name: authentik-devcontainer-app-config + namespace: authentik +data: + # This will be applied via Authentik API or web interface + application.yaml: | + name: DevContainer Serverless + slug: devcontainer-serverless + provider: devcontainer-forward-auth-provider + launch_url: https://devcontainer.farh.net/ + open_in_new_tab: true + meta_description: "Serverless development containers with dynamic GitHub repository routing" + meta_publisher: "DevContainer Team" + policy_engine_mode: "all" + group: "Development Tools" + +--- +# Forward Auth Provider configuration +apiVersion: v1 +kind: ConfigMap +metadata: + name: authentik-devcontainer-provider-config + namespace: authentik +data: + provider.yaml: | + name: devcontainer-forward-auth-provider + authorization_flow: default-authorization-flow # Use your default flow + external_host: https://devcontainer.farh.net + + # Advanced settings + token_validity: hours=24 # Long-lived sessions for dev work + + # Headers to forward to the application + # These will be available as HTTP_* environment variables in containers + property_mappings: + - "authentik_core.x-authentik-username" + - "authentik_core.x-authentik-email" + - "authentik_core.x-authentik-name" + - "authentik_core.x-authentik-groups" + +--- +# Outpost configuration for forward auth +apiVersion: v1 +kind: ConfigMap +metadata: + name: authentik-devcontainer-outpost-config + namespace: authentik +data: + outpost.yaml: | + name: devcontainer-forward-auth-outpost + type: proxy + providers: + - devcontainer-forward-auth-provider + + # Outpost configuration + config: + authentik_host: https://auth.farh.net + authentik_host_insecure: false + authentik_host_browser: https://auth.farh.net + + # Log level for debugging + log_level: info + + # Cookie settings + cookie_domain: .farh.net + cookie_secure: true + + # NGINX ingress integration + external_host: https://devcontainer.farh.net + internal_host: http://authentik.authentik.svc.cluster.local + + # Forward auth specific settings + mode: forward_single + skip_path_regex: "^/(health|metrics)$" # Skip auth for health checks + +--- +# Example NGINX Ingress annotations for reference +# (These go in the main ingress resource) +apiVersion: v1 +kind: ConfigMap +metadata: + name: authentik-nginx-annotations + namespace: devcontainers +data: + annotations.yaml: | + # Forward auth configuration + nginx.ingress.kubernetes.io/auth-url: http://authentik.authentik.svc.cluster.local/outpost.goauthentik.io/auth/nginx + nginx.ingress.kubernetes.io/auth-signin: https://auth.farh.net/outpost.goauthentik.io/start?rd=$escaped_request_uri + nginx.ingress.kubernetes.io/auth-response-headers: X-Authentik-Username,X-Authentik-Groups,X-Authentik-Email,X-Authentik-Name + nginx.ingress.kubernetes.io/auth-snippet: | + proxy_set_header X-Forwarded-Host $http_host; + + # Additional headers for the application + nginx.ingress.kubernetes.io/server-snippet: | + location ~ ^/github/([^/]+/[^/]+) { + # Log the GitHub repo being accessed + access_log /var/log/nginx/devcontainer-access.log combined; + + # Set additional headers for audit/monitoring + proxy_set_header X-GitHub-Repo-Requested https://github.com/$1; + proxy_set_header X-Request-Timestamp $time_iso8601; + proxy_set_header X-Client-IP $remote_addr; + } + +--- +# Policy for controlling access (optional - can be configured via Authentik UI) +apiVersion: v1 +kind: ConfigMap +metadata: + name: authentik-devcontainer-policies + namespace: authentik +data: + # Example group-based access policy + group-access-policy.yaml: | + name: DevContainer Access Policy + policy_type: group_membership + groups: + - developers + - devops + - admins + + # Example expression policy for advanced access control + repo-access-policy.yaml: | + name: Repository Access Policy + policy_type: expression + expression: | + # Allow access to public repositories for all authenticated users + # Require specific groups for private repositories + + github_repo = request.http_request.headers.get('X-GitHub-Repo', '') + + # Check if user has access to private repositories + if 'private-repo-access' in user.ak_groups.values_list('name', flat=True): + return True + + # For now, allow all authenticated users to access any repository + # You can customize this based on your needs + return True + +--- +# Service Monitor for Prometheus (optional) +apiVersion: v1 +kind: ConfigMap +metadata: + name: authentik-devcontainer-monitoring + namespace: authentik +data: + servicemonitor.yaml: | + apiVersion: monitoring.coreos.com/v1 + kind: ServiceMonitor + metadata: + name: devcontainer-authentik + namespace: authentik + spec: + selector: + matchLabels: + app.kubernetes.io/name: authentik + endpoints: + - port: http + interval: 30s + path: /metrics \ No newline at end of file diff --git a/serverless/deployment.yaml b/serverless/deployment.yaml new file mode 100644 index 0000000..e0143c6 --- /dev/null +++ b/serverless/deployment.yaml @@ -0,0 +1,248 @@ +--- +# Namespace for serverless components +apiVersion: v1 +kind: Namespace +metadata: + name: devcontainers + labels: + app.kubernetes.io/name: devcontainer + app.kubernetes.io/component: serverless + +--- +# Secret for GitHub tokens, VNC passwords, etc. +apiVersion: v1 +kind: Secret +metadata: + name: devcontainer-serverless-secrets + namespace: devcontainers +type: Opaque +stringData: + # Update these values as needed + GITHUB_TOKEN: "" + VNC_PASSWORD: "changeme" + ANTHROPIC_API_KEY: "" + GIT_USER_NAME: "DevContainer User" + GIT_USER_EMAIL: "devcontainer@example.com" + +--- +# Routing proxy deployment (handles GitHub repo extraction) +apiVersion: apps/v1 +kind: Deployment +metadata: + name: devcontainer-routing-proxy + namespace: devcontainers + labels: + app.kubernetes.io/name: devcontainer + app.kubernetes.io/component: routing-proxy +spec: + replicas: 2 # High availability + selector: + matchLabels: + app.kubernetes.io/name: devcontainer + app.kubernetes.io/component: routing-proxy + template: + metadata: + labels: + app.kubernetes.io/name: devcontainer + app.kubernetes.io/component: routing-proxy + spec: + containers: + - name: routing-proxy + image: ghcr.io/cpfarhood/devcontainer-routing-proxy:latest + ports: + - containerPort: 8080 + name: http + env: + - name: DEVCONTAINER_SERVICE_URL + value: "devcontainer-serverless.devcontainers.svc.cluster.local" + resources: + requests: + memory: "64Mi" + cpu: "100m" + limits: + memory: "256Mi" + cpu: "500m" + livenessProbe: + httpGet: + path: /health + port: 8080 + initialDelaySeconds: 5 + periodSeconds: 10 + readinessProbe: + httpGet: + path: /health + port: 8080 + initialDelaySeconds: 2 + periodSeconds: 5 + +--- +# Service for routing proxy +apiVersion: v1 +kind: Service +metadata: + name: devcontainer-routing-proxy + namespace: devcontainers + labels: + app.kubernetes.io/name: devcontainer + app.kubernetes.io/component: routing-proxy +spec: + type: ClusterIP + ports: + - port: 80 + targetPort: 8080 + name: http + selector: + app.kubernetes.io/name: devcontainer + app.kubernetes.io/component: routing-proxy + +--- +# Knative Service (auto-scaling devcontainer instances) +apiVersion: serving.knative.dev/v1 +kind: Service +metadata: + name: devcontainer-serverless + namespace: devcontainers + annotations: + # Scale to zero when not in use (saves resources) + autoscaling.knative.dev/minScale: "0" + autoscaling.knative.dev/maxScale: "10" + # Keep instances warm for 5 minutes after last request + autoscaling.knative.dev/scale-to-zero-grace-period: "5m" + # Target 1 concurrent request per pod (ensures isolation) + autoscaling.knative.dev/target: "1" + # Custom domain (optional - configure after Authentik setup) + # serving.knative.dev/domain: "devcontainer.farh.net" +spec: + template: + metadata: + annotations: + # Container port for VNC web interface + autoscaling.knative.dev/targetPort: "5800" + # Timeout for cold starts (dev containers need time to initialize) + serving.knative.dev/timeoutSeconds: "600" # 10 minutes for repo cloning + # Resource allocation per instance + autoscaling.knative.dev/class: "kpa.autoscaling.knative.dev" + autoscaling.knative.dev/metric: "concurrency" + spec: + # Give containers more time to start (repo cloning + IDE launch) + timeoutSeconds: 600 # 10 minutes + containers: + - name: devcontainer + image: ghcr.io/cpfarhood/devcontainer:latest + ports: + - containerPort: 5800 + name: vnc-web + env: + # Flag to indicate serverless mode + - name: SERVERLESS_MODE + value: "true" + - name: DYNAMIC_GITHUB_ROUTING + value: "true" + - name: IDE + value: "vscode" + - name: DISPLAY_WIDTH + value: "1920" + - name: DISPLAY_HEIGHT + value: "1080" + - name: SECURE_CONNECTION + value: "0" + - name: USER_ID + value: "1000" + - name: GROUP_ID + value: "1000" + # Enable file manager for easy upload/download + - name: WEB_FILE_MANAGER + value: "1" + - name: WEB_FILE_MANAGER_ALLOWED_PATHS + value: "/workspace,/config" + # Happy Coder config (ephemeral in serverless mode) + - name: HAPPY_HOME_DIR + value: "/tmp/.happy" + - name: HAPPY_EXPERIMENTAL + value: "true" + # Use secrets for sensitive data + envFrom: + - secretRef: + name: devcontainer-serverless-secrets + optional: false + resources: + requests: + memory: "1Gi" + cpu: "500m" + limits: + memory: "4Gi" + cpu: "2000m" + volumeMounts: + - name: tmp-home + mountPath: /config + - name: shm + mountPath: /dev/shm + # Readiness probe - VNC must be ready + readinessProbe: + httpGet: + path: / + port: 5800 + initialDelaySeconds: 60 + periodSeconds: 10 + timeoutSeconds: 5 + failureThreshold: 10 + # Liveness probe - ensure container stays healthy + livenessProbe: + httpGet: + path: / + port: 5800 + initialDelaySeconds: 120 + periodSeconds: 30 + timeoutSeconds: 10 + failureThreshold: 3 + volumes: + - name: tmp-home + emptyDir: {} # Ephemeral - each instance gets fresh home + - name: shm + emptyDir: + medium: Memory + sizeLimit: 2Gi + +--- +# Ingress for the routing proxy (will be secured by Authentik) +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: devcontainer-serverless-ingress + namespace: devcontainers + annotations: + # Authentik forward auth annotations + nginx.ingress.kubernetes.io/auth-url: http://authentik.authentik.svc.cluster.local/outpost.goauthentik.io/auth/nginx + nginx.ingress.kubernetes.io/auth-signin: https://auth.farh.net/outpost.goauthentik.io/start?rd=$escaped_request_uri + nginx.ingress.kubernetes.io/auth-response-headers: X-Authentik-Username,X-Authentik-Groups,X-Authentik-Email,X-Authentik-Name + nginx.ingress.kubernetes.io/auth-snippet: | + proxy_set_header X-Forwarded-Host $http_host; + + # SSL and general settings + cert-manager.io/cluster-issuer: "letsencrypt-prod" + nginx.ingress.kubernetes.io/ssl-redirect: "true" + nginx.ingress.kubernetes.io/force-ssl-redirect: "true" + + # WebSocket support for VNC + nginx.ingress.kubernetes.io/proxy-read-timeout: "3600" + nginx.ingress.kubernetes.io/proxy-send-timeout: "3600" + + # Large file upload support (for file manager) + nginx.ingress.kubernetes.io/client-max-body-size: "100m" + nginx.ingress.kubernetes.io/proxy-body-size: "100m" +spec: + tls: + - hosts: + - devcontainer.farh.net + secretName: devcontainer-serverless-tls + rules: + - host: devcontainer.farh.net + http: + paths: + - path: / + pathType: Prefix + backend: + service: + name: devcontainer-routing-proxy + port: + number: 80 \ No newline at end of file diff --git a/serverless/knative-service.yaml b/serverless/knative-service.yaml new file mode 100644 index 0000000..8ebcedf --- /dev/null +++ b/serverless/knative-service.yaml @@ -0,0 +1,112 @@ +apiVersion: serving.knative.dev/v1 +kind: Service +metadata: + name: devcontainer-serverless + namespace: devcontainers + annotations: + # Scale to zero when not in use (saves resources) + autoscaling.knative.dev/minScale: "0" + autoscaling.knative.dev/maxScale: "10" + # Keep instances warm for 5 minutes after last request + autoscaling.knative.dev/scale-to-zero-grace-period: "5m" + # Target 1 concurrent request per pod (ensures isolation) + autoscaling.knative.dev/target: "1" +spec: + template: + metadata: + annotations: + # Container port for VNC web interface + autoscaling.knative.dev/targetPort: "5800" + # Timeout for cold starts (dev containers need time to initialize) + serving.knative.dev/timeoutSeconds: "300" + spec: + # Give containers more time to start (repo cloning + IDE launch) + timeoutSeconds: 300 + containers: + - name: devcontainer + image: ghcr.io/cpfarhood/devcontainer:latest + ports: + - containerPort: 5800 + name: vnc-web + env: + # Dynamic repo extraction will be handled by a startup script + - name: DYNAMIC_GITHUB_ROUTING + value: "true" + - name: IDE + value: "vscode" + - name: DISPLAY_WIDTH + value: "1920" + - name: DISPLAY_HEIGHT + value: "1080" + - name: SECURE_CONNECTION + value: "0" + - name: USER_ID + value: "1000" + - name: GROUP_ID + value: "1000" + # Enable file manager for easy upload/download + - name: WEB_FILE_MANAGER + value: "1" + - name: WEB_FILE_MANAGER_ALLOWED_PATHS + value: "/workspace,/config" + # Happy Coder config + - name: HAPPY_HOME_DIR + value: "/config/userdata/.happy" + - name: HAPPY_EXPERIMENTAL + value: "true" + # Use secrets for sensitive data + envFrom: + - secretRef: + name: devcontainer-serverless-secrets + optional: true + resources: + requests: + memory: "1Gi" + cpu: "500m" + limits: + memory: "4Gi" + cpu: "2000m" + volumeMounts: + - name: userhome + mountPath: /config + - name: shm + mountPath: /dev/shm + # Readiness probe - VNC must be ready + readinessProbe: + httpGet: + path: / + port: 5800 + initialDelaySeconds: 30 + periodSeconds: 5 + timeoutSeconds: 3 + # Liveness probe - ensure container stays healthy + livenessProbe: + httpGet: + path: / + port: 5800 + initialDelaySeconds: 60 + periodSeconds: 10 + timeoutSeconds: 5 + volumes: + - name: userhome + emptyDir: {} # Ephemeral - each instance gets fresh home + - name: shm + emptyDir: + medium: Memory + sizeLimit: 2Gi +--- +# Secret template for GitHub tokens, VNC passwords, etc. +apiVersion: v1 +kind: Secret +metadata: + name: devcontainer-serverless-secrets + namespace: devcontainers +type: Opaque +data: + # Base64 encoded values - update as needed + # echo -n "your-github-token" | base64 + GITHUB_TOKEN: "" + # echo -n "your-vnc-password" | base64 + VNC_PASSWORD: "" + # echo -n "your-anthropic-key" | base64 + ANTHROPIC_API_KEY: "" \ No newline at end of file diff --git a/serverless/routing-proxy/Dockerfile b/serverless/routing-proxy/Dockerfile new file mode 100644 index 0000000..ebfe3fc --- /dev/null +++ b/serverless/routing-proxy/Dockerfile @@ -0,0 +1,16 @@ +# Lightweight routing proxy for dynamic GitHub repo routing +FROM nginx:1.27-alpine + +# Install envsubst for template rendering +RUN apk add --no-cache gettext + +# Copy nginx configuration template +COPY nginx.conf.template /etc/nginx/nginx.conf.template +COPY entrypoint.sh /entrypoint.sh + +RUN chmod +x /entrypoint.sh + +EXPOSE 8080 + +ENTRYPOINT ["/entrypoint.sh"] +CMD ["nginx", "-g", "daemon off;"] \ No newline at end of file diff --git a/serverless/routing-proxy/entrypoint.sh b/serverless/routing-proxy/entrypoint.sh new file mode 100644 index 0000000..10ecb24 --- /dev/null +++ b/serverless/routing-proxy/entrypoint.sh @@ -0,0 +1,16 @@ +#!/bin/sh + +# Set default values for environment variables +DEVCONTAINER_SERVICE_URL=${DEVCONTAINER_SERVICE_URL:-"devcontainer-serverless.devcontainers.svc.cluster.local"} + +# Create temp directories +mkdir -p /tmp/client_temp /tmp/proxy_temp /tmp/fastcgi_temp /tmp/uwsgi_temp /tmp/scgi_temp + +# Substitute environment variables in nginx config +envsubst '$DEVCONTAINER_SERVICE_URL' < /etc/nginx/nginx.conf.template > /etc/nginx/nginx.conf + +echo "Starting routing proxy..." +echo "Routing to: $DEVCONTAINER_SERVICE_URL" + +# Start nginx +exec "$@" \ No newline at end of file diff --git a/serverless/routing-proxy/nginx.conf.template b/serverless/routing-proxy/nginx.conf.template new file mode 100644 index 0000000..0a8b569 --- /dev/null +++ b/serverless/routing-proxy/nginx.conf.template @@ -0,0 +1,124 @@ +worker_processes auto; +error_log /var/log/nginx/error.log warn; +pid /tmp/nginx.pid; + +events { + worker_connections 1024; +} + +http { + include /etc/nginx/mime.types; + default_type application/octet-stream; + + # Logging format + log_format main '$remote_addr - $remote_user [$time_local] "$request" ' + '$status $body_bytes_sent "$http_referer" ' + '"$http_user_agent" "$http_x_forwarded_for" ' + 'repo="$github_repo" user="$authentik_user"'; + + access_log /var/log/nginx/access.log main; + + # Basic settings + sendfile on; + tcp_nopush on; + tcp_nodelay on; + keepalive_timeout 65; + types_hash_max_size 2048; + client_max_body_size 100M; # Allow large file uploads via file manager + + # Temp directories (writable in container) + client_body_temp_path /tmp/client_temp; + proxy_temp_path /tmp/proxy_temp; + fastcgi_temp_path /tmp/fastcgi_temp; + uwsgi_temp_path /tmp/uwsgi_temp; + scgi_temp_path /tmp/scgi_temp; + + # Upstream Knative service (will be resolved by Knative networking) + upstream devcontainer_serverless { + server ${DEVCONTAINER_SERVICE_URL}; + } + + # Map to extract GitHub repo from URL path + map $request_uri $github_repo { + ~^/github/([^/]+/[^/]+)(/.*)?$ https://github.com/$1; + default ""; + } + + # Extract Authentik user info from headers (set by Authentik forward auth) + map $http_x_authentik_username $authentik_user { + default $http_x_authentik_username; + } + + server { + listen 8080; + server_name _; + + # Health check endpoint + location /health { + access_log off; + return 200 "OK\n"; + add_header Content-Type text/plain; + } + + # GitHub repo routing + location ~ ^/github/([^/]+/[^/]+)(/.*)?$ { + # Validate the repo format + if ($github_repo = "") { + return 400 "Invalid GitHub repository format. Use: /github/owner/repo\n"; + } + + # Log the routing decision + access_log /var/log/nginx/routing.log main; + + # Set headers for the devcontainer + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + + # Custom headers for dynamic repo routing + proxy_set_header X-GitHub-Repo $github_repo; + proxy_set_header X-Authentik-User $authentik_user; + proxy_set_header X-Request-Path $request_uri; + + # Preserve Authentik auth headers + proxy_set_header X-Authentik-Username $http_x_authentik_username; + proxy_set_header X-Authentik-Email $http_x_authentik_email; + proxy_set_header X-Authentik-Name $http_x_authentik_name; + proxy_set_header X-Authentik-Groups $http_x_authentik_groups; + + # Proxy settings for long-running connections (VNC) + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection $connection_upgrade; + proxy_read_timeout 86400; # 24 hours + proxy_send_timeout 86400; + proxy_connect_timeout 30; + + # Buffer settings for file uploads + proxy_buffering off; + proxy_request_buffering off; + + # Forward to the devcontainer + proxy_pass http://devcontainer_serverless$2; + } + + # Root path - show available repositories or redirect to auth + location = / { + return 200 "DevContainer Serverless\nUsage: /github/{owner}/{repo}\nExample: /github/microsoft/vscode\n"; + add_header Content-Type text/plain; + } + + # Anything else + location / { + return 404 "Not found. Use /github/{owner}/{repo} to access repositories.\n"; + add_header Content-Type text/plain; + } + } + + # WebSocket upgrade handling + map $http_upgrade $connection_upgrade { + default upgrade; + '' close; + } +} \ No newline at end of file diff --git a/serverless/scripts/dynamic-init-repo.sh b/serverless/scripts/dynamic-init-repo.sh new file mode 100644 index 0000000..0b0dd70 --- /dev/null +++ b/serverless/scripts/dynamic-init-repo.sh @@ -0,0 +1,124 @@ +#!/bin/bash + +# Dynamic GitHub repository initialization for serverless mode +# This script extracts the GitHub repo from HTTP headers set by the routing proxy + +set -e + +log() { + echo "[$(date '+%Y-%m-%d %H:%M:%S')] DYNAMIC-INIT: $*" >&2 +} + +log "Starting dynamic repository initialization..." + +# In serverless mode, we expect the routing proxy to have set these environment variables +# from the HTTP headers. If running standalone, fallback to GITHUB_REPO env var. + +if [[ "$SERVERLESS_MODE" == "true" ]]; then + log "Serverless mode detected" + + # The routing proxy should have set these via HTTP headers -> env vars + # Check if we have the GitHub repo from the X-GitHub-Repo header + if [[ -n "$HTTP_X_GITHUB_REPO" ]]; then + GITHUB_REPO="$HTTP_X_GITHUB_REPO" + log "Using GitHub repo from header: $GITHUB_REPO" + elif [[ -n "$X_GITHUB_REPO" ]]; then + GITHUB_REPO="$X_GITHUB_REPO" + log "Using GitHub repo from X-GitHub-Repo: $GITHUB_REPO" + else + # Try to extract from a file written by an init container or sidecar + if [[ -f "/tmp/github-repo" ]]; then + GITHUB_REPO=$(cat /tmp/github-repo) + log "Using GitHub repo from file: $GITHUB_REPO" + else + log "ERROR: No GitHub repository specified in serverless mode" + log "Expected HTTP_X_GITHUB_REPO or X_GITHUB_REPO header from routing proxy" + exit 1 + fi + fi + + # Extract user info if available + if [[ -n "$HTTP_X_AUTHENTIK_USERNAME" ]]; then + export GIT_USER_NAME="${HTTP_X_AUTHENTIK_NAME:-$HTTP_X_AUTHENTIK_USERNAME}" + export GIT_USER_EMAIL="${HTTP_X_AUTHENTIK_EMAIL:-${HTTP_X_AUTHENTIK_USERNAME}@devcontainer.local}" + log "Using Authentik user: $GIT_USER_NAME <$GIT_USER_EMAIL>" + fi +else + log "Traditional mode - using GITHUB_REPO environment variable" + if [[ -z "$GITHUB_REPO" ]]; then + log "ERROR: GITHUB_REPO environment variable is required" + exit 1 + fi +fi + +# Validate the GitHub repo URL +if [[ ! "$GITHUB_REPO" =~ ^https://github\.com/[^/]+/[^/]+/?$ ]]; then + log "ERROR: Invalid GitHub repository URL: $GITHUB_REPO" + log "Expected format: https://github.com/owner/repo" + exit 1 +fi + +# Extract owner and repo name for workspace directory +REPO_OWNER=$(echo "$GITHUB_REPO" | sed 's|https://github.com/\([^/]*\)/.*|\1|') +REPO_NAME=$(echo "$GITHUB_REPO" | sed 's|https://github.com/[^/]*/\([^/]*\)/?|\1|') +WORKSPACE_DIR="/workspace/${REPO_OWNER}-${REPO_NAME}" + +log "Repository: $GITHUB_REPO" +log "Owner: $REPO_OWNER" +log "Name: $REPO_NAME" +log "Workspace: $WORKSPACE_DIR" + +# Configure git user (use defaults if not set via Authentik) +GIT_USER_NAME="${GIT_USER_NAME:-DevContainer User}" +GIT_USER_EMAIL="${GIT_USER_EMAIL:-devcontainer@example.com}" + +log "Configuring git user: $GIT_USER_NAME <$GIT_USER_EMAIL>" +git config --global user.name "$GIT_USER_NAME" +git config --global user.email "$GIT_USER_EMAIL" + +# Configure git credentials if GitHub token is available +if [[ -n "$GITHUB_TOKEN" ]]; then + log "Configuring GitHub credentials..." + git config --global credential.helper store + echo "https://oauth2:${GITHUB_TOKEN}@github.com" > ~/.git-credentials + chmod 600 ~/.git-credentials +else + log "No GitHub token provided - using public access only" +fi + +# Create workspace directory +mkdir -p "$(dirname "$WORKSPACE_DIR")" +cd "$(dirname "$WORKSPACE_DIR")" + +# Clone the repository +if [[ -d "$WORKSPACE_DIR" ]]; then + log "Repository directory exists, pulling latest changes..." + cd "$WORKSPACE_DIR" + git pull --ff-only || { + log "WARNING: Could not fast-forward, repository may have diverged" + log "Continuing with existing state..." + } +else + log "Cloning repository..." + git clone "$GITHUB_REPO" "$WORKSPACE_DIR" || { + log "ERROR: Failed to clone repository $GITHUB_REPO" + log "This may be a private repository or the URL may be incorrect" + exit 1 + } + cd "$WORKSPACE_DIR" +fi + +# Set the workspace directory for the IDE +export WORKSPACE_DIR + +log "Repository initialization complete!" +log "Workspace directory: $WORKSPACE_DIR" + +# Change to the workspace directory so the IDE opens in the right place +cd "$WORKSPACE_DIR" + +# Export variables for the parent script +export GITHUB_REPO +export WORKSPACE_DIR +export REPO_OWNER +export REPO_NAME \ No newline at end of file diff --git a/serverless/scripts/serverless-startapp.sh b/serverless/scripts/serverless-startapp.sh new file mode 100644 index 0000000..3cc03a1 --- /dev/null +++ b/serverless/scripts/serverless-startapp.sh @@ -0,0 +1,86 @@ +#!/bin/bash + +# Serverless-aware startup script for devcontainer +# This replaces the standard /startapp.sh when in serverless mode + +set -e + +log() { + echo "[$(date '+%Y-%m-%d %H:%M:%S')] SERVERLESS-START: $*" >&2 +} + +log "Starting serverless devcontainer..." +log "Mode: ${SERVERLESS_MODE:-traditional}" +log "IDE: ${IDE:-vscode}" + +# Wait for HTTP headers to be available (in case of init container pattern) +# In Knative, the headers should be available immediately as env vars +sleep 2 + +# Check if we're in serverless mode with dynamic routing +if [[ "$SERVERLESS_MODE" == "true" && "$DYNAMIC_GITHUB_ROUTING" == "true" ]]; then + log "Dynamic GitHub routing enabled" + + # In Knative, HTTP headers become environment variables with HTTP_ prefix + # But we also check for the unprefixed versions set by proxies + AVAILABLE_VARS=$(env | grep -E "(GITHUB|AUTHENTIK|X_)" | sort) + if [[ -n "$AVAILABLE_VARS" ]]; then + log "Available routing variables:" + echo "$AVAILABLE_VARS" | while read -r var; do + log " $var" + done + else + log "No routing variables found, checking for alternatives..." + # Check if there's a file with the repo info + if [[ -f "/tmp/github-repo" ]]; then + export GITHUB_REPO=$(cat /tmp/github-repo) + log "Found repo file: $GITHUB_REPO" + else + log "ERROR: No GitHub repository information available" + log "Expected routing headers or /tmp/github-repo file" + exit 1 + fi + fi + + # Use the dynamic initialization script + source /usr/local/bin/dynamic-init-repo +else + log "Using standard initialization..." + # Use the standard initialization + source /usr/local/bin/init-repo +fi + +# At this point, WORKSPACE_DIR should be set by the init script +WORKSPACE_DIR="${WORKSPACE_DIR:-/workspace}" +log "Working directory: $WORKSPACE_DIR" + +# Ensure we're in the workspace directory +cd "$WORKSPACE_DIR" + +# Launch the appropriate IDE based on the IDE environment variable +case "${IDE:-vscode}" in + "vscode") + log "Starting VSCode..." + exec code --new-window --wait "$WORKSPACE_DIR" + ;; + "antigravity") + log "Starting Antigravity..." + exec antigravity \ + --no-sandbox \ + --user-data-dir ~/.config/antigravity \ + --disable-dev-shm-usage \ + --disable-gpu \ + --disable-features=VizDisplayCompositor \ + --new-window \ + "$WORKSPACE_DIR" + ;; + "none") + log "No IDE requested, keeping container alive..." + exec sleep infinity + ;; + *) + log "ERROR: Unknown IDE type: $IDE" + log "Valid options: vscode, antigravity, none" + exit 1 + ;; +esac \ No newline at end of file