Skip to content

feat(installer): add legacy migration and prevent recursive loops#12

Merged
jbdevprimary merged 2 commits into
mainfrom
feat/installer-migration
Apr 10, 2026
Merged

feat(installer): add legacy migration and prevent recursive loops#12
jbdevprimary merged 2 commits into
mainfrom
feat/installer-migration

Conversation

@jbdevprimary
Copy link
Copy Markdown
Contributor

  • Migration: Automatically detects and migrates legacy ~/.bashrc.d and ~/.secrets.d folders into the managed ~/.get-bashed prefix.
  • Safety: Detects if ~/.bashrc or ~/.bash_profile are copies of templates and recovers from infinite recursive sourcing loops.
  • Robustness: Fixes installation order to ensure custom user modules are preserved during upgrades even when --force is used.
  • Usability: Uses absolute paths in shell snippets for more deterministic sourcing.

This solidifies the upgrade path and protects users from the 'broken terminal' state encountered during the PR 9 transition.

- Fix order of operations in install.bash to migrate AFTER asset copying

- Ensure rsync --delete does not wipe user's custom modules during upgrade

- Clean up .DS_Store artifacts
Copilot AI review requested due to automatic review settings April 10, 2026 16:09
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 10, 2026

Warning

Rate limit exceeded

@jbdevprimary has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 4 minutes and 4 seconds before requesting another review.

Your organization is not enrolled in usage-based pricing. Contact your admin to enable usage-based pricing to continue reviews beyond the rate limit, or try again in 4 minutes and 4 seconds.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 8e1aef55-f9fd-4144-8755-33a5b9af7b40

📥 Commits

Reviewing files that changed from the base of the PR and between 98d6fec and 0732751.

⛔ Files ignored due to path filters (1)
  • assets/.DS_Store is excluded by !**/.DS_Store
📒 Files selected for processing (1)
  • install.bash
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/installer-migration

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@jbdevprimary jbdevprimary merged commit 3190d63 into main Apr 10, 2026
6 of 7 checks passed
@jbdevprimary jbdevprimary deleted the feat/installer-migration branch April 10, 2026 16:09
@amazon-q-developer
Copy link
Copy Markdown

⚠️ Review Failed

I was unable to finalize my review because the pull request head or merge base was modified since I began my review. Please try again.

Request ID: 7ac0aa72-655a-5a06-92ee-c821478b33ab

Copy link
Copy Markdown

@gemini-code-assist gemini-code-assist Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces a migrate_legacy function to move legacy configuration directories (.bashrc.d and .secrets.d) to the new $PREFIX and resolve recursive loop hazards in shell profiles. Key feedback includes the need for the migration function to respect the $DRY_RUN flag to prevent unintended filesystem modifications. Furthermore, it is recommended to replace the find and cp logic with rsync to safely preserve directory structures and ensure successful migration before deleting legacy data.

Comment thread install.bash
fi

