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 <noreply@anthropic.com>
Co-Authored-By: Happy <yesreply@happy.engineering>
This commit is contained in:
@@ -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
|
||||
|
||||
+7
-1
@@ -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
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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: ""
|
||||
@@ -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;"]
|
||||
@@ -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 "$@"
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
@@ -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
|
||||
Reference in New Issue
Block a user