From ad148eca60bdc3d454f0178bae66ff227d3fb01a Mon Sep 17 00:00:00 2001 From: Andras Bacsai <5845193+andrasbacsai@users.noreply.github.com> Date: Fri, 14 Nov 2025 13:04:19 +0100 Subject: [PATCH 1/5] Add automatic shared dependencies for worktrees MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Setup Conductor to automatically share node_modules and vendor directories across all git worktrees to save disk space and speed up development. Changes: - Updated conductor-setup.sh to create symlinks to shared dependencies - Added documentation to CLAUDE.md explaining the system - Dependencies now stored in .shared-deps/ in main repository - All worktrees use the same dependency versions automatically Benefits: - Saves hundreds of MBs to GBs of disk space - No need to run npm install/composer install for each worktree - Consistent dependency versions across all worktrees Note: Add .shared-deps/ to .gitignore in the main repository 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- CLAUDE.md | 38 ++++++++++++++++++++++++++++++++++++++ scripts/conductor-setup.sh | 30 +++++++++++++++++++++++++++++- 2 files changed, 67 insertions(+), 1 deletion(-) diff --git a/CLAUDE.md b/CLAUDE.md index b7c496e42..e8c040402 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -10,6 +10,44 @@ ## Project Overview Coolify is an open-source, self-hostable platform for deploying applications and managing servers - an alternative to Heroku/Netlify/Vercel. It's built with Laravel (PHP) and uses Docker for containerization. +## Git Worktree Shared Dependencies + +This repository uses git worktrees for parallel development with **automatic shared dependency setup** via Conductor. + +### How It Works + +The `conductor.json` setup script (`scripts/conductor-setup.sh`) automatically: +1. Creates a shared `.shared-deps/` directory in the main repository +2. Creates symlinks from `node_modules` and `vendor` to the shared location +3. This happens automatically when Conductor creates a new worktree + +### Benefits + +- **Save disk space**: Only one copy of dependencies across all worktrees +- **Faster setup**: No need to run `npm install` or `composer install` for each worktree +- **Consistent versions**: All worktrees use the same dependency versions +- **Auto-configured**: Handled by Conductor's setup script + +### Manual Setup (If Needed) + +If you need to set up symlinks manually or for non-Conductor worktrees: + +```bash +# From the worktree directory +SHARED_DEPS="../../.shared-deps" +mkdir -p "$SHARED_DEPS/node_modules" "$SHARED_DEPS/vendor" +rm -rf node_modules vendor +ln -sf "$SHARED_DEPS/node_modules" node_modules +ln -sf "$SHARED_DEPS/vendor" vendor +``` + +### Important Notes + +- Dependencies are shared at `$CONDUCTOR_ROOT_PATH/.shared-deps/` +- Run `npm install` or `composer install` from any worktree to update all +- Ensure `.shared-deps/` is in `.gitignore` (should already be there) +- If different branches need different dependency versions, this won't work - remove symlinks and use separate directories + ## Development Commands ### Frontend Development diff --git a/scripts/conductor-setup.sh b/scripts/conductor-setup.sh index 7712f88be..963c8e808 100755 --- a/scripts/conductor-setup.sh +++ b/scripts/conductor-setup.sh @@ -1 +1,29 @@ -cp $CONDUCTOR_ROOT_PATH/.env .env \ No newline at end of file +#!/bin/bash +set -e + +# Copy .env file +cp $CONDUCTOR_ROOT_PATH/.env .env + +# Setup shared dependencies via symlinks +echo "Setting up shared node_modules and vendor directories..." + +# Create shared-deps directory in main repository if it doesn't exist +SHARED_DEPS="$CONDUCTOR_ROOT_PATH/.shared-deps" +mkdir -p "$SHARED_DEPS/node_modules" +mkdir -p "$SHARED_DEPS/vendor" + +# Remove existing directories if they exist and are not symlinks +[ -d "node_modules" ] && [ ! -L "node_modules" ] && rm -rf node_modules +[ -d "vendor" ] && [ ! -L "vendor" ] && rm -rf vendor + +# Calculate relative path from worktree to shared deps +WORKTREE_PATH=$(pwd) +RELATIVE_PATH=$(python3 -c "import os.path; print(os.path.relpath('$SHARED_DEPS', '$WORKTREE_PATH'))") + +# Create symlinks +ln -sf "$RELATIVE_PATH/node_modules" node_modules +ln -sf "$RELATIVE_PATH/vendor" vendor + +echo "✓ Shared dependencies linked successfully" +echo " node_modules -> $RELATIVE_PATH/node_modules" +echo " vendor -> $RELATIVE_PATH/vendor" \ No newline at end of file From b847e3801aa1a23d9b9325767efb0b78bf481087 Mon Sep 17 00:00:00 2001 From: Andras Bacsai <5845193+andrasbacsai@users.noreply.github.com> Date: Fri, 14 Nov 2025 13:06:25 +0100 Subject: [PATCH 2/5] Use main repo directories for shared dependencies MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Simplified the worktree setup to use the main repository's node_modules and vendor directories directly instead of creating a separate .shared-deps directory. Changes: - Updated conductor-setup.sh to symlink directly to main repo's directories - Updated CLAUDE.md to reflect the simpler approach - Symlinks now point to ../../node_modules and ../../vendor Benefits: - Simpler setup with no extra directories - All worktrees share the main repo's dependencies - No need to add .shared-deps to .gitignore 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- CLAUDE.md | 16 +++++++--------- scripts/conductor-setup.sh | 15 +++++++-------- 2 files changed, 14 insertions(+), 17 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index e8c040402..3982ec81c 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -17,8 +17,8 @@ ## Git Worktree Shared Dependencies ### How It Works The `conductor.json` setup script (`scripts/conductor-setup.sh`) automatically: -1. Creates a shared `.shared-deps/` directory in the main repository -2. Creates symlinks from `node_modules` and `vendor` to the shared location +1. Creates symlinks from worktree's `node_modules` and `vendor` to the main repository's directories +2. All worktrees share the same dependencies from the main repository 3. This happens automatically when Conductor creates a new worktree ### Benefits @@ -27,6 +27,7 @@ ### Benefits - **Faster setup**: No need to run `npm install` or `composer install` for each worktree - **Consistent versions**: All worktrees use the same dependency versions - **Auto-configured**: Handled by Conductor's setup script +- **Simple**: Uses the main repo's existing directories, no extra folders ### Manual Setup (If Needed) @@ -34,18 +35,15 @@ ### Manual Setup (If Needed) ```bash # From the worktree directory -SHARED_DEPS="../../.shared-deps" -mkdir -p "$SHARED_DEPS/node_modules" "$SHARED_DEPS/vendor" rm -rf node_modules vendor -ln -sf "$SHARED_DEPS/node_modules" node_modules -ln -sf "$SHARED_DEPS/vendor" vendor +ln -sf ../../node_modules node_modules +ln -sf ../../vendor vendor ``` ### Important Notes -- Dependencies are shared at `$CONDUCTOR_ROOT_PATH/.shared-deps/` -- Run `npm install` or `composer install` from any worktree to update all -- Ensure `.shared-deps/` is in `.gitignore` (should already be there) +- Dependencies are shared from the main repository (`$CONDUCTOR_ROOT_PATH`) +- Run `npm install` or `composer install` from the main repo or any worktree to update all - If different branches need different dependency versions, this won't work - remove symlinks and use separate directories ## Development Commands diff --git a/scripts/conductor-setup.sh b/scripts/conductor-setup.sh index 963c8e808..a1172e0d0 100755 --- a/scripts/conductor-setup.sh +++ b/scripts/conductor-setup.sh @@ -4,23 +4,22 @@ set -e # Copy .env file cp $CONDUCTOR_ROOT_PATH/.env .env -# Setup shared dependencies via symlinks +# Setup shared dependencies via symlinks to main repo echo "Setting up shared node_modules and vendor directories..." -# Create shared-deps directory in main repository if it doesn't exist -SHARED_DEPS="$CONDUCTOR_ROOT_PATH/.shared-deps" -mkdir -p "$SHARED_DEPS/node_modules" -mkdir -p "$SHARED_DEPS/vendor" +# Ensure main repo has the directories +mkdir -p "$CONDUCTOR_ROOT_PATH/node_modules" +mkdir -p "$CONDUCTOR_ROOT_PATH/vendor" # Remove existing directories if they exist and are not symlinks [ -d "node_modules" ] && [ ! -L "node_modules" ] && rm -rf node_modules [ -d "vendor" ] && [ ! -L "vendor" ] && rm -rf vendor -# Calculate relative path from worktree to shared deps +# Calculate relative path from worktree to main repo WORKTREE_PATH=$(pwd) -RELATIVE_PATH=$(python3 -c "import os.path; print(os.path.relpath('$SHARED_DEPS', '$WORKTREE_PATH'))") +RELATIVE_PATH=$(python3 -c "import os.path; print(os.path.relpath('$CONDUCTOR_ROOT_PATH', '$WORKTREE_PATH'))") -# Create symlinks +# Create symlinks to main repo's node_modules and vendor ln -sf "$RELATIVE_PATH/node_modules" node_modules ln -sf "$RELATIVE_PATH/vendor" vendor From 4507d99460982f2a2516a5b7551360c371f0e575 Mon Sep 17 00:00:00 2001 From: Andras Bacsai <5845193+andrasbacsai@users.noreply.github.com> Date: Fri, 14 Nov 2025 13:15:03 +0100 Subject: [PATCH 3/5] Use absolute paths in rm commands for safety MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Changed rm -rf commands to use absolute paths ($WORKTREE_PATH) instead of relative paths to prevent accidental deletion if symlinks behave unexpectedly. Also cleaned up duplicate WORKTREE_PATH definition. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- scripts/conductor-setup.sh | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/scripts/conductor-setup.sh b/scripts/conductor-setup.sh index a1172e0d0..b7e8ccb36 100755 --- a/scripts/conductor-setup.sh +++ b/scripts/conductor-setup.sh @@ -11,12 +11,14 @@ echo "Setting up shared node_modules and vendor directories..." mkdir -p "$CONDUCTOR_ROOT_PATH/node_modules" mkdir -p "$CONDUCTOR_ROOT_PATH/vendor" +# Get current worktree path +WORKTREE_PATH=$(pwd) + # Remove existing directories if they exist and are not symlinks -[ -d "node_modules" ] && [ ! -L "node_modules" ] && rm -rf node_modules -[ -d "vendor" ] && [ ! -L "vendor" ] && rm -rf vendor +[ -d "node_modules" ] && [ ! -L "node_modules" ] && rm -rf "$WORKTREE_PATH/node_modules" +[ -d "vendor" ] && [ ! -L "vendor" ] && rm -rf "$WORKTREE_PATH/vendor" # Calculate relative path from worktree to main repo -WORKTREE_PATH=$(pwd) RELATIVE_PATH=$(python3 -c "import os.path; print(os.path.relpath('$CONDUCTOR_ROOT_PATH', '$WORKTREE_PATH'))") # Create symlinks to main repo's node_modules and vendor From c6316272003917fa233c7ab1f00b774a74bf205e Mon Sep 17 00:00:00 2001 From: Andras Bacsai <5845193+andrasbacsai@users.noreply.github.com> Date: Fri, 14 Nov 2025 13:18:24 +0100 Subject: [PATCH 4/5] Add safety checks to prevent dangerous deletions MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Added multiple safety validations before executing rm -rf commands: - Check WORKTREE_PATH is not empty, /, /Users, or $HOME - Verify we're actually in a git repository (.git exists) This prevents accidental deletion of critical directories if the script is run in the wrong location or with unexpected environment variables. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- scripts/conductor-setup.sh | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/scripts/conductor-setup.sh b/scripts/conductor-setup.sh index b7e8ccb36..effad78fc 100755 --- a/scripts/conductor-setup.sh +++ b/scripts/conductor-setup.sh @@ -14,6 +14,18 @@ mkdir -p "$CONDUCTOR_ROOT_PATH/vendor" # Get current worktree path WORKTREE_PATH=$(pwd) +# Safety check: ensure WORKTREE_PATH is valid and not a dangerous location +if [ -z "$WORKTREE_PATH" ] || [ "$WORKTREE_PATH" = "/" ] || [ "$WORKTREE_PATH" = "/Users" ] || [ "$WORKTREE_PATH" = "$HOME" ]; then + echo "ERROR: Invalid or dangerous WORKTREE_PATH: $WORKTREE_PATH" + exit 1 +fi + +# Additional safety: ensure we're in a git worktree +if [ ! -f ".git" ] && [ ! -d ".git" ]; then + echo "ERROR: Not in a git repository" + exit 1 +fi + # Remove existing directories if they exist and are not symlinks [ -d "node_modules" ] && [ ! -L "node_modules" ] && rm -rf "$WORKTREE_PATH/node_modules" [ -d "vendor" ] && [ ! -L "vendor" ] && rm -rf "$WORKTREE_PATH/vendor" From ff7b27be61217742524bf4f5161da068e59fce55 Mon Sep 17 00:00:00 2001 From: Andras Bacsai <5845193+andrasbacsai@users.noreply.github.com> Date: Wed, 19 Nov 2025 10:58:21 +0100 Subject: [PATCH 5/5] Improve worktree setup safety and cross-platform compatibility MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add validation for CONDUCTOR_ROOT_PATH environment variable - Enhance safety checks with explicit blacklist of system directories - Improve directory detection (symlink vs regular directory) - Replace Python dependency with cross-platform bash+perl for path calculation - Use absolute paths consistently to prevent symlink following - Add detailed comments explaining each safety check 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- scripts/conductor-setup.sh | 73 +++++++++++++++++++++++++++++++++----- 1 file changed, 64 insertions(+), 9 deletions(-) diff --git a/scripts/conductor-setup.sh b/scripts/conductor-setup.sh index effad78fc..a88b457fb 100755 --- a/scripts/conductor-setup.sh +++ b/scripts/conductor-setup.sh @@ -1,8 +1,20 @@ #!/bin/bash set -e +# Validate CONDUCTOR_ROOT_PATH is set and valid before any operations +if [ -z "$CONDUCTOR_ROOT_PATH" ]; then + echo "ERROR: CONDUCTOR_ROOT_PATH environment variable is not set" + echo "This script must be run by Conductor with CONDUCTOR_ROOT_PATH set to the main repository path" + exit 1 +fi + +if [ ! -d "$CONDUCTOR_ROOT_PATH" ]; then + echo "ERROR: CONDUCTOR_ROOT_PATH ($CONDUCTOR_ROOT_PATH) is not a valid directory" + exit 1 +fi + # Copy .env file -cp $CONDUCTOR_ROOT_PATH/.env .env +cp "$CONDUCTOR_ROOT_PATH/.env" .env # Setup shared dependencies via symlinks to main repo echo "Setting up shared node_modules and vendor directories..." @@ -14,24 +26,67 @@ mkdir -p "$CONDUCTOR_ROOT_PATH/vendor" # Get current worktree path WORKTREE_PATH=$(pwd) -# Safety check: ensure WORKTREE_PATH is valid and not a dangerous location -if [ -z "$WORKTREE_PATH" ] || [ "$WORKTREE_PATH" = "/" ] || [ "$WORKTREE_PATH" = "/Users" ] || [ "$WORKTREE_PATH" = "$HOME" ]; then - echo "ERROR: Invalid or dangerous WORKTREE_PATH: $WORKTREE_PATH" +# Safety check 1: ensure WORKTREE_PATH is valid +if [ -z "$WORKTREE_PATH" ]; then + echo "ERROR: WORKTREE_PATH is empty" exit 1 fi -# Additional safety: ensure we're in a git worktree +# Safety check 2: CRITICAL FIRST - blacklist system directories +# This check runs BEFORE the positive check to prevent dangerous operations +# even if someone misconfigures CONDUCTOR_ROOT_PATH +case "$WORKTREE_PATH" in + /|/bin|/sbin|/usr|/usr/*|/etc|/etc/*|/var|/var/*|/System|/System/*|/Library|/Library/*|/Applications|/Applications/*|"$HOME") + echo "ERROR: WORKTREE_PATH ($WORKTREE_PATH) is in a dangerous system location" + exit 1 + ;; +esac + +# Safety check 3: positive check - verify we're under CONDUCTOR_ROOT_PATH +case "$WORKTREE_PATH" in + "$CONDUCTOR_ROOT_PATH"|"$CONDUCTOR_ROOT_PATH"/.conductor/*) + # Valid: either main repo or under .conductor/ + ;; + *) + echo "ERROR: WORKTREE_PATH ($WORKTREE_PATH) is not under CONDUCTOR_ROOT_PATH ($CONDUCTOR_ROOT_PATH)" + exit 1 + ;; +esac + +# Safety check 4: verify we're in a git repository if [ ! -f ".git" ] && [ ! -d ".git" ]; then echo "ERROR: Not in a git repository" exit 1 fi -# Remove existing directories if they exist and are not symlinks -[ -d "node_modules" ] && [ ! -L "node_modules" ] && rm -rf "$WORKTREE_PATH/node_modules" -[ -d "vendor" ] && [ ! -L "vendor" ] && rm -rf "$WORKTREE_PATH/vendor" +# Remove existing directories/symlinks if they exist +# For symlinks: use 'rm' without -r to remove the symlink itself (not following it) +# For directories: use 'rm -rf' to remove the directory and contents +if [ -L "node_modules" ]; then + # It's a symlink - remove it without following (no -r flag) + rm "$WORKTREE_PATH/node_modules" +elif [ -e "node_modules" ]; then + # It's a regular directory or file - safe to use -rf + rm -rf "$WORKTREE_PATH/node_modules" +fi + +if [ -L "vendor" ]; then + # It's a symlink - remove it without following (no -r flag) + rm "$WORKTREE_PATH/vendor" +elif [ -e "vendor" ]; then + # It's a regular directory or file - safe to use -rf + rm -rf "$WORKTREE_PATH/vendor" +fi # Calculate relative path from worktree to main repo -RELATIVE_PATH=$(python3 -c "import os.path; print(os.path.relpath('$CONDUCTOR_ROOT_PATH', '$WORKTREE_PATH'))") +# Use bash-native approach: try realpath first (GNU coreutils), fallback to perl +if command -v realpath &> /dev/null && realpath --relative-to / / &> /dev/null 2>&1; then + # GNU coreutils realpath with --relative-to support + RELATIVE_PATH=$(realpath --relative-to="$WORKTREE_PATH" "$CONDUCTOR_ROOT_PATH") +else + # Fallback: use perl which is standard on macOS and most Unix systems + RELATIVE_PATH=$(perl -e 'use File::Spec; print File::Spec->abs2rel($ARGV[0], $ARGV[1])' "$CONDUCTOR_ROOT_PATH" "$WORKTREE_PATH") +fi # Create symlinks to main repo's node_modules and vendor ln -sf "$RELATIVE_PATH/node_modules" node_modules