Skip to content

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 

History

9 Commits
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

πŸ”„ Auto Update Programs

A shell function that runs your update commands at most once every N days, triggered when you open a new terminal. Keeps development tools current without you remembering to do it.

✨ Features

  • ⏰ Time-based execution: Only runs updates after a specified number of days
  • πŸ”§ Configurable commands: Define your own list of update commands
  • 🌱 Git repositories too: Point it at a local clone and it fast-forwards it β€” only when that needs no merge
  • πŸ“ Timestamp tracking: Remembers when updates were last run
  • ⚑ Command validation: Skips an entry when its program isn't installed
  • πŸ”’ Concurrency safe: Opening ten tabs at once still runs the updates once
  • πŸš€ Cheap startup: The "nothing to do" path uses shell builtins only β€” no subprocesses
  • πŸ” Status checking: View when updates last ran and when they're next due
  • πŸ”„ Manual reset: Force updates to run on next terminal start

πŸ“‹ Requirements

  • bash 4.2+ or zsh (both are sourced the same way)
  • Works on macOS's stock bash 3.2 too β€” it just falls back to calling date
  • git 1.8.5+ only if you list git repositories to update

βš™οΈ Installation

  1. Download the script:

    mkdir -p ~/Scripts/auto_update_programs
    curl -o ~/Scripts/auto_update_programs/auto_update_programs.sh \
      https://raw.githubusercontent.com/Code-Samples-Galore/Terminal-Auto-Update-Programs/main/auto_update_programs.sh
  2. Source it in your shell config:

    Add to your ~/.zshrc or ~/.bashrc:

    source ~/Scripts/auto_update_programs/auto_update_programs.sh
    auto_update_check 7 "brew update && brew upgrade" "npm update -g"

    The file is meant to be sourced, not executed β€” running it directly just defines the functions in a shell that immediately exits. No chmod +x needed.

πŸš€ Usage

πŸ”° Basic Usage

Add this to your ~/.zshrc or ~/.bashrc to run updates every 7 days:

auto_update_check 7 "brew update && brew upgrade" "npm update -g" "pip install --upgrade pip"

πŸ§‘β€πŸ’» Advanced Usage with Array

For better organization, use an array:

# Define your update commands
MY_UPDATE_COMMANDS=(
    "conda update -n base -c defaults conda -y"
    "brew update && brew upgrade"
    "npm update -g"
    "pip install --upgrade pip"
    "rustup update"
    "gem update --system"
)

# Run every 3 days
auto_update_check 3 "${MY_UPDATE_COMMANDS[@]}"

🌱 Keeping Git Repositories Up To Date

Any entry that is a path to a directory is treated as a local git repository instead of a command, and is pulled on the same schedule:

auto_update_check 7 "brew update && brew upgrade" ~/Projects/dotfiles "$HOME/src/notes"

Quoted ~ paths work too β€” "~/Projects/dotfiles" is expanded before the directory is looked up.

Repositories are only ever fast-forwarded. If your branch can't simply be moved forward onto its upstream, the repository is reported and left exactly as it was β€” nothing is merged, rebased, stashed, or committed on your behalf:

Situation What happens
Behind upstream, nothing local βœ… Fast-forwarded, and the number of new commits is printed
Already up to date βœ… Reported as current
Uncommitted changes to tracked files ⏭️ Skipped β€” commit or stash them yourself
Local commits, or diverged from upstream ⏭️ Skipped β€” a merge is yours to make
Detached HEAD, or a branch with no upstream ⏭️ Skipped
Merge, rebase or cherry-pick in progress ⏭️ Skipped
Not a git repository, or a bare one ⏭️ Skipped
Fetch failed (offline, no credentials) ❌ Reported as a failure

A path pointing inside a repository works β€” the repository containing it is the one that gets updated, and the message names its top-level directory.

πŸ› οΈ Available Functions

  • auto_update_check [days] [commands-or-repo-paths...] β€” Check the interval and run updates if it elapsed
  • auto_update_status β€” Show when updates last ran and when they're next due
  • auto_update_reset β€” Clear state so the next terminal start updates

πŸ’‘ Examples

# Check every 5 days with specific commands
auto_update_check 5 "brew update && brew upgrade" "npm update -g"

# Mix commands and git repositories freely
auto_update_check 5 "brew update && brew upgrade" ~/Projects/dotfiles

# Use default commands (conda, brew, npm) every 7 days
auto_update_check 7

# Check status
auto_update_status

# Force updates on next terminal start
auto_update_reset

πŸ“ Configuration

🌱 Environment Variables

