diff --git a/.env.example b/.devcontainer/.env.example similarity index 71% rename from .env.example rename to .devcontainer/.env.example index a55b3a9..52f0db2 100644 --- a/.env.example +++ b/.devcontainer/.env.example @@ -54,27 +54,39 @@ OP_CONNECT_TOKEN= # ============================================================================ # CUSTOM DOMAIN CONFIGURATION # ============================================================================ -# Two ways to allow custom domains: +# Two ways to allow custom domains beyond the built-in ones (GitHub, npm, Anthropic): # -# 1. CUSTOM_ALLOWED_DOMAINS (below) - User/machine specific domains from .env -# - Stored in your local .env file (not committed to repo) +# 1. CUSTOM_ALLOWED_DOMAINS (below) - Personal/temporary domains from .env +# - Stored in your .devcontainer/.env file (not committed to repo) # - For personal/local services (e.g., local test servers, personal APIs) -# - Processed at container startup +# - Comma-separated list of domains (without https://) +# - Supports: domain names, IP addresses, and CIDR ranges # # 2. allowed-domains.txt file - Project-wide domains -# - Create file in project root (committed to repo) +# - Create file in .devcontainer/ folder (committed to repo) # - For team-shared domains (e.g., company APIs, private registries) # - One domain/IP per line, supports comments with # # -# Format: comma-separated list of domains (without https://) -# Example: CUSTOM_ALLOWED_DOMAINS=api.mycompany.com,registry.mycompany.io,192.168.1.100 -# Supports: domain names, IP addresses, and CIDR ranges +# Example: CUSTOM_ALLOWED_DOMAINS=api.mycompany.com,staging.myapp.io,192.168.1.100 CUSTOM_ALLOWED_DOMAINS= # ============================================================================ # SOCKS5 PROXY CONFIGURATION # ============================================================================ -# The container can use a SOCKS5 proxy running on your host machine -# Default port is 1080, accessible via host.docker.internal:1080 -# To use with curl: curl --socks5 host.docker.internal:1080 https://example.com -# To use with git: git config --global http.proxy socks5://host.docker.internal:1080 \ No newline at end of file +# Configure access to a SOCKS5 proxy for routing traffic through a VPN or proxy server +# This is useful for accessing resources behind a corporate firewall or VPN + +# Enable/disable SOCKS5 proxy access (true/false) +SOCKS5_ENABLED=true + +# SOCKS5 proxy host (can be hostname or IP address) +# Default: host.docker.internal (your host machine) +# Examples: proxy.company.com, 192.168.1.100, host.docker.internal +SOCKS5_HOST=host.docker.internal + +# SOCKS5 proxy port +SOCKS5_PORT=1080 + +# To use the proxy in the container: +# curl --socks5 ${SOCKS5_HOST}:${SOCKS5_PORT} https://example.com +# git config --global http.proxy socks5://${SOCKS5_HOST}:${SOCKS5_PORT} \ No newline at end of file diff --git a/.devcontainer/.gitignore b/.devcontainer/.gitignore new file mode 100644 index 0000000..d73a9b3 --- /dev/null +++ b/.devcontainer/.gitignore @@ -0,0 +1,9 @@ +# Environment file with personal settings +.env + +# Project-specific allowed domains (optional) +allowed-domains.txt + +# But keep the examples +!.env.example +!allowed-domains.txt.example \ No newline at end of file diff --git a/allowed-domains.example b/.devcontainer/allowed-domains.txt.example similarity index 98% rename from allowed-domains.example rename to .devcontainer/allowed-domains.txt.example index 7205e8c..6525014 100644 --- a/allowed-domains.example +++ b/.devcontainer/allowed-domains.txt.example @@ -1,6 +1,6 @@ # Custom Allowed Domains and IP Ranges # Copy this file to 'allowed-domains.txt' in your project root to add custom allowed domains/IPs -# +# # Format: # - One entry per line # - Domain names will be resolved to IPs (e.g., example.com) @@ -28,7 +28,7 @@ # bedrock-runtime.eu-west-3.amazonaws.com # bedrock-runtime.eu-central-1.amazonaws.com -# Asia Pacific Regions +# Asia Pacific Regions # bedrock.ap-southeast-1.amazonaws.com # bedrock.ap-southeast-2.amazonaws.com # bedrock.ap-northeast-1.amazonaws.com diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index 8e06e2f..d7844b9 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -1,13 +1,9 @@ { "name": "Liquescent Development Environment", - // Use pre-built image from GitHub Container Registry - // To build locally: cd docker-image && ./build.sh - "image": "ghcr.io/liquescent-development/devcontainer:latest", - "runArgs": [ - "--cap-add=NET_ADMIN", - "--cap-add=NET_RAW", - "--add-host=host.docker.internal:host-gateway" - ], + // Uses Docker Compose to load .env file automatically + "dockerComposeFile": "docker-compose.yml", + "service": "devcontainer", + "workspaceFolder": "/workspace", "customizations": { "vscode": { "extensions": [ @@ -17,7 +13,6 @@ ], "settings": { "editor.formatOnSave": true, - "editor.defaultFormatter": "esbenp.prettier-vscode", "editor.codeActionsOnSave": { "source.fixAll.eslint": "explicit" }, @@ -55,52 +50,11 @@ }, "ghcr.io/itsmechlark/features/1password:1": { "version": "latest" - }, - "ghcr.io/devcontainers-community/features/direnv:1": { - "version": "latest" } }, "remoteUser": "node", "userEnvProbe": "loginInteractiveShell", "updateRemoteUserUID": true, - "secrets": { - "OP_SERVICE_ACCOUNT_TOKEN": { - "description": "1Password Service Account token for secure environment variable management", - "documentationUrl": "https://developer.1password.com/docs/service-accounts/" - }, - "OP_CONNECT_TOKEN": { - "description": "1Password Connect Server token (alternative to service account)", - "documentationUrl": "https://developer.1password.com/docs/connect/" - } - }, - "mounts": [ - "source=devcontainer-history-${devcontainerId},target=/commandhistory,type=volume", - "source=${localEnv:HOME}/.claude/agents,target=/home/node/.claude/agents,type=bind,readonly", - "source=${localEnv:HOME}/.claude/CLAUDE.md,target=/home/node/.claude/CLAUDE.md,type=bind,readonly", - "source=${localEnv:HOME}/.claude/settings.json,target=/home/node/.claude/settings.json,type=bind,readonly", - "source=${localEnv:HOME}/Library/Fonts,target=/usr/share/fonts/truetype/custom,type=bind,readonly", - "source=${localEnv:HOME}/.gitconfig,target=/home/node/.gitconfig.host,type=bind,readonly", - "source=${localEnv:HOME}/.ssh,target=/home/node/.ssh-host,type=bind,readonly", - "source=${localEnv:SSH_AUTH_SOCK},target=/ssh-agent,type=bind" - ], - "containerEnv": { - "NODE_OPTIONS": "--max-old-space-size=4096", - "CLAUDE_CONFIG_DIR": "/home/node/.claude", - "SSH_AUTH_SOCK": "/ssh-agent" - }, - "remoteEnv": { - "OP_SERVICE_ACCOUNT_TOKEN": "${localEnv:OP_SERVICE_ACCOUNT_TOKEN}", - "OP_CREATE_SERVICE_ACCOUNT": "${localEnv:OP_CREATE_SERVICE_ACCOUNT}", - "OP_SA_EXPIRES_IN": "${localEnv:OP_SA_EXPIRES_IN}", - "OP_SA_VAULTS": "${localEnv:OP_SA_VAULTS}", - "OP_SA_NAME": "${localEnv:OP_SA_NAME}", - "OP_CONNECT_HOST": "${localEnv:OP_CONNECT_HOST}", - "OP_CONNECT_TOKEN": "${localEnv:OP_CONNECT_TOKEN}", - "CUSTOM_ALLOWED_DOMAINS": "${localEnv:CUSTOM_ALLOWED_DOMAINS}", - "MOUNT_HOST_GIT_CONFIG": "${localEnv:MOUNT_HOST_GIT_CONFIG}" - }, - "workspaceMount": "source=${localWorkspaceFolder},target=/workspace,type=bind,consistency=delegated", - "workspaceFolder": "/workspace", "hostRequirements": { "cpus": 2, "memory": "4gb", diff --git a/.devcontainer/docker-compose.yml b/.devcontainer/docker-compose.yml new file mode 100644 index 0000000..0be647a --- /dev/null +++ b/.devcontainer/docker-compose.yml @@ -0,0 +1,77 @@ +services: + devcontainer: + image: ghcr.io/liquescent-development/devcontainer:latest + + # Environment variables from .env file are automatically loaded + environment: + # Timezone + - TZ=${TZ:-UTC} + + # 1Password Configuration + - OP_SERVICE_ACCOUNT_TOKEN=${OP_SERVICE_ACCOUNT_TOKEN:-} + - OP_CREATE_SERVICE_ACCOUNT=${OP_CREATE_SERVICE_ACCOUNT:-false} + - OP_SA_EXPIRES_IN=${OP_SA_EXPIRES_IN:-30d} + - OP_SA_VAULTS=${OP_SA_VAULTS:-} + - OP_SA_NAME=${OP_SA_NAME:-} + - OP_CONNECT_HOST=${OP_CONNECT_HOST:-} + - OP_CONNECT_TOKEN=${OP_CONNECT_TOKEN:-} + + # Custom allowed domains for firewall + - CUSTOM_ALLOWED_DOMAINS=${CUSTOM_ALLOWED_DOMAINS:-} + + # SOCKS5 proxy configuration + - SOCKS5_ENABLED=${SOCKS5_ENABLED:-true} + - SOCKS5_HOST=${SOCKS5_HOST:-host.docker.internal} + - SOCKS5_PORT=${SOCKS5_PORT:-1080} + + # Git configuration mounting + - MOUNT_HOST_GIT_CONFIG=${MOUNT_HOST_GIT_CONFIG:-false} + + # Container environment + - NODE_OPTIONS=--max-old-space-size=4096 + - CLAUDE_CONFIG_DIR=/home/node/.claude + - SSH_AUTH_SOCK=/ssh-agent + - DEVCONTAINER=true + + volumes: + # Workspace + - ..:/workspace:cached + + # Claude configuration + - ${HOME}/.claude/agents:/home/node/.claude/agents:ro + - ${HOME}/.claude/CLAUDE.md:/home/node/.claude/CLAUDE.md:ro + - ${HOME}/.claude/settings.json:/home/node/.claude/settings.json:ro + + # Git configuration and SSH + - ${HOME}/.gitconfig:/home/node/.gitconfig.host:ro + - ${HOME}/.ssh:/home/node/.ssh-host:ro + + # SSH agent forwarding + - ${SSH_AUTH_SOCK:-/dev/null}:/ssh-agent + + # Command history persistence + - devcontainer-history:/commandhistory + + # Fonts (macOS specific path, will be ignored on other systems) + - ${HOME}/Library/Fonts:/usr/share/fonts/truetype/custom:ro + + # Network capabilities for firewall + cap_add: + - NET_ADMIN + - NET_RAW + + # Add host.docker.internal for accessing host services + extra_hosts: + - "host.docker.internal:host-gateway" + + # Keep container running + command: sleep infinity + + # Set working directory + working_dir: /workspace + + # Run as node user + user: node + +volumes: + devcontainer-history: \ No newline at end of file diff --git a/README.md b/README.md index 5594d12..d668093 100644 --- a/README.md +++ b/README.md @@ -42,8 +42,8 @@ A secure, polyglot development container with network isolation, comprehensive l 2. **Configure your environment**: ```bash - cp .env.example .env - # Edit .env with your settings + cp .devcontainer/.env.example .devcontainer/.env + # Edit .devcontainer/.env with your settings ``` 3. **Open in VS Code**: @@ -58,7 +58,9 @@ The container will automatically pull from `ghcr.io/liquescent-development/devco ### Environment Variables -Key configuration options in `.env`: +The devcontainer uses Docker Compose to automatically load environment variables from `.devcontainer/.env`. + +Key configuration options in `.devcontainer/.env`: ```bash # Timezone @@ -91,13 +93,14 @@ By default, only these domains are accessible: **Method 1: Environment Variable** (personal/temporary) ```bash -# In .env +# In .devcontainer/.env CUSTOM_ALLOWED_DOMAINS=api.example.com,db.internal.net ``` **Method 2: Project Configuration** (team/permanent) +Create an `allowed-domains.txt` file in `.devcontainer/`: ```bash -# In allowed-domains.txt (project root) +# In .devcontainer/allowed-domains.txt api.example.com staging.example.com 192.168.1.0/24 @@ -156,20 +159,34 @@ export API_KEY=$(op read "op://vault/item/field") ### Using SOCKS5 Proxy -Access external resources through host proxy: +The container supports configurable SOCKS5 proxy access for routing traffic through VPNs or corporate proxies. +Configuration in `.devcontainer/.env`: +```bash +SOCKS5_ENABLED=true # Enable/disable proxy access +SOCKS5_HOST=host.docker.internal # Proxy host (hostname or IP) +SOCKS5_PORT=1080 # Proxy port +``` + +Using the proxy in the container: ```bash # Configure git -git config --global http.proxy socks5://host.docker.internal:1080 +git config --global http.proxy socks5://${SOCKS5_HOST}:${SOCKS5_PORT} # Use with curl -curl --socks5 host.docker.internal:1080 https://example.com +curl --socks5 ${SOCKS5_HOST}:${SOCKS5_PORT} https://example.com # Use with Python -export HTTP_PROXY=socks5://host.docker.internal:1080 -export HTTPS_PROXY=socks5://host.docker.internal:1080 +export HTTP_PROXY=socks5://${SOCKS5_HOST}:${SOCKS5_PORT} +export HTTPS_PROXY=socks5://${SOCKS5_HOST}:${SOCKS5_PORT} ``` +Common proxy scenarios: +- **Local SSH tunnel**: `ssh -D 1080 user@jumphost` then use default settings +- **Corporate proxy**: Set `SOCKS5_HOST` to your proxy server +- **VPN client**: Many VPN clients provide SOCKS5 on localhost:1080 +- **Disable proxy**: Set `SOCKS5_ENABLED=false` if not needed + ### Debugging Network Issues ```bash diff --git a/docker-image/Dockerfile b/docker-image/Dockerfile index 68c6a49..fb69bcc 100644 --- a/docker-image/Dockerfile +++ b/docker-image/Dockerfile @@ -31,7 +31,7 @@ RUN apt-get update && apt-get install -y --no-install-recommends \ build-essential \ pkg-config \ libssl-dev \ - # Note: direnv now installed via ghcr.io/devcontainers-community/features/direnv:1 + direnv \ && apt-get clean && rm -rf /var/lib/apt/lists/* # Note: 1Password CLI is now installed via Dev Container Feature: diff --git a/docker-image/scripts/init-firewall.sh b/docker-image/scripts/init-firewall.sh index 690de2a..ee6bb98 100644 --- a/docker-image/scripts/init-firewall.sh +++ b/docker-image/scripts/init-firewall.sh @@ -76,10 +76,10 @@ for domain in \ done < <(echo "$ips") done -# Process environment variable domains first (user/machine specific from .env) -if [ -n "$CUSTOM_ALLOWED_DOMAINS" ]; then - echo "Processing user-specific allowed domains from CUSTOM_ALLOWED_DOMAINS environment variable..." - IFS=',' read -ra DOMAINS <<< "$CUSTOM_ALLOWED_DOMAINS" +# Process environment variable domains (from .env file via Docker Compose) +if [ -n "${CUSTOM_ALLOWED_DOMAINS:-}" ]; then + echo "Processing custom allowed domains from CUSTOM_ALLOWED_DOMAINS environment variable..." + IFS=',' read -ra DOMAINS <<< "${CUSTOM_ALLOWED_DOMAINS:-}" for domain in "${DOMAINS[@]}"; do # Trim whitespace domain=$(echo "$domain" | xargs) @@ -109,7 +109,7 @@ if [ -n "$CUSTOM_ALLOWED_DOMAINS" ]; then fi # Process project-specific allowed domains file (committed to repo) -if [ -f "/workspace/allowed-domains.txt" ]; then +if [ -f "/workspace/.devcontainer/allowed-domains.txt" ]; then echo "Processing project-specific allowed domains from allowed-domains.txt..." while IFS= read -r line; do # Skip comments and empty lines @@ -138,10 +138,10 @@ if [ -f "/workspace/allowed-domains.txt" ]; then done < <(echo "$ips") fi fi - done < "/workspace/allowed-domains.txt" + done < "/workspace/.devcontainer/allowed-domains.txt" echo "Finished processing project-specific allowed domains" else - echo "No project allowed-domains.txt file found (optional)" + echo "No .devcontainer/allowed-domains.txt file found (optional)" fi # Get host IP from default route @@ -179,6 +179,34 @@ iptables -A INPUT -p tcp --sport 53 -j ACCEPT # Allow outbound SSH iptables -A OUTPUT -p tcp --dport 22 -j ACCEPT +# SOCKS5 proxy configuration +if [ "${SOCKS5_ENABLED:-true}" = "true" ]; then + SOCKS5_HOST="${SOCKS5_HOST:-host.docker.internal}" + SOCKS5_PORT="${SOCKS5_PORT:-1080}" + + echo "Configuring SOCKS5 proxy access:" + echo " Host: $SOCKS5_HOST" + echo " Port: $SOCKS5_PORT" + + # Resolve SOCKS5 host if it's a hostname + if [[ "$SOCKS5_HOST" =~ ^[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}$ ]]; then + # It's already an IP + SOCKS5_IP="$SOCKS5_HOST" + iptables -A OUTPUT -p tcp -d "$SOCKS5_IP" --dport "$SOCKS5_PORT" -j ACCEPT + else + # It's a hostname, resolve it + SOCKS5_IP=$(getent hosts "$SOCKS5_HOST" | awk '{print $1}') + if [ -z "$SOCKS5_IP" ]; then + echo " Warning: Failed to resolve SOCKS5 host $SOCKS5_HOST" + else + echo " Resolved to: $SOCKS5_IP" + iptables -A OUTPUT -p tcp -d "$SOCKS5_IP" --dport "$SOCKS5_PORT" -j ACCEPT + fi + fi +else + echo "SOCKS5 proxy access disabled" +fi + # Allow specific ports to host machine for common development services # Function to add rules for both IPs add_host_port_rule() { @@ -189,9 +217,6 @@ add_host_port_rule() { iptables -A OUTPUT -p tcp -d "$HOST_DOCKER_IP" --dport "$port" -j ACCEPT fi } - -# SOCKS5 proxy -add_host_port_rule 1080 "SOCKS5 proxy" # HTTP add_host_port_rule 80 "HTTP" add_host_port_rule 8080 "HTTP alt"