Skip to content

organicnz/brew-auto-update

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 

History

16 Commits
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

Homebrew Auto-Update

macOS License Homebrew

Production-grade automated Homebrew and NPM package management for macOS only. Runs 3x daily via launchd with intelligent pre-flight checks, differential logging, and graceful error handling.

⚠️ macOS Only: This tool uses macOS-specific features (launchd, plist files) and will not work on Linux or Windows.

Table of Contents

Features

βœ… Smart Pre-flight Checks

  • Network connectivity verification
  • Disk space validation (minimum 5GB)
  • Homebrew installation check

βœ… Comprehensive Updates

  • Update Homebrew itself
  • Upgrade all formulae
  • Upgrade all casks (with --greedy flag)
  • Cleanup old versions (30-day retention)
  • Autoremove unused dependencies

βœ… NPM Integration

  • Checks for global npm installation
  • Updates all global npm packages
  • Skips gracefully if npm is missing

βœ… Robust Error Handling

  • Lock file prevents concurrent runs
  • Automatic stale lock removal (>2 hours)
  • Process cleanup on exit/interrupt
  • Graceful failure handling

βœ… Intelligent Logging

  • Differential logging (only logs changes)
  • Automatic log rotation at 10MB
  • 24-hour log retention
  • Timestamped entries
  • Separate error log

βœ… System Integration

  • Runs 3x daily (9 AM, 3 PM, 9 PM)
  • Low priority I/O and CPU
  • Desktop notifications on completion
  • Health checks and summaries

How It Works

