Compare commits

..

5 Commits

Author SHA1 Message Date
DevCats e5ccf74ccc feat: claude-code workspace persistence (#154)
## Description

Add Tmux Plugin Manager with resurrect and continuum plugins. Add
functionality to be able to enable workspace persistence to save the
tmux session automatically so that it can persist through workspace
restarts.

---

## Type of Change

- [ ] New module
- [ ] Bug fix
- [X] Feature/enhancement
- [ ] Documentation
- [ ] Other

---

## Module Information

**Path:** `registry/coder/modules/claude-code`  
**New version:** `v1.4.0`  
**Breaking change:** [ ] Yes [X] No

---

## Testing & Validation

- [X] Tests pass (`bun test`)
- [X] Code formatted (`bun run fmt`)
- [X] Changes tested locally

---

## Related Issues

Closes [#29](https://github.com/coder/registry/issues/29)
2025-06-19 16:46:18 -05:00
DevCats a47ff911e1 fix: Version-Bump Workflow/Script - Formatting before diff (#155)
## Description

Set up Pre-Req's, and ensure that formatting is done before checking
diff since it likes to not respect prettier formatting.

---

## Type of Change

- [ ] New module
- [X] Bug fix
- [ ] Feature/enhancement
- [ ] Documentation
- [ ] Other

---

## Testing & Validation

- [X] Tests pass (`bun test`)
- [X] Code formatted (`bun run fmt`)
- [ ] Changes tested locally

---------

Co-authored-by: Atif Ali <atif@coder.com>
2025-06-19 16:35:10 -05:00
Ben Potter a8e23647c5 feat: add option to disable VS Code Web workspace trust protection (#131)
for admins with certainty about what is installed in the environment,
this is ideal. otherwise, it's best to get user trust

---------

Co-authored-by: DevelopmentCats <christofer@coder.com>
Co-authored-by: Atif Ali <atif@coder.com>
2025-06-16 21:24:51 -05:00
DevCats 960ec18d35 fix: clean up version-bump workflow script output handling (#153)
## Description

Removed unnecessary comments and added commands to reset the working
directory and clean untracked files in the version-bump workflow. This
improves the script's reliability by ensuring a clean state after
executing version checks.

---

## Type of Change

- [ ] New module
- [X] Bug fix
- [ ] Feature/enhancement
- [ ] Documentation
- [ ] Other

---

## Related Issues

None
2025-06-16 19:52:56 -05:00
DevCats eae64160bd fix: update GitHub Actions permissions in version-bump workflow (#152)
## Description

update GitHub Actions permissions in version-bump workflow by adding
issues permission for commenting on PR's

---

## Type of Change

- [ ] New module
- [X] Bug fix
- [ ] Feature/enhancement
- [ ] Documentation
- [ ] Other

---

## Related Issues

None
2025-06-16 14:32:09 -05:00
9 changed files with 204 additions and 126 deletions
+9
View File
@@ -190,6 +190,15 @@ main() {
done <<< "$modules"
# Always run formatter to ensure consistent formatting
echo "🔧 Running formatter to ensure consistent formatting..."
if command -v bun >/dev/null 2>&1; then
bun fmt >/dev/null 2>&1 || echo "⚠️ Warning: bun fmt failed, but continuing..."
else
echo "⚠️ Warning: bun not found, skipping formatting"
fi
echo ""
echo "📋 Summary:"
echo "Bump Type: $bump_type"
echo ""
+17 -4
View File
@@ -17,6 +17,7 @@ jobs:
permissions:
contents: read
pull-requests: write
issues: write
steps:
- name: Checkout code
uses: actions/checkout@v4
@@ -24,6 +25,17 @@ jobs:
fetch-depth: 0
token: ${{ secrets.GITHUB_TOKEN }}
- name: Set up Bun
uses: oven-sh/setup-bun@v2
with:
bun-version: latest
- name: Set up Terraform
uses: coder/coder/.github/actions/setup-tf@main
- name: Install dependencies
run: bun install
- name: Extract bump type from label
id: bump-type
run: |
@@ -46,7 +58,6 @@ jobs:
- name: Check version bump requirements
id: version-check
run: |
# Run the script to check what versions should be
output_file=$(mktemp)
if ./.github/scripts/version-bump.sh "${{ steps.bump-type.outputs.type }}" origin/main > "$output_file" 2>&1; then
echo "Script completed successfully"
@@ -56,17 +67,14 @@ jobs:
exit 1
fi
# Store output for PR comment
{
echo "output<<EOF"
cat "$output_file"
echo "EOF"
} >> $GITHUB_OUTPUT
# Show output
cat "$output_file"
# Check if any files would be modified by the script
if git diff --quiet; then
echo "versions_up_to_date=true" >> $GITHUB_OUTPUT
echo "✅ All module versions are already up to date"
@@ -78,6 +86,10 @@ jobs:
echo ""
echo "Diff preview:"
git diff
git checkout .
git clean -fd
exit 1
fi
@@ -85,6 +97,7 @@ jobs:
if: failure() && steps.version-check.outputs.versions_up_to_date == 'false'
uses: actions/github-script@v7
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
script: |
const output = `${{ steps.version-check.outputs.output }}`;
const bumpType = `${{ steps.bump-type.outputs.type }}`;
+26 -3
View File
@@ -14,7 +14,7 @@ Run the [Claude Code](https://docs.anthropic.com/en/docs/agents-and-tools/claude
```tf
module "claude-code" {
source = "registry.coder.com/coder/claude-code/coder"
version = "1.3.1"
version = "1.4.0"
agent_id = coder_agent.example.id
folder = "/home/coder"
install_claude_code = true
@@ -88,7 +88,7 @@ resource "coder_agent" "main" {
module "claude-code" {
count = data.coder_workspace.me.start_count
source = "registry.coder.com/coder/claude-code/coder"
version = "1.3.1"
version = "1.4.0"
agent_id = coder_agent.example.id
folder = "/home/coder"
install_claude_code = true
@@ -100,6 +100,29 @@ module "claude-code" {
}
```
## Session Persistence (Experimental)
Enable automatic session persistence to maintain Claude Code sessions across workspace restarts:
```tf
module "claude-code" {
count = data.coder_workspace.me.start_count
source = "registry.coder.com/coder/claude-code/coder"
version = "1.4.0"
agent_id = coder_agent.example.id
folder = "/home/coder"
install_claude_code = true
# Enable tmux with session persistence
experiment_use_tmux = true
experiment_tmux_session_persistence = true
experiment_tmux_session_save_interval = "10" # Save every 10 minutes
experiment_report_tasks = true
}
```
Session persistence automatically saves and restores your Claude Code environment, including working directory and command history.
## Run standalone
Run Claude Code as a standalone app in your workspace. This will install Claude Code and run it directly without using screen or any task reporting to the Coder UI.
@@ -107,7 +130,7 @@ Run Claude Code as a standalone app in your workspace. This will install Claude
```tf
module "claude-code" {
source = "registry.coder.com/coder/claude-code/coder"
version = "1.3.1"
version = "1.4.0"
agent_id = coder_agent.example.id
folder = "/home/coder"
install_claude_code = true
+124 -25
View File
@@ -84,6 +84,18 @@ variable "experiment_post_install_script" {
default = null
}
variable "experiment_tmux_session_persistence" {
type = bool
description = "Whether to enable tmux session persistence across workspace restarts."
default = false
}
variable "experiment_tmux_session_save_interval" {
type = string
description = "How often to save tmux sessions in minutes."
default = "15"
}
locals {
encoded_pre_install_script = var.experiment_pre_install_script != null ? base64encode(var.experiment_pre_install_script) : ""
encoded_post_install_script = var.experiment_post_install_script != null ? base64encode(var.experiment_post_install_script) : ""
@@ -98,12 +110,28 @@ resource "coder_script" "claude_code" {
#!/bin/bash
set -e
# Function to check if a command exists
command_exists() {
command -v "$1" >/dev/null 2>&1
}
# Check if the specified folder exists
install_tmux() {
echo "Installing tmux..."
if command_exists apt-get; then
sudo apt-get update && sudo apt-get install -y tmux
elif command_exists yum; then
sudo yum install -y tmux
elif command_exists dnf; then
sudo dnf install -y tmux
elif command_exists pacman; then
sudo pacman -S --noconfirm tmux
elif command_exists apk; then
sudo apk add tmux
else
echo "Error: Unable to install tmux automatically. Package manager not recognized."
exit 1
fi
}
if [ ! -d "${var.folder}" ]; then
echo "Warning: The specified folder '${var.folder}' does not exist."
echo "Creating the folder..."
@@ -112,8 +140,6 @@ resource "coder_script" "claude_code" {
mkdir -p "${var.folder}"
echo "Folder created successfully."
fi
# Run pre-install script if provided
if [ -n "${local.encoded_pre_install_script}" ]; then
echo "Running pre-install script..."
echo "${local.encoded_pre_install_script}" | base64 -d > /tmp/pre_install.sh
@@ -121,11 +147,30 @@ resource "coder_script" "claude_code" {
/tmp/pre_install.sh
fi
# Install Claude Code if enabled
if [ "${var.install_claude_code}" = "true" ]; then
if ! command_exists npm; then
echo "Error: npm is not installed. Please install Node.js and npm first."
exit 1
echo "npm not found, checking for Node.js installation..."
if ! command_exists node; then
echo "Node.js not found, installing Node.js via NVM..."
export NVM_DIR="$HOME/.nvm"
if [ ! -d "$NVM_DIR" ]; then
mkdir -p "$NVM_DIR"
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.7/install.sh | bash
[ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh"
else
[ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh"
fi
nvm install --lts
nvm use --lts
nvm alias default node
echo "Node.js installed: $(node --version)"
echo "npm installed: $(npm --version)"
else
echo "Node.js is installed but npm is not available. Please install npm manually."
exit 1
fi
fi
echo "Installing Claude Code..."
npm install -g @anthropic-ai/claude-code@${var.claude_code_version}
@@ -136,7 +181,6 @@ resource "coder_script" "claude_code" {
coder exp mcp configure claude-code ${var.folder}
fi
# Run post-install script if provided
if [ -n "${local.encoded_post_install_script}" ]; then
echo "Running post-install script..."
echo "${local.encoded_post_install_script}" | base64 -d > /tmp/post_install.sh
@@ -144,46 +188,97 @@ resource "coder_script" "claude_code" {
/tmp/post_install.sh
fi
# Handle terminal multiplexer selection (tmux or screen)
if [ "${var.experiment_use_tmux}" = "true" ] && [ "${var.experiment_use_screen}" = "true" ]; then
echo "Error: Both experiment_use_tmux and experiment_use_screen cannot be true simultaneously."
echo "Please set only one of them to true."
exit 1
fi
# Run with tmux if enabled
if [ "${var.experiment_use_tmux}" = "true" ]; then
echo "Running Claude Code in the background with tmux..."
if [ "${var.experiment_tmux_session_persistence}" = "true" ] && [ "${var.experiment_use_tmux}" != "true" ]; then
echo "Error: Session persistence requires tmux to be enabled."
echo "Please set experiment_use_tmux = true when using session persistence."
exit 1
fi
# Check if tmux is installed
if [ "${var.experiment_use_tmux}" = "true" ]; then
if ! command_exists tmux; then
echo "Error: tmux is not installed. Please install tmux manually."
exit 1
install_tmux
fi
touch "$HOME/.claude-code.log"
if [ "${var.experiment_tmux_session_persistence}" = "true" ]; then
echo "Setting up tmux session persistence..."
if ! command_exists git; then
echo "Git not found, installing git..."
if command_exists apt-get; then
sudo apt-get update && sudo apt-get install -y git
elif command_exists yum; then
sudo yum install -y git
elif command_exists dnf; then
sudo dnf install -y git
elif command_exists pacman; then
sudo pacman -S --noconfirm git
elif command_exists apk; then
sudo apk add git
else
echo "Error: Unable to install git automatically. Package manager not recognized."
echo "Please install git manually to enable session persistence."
exit 1
fi
fi
mkdir -p ~/.tmux/plugins
if [ ! -d ~/.tmux/plugins/tpm ]; then
git clone https://github.com/tmux-plugins/tpm ~/.tmux/plugins/tpm
fi
cat > ~/.tmux.conf << EOF
# Claude Code tmux persistence configuration
set -g @plugin 'tmux-plugins/tmux-resurrect'
set -g @plugin 'tmux-plugins/tmux-continuum'
# Configure session persistence
set -g @resurrect-processes ':all:'
set -g @resurrect-capture-pane-contents 'on'
set -g @resurrect-save-bash-history 'on'
set -g @continuum-restore 'on'
set -g @continuum-save-interval '${var.experiment_tmux_session_save_interval}'
set -g @continuum-boot 'on'
set -g @continuum-save-on 'on'
# Initialize plugin manager
run '~/.tmux/plugins/tpm/tpm'
EOF
~/.tmux/plugins/tpm/scripts/install_plugins.sh
fi
echo "Running Claude Code in the background with tmux..."
touch "$HOME/.claude-code.log"
export LANG=en_US.UTF-8
export LC_ALL=en_US.UTF-8
# Create a new tmux session in detached mode
tmux new-session -d -s claude-code -c ${var.folder} "claude --dangerously-skip-permissions \"$CODER_MCP_CLAUDE_TASK_PROMPT\""
if [ "${var.experiment_tmux_session_persistence}" = "true" ]; then
sleep 3
if ! tmux has-session -t claude-code 2>/dev/null; then
# Only create a new session if one doesn't exist
tmux new-session -d -s claude-code -c ${var.folder} "claude --dangerously-skip-permissions \"$CODER_MCP_CLAUDE_TASK_PROMPT\""
fi
else
if ! tmux has-session -t claude-code 2>/dev/null; then
tmux new-session -d -s claude-code -c ${var.folder} "claude --dangerously-skip-permissions \"$CODER_MCP_CLAUDE_TASK_PROMPT\""
fi
fi
fi
# Run with screen if enabled
if [ "${var.experiment_use_screen}" = "true" ]; then
echo "Running Claude Code in the background..."
# Check if screen is installed
if ! command_exists screen; then
echo "Error: screen is not installed. Please install screen manually."
exit 1
fi
touch "$HOME/.claude-code.log"
# Ensure the screenrc exists
if [ ! -f "$HOME/.screenrc" ]; then
echo "Creating ~/.screenrc and adding multiuser settings..." | tee -a "$HOME/.claude-code.log"
echo -e "multiuser on\nacladd $(whoami)" > "$HOME/.screenrc"
@@ -198,6 +293,7 @@ resource "coder_script" "claude_code" {
echo "Adding 'acladd $(whoami)' to ~/.screenrc..." | tee -a "$HOME/.claude-code.log"
echo "acladd $(whoami)" >> "$HOME/.screenrc"
fi
export LANG=en_US.UTF-8
export LC_ALL=en_US.UTF-8
@@ -207,7 +303,6 @@ resource "coder_script" "claude_code" {
exec bash
'
else
# Check if claude is installed before running
if ! command_exists claude; then
echo "Error: Claude Code is not installed. Please enable install_claude_code or install it manually."
exit 1
@@ -231,6 +326,10 @@ resource "coder_app" "claude_code" {
if [ "${var.experiment_use_tmux}" = "true" ]; then
if tmux has-session -t claude-code 2>/dev/null; then
echo "Attaching to existing Claude Code tmux session." | tee -a "$HOME/.claude-code.log"
# If Claude isn't running in the session, start it without the prompt
if ! tmux list-panes -t claude-code -F '#{pane_current_command}' | grep -q "claude"; then
tmux send-keys -t claude-code "cd ${var.folder} && claude -c --dangerously-skip-permissions" C-m
fi
tmux attach-session -t claude-code
else
echo "Starting a new Claude Code tmux session." | tee -a "$HOME/.claude-code.log"
-24
View File
@@ -54,29 +54,6 @@ variable "subdomain" {
description = "Is subdomain sharing enabled in your cluster?"
}
variable "kasm_config" {
type = string
default = ""
description = <<-EOT
Additional KasmVNC configuration in YAML format. Can be used to set DLP policies and other advanced settings.
Example for DLP policies (according to KasmVNC documentation):
```yaml
data_loss_prevention:
clipboard:
server_to_client:
enabled: false
client_to_server:
enabled: false
printing: false
download: false
```
For more advanced configuration options, see the KasmVNC documentation:
https://kasmweb.com/docs/latest/how_to/kasmvnc_dlp_policies.html
EOT
}
resource "coder_script" "kasm_vnc" {
agent_id = var.agent_id
display_name = "KasmVNC"
@@ -88,7 +65,6 @@ resource "coder_script" "kasm_vnc" {
KASM_VERSION = var.kasm_version
SUBDOMAIN = tostring(var.subdomain)
PATH_VNC_HTML = var.subdomain ? "" : file("${path.module}/path_vnc.html")
KASM_CONFIG = var.kasm_config
})
}
+8 -63
View File
@@ -193,35 +193,19 @@ else
SUDO=""
echo "WARNING: Sudo access not available, using user config dir!"
# Always ensure the directory exists
mkdir -p "$HOME/.vnc"
# We'll handle existing configs differently - we'll merge instead of skipping
if [[ -f "$kasm_config_file" ]]; then
echo "INFO: Custom user KasmVNC config exists, will merge with new settings."
# Create a backup of the existing config
cp "$kasm_config_file" "$${kasm_config_file}.bak"
echo "WARNING: Custom user KasmVNC config exists, not overwriting!"
echo "WARNING: Ensure that you manually configure the appropriate settings."
kasm_config_file="/dev/stderr"
else
echo "WARNING: This may prevent custom user KasmVNC settings from applying!"
mkdir -p "$HOME/.vnc"
fi
fi
echo "Writing KasmVNC config to $kasm_config_file"
# Create a temporary file for our config
TEMP_CONFIG_FILE=$(mktemp)
# Check if existing config file exists and preserve its content
if [[ -f "$kasm_config_file" ]]; then
echo "Preserving existing KasmVNC configuration settings."
cp "$kasm_config_file" "$TEMP_CONFIG_FILE"
# Update only the network section
if grep -q "^network:" "$TEMP_CONFIG_FILE"; then
# Network section exists, update only the websocket_port
sed -i "s/\([ \t]*websocket_port:\).*/\1 ${PORT}/" "$TEMP_CONFIG_FILE"
else
# Network section doesn't exist, add it
cat >> "$TEMP_CONFIG_FILE" << EOF
$SUDO tee "$kasm_config_file" > /dev/null << EOF
network:
protocol: http
interface: 127.0.0.1
@@ -233,45 +217,6 @@ network:
udp:
public_ip: 127.0.0.1
EOF
fi
else
# Start with base network configuration for new config
cat > "$TEMP_CONFIG_FILE" << EOF
network:
protocol: http
interface: 127.0.0.1
websocket_port: ${PORT}
ssl:
require_ssl: false
pem_certificate:
pem_key:
udp:
public_ip: 127.0.0.1
EOF
fi
# Add additional KasmVNC configuration if provided
if [[ -n "${KASM_CONFIG}" ]]; then
echo "Adding custom KasmVNC configuration."
# Add a comment to mark the start of custom config
echo "" >> "$TEMP_CONFIG_FILE"
echo "# ---- START CUSTOM KASMVNC CONFIG ----" >> "$TEMP_CONFIG_FILE"
echo "" >> "$TEMP_CONFIG_FILE"
# Directly append the YAML configuration
echo "${KASM_CONFIG}" >> "$TEMP_CONFIG_FILE"
# Add a comment to mark the end of custom config
echo "" >> "$TEMP_CONFIG_FILE"
echo "# ---- END CUSTOM KASMVNC CONFIG ----" >> "$TEMP_CONFIG_FILE"
fi
# Apply the configuration
$SUDO cp "$TEMP_CONFIG_FILE" "$kasm_config_file"
# Clean up
rm "$TEMP_CONFIG_FILE"
# This password is not used since we start the server without auth.
# The server is protected via the Coder session token / tunnel
+5 -5
View File
@@ -15,7 +15,7 @@ Automatically install [Visual Studio Code Server](https://code.visualstudio.com/
module "vscode-web" {
count = data.coder_workspace.me.start_count
source = "registry.coder.com/coder/vscode-web/coder"
version = "1.2.0"
version = "1.3.0"
agent_id = coder_agent.example.id
accept_license = true
}
@@ -31,7 +31,7 @@ module "vscode-web" {
module "vscode-web" {
count = data.coder_workspace.me.start_count
source = "registry.coder.com/coder/vscode-web/coder"
version = "1.2.0"
version = "1.3.0"
agent_id = coder_agent.example.id
install_prefix = "/home/coder/.vscode-web"
folder = "/home/coder"
@@ -45,7 +45,7 @@ module "vscode-web" {
module "vscode-web" {
count = data.coder_workspace.me.start_count
source = "registry.coder.com/coder/vscode-web/coder"
version = "1.2.0"
version = "1.3.0"
agent_id = coder_agent.example.id
extensions = ["github.copilot", "ms-python.python", "ms-toolsai.jupyter"]
accept_license = true
@@ -60,7 +60,7 @@ Configure VS Code's [settings.json](https://code.visualstudio.com/docs/getstarte
module "vscode-web" {
count = data.coder_workspace.me.start_count
source = "registry.coder.com/coder/vscode-web/coder"
version = "1.2.0"
version = "1.3.0"
agent_id = coder_agent.example.id
extensions = ["dracula-theme.theme-dracula"]
settings = {
@@ -78,7 +78,7 @@ By default, this module installs the latest. To pin a specific version, retrieve
module "vscode-web" {
count = data.coder_workspace.me.start_count
source = "registry.coder.com/coder/vscode-web/coder"
version = "1.2.0"
version = "1.3.0"
agent_id = coder_agent.example.id
commit_id = "e54c774e0add60467559eb0d1e229c6452cf8447"
accept_license = true
@@ -121,6 +121,12 @@ variable "use_cached" {
default = false
}
variable "disable_trust" {
type = bool
description = "Disables workspace trust protection for VS Code Web."
default = false
}
variable "extensions_dir" {
type = string
description = "Override the directory to store extensions in."
@@ -169,6 +175,7 @@ resource "coder_script" "vscode-web" {
SETTINGS : replace(jsonencode(var.settings), "\"", "\\\""),
OFFLINE : var.offline,
USE_CACHED : var.use_cached,
DISABLE_TRUST : var.disable_trust,
EXTENSIONS_DIR : var.extensions_dir,
FOLDER : var.folder,
AUTO_INSTALL_EXTENSIONS : var.auto_install_extensions,
+8 -2
View File
@@ -16,10 +16,16 @@ if [ -n "${SERVER_BASE_PATH}" ]; then
SERVER_BASE_PATH_ARG="--server-base-path=${SERVER_BASE_PATH}"
fi
# Set disable workspace trust
DISABLE_TRUST_ARG=""
if [ "${DISABLE_TRUST}" = true ]; then
DISABLE_TRUST_ARG="--disable-workspace-trust"
fi
run_vscode_web() {
echo "👷 Running $VSCODE_WEB serve-local $EXTENSION_ARG $SERVER_BASE_PATH_ARG --port ${PORT} --host 127.0.0.1 --accept-server-license-terms --without-connection-token --telemetry-level ${TELEMETRY_LEVEL} in the background..."
echo "👷 Running $VSCODE_WEB serve-local $EXTENSION_ARG $SERVER_BASE_PATH_ARG $DISABLE_TRUST_ARG --port ${PORT} --host 127.0.0.1 --accept-server-license-terms --without-connection-token --telemetry-level ${TELEMETRY_LEVEL} in the background..."
echo "Check logs at ${LOG_PATH}!"
"$VSCODE_WEB" serve-local "$EXTENSION_ARG" "$SERVER_BASE_PATH_ARG" --port "${PORT}" --host 127.0.0.1 --accept-server-license-terms --without-connection-token --telemetry-level "${TELEMETRY_LEVEL}" > "${LOG_PATH}" 2>&1 &
"$VSCODE_WEB" serve-local "$EXTENSION_ARG" "$SERVER_BASE_PATH_ARG" "$DISABLE_TRUST_ARG" --port "${PORT}" --host 127.0.0.1 --accept-server-license-terms --without-connection-token --telemetry-level "${TELEMETRY_LEVEL}" > "${LOG_PATH}" 2>&1 &
}
# Check if the settings file exists...