Variable Default Purpose
AUTO_UPDATE_DISABLE unset Set to any non-empty value to skip the check entirely. Useful in CI, scripts, and remote sessions.
AUTO_UPDATE_INTERVAL 7 Interval auto_update_status assumes when no run has been recorded yet.
AUTO_UPDATE_LOCK_TIMEOUT 21600 (6h) Seconds before a lock left behind by an interrupted run is considered stale.

πŸ—‚οΈ State Files

Path Contents
~/.auto_update_timestamp Unix epoch of the last run
~/.auto_update_interval Interval the last run used, so status reports the real next run
~/.auto_update_lock Lock directory held only while updates are running

Deleting these resets the timer β€” auto_update_reset does exactly that.

πŸ—οΈ Default Commands

If no commands are provided, these defaults are used:

  • conda update -n base -c defaults conda -y
  • brew update && brew upgrade
  • npm update -g

🎨 Customization

You can customize the commands for your specific setup:

# For Python developers
PYTHON_UPDATES=(
    "pip install --upgrade pip"
    "conda update --all -y"
    "pipx upgrade-all"
)

# For Node.js developers
NODE_UPDATES=(
    "npm update -g"
    "yarn global upgrade"
    "pnpm update -g"
)

# For Rust developers
RUST_UPDATES=(
    "rustup update"
    "cargo install-update -a"
)

# Combine and run
auto_update_check 7 "${PYTHON_UPDATES[@]}" "${NODE_UPDATES[@]}" "${RUST_UPDATES[@]}"

🧩 How It Works

  1. When you start a terminal, the function compares now against the recorded timestamp. If the interval hasn't elapsed it returns immediately, without spawning a single subprocess.
  2. Otherwise it takes a lock, so several shells starting at once produce exactly one update run.
  3. It records the timestamp before running anything, claiming the interval. An update interrupted halfway through therefore won't restart on every subsequent terminal.
  4. Each entry that names an existing directory is handled as a git repository: it is fetched, and fast-forwarded only if that requires no merge.
  5. Every other entry is checked for its leading program and skipped if that program isn't installed.
  6. Remaining entries run in order via eval; successes and failures are both reported.
  7. The lock is released and a summary is printed.

⚠️ Things Worth Knowing

  • Updates block your prompt. The commands run synchronously during shell startup, so a large brew upgrade delays the terminal you just opened. Set AUTO_UPDATE_DISABLE=1 for shells where that's unacceptable.
  • Commands run through eval. That's what makes "brew update && brew upgrade" work as one entry. Only put commands you trust in your shell config.
  • Only the leading program of an entry is checked. In "brew update && npm -g update", a missing npm isn't caught in advance β€” it surfaces as a normal command failure.
  • The timestamp is kept even if commands fail. A permanently broken entry would otherwise retry on every single terminal start. Use auto_update_reset to retry sooner.
  • Repository entries hit the network. Each one runs a git fetch, so a slow or unreachable remote delays your prompt just like a slow command does.
  • Credential prompts are disabled during the fetch. A repository whose credentials aren't cached (and an SSH key that isn't in an agent) fails quickly and is reported, rather than stopping your shell startup at a password prompt.
  • Submodules are left alone. Only the repository you name is fast-forwarded.
  • An entry is a repository if the directory exists. A path with a typo in it doesn't match any directory, so it falls through and is reported as missing β€” check the spelling if a repository is never picked up.

πŸ› οΈ Troubleshooting

Updates not running?

  • Check auto_update_status to see when they last ran and when they're next due
  • Use auto_update_reset to force them to run
  • Confirm AUTO_UPDATE_DISABLE isn't set

Command not found errors?

  • The script skips entries whose leading program isn't installed
  • Make sure your PATH is set before auto_update_check runs in your shell config

auto_update_status says the state file is corrupt?

  • Run auto_update_reset to clear it

A git repository is never updated?

  • The run prints a reason for every repository it leaves alone β€” uncommitted changes, local commits, a detached HEAD or a missing upstream are the common ones
  • git -C /path/to/repo status -sb shows the same state the script is reacting to
  • If the message says "no such file or directory", the path in your shell config doesn't exist

Status says an update is in progress but nothing is happening?

  • An interrupted run can leave ~/.auto_update_lock behind. It's reclaimed automatically after AUTO_UPDATE_LOCK_TIMEOUT (6h by default), or immediately via auto_update_reset.

Want to test without waiting?

  • Use auto_update_reset then restart your terminal
  • Or call auto_update_check 0 "your commands" to bypass the time check

πŸ“„ License

MIT License

About

Auto update terminal programs.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Contributors

Languages