diff --git a/.gitignore b/.gitignore index 9cc981b4d5..92f71bf2ad 100644 --- a/.gitignore +++ b/.gitignore @@ -98,3 +98,6 @@ AGENTS.local.md # Ignore plans written by AI agents. PLAN.md + +# Ignore any dev licenses +license.txt diff --git a/compose.dev.yaml b/compose.dev.yaml new file mode 100644 index 0000000000..a01a0a2c54 --- /dev/null +++ b/compose.dev.yaml @@ -0,0 +1,363 @@ +# docker-compose.dev.yml — Development environment +services: + database: + labels: + - "com.coder.dev" + networks: + - coder-dev + image: postgres:17 + environment: + POSTGRES_USER: coder + POSTGRES_PASSWORD: coder + POSTGRES_DB: coder + ports: + - "5432:5432" + volumes: + - coder_dev_data:/var/lib/postgresql/data + healthcheck: + test: ["CMD-SHELL", "pg_isready -U coder"] + interval: 2s + timeout: 5s + retries: 10 + + # Ensure named volumes are owned by the coder user (uid 1000) + # since Docker creates them as root by default. + init-volumes: + labels: + - "com.coder.dev" + image: codercom/oss-dogfood:latest + user: "0:0" + volumes: + - go_cache:/go-cache + - coder_cache:/cache + - bootstrap_token:/bootstrap + - site_node_modules:/app/site/node_modules + command: > + chown -R 1000:1000 + /go-cache + /cache + /bootstrap + /app/site/node_modules + + build-slim: + labels: + - "com.coder.dev" + network_mode: "host" + image: codercom/oss-dogfood:latest + depends_on: + init-volumes: + condition: service_completed_successfully + database: + condition: service_healthy + working_dir: /app + # Add the Docker group so coderd can access the Docker socket. + # If your Docker group is not 999, the below should work: + # export DOCKER_GROUP=$(getent group docker | cut -d: -f3) + group_add: + - "${DOCKER_GROUP:-999}" + environment: + GOMODCACHE: /go-cache/mod + GOCACHE: /go-cache/build + DOCKER_HOST: "${CODER_DEV_DOCKER_HOST:-unix:///var/run/docker.sock}" + volumes: + - .:/app + - go_cache:/go-cache + - coder_cache:/cache + - "${DOCKER_SOCKET:-/var/run/docker.sock}:/var/run/docker.sock" + command: > + sh -c ' + if [ "${CODER_BUILD_AGPL:-0}" = "1" ]; then + make -j build-slim CODER_BUILD_AGPL=1 + else + make -j build-slim + fi && + mkdir -p /cache/site/orig/bin && + cp site/out/bin/coder-* /cache/site/orig/bin/ + ' + + coderd: + labels: + - "com.coder.dev" + networks: + - coder-dev + image: codercom/oss-dogfood:latest + depends_on: + database: + condition: service_healthy + build-slim: + condition: service_completed_successfully + environment: + CODER_PG_CONNECTION_URL: "postgresql://coder:coder@database:5432/coder?sslmode=disable" + CODER_HTTP_ADDRESS: "0.0.0.0:3000" + CODER_ACCESS_URL: "${CODER_DEV_ACCESS_URL:-http://localhost:3000}" + CODER_DEV_ADMIN_PASSWORD: "${CODER_DEV_ADMIN_PASSWORD:-SomeSecurePassword!}" + CODER_SWAGGER_ENABLE: "true" + CODER_DANGEROUS_ALLOW_CORS_REQUESTS: "true" + CODER_TELEMETRY_ENABLE: "false" + GOMODCACHE: /go-cache/mod + GOCACHE: /go-cache/build + CODER_CACHE_DIRECTORY: /cache + DOCKER_HOST: "${CODER_DEV_DOCKER_HOST:-unix:///var/run/docker.sock}" + # Add the Docker group so coderd can access the Docker socket. + # Override DOCKER_GROUP if your host's docker group is not 999. + group_add: + - "${DOCKER_GROUP:-999}" + ports: + - "3000:3000" + healthcheck: + test: ["CMD-SHELL", "curl -sf http://localhost:3000/healthz || exit 1"] + interval: 5s + timeout: 5s + retries: 30 + start_period: 120s + working_dir: /app + volumes: + - .:/app + - go_cache:/go-cache + - coder_cache:/cache + - "${DOCKER_SOCKET:-/var/run/docker.sock}:/var/run/docker.sock" + command: > + sh -c ' + CMD_PATH="./enterprise/cmd/coder" + [ "${CODER_BUILD_AGPL:-0}" = "1" ] && CMD_PATH="./cmd/coder" + exec go run "$$CMD_PATH" server \ + --http-address 0.0.0.0:3000 \ + --access-url "${CODER_DEV_ACCESS_URL:-http://localhost:3000}" \ + --swagger-enable \ + --dangerous-allow-cors-requests=true \ + --enable-terraform-debug-mode + ' + + setup-init: + labels: + - "com.coder.dev" + networks: + - coder-dev + image: codercom/oss-dogfood:latest + depends_on: + coderd: + condition: service_healthy + working_dir: /app + environment: + CODER_URL: "http://coderd:3000" + CODER_DEV_ADMIN_PASSWORD: "${CODER_DEV_ADMIN_PASSWORD:-SomeSecurePassword!}" + GOMODCACHE: /go-cache/mod + GOCACHE: /go-cache/build + volumes: + - .:/app + - go_cache:/go-cache + - bootstrap_token:/bootstrap + - ./scripts/docker-dev:/scripts:ro + command: ["sh", "/scripts/setup-init.sh"] + + setup-users: + labels: + - "com.coder.dev" + networks: + - coder-dev + image: codercom/oss-dogfood:latest + depends_on: + setup-init: + condition: service_completed_successfully + working_dir: /app + environment: + CODER_URL: "http://coderd:3000" + CODER_DEV_MEMBER_PASSWORD: "${CODER_DEV_MEMBER_PASSWORD:-SomeSecurePassword!}" + GOMODCACHE: /go-cache/mod + GOCACHE: /go-cache/build + volumes: + - .:/app + - go_cache:/go-cache + - bootstrap_token:/bootstrap:ro + - ./scripts/docker-dev:/scripts:ro + command: ["sh", "/scripts/setup-users.sh"] + + setup-template: + labels: + - "com.coder.dev" + networks: + - coder-dev + image: codercom/oss-dogfood:latest + depends_on: + setup-init: + condition: service_completed_successfully + working_dir: /app + environment: + CODER_URL: "http://coderd:3000" + DOCKER_HOST: "${CODER_DEV_DOCKER_HOST:-unix:///var/run/docker.sock}" + GOMODCACHE: /go-cache/mod + GOCACHE: /go-cache/build + volumes: + - .:/app + - go_cache:/go-cache + - bootstrap_token:/bootstrap:ro + - ./scripts/docker-dev:/scripts:ro + - "${DOCKER_SOCKET:-/var/run/docker.sock}:/var/run/docker.sock" + command: ["sh", "/scripts/setup-template.sh"] + + site: + labels: + - "com.coder.dev" + networks: + - coder-dev + image: codercom/oss-dogfood:latest + depends_on: + setup-template: + condition: service_completed_successfully + working_dir: /app/site + environment: + CODER_HOST: "http://coderd:3000" + ports: + - "8080:8080" + volumes: + - ./site:/app/site + - site_node_modules:/app/site/node_modules + command: sh -c "pnpm install --frozen-lockfile && pnpm dev --host" + + wsproxy: + profiles: ["proxy"] + labels: + - "com.coder.dev" + networks: + - coder-dev + image: codercom/oss-dogfood:latest + depends_on: + setup-init: + condition: service_completed_successfully + working_dir: /app + environment: + CODER_URL: "http://coderd:3000" + GOMODCACHE: /go-cache/mod + GOCACHE: /go-cache/build + volumes: + - .:/app + - go_cache:/go-cache + - bootstrap_token:/bootstrap:ro + ports: + - "3010:3010" + command: > + sh -c ' + export CODER_SESSION_TOKEN=$$(cat /bootstrap/token) && + go run ./cmd/coder wsproxy delete local-proxy --yes 2>/dev/null || true + PROXY_TOKEN=$$(go run ./cmd/coder wsproxy create \ + --name=local-proxy \ + --display-name="Local Proxy" \ + --icon="/emojis/1f4bb.png" \ + --only-token) + exec go run ./cmd/coder wsproxy server \ + --dangerous-allow-cors-requests=true \ + --http-address=0.0.0.0:3010 \ + --proxy-session-token="$$PROXY_TOKEN" \ + --primary-access-url=http://coderd:3000 + ' + + setup-multi-org: + profiles: ["multi-org"] + labels: + - "com.coder.dev" + networks: + - coder-dev + image: codercom/oss-dogfood:latest + depends_on: + setup-users: + condition: service_completed_successfully + setup-template: + condition: service_completed_successfully + working_dir: /app + environment: + CODER_URL: "http://coderd:3000" + DOCKER_HOST: "${CODER_DEV_DOCKER_HOST:-unix:///var/run/docker.sock}" + LICENSE_FILE: "${CODER_DEV_LICENSE_FILE:-./license.txt}" + GOMODCACHE: /go-cache/mod + GOCACHE: /go-cache/build + volumes: + - .:/app + - go_cache:/go-cache + - bootstrap_token:/bootstrap:ro + - ./scripts/docker-dev:/scripts:ro + - "${CODER_DEV_LICENSE_FILE:-./license.txt}:/license.txt:ro" + command: ["sh", "/scripts/setup-multi-org.sh"] + + ext-provisioner: + profiles: ["multi-org"] + labels: + - "com.coder.dev" + networks: + - coder-dev + healthcheck: + test: ["CMD", "curl", "--fail", "http://localhost:2112"] + image: codercom/oss-dogfood:latest + depends_on: + setup-multi-org: + condition: service_completed_successfully + group_add: + - "${DOCKER_GROUP:-999}" + working_dir: /app + environment: + CODER_URL: "http://coderd:3000" + DOCKER_HOST: "${CODER_DEV_DOCKER_HOST:-unix:///var/run/docker.sock}" + GOMODCACHE: /go-cache/mod + GOCACHE: /go-cache/build + CODER_PROMETHEUS_ENABLE: "1" + volumes: + - .:/app + - go_cache:/go-cache + - bootstrap_token:/bootstrap:ro + - "${DOCKER_SOCKET:-/var/run/docker.sock}:/var/run/docker.sock" + command: > + sh -c ' + export CODER_SESSION_TOKEN=$$(cat /bootstrap/token) && + exec go run ./enterprise/cmd/coder provisionerd start \ + --tag "scope=organization" \ + --name second-org-daemon \ + --org second-organization + ' + + setup-multi-org-template: + profiles: ["multi-org"] + labels: + - "com.coder.dev" + networks: + - coder-dev + image: codercom/oss-dogfood:latest + depends_on: + setup-multi-org: + condition: service_completed_successfully + ext-provisioner: + condition: service_healthy + working_dir: /app + environment: + CODER_URL: "http://coderd:3000" + GOMODCACHE: /go-cache/mod + GOCACHE: /go-cache/build + volumes: + - .:/app + - go_cache:/go-cache + - bootstrap_token:/bootstrap:ro + - ./scripts/docker-dev:/scripts:ro + command: ["sh", "-c", "/scripts/setup-template.sh second-organization"] + + +volumes: + coder_dev_data: + labels: + - "com.coder.dev" + go_cache: + labels: + - "com.coder.dev" + coder_cache: + labels: + - "com.coder.dev" + site_node_modules: + labels: + - "com.coder.dev" + bootstrap_token: + labels: + - "com.coder.dev" + +networks: + coder-dev: + labels: + - "com.coder.dev" + name: coder-dev + driver: bridge diff --git a/scripts/docker-dev/setup-init.sh b/scripts/docker-dev/setup-init.sh new file mode 100755 index 0000000000..9844d7c393 --- /dev/null +++ b/scripts/docker-dev/setup-init.sh @@ -0,0 +1,38 @@ +#!/bin/sh +set -e + +CODER="go run ./cmd/coder" +PASSWORD="${CODER_DEV_ADMIN_PASSWORD:-SomeSecurePassword!}" +TOKEN_FILE="/bootstrap/token" +TOKEN_NAME="bootstrap" + +echo "=== Coder Dev Environment Init ===" + +if curl -s -o /dev/null -w "%{http_code}" http://coderd:3000/api/v2/users/first | grep -q "200"; then + echo "First user already exists, skipping setup" + exit 0 +fi + +# Step 1: Create first user (idempotent - creates OR logs in) +echo "Creating/logging in first user..." +$CODER login http://coderd:3000 \ + --first-user-username=admin \ + --first-user-email=admin@coder.com \ + --first-user-password="$PASSWORD" \ + --first-user-full-name="Admin User" \ + --first-user-trial=false + +# Step 2: Create or retrieve bootstrap token +if [ -f "$TOKEN_FILE" ] && [ -s "$TOKEN_FILE" ]; then + echo "Bootstrap token already exists." +else + echo "Creating bootstrap token..." + # Delete existing token if it exists (in case file was lost but token exists) + $CODER tokens delete "$TOKEN_NAME" 2>/dev/null || true + # Create new token with no expiry + TOKEN=$($CODER tokens create --name "$TOKEN_NAME" --lifetime 0) + echo "$TOKEN" >"$TOKEN_FILE" + echo "Bootstrap token created and saved." +fi + +echo "=== Init complete ===" diff --git a/scripts/docker-dev/setup-multi-org.sh b/scripts/docker-dev/setup-multi-org.sh new file mode 100755 index 0000000000..14e2a1a8bd --- /dev/null +++ b/scripts/docker-dev/setup-multi-org.sh @@ -0,0 +1,44 @@ +#!/bin/sh +set -e + +CODER="go run ./enterprise/cmd/coder" +TOKEN_FILE="/bootstrap/token" +LICENSE_FILE="/license.txt" +ORG_NAME="${ORG_NAME:-second-organization}" + +echo "=== Multi-Organization Setup ===" + +# Load bootstrap token +CODER_SESSION_TOKEN=$(cat "$TOKEN_FILE") +if [ -z "${CODER_SESSION_TOKEN}" ]; then + echo "Bootstrap token not found in ${TOKEN_FILE}" + exit 1 +fi +export CODER_SESSION_TOKEN + +# Check if a license has not yet been added +LICENSES=$($CODER license list | tail -n +2) +if [ -z "${LICENSES}" ]; then + echo "No existing license found." + if [ ! -f "${LICENSE_FILE}" ]; then + echo "License required, set CODER_DEV_LICENSE_FILE=path/to/license.txt" + exit 1 + fi + echo "Adding license..." + $CODER license add --file "${LICENSE_FILE}" +fi + +# Create second organization if it doesn't exist. +if ! $CODER organizations show "$ORG_NAME" >/dev/null 2>&1; then + echo "Creating organization '$ORG_NAME'..." + $CODER organizations create -y "$ORG_NAME" +else + echo "Organization '$ORG_NAME' already exists." +fi + +# Add member user to the organization. +echo "Adding member user to organization '$ORG_NAME'..." +$CODER organizations members add member --org "$ORG_NAME" 2>/dev/null || + echo "Member already in organization or failed to add." + +echo "=== Multi-org setup complete ===" diff --git a/scripts/docker-dev/setup-template.sh b/scripts/docker-dev/setup-template.sh new file mode 100755 index 0000000000..8d43a075fc --- /dev/null +++ b/scripts/docker-dev/setup-template.sh @@ -0,0 +1,50 @@ +#!/bin/sh +set -e + +CODER="go run ./cmd/coder" +TOKEN_FILE="/bootstrap/token" + +# Accept optional org argument. If not provided, use the user's default org. +ORG_NAME="${1:-}" + +echo "=== Setting up docker template ===" + +# Load bootstrap token +CODER_SESSION_TOKEN=$(cat "$TOKEN_FILE") +if [ -z "${CODER_SESSION_TOKEN}" ]; then + echo "Bootstrap token not found in ${TOKEN_FILE}" + exit 1 +fi +export CODER_SESSION_TOKEN + +# If no org provided, get user's default org. +if [ -z "$ORG_NAME" ]; then + ORG_NAME=$($CODER organizations show me -o json | jq -r '.[] | select(.is_default) | .name') +fi + +echo "Target organization: $ORG_NAME" + +# Check if template already exists in this org. +if $CODER templates versions list docker --org "$ORG_NAME" >/dev/null 2>&1; then + echo "Docker template already exists in '$ORG_NAME'." + exit 0 +fi + +# Create and push docker template. +echo "Creating docker template in '$ORG_NAME'..." +TEMPLATE_DIR="$(mktemp -d)" +$CODER templates init --id docker "$TEMPLATE_DIR" +(cd "$TEMPLATE_DIR" && terraform init) + +ARCH="$(go env GOARCH)" +printf 'docker_arch: "%s"\ndocker_host: "%s"\n' \ + "$ARCH" "${DOCKER_HOST:-unix:///var/run/docker.sock}" \ + >"$TEMPLATE_DIR/params.yaml" + +$CODER templates push docker \ + --directory "$TEMPLATE_DIR" \ + --variables-file "$TEMPLATE_DIR/params.yaml" \ + --yes --org "$ORG_NAME" + +rm -rf "$TEMPLATE_DIR" +echo "=== Docker template setup complete ===" diff --git a/scripts/docker-dev/setup-users.sh b/scripts/docker-dev/setup-users.sh new file mode 100755 index 0000000000..0142a420f2 --- /dev/null +++ b/scripts/docker-dev/setup-users.sh @@ -0,0 +1,26 @@ +#!/bin/sh +set -e + +CODER="go run ./cmd/coder" +PASSWORD="${CODER_DEV_MEMBER_PASSWORD:-SomeSecurePassword!}" +TOKEN_FILE="/bootstrap/token" + +echo "=== Setting up users ===" + +# Load bootstrap token +CODER_SESSION_TOKEN=$(cat "$TOKEN_FILE") +if [ -z "${CODER_SESSION_TOKEN}" ]; then + echo "Bootstrap token not found in ${TOKEN_FILE}" + exit 1 +fi +export CODER_SESSION_TOKEN + +# Create member user (idempotent) +echo "Creating member user..." +$CODER users create \ + --email=member@coder.com \ + --username=member \ + --full-name="Regular User" \ + --password="$PASSWORD" 2>/dev/null || echo "Member user already exists." + +echo "=== Users setup complete ==="