692 lines
22 KiB
HCL
692 lines
22 KiB
HCL
locals {
|
|
workspace_agent_scripts = {
|
|
"workspace-setup.sh" = file("${path.module}/scripts/workspace-setup.sh")
|
|
"dev-tools.sh" = file("${path.module}/scripts/dev-tools.sh")
|
|
"terminal-tools.sh" = file("${path.module}/scripts/terminal-tools.sh")
|
|
"git-hooks.sh" = file("${path.module}/scripts/git-hooks.sh")
|
|
"marimo-setup.sh" = file("${path.module}/scripts/marimo-setup.sh")
|
|
"claude-install.sh" = file("${path.module}/scripts/claude-install.sh")
|
|
"codex-setup.sh" = file("${path.module}/scripts/codex-setup.sh")
|
|
"cursor-setup.sh" = file("${path.module}/scripts/cursor-setup.sh")
|
|
"windsurf-setup.sh" = file("${path.module}/scripts/windsurf-setup.sh")
|
|
}
|
|
}
|
|
|
|
resource "coder_agent" "main" {
|
|
arch = data.coder_provisioner.me.arch
|
|
os = "linux"
|
|
dir = "/workspaces"
|
|
|
|
env = merge(
|
|
{
|
|
"GIT_AUTHOR_NAME" = local.git_author_name
|
|
"GIT_AUTHOR_EMAIL" = local.git_author_email
|
|
"CODER_WORKSPACE_ID" = local.workspace_id
|
|
"CODER_WORKSPACE_REPO" = local.project_repo_url
|
|
"ENABLE_MARIMO" = tostring(local.marimo_enabled)
|
|
"ENABLE_SERVICES" = tostring(local.services_enabled)
|
|
"CODER_AGENT_BLOCK_FILE_TRANSFER" = var.block_file_transfer ? "1" : ""
|
|
"GEM_HOME" = "/home/coder/.gem"
|
|
"GEM_PATH" = "/home/coder/.gem"
|
|
"rvm_silence_path_mismatch_check_flag" = "1"
|
|
"RVM_SILENCE_PATH_MISMATCH_CHECK_FLAG" = "1"
|
|
"rvmsudo_secure_path" = "1"
|
|
"RVMSUDO_SECURE_PATH" = "1"
|
|
"NVM_DIR" = "/usr/local/share/nvm"
|
|
},
|
|
local.gitea_pat != "" ? { "GITEA_PAT" = local.gitea_pat } : {},
|
|
local.github_pat != "" ? { "GITHUB_PAT" = local.github_pat } : {}
|
|
)
|
|
|
|
startup_script = <<-EOT
|
|
#!/bin/bash
|
|
set -euo pipefail
|
|
|
|
# Basic setup
|
|
${local.agent_startup}
|
|
|
|
# Ensure coder user can access Docker socket
|
|
if getent group docker >/dev/null 2>&1; then
|
|
if id -nG coder | tr ' ' '\n' | grep -qx docker; then
|
|
:
|
|
else
|
|
echo "Adding coder user to docker group..."
|
|
usermod -aG docker coder
|
|
fi
|
|
else
|
|
echo "docker group not found; skipping docker socket setup"
|
|
fi
|
|
|
|
# Switch to coder user for service startup with gem environment pre-set
|
|
sudo --preserve-env=CODER_WORKSPACE_ID,CODER_WORKSPACE_REPO,GITEA_PAT,GITHUB_PAT -u coder \
|
|
env -i \
|
|
HOME=/home/coder \
|
|
PATH="/home/coder/.venv/bin:/home/coder/.local/bin:/home/coder/bin:/home/coder/.cargo/bin:/usr/local/go/bin:/usr/local/bin:/usr/bin:/bin" \
|
|
GEM_HOME=/home/coder/.gem \
|
|
GEM_PATH=/home/coder/.gem \
|
|
NVM_DIR=/usr/local/share/nvm \
|
|
CODER_WORKSPACE_ID="$${CODER_WORKSPACE_ID-}" \
|
|
CODER_WORKSPACE_REPO="$${CODER_WORKSPACE_REPO-}" \
|
|
GITEA_PAT="$${GITEA_PAT-}" \
|
|
GITHUB_PAT="$${GITHUB_PAT-}" \
|
|
rvm_silence_path_mismatch_check_flag=1 \
|
|
RVM_SILENCE_PATH_MISMATCH_CHECK_FLAG=1 \
|
|
rvmsudo_secure_path=1 \
|
|
RVMSUDO_SECURE_PATH=1 \
|
|
bash <<'CODER_SETUP'
|
|
set -euo pipefail
|
|
|
|
# Consistent RubyGems environment for every shell
|
|
export GEM_HOME="$HOME/.gem"
|
|
export GEM_PATH="$GEM_HOME"
|
|
mkdir -p "$GEM_HOME/bin"
|
|
|
|
case ":$PATH:" in
|
|
*":$GEM_HOME/bin:"*) ;;
|
|
*) export PATH="$GEM_HOME/bin:$PATH" ;;
|
|
esac
|
|
|
|
ensure_shell_env_block() {
|
|
local target="$1"
|
|
local start_marker="# >>> coder gem environment >>>"
|
|
local end_marker="# <<< coder gem environment <<<"
|
|
|
|
mkdir -p "$(dirname "$target")"
|
|
touch "$target"
|
|
|
|
if grep -q "$start_marker" "$target"; then
|
|
tmp_file=$(mktemp)
|
|
sed "/$start_marker/,/$end_marker/d" "$target" > "$tmp_file"
|
|
mv "$tmp_file" "$target"
|
|
fi
|
|
|
|
sed -i '/# RVM environment fix/,+3d' "$target" 2>/dev/null || true
|
|
|
|
cat <<'EOF' >> "$target"
|
|
# >>> coder gem environment >>>
|
|
export GEM_HOME="$HOME/.gem"
|
|
export GEM_PATH="$GEM_HOME"
|
|
export rvm_silence_path_mismatch_check_flag=1
|
|
export RVM_SILENCE_PATH_MISMATCH_CHECK_FLAG=1
|
|
if [ -d "$HOME/.gem/bin" ]; then
|
|
case ":$PATH:" in
|
|
*":$HOME/.gem/bin:"*) ;;
|
|
*) export PATH="$HOME/.gem/bin:$PATH";;
|
|
esac
|
|
else
|
|
mkdir -p "$HOME/.gem/bin"
|
|
case ":$PATH:" in
|
|
*":$HOME/.gem/bin:"*) ;;
|
|
*) export PATH="$HOME/.gem/bin:$PATH";;
|
|
esac
|
|
fi
|
|
# <<< coder gem environment <<<
|
|
EOF
|
|
}
|
|
|
|
current_nvm=$(printenv NVM_DIR 2>/dev/null || true)
|
|
if [ -z "$current_nvm" ]; then
|
|
NVM_DIR=/usr/local/share/nvm
|
|
else
|
|
NVM_DIR="$current_nvm"
|
|
fi
|
|
export NVM_DIR
|
|
if [ -d "$NVM_DIR" ]; then
|
|
if [ -s "$NVM_DIR/nvm.sh" ]; then
|
|
# shellcheck disable=SC1090,SC1091
|
|
. "$NVM_DIR/nvm.sh" >/dev/null 2>&1 || true
|
|
fi
|
|
shopt -s nullglob
|
|
for node_bin in "$NVM_DIR"/versions/node/*/bin; do
|
|
PATH="$node_bin:$PATH"
|
|
done
|
|
shopt -u nullglob
|
|
fi
|
|
|
|
for shell_rc in ~/.bashrc ~/.profile ~/.zprofile ~/.zshrc ~/.zshenv; do
|
|
ensure_shell_env_block "$shell_rc"
|
|
done
|
|
|
|
# Clone requested repository into /workspaces if not already present
|
|
mkdir -p /workspaces
|
|
workspace_has_content=false
|
|
if [ -n "$(find /workspaces -mindepth 1 -maxdepth 1 ! -name '.git' ! -name 'lost+found' -print -quit 2>/dev/null)" ]; then
|
|
workspace_has_content=true
|
|
fi
|
|
repo_value=$(printenv CODER_WORKSPACE_REPO 2>/dev/null || true)
|
|
repo_display="<empty>"
|
|
if [ -n "$repo_value" ]; then
|
|
repo_display="$repo_value"
|
|
fi
|
|
echo "CODER_WORKSPACE_REPO=$repo_display"
|
|
if [ -n "$repo_value" ]; then
|
|
if [ -d /workspaces/.git ]; then
|
|
echo "Git repository already present in /workspaces"
|
|
else
|
|
if [ "$workspace_has_content" = "false" ]; then
|
|
echo "Cloning $${CODER_WORKSPACE_REPO} into /workspaces"
|
|
if (cd /workspaces && git clone "$${CODER_WORKSPACE_REPO}" .); then
|
|
echo "Repository clone completed"
|
|
else
|
|
echo "Git clone failed; initializing empty repository"
|
|
if (cd /workspaces && git init .); then
|
|
echo "Initialized empty workspace directory"
|
|
else
|
|
echo "Failed to initialise repository; please check permissions"
|
|
fi
|
|
fi
|
|
else
|
|
echo "/workspaces already contains files; skipping automatic clone"
|
|
fi
|
|
fi
|
|
else
|
|
echo "No repository requested"
|
|
fi
|
|
cd "$HOME"
|
|
|
|
mkdir -p "$HOME/.config/fish/conf.d"
|
|
cat <<'EOF' > "$HOME/.config/fish/conf.d/gem_env.fish"
|
|
set -gx GEM_HOME $HOME/.gem
|
|
set -gx GEM_PATH $HOME/.gem
|
|
set -gx rvm_silence_path_mismatch_check_flag 1
|
|
set -gx RVM_SILENCE_PATH_MISMATCH_CHECK_FLAG 1
|
|
if not contains $HOME/.gem/bin $PATH
|
|
set -gx PATH $HOME/.gem/bin $PATH
|
|
end
|
|
EOF
|
|
|
|
|
|
# Materialize bundled scripts for tooling, workspace setup, and optional AI helpers
|
|
SCRIPT_ROOT="$HOME/.coder/scripts"
|
|
mkdir -p "$SCRIPT_ROOT"
|
|
|
|
install_embedded_script() {
|
|
local name="$1"
|
|
local encoded="$2"
|
|
if [ -z "$encoded" ]; then
|
|
echo "Skipping $name; embedded content missing"
|
|
return
|
|
fi
|
|
printf '%s' "$encoded" | base64 -d > "$SCRIPT_ROOT/$name"
|
|
chmod +x "$SCRIPT_ROOT/$name"
|
|
}
|
|
|
|
%{for script_name, script_content in local.workspace_agent_scripts}
|
|
install_embedded_script "${script_name}" "${base64encode(script_content)}"
|
|
%{endfor}
|
|
|
|
# Run bundled scripts after ensuring they exist on disk
|
|
run_script() {
|
|
local label="$1"
|
|
local path="$2"
|
|
if [ -x "$path" ]; then
|
|
echo "Running $label..."
|
|
"$path"
|
|
elif [ -f "$path" ]; then
|
|
echo "Running $label from $path..."
|
|
bash "$path"
|
|
else
|
|
echo "Skipping $label; script not found at $path"
|
|
fi
|
|
}
|
|
|
|
run_script "workspace setup" "$SCRIPT_ROOT/workspace-setup.sh"
|
|
run_script "developer tools check" "$SCRIPT_ROOT/dev-tools.sh"
|
|
run_script "terminal tools verifier" "$SCRIPT_ROOT/terminal-tools.sh"
|
|
run_script "git hooks configuration" "$SCRIPT_ROOT/git-hooks.sh"
|
|
|
|
if [ "${tostring(local.marimo_enabled)}" = "true" ]; then
|
|
run_script "Marimo setup" "$SCRIPT_ROOT/marimo-setup.sh"
|
|
else
|
|
echo "Marimo disabled (enable_marimo = ${tostring(local.marimo_enabled)})"
|
|
fi
|
|
|
|
if [ "${tostring(local.ai_enabled)}" = "true" ]; then
|
|
run_script "Claude CLI setup" "$SCRIPT_ROOT/claude-install.sh"
|
|
run_script "Codex CLI setup" "$SCRIPT_ROOT/codex-setup.sh"
|
|
run_script "Cursor setup" "$SCRIPT_ROOT/cursor-setup.sh"
|
|
run_script "Windsurf setup" "$SCRIPT_ROOT/windsurf-setup.sh"
|
|
else
|
|
echo "AI tooling disabled (enable_ai_tools = ${tostring(local.ai_enabled)})"
|
|
fi
|
|
|
|
echo "Services started successfully"
|
|
CODER_SETUP
|
|
|
|
echo "Agent startup completed"
|
|
EOT
|
|
|
|
metadata {
|
|
display_name = "CPU Usage"
|
|
key = "0_cpu_usage"
|
|
script = <<-EOT
|
|
# Get comprehensive CPU usage breakdown
|
|
container_cpu="n/a"
|
|
vm_cpu="n/a"
|
|
device_cores="n/a"
|
|
|
|
# Container CPU (if available)
|
|
if command -v docker >/dev/null 2>&1; then
|
|
container_cpu=$(docker stats --no-stream --format "{{.CPUPerc}}" $(hostname) 2>/dev/null || echo "n/a")
|
|
fi
|
|
|
|
# VM CPU from /proc/stat
|
|
vm_cpu=$(awk '/cpu /{u=$2+$4; t=$2+$3+$4+$5; if (NR==1){u1=u; t1=t;} else printf "%.1f%%", (u-u1) * 100 / (t-t1); }' \
|
|
<(grep 'cpu ' /proc/stat) <(sleep 1; grep 'cpu ' /proc/stat) 2>/dev/null || echo "n/a")
|
|
|
|
# Device cores
|
|
device_cores=$(nproc 2>/dev/null || echo "n/a")
|
|
|
|
# Format output on single line
|
|
if [ "$container_cpu" != "n/a" ]; then
|
|
echo "C:$container_cpu | Device:$${device_cores}c"
|
|
else
|
|
echo "VM:$vm_cpu | Device:$${device_cores}c"
|
|
fi
|
|
EOT
|
|
interval = 30
|
|
timeout = 15
|
|
}
|
|
|
|
metadata {
|
|
display_name = "Memory Usage"
|
|
key = "1_memory_usage"
|
|
script = <<-EOT
|
|
# Get comprehensive memory usage breakdown
|
|
container_mem="n/a"
|
|
vm_mem="n/a"
|
|
device_total="128GB"
|
|
device_vram="64GB"
|
|
|
|
# Container memory (if available)
|
|
if command -v docker >/dev/null 2>&1; then
|
|
container_mem=$(docker stats --no-stream --format "{{.MemUsage}}" $(hostname) 2>/dev/null || echo "n/a")
|
|
fi
|
|
|
|
# VM memory from /proc/meminfo (should show ~32GB allocated to VM)
|
|
vm_mem=$(awk '/^MemTotal:/{total=$2} /^MemAvailable:/{avail=$2} END{used=total-avail; printf "%.2f/%.2f GB", used/1024/1024, total/1024/1024}' /proc/meminfo 2>/dev/null || echo "n/a")
|
|
|
|
# Format output on single line
|
|
if [ "$container_mem" != "n/a" ]; then
|
|
echo "C:$container_mem | Device:$device_total ($device_vram VRAM)"
|
|
else
|
|
echo "VM:$vm_mem | Device:$device_total ($device_vram VRAM)"
|
|
fi
|
|
EOT
|
|
interval = 30
|
|
timeout = 15
|
|
}
|
|
|
|
metadata {
|
|
display_name = "Disk Usage"
|
|
key = "2_disk_usage"
|
|
script = <<-EOT
|
|
# Get comprehensive disk usage breakdown
|
|
workspace_disk="n/a"
|
|
home_disk="n/a"
|
|
root_disk="n/a"
|
|
|
|
# Workspace volume (/workspaces)
|
|
if [ -d "/workspaces" ]; then
|
|
workspace_disk=$(df -BG /workspaces 2>/dev/null | awk 'NR==2 {
|
|
used = $3; gsub(/G/, "", used);
|
|
total = $2; gsub(/G/, "", total);
|
|
printf "%dGB/%dGB", used, total
|
|
}' || echo "n/a")
|
|
fi
|
|
|
|
# Home volume (/home) - should be ~1TB
|
|
if [ -d "/home" ]; then
|
|
home_disk=$(df -BG /home 2>/dev/null | awk 'NR==2 {
|
|
used = $3; gsub(/G/, "", used);
|
|
total = $2; gsub(/G/, "", total);
|
|
printf "%dGB/%dTB", used, int(total/1000)
|
|
}' || echo "n/a")
|
|
fi
|
|
|
|
# Root filesystem (/) - should be ~98GB
|
|
root_disk=$(df -BG / 2>/dev/null | awk 'NR==2 {
|
|
used = $3; gsub(/G/, "", used);
|
|
total = $2; gsub(/G/, "", total);
|
|
printf "%dGB/%dGB", used, total
|
|
}' || echo "n/a")
|
|
|
|
# Format output on single line
|
|
if [ "$workspace_disk" != "n/a" ] && [ "$home_disk" != "n/a" ]; then
|
|
echo "W:$workspace_disk | H:$home_disk"
|
|
elif [ "$workspace_disk" != "n/a" ]; then
|
|
echo "W:$workspace_disk | Root:$root_disk"
|
|
elif [ "$home_disk" != "n/a" ]; then
|
|
echo "Root:$root_disk | H:$home_disk"
|
|
else
|
|
echo "Root:$root_disk"
|
|
fi
|
|
EOT
|
|
interval = 300
|
|
timeout = 15
|
|
}
|
|
|
|
metadata {
|
|
display_name = "Git Branch"
|
|
key = "3_git_branch"
|
|
script = <<-EOT
|
|
# Check for git repository dynamically
|
|
if [ -n "$CODER_WORKSPACE_REPO" ] && [ "$CODER_WORKSPACE_REPO" != "" ]; then
|
|
# Extract repo name from URL and look for it
|
|
repo_name=$(basename "$CODER_WORKSPACE_REPO" .git)
|
|
if [ -d "/workspaces/$repo_name/.git" ]; then
|
|
cd "/workspaces/$repo_name" && git branch --show-current 2>/dev/null || echo 'detached'
|
|
elif [ -d "/workspaces/.git" ]; then
|
|
cd "/workspaces" && git branch --show-current 2>/dev/null || echo 'detached'
|
|
else
|
|
echo 'no-repo'
|
|
fi
|
|
else
|
|
# Fallback to checking workspace root
|
|
cd /workspaces && git branch --show-current 2>/dev/null || echo 'no-repo'
|
|
fi
|
|
EOT
|
|
interval = 300
|
|
timeout = 5
|
|
}
|
|
|
|
metadata {
|
|
display_name = "PostgreSQL"
|
|
key = "4_postgres"
|
|
script = local.services_enabled ? format("pg_isready -h postgres-%s -p 5432 -U postgres >/dev/null && echo healthy || echo down", local.workspace_id) : "echo 'disabled'"
|
|
interval = 60
|
|
timeout = 5
|
|
}
|
|
|
|
metadata {
|
|
display_name = "Redis"
|
|
key = "5_redis"
|
|
script = local.services_enabled ? format("redis-cli -h redis-%s -a %s ping 2>/dev/null | grep -qi pong && echo healthy || echo down", local.workspace_id, var.redis_password) : "echo 'disabled'"
|
|
interval = 60
|
|
timeout = 5
|
|
}
|
|
|
|
metadata {
|
|
display_name = "Qdrant"
|
|
key = "6_qdrant"
|
|
script = <<-EOT
|
|
if [ "${tostring(local.services_enabled)}" = "true" ]; then
|
|
target="qdrant-${local.workspace_id}"
|
|
if command -v curl >/dev/null 2>&1; then
|
|
status=$(curl -s -o /dev/null -w "%%{http_code}" --connect-timeout 3 "http://$target:6333/healthz")
|
|
if [ "$status" = "200" ]; then echo healthy; else echo "unhealthy($status)"; fi
|
|
elif command -v wget >/dev/null 2>&1; then
|
|
if wget -qO- --timeout=3 "http://$target:6333/healthz" >/dev/null; then echo healthy; else echo down; fi
|
|
else
|
|
echo unknown
|
|
fi
|
|
else
|
|
echo 'disabled'
|
|
fi
|
|
EOT
|
|
interval = 60
|
|
timeout = 5
|
|
}
|
|
}
|
|
|
|
resource "docker_container" "workspace" {
|
|
count = data.coder_workspace.me.start_count
|
|
image = docker_image.devcontainer.image_id
|
|
name = local.container_name
|
|
hostname = data.coder_workspace.me.name
|
|
|
|
memory = var.workspace_memory_limit > 0 ? var.workspace_memory_limit * 1024 * 1024 : null
|
|
|
|
# Enable privileged mode for full system access
|
|
privileged = true
|
|
|
|
# Run as root to avoid permission issues
|
|
user = "root"
|
|
|
|
env = compact([
|
|
"GIT_AUTHOR_NAME=${local.git_author_name}",
|
|
"GIT_AUTHOR_EMAIL=${local.git_author_email}",
|
|
"CODER_AGENT_TOKEN=${coder_agent.main.token}",
|
|
"CODER_AGENT_DEVCONTAINERS_ENABLE=true",
|
|
local.project_repo_url != "" ? "CODER_WORKSPACE_REPO=${local.project_repo_url}" : "",
|
|
"HOME=/home/coder",
|
|
"USER=coder",
|
|
])
|
|
|
|
networks_advanced {
|
|
name = docker_network.workspace.name
|
|
}
|
|
|
|
host {
|
|
host = "host.docker.internal"
|
|
ip = "host-gateway"
|
|
}
|
|
|
|
volumes {
|
|
container_path = "/workspaces"
|
|
volume_name = docker_volume.workspaces.name
|
|
}
|
|
|
|
|
|
dynamic "volumes" {
|
|
for_each = local.bind_mounts
|
|
content {
|
|
host_path = volumes.value.host
|
|
container_path = volumes.value.container
|
|
read_only = try(volumes.value.read_only, false)
|
|
}
|
|
}
|
|
|
|
dynamic "volumes" {
|
|
for_each = var.enable_docker_in_docker ? [1] : []
|
|
content {
|
|
host_path = "/var/run/docker.sock"
|
|
container_path = "/var/run/docker.sock"
|
|
}
|
|
}
|
|
|
|
working_dir = "/workspaces"
|
|
command = ["/bin/bash", "-c", <<-EOT
|
|
# Run init script as root to handle permissions
|
|
${coder_agent.main.init_script}
|
|
|
|
# Switch to coder user for ongoing operations
|
|
echo "Switching to coder user for services..."
|
|
exec sudo -u coder -i bash -c 'cd /workspaces && sleep infinity'
|
|
EOT
|
|
]
|
|
|
|
labels {
|
|
label = "coder.owner"
|
|
value = data.coder_workspace_owner.me.name
|
|
}
|
|
|
|
labels {
|
|
label = "coder.workspace_id"
|
|
value = local.workspace_id
|
|
}
|
|
|
|
depends_on = [
|
|
docker_network.workspace,
|
|
docker_volume.workspaces,
|
|
docker_image.devcontainer
|
|
]
|
|
}
|
|
|
|
module "code_server" {
|
|
count = data.coder_workspace.me.start_count
|
|
source = "registry.coder.com/coder/code-server/coder"
|
|
version = "1.3.1"
|
|
agent_id = coder_agent.main.id
|
|
install_prefix = "/opt/code-server"
|
|
offline = true
|
|
port = 13337
|
|
folder = "/workspaces"
|
|
display_name = "code-server"
|
|
group = "Web IDEs"
|
|
order = 10
|
|
}
|
|
|
|
module "cursor_desktop" {
|
|
count = data.coder_workspace.me.start_count > 0 && data.coder_parameter.enable_ai_tools.value ? 1 : 0
|
|
source = "registry.coder.com/coder/cursor/coder"
|
|
agent_id = coder_agent.main.id
|
|
folder = "/workspaces"
|
|
group = "Desktop IDEs"
|
|
order = 40
|
|
}
|
|
|
|
module "windsurf_desktop" {
|
|
count = data.coder_workspace.me.start_count > 0 && data.coder_parameter.enable_ai_tools.value ? 1 : 0
|
|
source = "registry.coder.com/coder/windsurf/coder"
|
|
agent_id = coder_agent.main.id
|
|
folder = "/workspaces"
|
|
group = "Desktop IDEs"
|
|
order = 50
|
|
}
|
|
|
|
module "pycharm_desktop" {
|
|
count = data.coder_workspace.me.start_count > 0 && data.coder_parameter.enable_jetbrains.value ? 1 : 0
|
|
source = "registry.coder.com/coder/jetbrains-gateway/coder"
|
|
agent_id = coder_agent.main.id
|
|
folder = "/workspaces"
|
|
jetbrains_ides = ["PY"]
|
|
default = "PY"
|
|
group = "Desktop IDEs"
|
|
order = 60
|
|
coder_parameter_order = 6
|
|
slug = "pycharm-gateway"
|
|
}
|
|
|
|
# module "claude_code" {
|
|
# count = data.coder_workspace.me.start_count > 0 && data.coder_parameter.enable_ai_tools.value ? 1 : 0
|
|
# source = "registry.coder.com/coder/claude-code/coder"
|
|
# agent_id = coder_agent.main.id
|
|
# workdir = "/workspaces"
|
|
# group = "AI Tools"
|
|
# order = 30
|
|
# install_claude_code = false
|
|
# install_agentapi = true
|
|
# pre_install_script = <<-EOT
|
|
# #!/usr/bin/env bash
|
|
# set -euo pipefail
|
|
#
|
|
# # We're running as root, so set up the coder user properly
|
|
# target_user="coder"
|
|
# target_home="/home/coder"
|
|
#
|
|
# # Ensure coder user exists
|
|
# if ! id "$target_user" >/dev/null 2>&1; then
|
|
# useradd -m -s /bin/bash "$target_user" || true
|
|
# fi
|
|
#
|
|
# # Create home directory if it doesn't exist
|
|
# mkdir -p "$target_home"
|
|
# chown "$target_user:$target_user" "$target_home"
|
|
#
|
|
# # Setup passwordless sudo for coder user
|
|
# echo "$target_user ALL=(ALL) NOPASSWD:ALL" > "/etc/sudoers.d/$target_user"
|
|
# chmod 440 "/etc/sudoers.d/$target_user"
|
|
# echo "Passwordless sudo configured for $target_user"
|
|
#
|
|
# # Ensure proper ownership of important directories
|
|
# mkdir -p "$target_home/.local/bin"
|
|
# chown -R "$target_user:$target_user" "$target_home/.local"
|
|
#
|
|
# real_curl="$(command -v curl || true)"
|
|
# wrapper=/usr/local/bin/curl
|
|
# if [ -n "$real_curl" ]; then
|
|
# if ! curl --help 2>/dev/null | grep -q -- "--retry-all-errors"; then
|
|
# real_curl="$(readlink -f "$real_curl" 2>/dev/null || echo "$real_curl")"
|
|
# if [ "$real_curl" != "$wrapper" ]; then
|
|
# python3 - <<'PY' "$real_curl" "$wrapper"
|
|
# import os, sys, stat
|
|
# real=sys.argv[1]; wrapper=sys.argv[2]
|
|
# code=f"""#!/usr/bin/env python3
|
|
# import os, sys
|
|
# real={real!r}
|
|
# args=[a for a in sys.argv[1:] if a != "--retry-all-errors"]
|
|
# os.execv(real,[real]+args)
|
|
# """
|
|
# with open(wrapper,'w', encoding='utf-8') as f:
|
|
# f.write(code)
|
|
# os.chmod(wrapper, 0o755)
|
|
# PY
|
|
# fi
|
|
# fi
|
|
# fi
|
|
#
|
|
# agentapi_version="v0.7.1"
|
|
# bin_dir="$HOME/.local/bin"
|
|
# mkdir -p "$bin_dir"
|
|
#
|
|
# # Ensure agentapi scripts directory exists
|
|
# agentapi_scripts_dir="$HOME/code-tools/tf/scripts/agentapi"
|
|
# if [ ! -d "$agentapi_scripts_dir" ]; then
|
|
# echo "Creating agentapi scripts directory: $agentapi_scripts_dir"
|
|
# mkdir -p "$agentapi_scripts_dir"
|
|
# # Create minimal placeholder scripts if they don't exist
|
|
# if [ ! -f "$agentapi_scripts_dir/agentapi-start.sh" ]; then
|
|
# cat > "$agentapi_scripts_dir/agentapi-start.sh" << 'SCRIPT_EOF'
|
|
# #!/bin/bash
|
|
# echo "AgentAPI start script placeholder"
|
|
# SCRIPT_EOF
|
|
# chmod +x "$agentapi_scripts_dir/agentapi-start.sh"
|
|
# fi
|
|
# fi
|
|
#
|
|
# ensure_agentapi() {
|
|
# if command -v agentapi >/dev/null 2>&1; then
|
|
# return 0
|
|
# fi
|
|
#
|
|
# arch=$(uname -m)
|
|
# case "$arch" in
|
|
# x86_64|amd64)
|
|
# asset="agentapi-linux-amd64"
|
|
# ;;
|
|
# aarch64|arm64)
|
|
# asset="agentapi-linux-arm64"
|
|
# ;;
|
|
# *)
|
|
# echo "warning: unsupported architecture $arch; skipping agentapi bootstrap" >&2
|
|
# return 1
|
|
# ;;
|
|
# esac
|
|
#
|
|
# if [ "$agentapi_version" = "latest" ]; then
|
|
# url="https://github.com/coder/agentapi/releases/latest/download/$${asset}"
|
|
# else
|
|
# url="https://github.com/coder/agentapi/releases/download/$${agentapi_version}/$${asset}"
|
|
# fi
|
|
#
|
|
# tmp_file=$(mktemp)
|
|
# if ! curl -fsSL "$url" -o "$tmp_file"; then
|
|
# echo "warning: failed to download agentapi from $url" >&2
|
|
# rm -f "$tmp_file"
|
|
# return 1
|
|
# fi
|
|
#
|
|
# if command -v install >/dev/null 2>&1; then
|
|
# install -m 0755 "$tmp_file" "$bin_dir/agentapi"
|
|
# else
|
|
# mv "$tmp_file" "$bin_dir/agentapi"
|
|
# chmod 0755 "$bin_dir/agentapi"
|
|
# fi
|
|
# rm -f "$tmp_file"
|
|
# echo "Installed agentapi CLI into $bin_dir/agentapi"
|
|
# }
|
|
#
|
|
# ensure_agentapi
|
|
#
|
|
# if ! command -v claude >/dev/null 2>&1; then
|
|
# echo "warning: claude CLI not found; expected pre-installed in base image." >&2
|
|
# fi
|
|
#
|
|
# case ":$PATH:" in
|
|
# *:"$bin_dir":*) ;;
|
|
# *) echo "PATH does not include $bin_dir; adding for current session." >&2; export PATH="$bin_dir:$PATH" ;;
|
|
# esac
|
|
# EOT
|
|
# }
|