This tool uses launchd (macOS's native task scheduler) to run a compiled Rust binary on schedule.

Architecture

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚                         macOS launchd                          β”‚
β”‚  (reads ~/Library/LaunchAgents/com.USER.brew-update.plist)     β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                              β”‚
                              β–Ό Executes at 9AM, 3PM, 9PM
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚                   ~/Scripts/brew-update                         β”‚
β”‚              (Native ARM64/x86 Mach-O binary)                   β”‚
β”‚                   Compiled from Rust source                     β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                              β”‚
          β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
          β–Ό                   β–Ό                   β–Ό
    β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”       β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”    β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
    β”‚ Pre-flightβ”‚       β”‚ brew update  β”‚    β”‚   Logs    β”‚
    β”‚  Checks   β”‚       β”‚ brew upgrade β”‚    β”‚ & Notify  β”‚
    β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜       β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜    β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Why launchd (not cron)?

macOS deprecated cron in favor of launchd, which offers:

  • Power-aware scheduling β€” skips runs when on battery if configured
  • Missed run recovery β€” runs immediately after wake if a schedule was missed
  • Better process management β€” proper signals, resource limits, sandboxing
  • Native integration β€” works with macOS login/logout seamlessly

The plist Configuration

The plist file tells launchd when and how to run the binary:

<key>ProgramArguments</key>
<array>
    <string>/Users/YOUR_USERNAME/Scripts/brew-update</string>
</array>

<key>StartCalendarInterval</key>
<array>
    <dict><key>Hour</key><integer>9</integer><key>Minute</key><integer>0</integer></dict>
    <dict><key>Hour</key><integer>15</integer><key>Minute</key><integer>0</integer></dict>
    <dict><key>Hour</key><integer>21</integer><key>Minute</key><integer>0</integer></dict>
</array>

The binary is not a script β€” it's compiled native machine code (Mach-O ARM64 on Apple Silicon, x86_64 on Intel), making it fast and dependency-free at runtime.

Installation

Quick Install (Fully Automated)

# Clone the repository
git clone https://github.com/organicnz/brew-auto-update.git
cd brew-auto-update

# Run the installer
cargo run --release --bin install

The installer automatically:

  • Detects your username
  • Detects Homebrew installation path
  • Creates necessary directories
  • Installs and configures everything
  • Runs an initial test

Manual Install

  1. Install and Build:
cargo run --release --bin install
# This will compile the Rust binary and install it to ~/Scripts/brew-update
  1. Install the launchd plist:
# Replace USER with your username
sed "s/USER/$(whoami)/g" com.organic.brew-update.plist > ~/Library/LaunchAgents/com.$(whoami).brew-update.plist

# Load the agent
launchctl load ~/Library/LaunchAgents/com.$(whoami).brew-update.plist
  1. Verify installation:
launchctl list | grep brew-update

Configuration

Installation-Time Variables

Customize installation by setting these before running ./install.sh:

# Schedule (default: 9 AM, 3 PM, 9 PM)
export BREW_UPDATE_HOUR1=8
export BREW_UPDATE_MINUTE1=0
export BREW_UPDATE_HOUR2=14
export BREW_UPDATE_MINUTE2=30
export BREW_UPDATE_HOUR3=20
export BREW_UPDATE_MINUTE3=0

# System settings
export BREW_UPDATE_NICE_LEVEL=10              # CPU priority (0-20, higher = lower priority)
export BREW_UPDATE_THROTTLE_INTERVAL=300      # Min seconds between runs
export BREW_UPDATE_EXIT_TIMEOUT=7200          # Max runtime (2 hours)
export BREW_UPDATE_LOG_RETENTION_DAYS=1       # Keep logs for 24 hours
export BREW_UPDATE_MIN_DISK_SPACE_GB=5        # Minimum free space required

# Then install
cargo run --release --bin install

Runtime Variables

These can be set in the script or plist environment:

export LOG_DIR="$HOME/Library/Logs"           # Log directory
export LOG_RETENTION_DAYS=1                    # Keep logs for 24 hours
export MIN_DISK_SPACE_GB=5                     # Minimum free space required
export LOCK_TIMEOUT=7200                       # Max runtime (2 hours)
export MAX_LOG_SIZE=10485760                   # Log rotation size (10MB)

Schedule

Edit the plist file to change run times. Default schedule:

  • 9:00 AM
  • 3:00 PM
  • 9:00 PM
<key>StartCalendarInterval</key>
<array>
    <dict>
        <key>Hour</key>
        <integer>9</integer>
        <key>Minute</key>
        <integer>0</integer>
    </dict>
    <!-- Add more time slots as needed -->
</array>

Usage

Automatic Runs

Once installed, the script runs automatically on schedule. No action needed!

Manual Run

~/Scripts/brew-update

View Logs

# Main log
tail -f ~/Library/Logs/brew-updates.log

# Error log
tail -f ~/Library/Logs/brew-updates-error.log

# Launchd output
tail -f ~/Library/Logs/brew-update-stdout.log

Management Commands

# Check status
launchctl list | grep brew-update

# Disable automatic runs
launchctl unload ~/Library/LaunchAgents/com.$(whoami).brew-update.plist

# Enable automatic runs
launchctl load ~/Library/LaunchAgents/com.$(whoami).brew-update.plist

# Trigger immediate run
launchctl start com.$(whoami).brew-update

# View next scheduled run
launchctl print gui/$(id -u)/com.$(whoami).brew-update | grep next

Troubleshooting

Updates aren't running

# Check if loaded
launchctl list | grep brew-update

# Check for errors
tail ~/Library/Logs/brew-update-stderr.log

# Verify binary permissions
ls -la ~/Scripts/brew-update

Lock file stuck

# Remove manually (script auto-removes stale locks >2 hours)
rm -f /tmp/brew-update.lock

No network notification

The script will skip updates and notify you if no network is available. This is normal behavior.

Uninstallation

# Unload the agent
launchctl unload ~/Library/LaunchAgents/com.$(whoami).brew-update.plist

# Remove files
rm ~/Library/LaunchAgents/com.$(whoami).brew-update.plist
rm ~/Scripts/brew-update
rm -rf ~/Library/Logs/brew-update*

Requirements

  • macOS 10.14 (Mojave) or later (required)
  • Homebrew installed (install here)
  • Rust/Cargo installed (for building)
  • Write access to ~/Library/Logs

Note: This tool is designed exclusively for macOS and uses launchd for scheduling. It will not work on Linux or Windows systems.

Security

  • Runs as user (not root)
  • No sudo required
  • Sandboxed to user environment
  • Safe PATH configuration

Development

Setup Development Environment

# Clone the repo
git clone https://github.com/organicnz/brew-auto-update.git
cd brew-auto-update

# Setup development tools (lefthook, rustfmt, clippy)
cargo run --bin setup

Pre-commit Hooks

Lefthook runs automatically on commit:

  • ShellCheck linting
  • Bash syntax validation
  • Plist template validation
  • Secrets detection
  • Markdown linting

Run manually:

lefthook run pre-commit

Documentation

Contributing

Contributions welcome! Please see Contributing Guidelines for details.

License

MIT License - see LICENSE for details

Author

Created for automated Homebrew maintenance on macOS systems.

Changelog

v1.0.0

  • Initial release
  • Network connectivity check
  • Disk space validation
  • Differential logging
  • Automatic log rotation
  • 3x daily scheduling
  • Desktop notifications

v1.1.0

  • Added global NPM package auto-updates

v2.0.0

  • Rewrite in Rust for safety and performance
  • Native binary instead of Bash script

About

No description, website, or topics provided.

Resources

License

Contributing

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors

Languages