From b731da3384ca21b13f0849336dc9e125dec27cf7 Mon Sep 17 00:00:00 2001 From: Otto Jongerius Date: Sat, 14 Feb 2026 10:46:41 +1300 Subject: [PATCH 1/5] Remove Transmission.app settings from osx script --- osx/osx.sh | 21 +-------------------- 1 file changed, 1 insertion(+), 20 deletions(-) diff --git a/osx/osx.sh b/osx/osx.sh index e83d9e9..92a169a 100644 --- a/osx/osx.sh +++ b/osx/osx.sh @@ -420,31 +420,12 @@ defaults write com.google.Chrome.canary AppleEnableMouseSwipeNavigateWithScrolls defaults write com.google.Chrome DisablePrintPreview -bool true defaults write com.google.Chrome.canary DisablePrintPreview -bool true -############################################################################### -# Transmission.app # -############################################################################### - -# Use `~/Documents/Torrents` to store incomplete downloads -defaults write org.m0k.transmission UseIncompleteDownloadFolder -bool true -defaults write org.m0k.transmission IncompleteDownloadFolder -string "${HOME}/Documents/Torrents" - -# Don’t prompt for confirmation before downloading -defaults write org.m0k.transmission DownloadAsk -bool false - -# Trash original torrent files -defaults write org.m0k.transmission DeleteOriginalTorrent -bool true - -# Hide the donate message -defaults write org.m0k.transmission WarningDonate -bool false -# Hide the legal disclaimer -defaults write org.m0k.transmission WarningLegal -bool false - ############################################################################### # Kill affected applications # ############################################################################### for app in "Activity Monitor" "cfprefsd" "Dock" "Finder" "Messages" \ - "SystemUIServer" "Terminal" "Transmission" "Calendar"; do + "SystemUIServer" "Terminal" "Calendar"; do killall "${app}" > /dev/null 2>&1 done echo "Done. Note that some of these changes require a logout/restart to take effect." From 7dc5af9b4eef4bdc6617f559609e1c8bd8079e34 Mon Sep 17 00:00:00 2001 From: Otto Jongerius Date: Sat, 14 Feb 2026 11:02:21 +1300 Subject: [PATCH 2/5] Add dry-run mode, timestamped backups, expanded tests, and CI - Add --dry-run flag to preview actions without making changes - Use timestamped backups (.orig.YYYYMMDDHHMMSS) to avoid overwrites - Add idempotency guard to do_oh_my_zsh (skip if already installed) - Remove set -x from osx.sh - Add root .gitignore and remove stale __pycache__ - Expand test suite from 6 to 13 tests (ghostty, claude, oh-my-zsh, dry-run) - Add GitHub Actions CI with shellcheck and bats --- .github/workflows/ci.yml | 27 +++++++++ .gitignore | 3 + install.sh | 30 +++++++++- osx/osx.sh | 2 - test_install.bats | 125 ++++++++++++++++++++++++++++++++++++--- 5 files changed, 176 insertions(+), 11 deletions(-) create mode 100644 .github/workflows/ci.yml create mode 100644 .gitignore diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..3ba3961 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,27 @@ +name: CI + +on: + push: + branches: [master] + pull_request: + branches: [master] + +jobs: + lint: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Run ShellCheck + uses: ludeeus/action-shellcheck@2.0.0 + with: + scandir: . + additional_files: install.sh osx/osx.sh + + test: + runs-on: macos-latest + steps: + - uses: actions/checkout@v4 + - name: Install bats-core + run: brew install bats-core + - name: Run tests + run: bats test_install.bats diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..b908d4c --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +__pycache__/ +*.pyc +.DS_Store diff --git a/install.sh b/install.sh index a64853b..a8ae931 100755 --- a/install.sh +++ b/install.sh @@ -3,6 +3,7 @@ set -euo pipefail REPO_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +DRY_RUN=false # --------------------------------------------------------------------------- # Helpers @@ -11,6 +12,11 @@ REPO_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" _symlink() { local source="$1" destination="$2" + if $DRY_RUN; then + echo "DRY-RUN: Would link $source -> $destination" + return + fi + # Already linked correctly — nothing to do if [ -L "$destination" ] && [ "$(readlink "$destination")" = "$source" ]; then echo "INFO: Nothing to do here: $destination LGTM." @@ -19,8 +25,10 @@ _symlink() { # Back up existing file/symlink if [ -e "$destination" ] || [ -L "$destination" ]; then - mv "$destination" "${destination}.orig" - echo "INFO: Created backup ${destination}.orig" + local backup + backup="${destination}.orig.$(date +%Y%m%d%H%M%S)" + mv "$destination" "$backup" + echo "INFO: Created backup $backup" fi ln -s "$source" "$destination" @@ -38,6 +46,7 @@ Options: --osx Run macOS system tweaks (osx/osx.sh) --oh-my-zsh Install Oh-my-zsh --claude Link Claude Code skills to ~/.claude/skills/ + --dry-run Preview what would be done without making changes --help Show this help message EOF } @@ -67,6 +76,10 @@ do_ghostty() { do_brew() { echo "Working on Brew and programs.." + if $DRY_RUN; then + echo "DRY-RUN: Would install Xcode CLI tools, Homebrew, and packages from Brewfile" + return + fi if ! xcode-select -p &>/dev/null; then xcode-select --install fi @@ -78,11 +91,23 @@ do_brew() { do_osx() { echo "Working on osx customisations.." + if $DRY_RUN; then + echo "DRY-RUN: Would run macOS system tweaks from osx/osx.sh" + return + fi bash "$REPO_DIR/osx/osx.sh" } do_oh_my_zsh() { echo "Working on Oh-my-zsh.." + if [ -d "$HOME/.oh-my-zsh" ]; then + echo "INFO: Oh-my-zsh is already installed, skipping." + return + fi + if $DRY_RUN; then + echo "DRY-RUN: Would install Oh-my-zsh" + return + fi curl -L https://raw.github.com/robbyrussell/oh-my-zsh/master/tools/install.sh | sh } @@ -108,6 +133,7 @@ if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then for arg in "$@"; do case "$arg" in + --dry-run) DRY_RUN=true ;; --dotfiles) do_dotfiles ;; --ghostty) do_ghostty ;; --brew) do_brew ;; diff --git a/osx/osx.sh b/osx/osx.sh index 92a169a..c6cc899 100644 --- a/osx/osx.sh +++ b/osx/osx.sh @@ -1,7 +1,5 @@ #!/usr/bin/env bash -set -x - #From https://mths.be/osx # Ask for the administrator password upfront diff --git a/test_install.bats b/test_install.bats index 487ec01..24a5e22 100644 --- a/test_install.bats +++ b/test_install.bats @@ -42,8 +42,10 @@ setup() { [ -L "$dest" ] [ "$(readlink "$dest")" = "$src" ] - [ -f "${dest}.orig" ] - [ "$(cat "${dest}.orig")" = "old" ] + local backup + backup=$(ls "${dest}".orig.* 2>/dev/null) + [ -f "$backup" ] + [ "$(cat "$backup")" = "old" ] } @test "_symlink backs up existing wrong symlink and creates correct one" { @@ -58,8 +60,10 @@ setup() { [ -L "$dest" ] [ "$(readlink "$dest")" = "$src" ] - [ -L "${dest}.orig" ] - [ "$(readlink "${dest}.orig")" = "$wrong" ] + local backup + backup=$(ls "${dest}".orig.* 2>/dev/null) + [ -L "$backup" ] + [ "$(readlink "$backup")" = "$wrong" ] } # --- do_dotfiles test (ported from TestProccessDotfiles) --- @@ -86,7 +90,7 @@ setup() { # --- backup test (ported from TestBackup) --- -@test "backup via _symlink renames file with .orig suffix" { +@test "backup via _symlink renames file with .orig timestamp suffix" { local dest="$TEST_DIR/myfile" local src="$TEST_DIR/newsrc" echo "content" > "$dest" @@ -95,6 +99,113 @@ setup() { _symlink "$src" "$dest" [ -L "$dest" ] - [ -f "${dest}.orig" ] - [ "$(cat "${dest}.orig")" = "content" ] + local backup + backup=$(ls "${dest}".orig.* 2>/dev/null) + [ -f "$backup" ] + [ "$(cat "$backup")" = "content" ] +} + +# --- do_ghostty tests --- + +@test "do_ghostty creates config directory and symlinks config" { + local fake_home="$TEST_DIR/home" + mkdir -p "$fake_home" + + HOME="$fake_home" + + do_ghostty + + [ -d "$fake_home/.config/ghostty" ] + [ -L "$fake_home/.config/ghostty/config" ] +} + +@test "do_ghostty is idempotent" { + local fake_home="$TEST_DIR/home" + mkdir -p "$fake_home" + + HOME="$fake_home" + + do_ghostty + run do_ghostty + + [ "$status" -eq 0 ] + [[ "$output" == *"LGTM"* ]] +} + +# --- do_claude tests --- + +@test "do_claude symlinks skill directories" { + local fake_home="$TEST_DIR/home" + local repo="$TEST_DIR/repo" + mkdir -p "$fake_home" "$repo/claude/skills/my-skill" + echo "skill" > "$repo/claude/skills/my-skill/SKILL.md" + + HOME="$fake_home" + REPO_DIR="$repo" + + do_claude + + [ -d "$fake_home/.claude/skills" ] + [ -L "$fake_home/.claude/skills/my-skill" ] +} + +@test "do_claude is idempotent" { + local fake_home="$TEST_DIR/home" + local repo="$TEST_DIR/repo" + mkdir -p "$fake_home" "$repo/claude/skills/my-skill" + echo "skill" > "$repo/claude/skills/my-skill/SKILL.md" + + HOME="$fake_home" + REPO_DIR="$repo" + + do_claude + run do_claude + + [ "$status" -eq 0 ] + [[ "$output" == *"LGTM"* ]] +} + +# --- do_oh_my_zsh tests --- + +@test "do_oh_my_zsh skips when already installed" { + local fake_home="$TEST_DIR/home" + mkdir -p "$fake_home/.oh-my-zsh" + + HOME="$fake_home" + + run do_oh_my_zsh + + [ "$status" -eq 0 ] + [[ "$output" == *"already installed"* ]] +} + +# --- dry-run tests --- + +@test "dry-run mode prevents _symlink from creating links" { + local src="$TEST_DIR/source" + local dest="$TEST_DIR/dest" + echo "data" > "$src" + + DRY_RUN=true + run _symlink "$src" "$dest" + + [ "$status" -eq 0 ] + [[ "$output" == *"DRY-RUN"* ]] + [ ! -e "$dest" ] +} + +@test "dry-run mode prevents _symlink from backing up files" { + local src="$TEST_DIR/source" + local dest="$TEST_DIR/dest" + echo "new" > "$src" + echo "old" > "$dest" + + DRY_RUN=true + run _symlink "$src" "$dest" + + [ "$status" -eq 0 ] + [[ "$output" == *"DRY-RUN"* ]] + # Original file should be untouched + [ -f "$dest" ] + [ "$(cat "$dest")" = "old" ] } From 882d8f98b909046ef6153dadb840f086f8debd42 Mon Sep 17 00:00:00 2001 From: Otto Jongerius Date: Sat, 14 Feb 2026 11:02:21 +1300 Subject: [PATCH 3/5] Fix CI: scope shellcheck to bash scripts only The scandir action was picking up .zshrc which has expected shellcheck warnings (oh-my-zsh variables, zsh-specific syntax). --- .github/workflows/ci.yml | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 3ba3961..c70edbe 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -12,10 +12,7 @@ jobs: steps: - uses: actions/checkout@v4 - name: Run ShellCheck - uses: ludeeus/action-shellcheck@2.0.0 - with: - scandir: . - additional_files: install.sh osx/osx.sh + run: shellcheck install.sh osx/osx.sh test: runs-on: macos-latest From 9f2915a8c33c9609a0ae424577023cff81c51269 Mon Sep 17 00:00:00 2001 From: Otto Jongerius Date: Sat, 14 Feb 2026 11:02:21 +1300 Subject: [PATCH 4/5] Scan all shell scripts in CI, only exclude zsh directory --- .github/workflows/ci.yml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index c70edbe..c28d468 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -12,7 +12,10 @@ jobs: steps: - uses: actions/checkout@v4 - name: Run ShellCheck - run: shellcheck install.sh osx/osx.sh + uses: ludeeus/action-shellcheck@2.0.0 + with: + scandir: . + ignore_paths: zsh test: runs-on: macos-latest From 5439cf71fd859d5f23386dea02d314038701103a Mon Sep 17 00:00:00 2001 From: Otto Jongerius Date: Sat, 14 Feb 2026 11:02:21 +1300 Subject: [PATCH 5/5] Use find + shellcheck directly instead of third-party action --- .github/workflows/ci.yml | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index c28d468..06bf713 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -12,10 +12,7 @@ jobs: steps: - uses: actions/checkout@v4 - name: Run ShellCheck - uses: ludeeus/action-shellcheck@2.0.0 - with: - scandir: . - ignore_paths: zsh + run: find . -name '*.sh' -not -path './zsh/*' -print0 | xargs -0 shellcheck test: runs-on: macos-latest