# @internal
migrate_legacy() {
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

critical

The migrate_legacy function performs destructive operations (like rm -rf) and modifies the filesystem, but it does not respect the $DRY_RUN flag. This violates the expectation that --dry-run should not modify the system.

migrate_legacy() {
  [[ "$DRY_RUN" -eq 1 ]] && return 0

Comment thread install.bash
Comment on lines +527 to +528
find "$legacy_rc_d" -type f -exec cp -p {} "$PREFIX/bashrc.d/" \;
rm -rf "$legacy_rc_d"
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

Using find -exec cp flattens the directory structure, which can lead to filename collisions and loss of organization if the legacy directory contains subdirectories. Additionally, the legacy directory is removed without explicitly verifying the success of the copy operation. Since rsync is already a dependency for this script, it is a more robust and efficient choice for migration.

    rsync -a "$legacy_rc_d/" "$PREFIX/bashrc.d/" && rm -rf "$legacy_rc_d"

Comment thread install.bash
Comment on lines +535 to +536
find "$legacy_secrets_d" -type f -exec cp -p {} "$PREFIX/secrets.d/" \;
rm -rf "$legacy_secrets_d"
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

Similar to the .bashrc.d migration, using find -exec cp flattens the directory structure and the subsequent rm -rf is not explicitly guarded by the success of the copy operation.

    rsync -a "$legacy_secrets_d/" "$PREFIX/secrets.d/" && rm -rf "$legacy_secrets_d"

Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds installer-side safeguards to improve upgrades from legacy layouts and reduce the chance of users ending up with broken shell startup behavior.

Changes:

  • Introduces legacy migration from ~/.bashrc.d and ~/.secrets.d into the managed $PREFIX layout.
  • Adds detection/recovery logic intended to prevent recursive sourcing loops from template-derived ~/.bashrc / ~/.bash_profile.
  • Updates the installer’s shell-init wiring to source via deterministic (prefix-based) paths.

Reviewed changes

Copilot reviewed 1 out of 2 changed files in this pull request and generated 4 comments.

File Description
install.bash Adds migrate_legacy migration + loop recovery logic; updates installation wiring/snippets.
assets/.DS_Store Adds a macOS Finder metadata file (should not be tracked).
Comments suppressed due to low confidence (1)

install.bash:567

  • Legacy migration and loop-recovery are new behaviors but there’s no corresponding Bats coverage. Add tests that create $HOME/.bashrc.d and $HOME/.secrets.d with sample files and verify they end up under $PREFIX/... (and are removed from $HOME), and a test that simulates the loop-hazard dotfile and verifies it’s backed up and rewritten safely.
# @internal
migrate_legacy() {
  local legacy_rc_d="$HOME/.bashrc.d"
  local legacy_secrets_d="$HOME/.secrets.d"

  if [[ -d "$legacy_rc_d" && ! -L "$legacy_rc_d" ]]; then
    echo "Migrating legacy .bashrc.d to $PREFIX/bashrc.d..."
    mkdir -p "$PREFIX/bashrc.d"
    # Copy files, avoid error if empty
    find "$legacy_rc_d" -type f -exec cp -p {} "$PREFIX/bashrc.d/" \;
    rm -rf "$legacy_rc_d"
  fi

  if [[ -d "$legacy_secrets_d" && ! -L "$legacy_secrets_d" ]]; then
    echo "Migrating legacy .secrets.d to $PREFIX/secrets.d..."
    mkdir -p "$PREFIX/secrets.d"
    chmod 700 "$PREFIX/secrets.d"
    find "$legacy_secrets_d" -type f -exec cp -p {} "$PREFIX/secrets.d/" \;
    rm -rf "$legacy_secrets_d"
  fi

  # Detect and fix "template as file" loop hazard
  for f in "$HOME/.bashrc" "$HOME/.bash_profile"; do
    if [[ -f "$f" && ! -L "$f" ]]; then
      if grep -q "@file bashrc" "$f" || grep -q "@file bash_profile" "$f"; then
        if [[ "$LINK_DOTFILES" -eq 0 ]]; then
          echo "Detected recursive loop hazard in $f. Cleaning..."
          backup_file "$f"
          echo "# get-bashed: recovered from loop" > "$f"
        fi
      fi
    fi
  done
}

mkdir -p "$PREFIX"
export GET_BASHED_HOME="$PREFIX"
export GET_BASHED_VIMRC_MODE="$VIMRC_MODE"

copy_tree() {
  local src="$1" dest="$2"
  mkdir -p "$dest"
  if [[ "${FORCE:-0}" -eq 1 ]]; then
    rsync -a --delete "$src"/ "$dest"/
  else
    rsync -a "$src"/ "$dest"/
  fi
}

# Copy base assets

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread install.bash
Comment on lines +519 to +537
migrate_legacy() {
local legacy_rc_d="$HOME/.bashrc.d"
local legacy_secrets_d="$HOME/.secrets.d"

if [[ -d "$legacy_rc_d" && ! -L "$legacy_rc_d" ]]; then
echo "Migrating legacy .bashrc.d to $PREFIX/bashrc.d..."
mkdir -p "$PREFIX/bashrc.d"
# Copy files, avoid error if empty
find "$legacy_rc_d" -type f -exec cp -p {} "$PREFIX/bashrc.d/" \;
rm -rf "$legacy_rc_d"
fi

if [[ -d "$legacy_secrets_d" && ! -L "$legacy_secrets_d" ]]; then
echo "Migrating legacy .secrets.d to $PREFIX/secrets.d..."
mkdir -p "$PREFIX/secrets.d"
chmod 700 "$PREFIX/secrets.d"
find "$legacy_secrets_d" -type f -exec cp -p {} "$PREFIX/secrets.d/" \;
rm -rf "$legacy_secrets_d"
fi
Copy link

Copilot AI Apr 10, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

migrate_legacy performs filesystem writes (mkdir/cp/rm) even when --dry-run is set, which contradicts the earlier "Dry run enabled. No changes will be made." message. Add an early return when DRY_RUN=1 (or guard the call site) so legacy directories and dotfiles aren’t modified during dry runs.

Copilot uses AI. Check for mistakes.
Comment thread install.bash
Comment on lines +539 to +547
# Detect and fix "template as file" loop hazard
for f in "$HOME/.bashrc" "$HOME/.bash_profile"; do
if [[ -f "$f" && ! -L "$f" ]]; then
if grep -q "@file bashrc" "$f" || grep -q "@file bash_profile" "$f"; then
if [[ "$LINK_DOTFILES" -eq 0 ]]; then
echo "Detected recursive loop hazard in $f. Cleaning..."
backup_file "$f"
echo "# get-bashed: recovered from loop" > "$f"
fi
Copy link

Copilot AI Apr 10, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The recursive-loop recovery logic keys off the presence of @file bashrc / @file bash_profile anywhere in the user’s dotfiles, then backs up and replaces the entire file. This heuristic can trigger on legitimately customized files derived from the templates (or any file containing those strings), unexpectedly wiping active configuration. Consider detecting the actual recursive sourcing pattern (or comparing the file content to the shipped templates) and applying the minimal edit needed rather than clobbering the file.

Copilot uses AI. Check for mistakes.
Comment thread install.bash
Comment on lines 634 to 643
# shellcheck disable=SC2016
BASHRC_LINE="# get-bashed: source modular bashrc"
# shellcheck disable=SC2016
BASHRC_SNIP='if [[ -r "$HOME/.get-bashed/bashrc" ]]; then source "$HOME/.get-bashed/bashrc"; fi'
BASHRC_SNIP="if [[ -r \"$PREFIX/bashrc\" ]]; then source \"$PREFIX/bashrc\"; fi"
BASH_PROFILE_LINE="# get-bashed: source login bash_profile"
# shellcheck disable=SC2016
BASH_PROFILE_SNIP='if [[ -r "$HOME/.get-bashed/bash_profile" ]]; then source "$HOME/.get-bashed/bash_profile"; fi'
BASH_PROFILE_SNIP="if [[ -r \"$PREFIX/bash_profile\" ]]; then source \"$PREFIX/bash_profile\"; fi"

ensure_block "$HOME/.bashrc" "$BASHRC_LINE" "$BASHRC_SNIP"
ensure_block "$HOME/.bash_profile" "$BASH_PROFILE_LINE" "$BASH_PROFILE_SNIP"
Copy link

Copilot AI Apr 10, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The .bashrc/.bash_profile snippet update is gated only on the marker line via ensure_block. If a user already has the marker from a previous install, changing the snippet (e.g., switching from $HOME/.get-bashed/... to an absolute $PREFIX/...) will not update the existing block, so the new deterministic/absolute-path behavior won’t take effect for upgrades or for users changing --prefix. Update ensure_block to replace the existing block when the marker exists but the snippet differs, or version the marker to force an update.

Copilot uses AI. Check for mistakes.
Comment thread install.bash
Comment on lines 573 to +577
cp -f "$REPO_DIR/vimrc" "$PREFIX/vimrc"
cp -f "$REPO_DIR/gitconfig" "$PREFIX/gitconfig"

migrate_legacy

Copy link

Copilot AI Apr 10, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The PR description mentions preserving custom user modules during upgrades when --force is used, but FORCE is parsed and never referenced anywhere in the installer (no behavior change occurs when passing --force). Either implement the described --force behavior (and document what it does) or remove the flag from usage/arg parsing to avoid misleading users.

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants