commit 0cd1cbb1320cf26ef3801297b4fb8daa439f6217 Author: Pipo Date: Wed Sep 24 17:18:02 2025 +0200 Initial commit diff --git a/.DS_Store b/.DS_Store new file mode 100644 index 0000000..a970548 Binary files /dev/null and b/.DS_Store differ diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..9457651 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2025 Pipo + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..9fa9966 --- /dev/null +++ b/README.md @@ -0,0 +1,46 @@ +# auto-snapclean-listener + +A small macOS utility script that automatically deletes all local Time Machine snapshots once they are created. +It is useful for users who do not want macOS to keep local snapshots and prefer to rely only on external Time Machine backups. + +⚠️ Tested on **macOS Tahoe**. Other versions may work, but are not guaranteed. + +--- + +## Features + +- Listens for local Time Machine snapshot creation events. +- Waits until the backup process is finished (avoids conflicts). +- Automatically deletes all local snapshots afterwards. +- Lightweight: no constant polling, event-driven. + +--- + +## Installation + +Clone the repository: + +```bash +git clone https://github.com/YOURUSERNAME/auto-snapclean-listener.git +cd auto-snapclean-listener +``` + +Make the script executable: + +```bash +chmod +x auto-snapclean-listener.sh +``` + +Install the LaunchDaemon: + +```bash +sudo cp auto-snapclean-listener.plist /Library/LaunchDaemons/ +sudo launchctl load /Library/LaunchDaemons/auto-snapclean-listener.plist +``` + +Uninstall (if needed): + +```bash +sudo launchctl unload /Library/LaunchDaemons/auto-snapclean-listener.plist +sudo rm /Library/LaunchDaemons/auto-snapclean-listener.plist +``` diff --git a/launchd/com.snapclean.listener.plist b/launchd/com.snapclean.listener.plist new file mode 100644 index 0000000..4ffb64a --- /dev/null +++ b/launchd/com.snapclean.listener.plist @@ -0,0 +1,15 @@ + + + + + Labelcom.snapclean.listener + ProgramArguments + + /usr/local/bin/auto-snapclean-listener.sh + + RunAtLoad + KeepAlive + UserNameroot + + \ No newline at end of file diff --git a/scripts/auto-snapclean-listener.sh b/scripts/auto-snapclean-listener.sh new file mode 100644 index 0000000..935f82f --- /dev/null +++ b/scripts/auto-snapclean-listener.sh @@ -0,0 +1,43 @@ +#!/bin/zsh +set -euo pipefail + +LOGF="/var/log/snapclean.log" +log() { print -r -- "$(date '+%F %T') $*" >> "$LOGF"; } + +is_running() { + /usr/bin/tmutil status 2>/dev/null | /usr/bin/grep -Eq '"Running" = 1|Running = 1' +} + +wait_until_tm_idle() { + local i=0 + while is_running; do + sleep 2 + (( i++ )) + [[ $i -gt 150 ]] && log "Timeout: TM still running. Waiting until it's finished." && break + done +} + +delete_all_snapshots() { + log "Deletes all local Snapshots at / …" + /usr/bin/tmutil deletelocalsnapshots / || true + log "Finished deleting." +} + +# Make sure logfile exists +[[ -f "$LOGF" ]] || { sudo touch "$LOGF"; sudo chmod 644 "$LOGF"; } + +while IFS= read -r line; do + if print -r -- "$line" | /usr/bin/grep -q \ + "com.apple.TimeMachine:LocalSnapshotManagement] Created Time Machine local snapshot"; then + log "Event erkannt: $line" + # Short break until the snapshot is in index + sleep 2 + # Waiting until TM is finished + wait_until_tm_idle + # delete all local snapshots + delete_all_snapshots + fi +done < <( + /usr/bin/log stream --style syslog --level info \ + --predicate 'process == "backupd" AND subsystem == "com.apple.TimeMachine"' +) \ No newline at end of file diff --git a/scripts/install.sh b/scripts/install.sh new file mode 100644 index 0000000..57e81d0 --- /dev/null +++ b/scripts/install.sh @@ -0,0 +1,17 @@ +#!/bin/zsh +set -euo pipefail + +PLIST_SRC="$(cd -- "$(dirname "$0")/.." && pwd)/launchd/com.snapclean.listener.plist" +SCRIPT_SRC="$(cd -- "$(dirname "$0")/.." && pwd)/scripts/auto-snapclean-listener.sh" + +sudo install -m 0755 "$SCRIPT_SRC" /usr/local/bin/auto-snapclean-listener.sh +sudo install -m 0644 "$PLIST_SRC" /Library/LaunchDaemons/com.snapclean.listener.plist +sudo touch /var/log/snapclean.log /var/log/snapclean.out /var/log/snapclean.err +sudo chmod 644 /var/log/snapclean.* + +# (re)load +sudo launchctl unload /Library/LaunchDaemons/com.snapclean.listener.plist 2>/dev/null || true +sudo launchctl load -w /Library/LaunchDaemons/com.snapclean.listener.plist +sudo launchctl list | grep snapclean || true + +echo "✅ snapcleaner installed." \ No newline at end of file diff --git a/scripts/uninstall.sh b/scripts/uninstall.sh new file mode 100644 index 0000000..78a3487 --- /dev/null +++ b/scripts/uninstall.sh @@ -0,0 +1,6 @@ +#!/bin/zsh +set -euo pipefail +sudo launchctl unload /Library/LaunchDaemons/com.snapclean.listener.plist 2>/dev/null || true +sudo rm -f /Library/LaunchDaemons/com.snapclean.listener.plist +sudo rm -f /usr/local/bin/auto-snapclean-listener.sh +echo "✅ snapcleaner deleted. Logs stay in /var/log/snapclean.*" \ No newline at end of file