A comprehensive cross-platform system update script that handles package management, service restarts, and Docker maintenance across Linux and macOS systems. Features auto-yes by default with optional interactive mode and granular control over update operations.
- Linux: Ubuntu, Fedora, RHEL/CentOS
- macOS: Full macOS support with multiple package managers
- Automatic OS Detection: Runs appropriate commands based on detected system
- Smart Service Restart: Detects and restarts services that need it (Fedora/RHEL)
- System Reboot Detection: Prompts for reboot when required
- Pending Actions Summary: Provides a clear, consolidated list of required follow-up actions, such as system reboots, at the end of the script.
- Docker Container Management: Only restarts containers when updates are detected
- Vim Plugin Management: Automatically updates Vim plugins (Vundle and vim-plug).
- Tmux Plugin Management: Automatically updates tmux plugins via TPM if
~/.tmux/plugins/tpmis present. - Oh My Zsh: Keeps your Oh My Zsh installation up-to-date (macOS only).
- Graceful Fallbacks: Skips unavailable package managers without errors
sudo apt update- Update package listssudo apt upgrade- Upgrade installed packages
- Snap Packages:
sudo snap refresh - Flatpak:
flatpak update --appstream && flatpak update -y - Firmware:
fwupdmgr refresh && fwupdmgr get-updatesβ the script defaults to auto-yes and will automatically apply firmware updates when detected (it will runfwupdmgr refresh --forcethenfwupdmgr updatewhen applying). If you prefer prompts, run the script with-i/--interactiveto confirm before applying. Use--apply-firmwareor set"apply-firmware": truein the config to enable unattended apply; use--skip-firmwareto skip firmware checks entirely.
- pip3: Updates outdated Python packages
- Docker: Compose pull/restart + system prune
sudo dnf upgrade- Update all packages- Smart Restart Detection: Uses
needs-restartingto detect:- Services requiring restart (
needs-restarting -s) - System reboot requirements (
needs-restarting -r) - Automatic service restart with user confirmation (or use
--service-restartto skip confirmation) - System reboot prompt with 10-second countdown
- Services requiring restart (
- Snap Packages:
sudo snap refresh - Flatpak:
flatpak update --appstream && flatpak update -y - Firmware:
fwupdmgr refresh && fwupdmgr get-updatesβ the script defaults to auto-yes and will automatically apply firmware updates when detected (it will runfwupdmgr refresh --forcethenfwupdmgr updatewhen applying). If you prefer prompts, run the script with-i/--interactiveto confirm before applying. Use--apply-firmwareor set"apply-firmware": truein the config to enable unattended apply; use--skip-firmwareto skip firmware checks entirely.
- pip3: Updates outdated Python packages
- Docker: Compose pull/restart + system prune
softwareupdate -ia- Install all available macOS system updates
- Homebrew:
brew update- Update Homebrew itselfbrew upgrade- Upgrade formulaebrew upgrade --cask- Upgrade casksbrew autoremove- Remove outdated downloadsbrew cleanup- Clean up old versions
- Mac App Store:
mas outdated && mas upgrade(via mas CLI)
- Ruby Gems:
gem outdated --user-install && gem update --user-install(user gems only) - npm: Global and user packages (
npm outdated -g && npm update -g,npm outdated && npm update) - pip3: System and user Python packages with separate handling
- Vim Plugins: Vundle:
vim +PluginUpdate +qall(if installed); vim-plug:vim +PlugUpgrade +PlugUpdate --sync +qall(if installed) - Tmux Plugins:
~/.tmux/plugins/tpm/update_plugins all(TPM, if installed) - Oh My Zsh:
omz update(if installed) - Docker: Compose pull/restart + system prune
- Smart Pull:
docker-compose pullwith update detection - Conditional Restart: Only restarts containers when updates are detected
- Update Detection: Scans pull output for actual image updates
When container image updates are detected the script performs a targeted restart:
- Parses the
docker-compose.ymlto build a dependency graph - Identifies which containers use the updated images
- Walks the dependency graph to find all affected dependents
- Stops only those containers in reverse dependency order
- Runs
docker-compose up -dto bring everything back up in the correct order with full health check awareness
This means unrelated containers are never restarted, minimising downtime.
If the compose file cannot be parsed, the script falls back to a full stack restart automatically.
βΉοΈ Note: PyYAML is required for docker-compose dependency graph parsing and is automatically installed with the Homebrew formula.
- System Cleanup:
docker system prunewith auto-confirmation (dangling images only β layer cache is preserved to ensure accurate update detection on subsequent runs)
# Add the tap
brew tap waynelloyd/system-updater
# Install system-updater (includes PyYAML for docker-compose support)
brew install system-updater
# Run it
system-updater# Download and make executable
curl -fsSL https://raw.githubusercontent.com/waynelloyd/homebrew-system-updater/refs/heads/main/system-updater.py -o system-updater
chmod +x system-updater
sudo mv system-updater /usr/local/bin/
# Install PyYAML for docker-compose support
pip3 install PyYAML>=6.0# Ubuntu/Debian
sudo apt install flatpak fwupd
# Fedora/RHEL
sudo dnf install flatpak fwupd# Install Homebrew (if not already installed)
/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
# Install optional tools
brew install mas # Mac App Store CLI# Run all updates (auto-yes is default)
system-updater
# Run with manual confirmation prompts
system-updater -i
# or
system-updater --interactive# Skip system package updates
system-updater --skip-os-updates
# Skip snap packages (Linux)
system-updater --skip-snap
# Skip Flatpak updates (Linux)
system-updater --skip-flatpak
# Skip firmware updates (Linux)
system-updater --skip-firmware
# Skip pip package updates
system-updater --skip-pip
# Skip Vim plugin updates
system-updater --skip-vim
# Skip Tmux plugin updates (TPM)
system-updater --skip-tmux
# Skip Oh My Zsh update (macOS)
system-updater --skip-omz
# Skip Docker operations
system-updater --skip-docker-pull --skip-docker-pruneThe script supports a per-user config file at ~/.config/system-updater/config.json. Keys in this file are used as defaults for command-line flags (you can still pass flags on the command-line to override them).
Example ~/.config/system-updater/config.json:
{
"skip-docker-prune": true,
"skip-tmux": true,
"apply-firmware": false
}Both hyphenated and underscored key styles are accepted (e.g. skip-docker-prune or skip_docker_prune).
Tip: run system-updater --help to see a short config-file example in the help output.
You can run an interactive configuration wizard to write ~/.config/system-updater/config.json:
system-updater --configureTo print the effective configuration (config file merged with CLI flags):
system-updater --print-configTmux plugin updates are performed automatically when TPM is installed at ~/.tmux/plugins/tpm. They run by default as part of the normal update flow unless you explicitly skip them with --skip-tmux or set skip-tmux in the config file. Example:
{
"skip-tmux": true
}Note: TPM updates run by default (no separate "run-only" flag). To skip them permanently set skip-tmux in ~/.config/system-updater/config.json or pass --skip-tmux on the command-line.
system-updater --helpAvailable Options:
-i, --interactive- Interactive mode with prompts (default is auto-yes)--skip-os-updates- Skip system package updates--skip-snap- Skip snap refresh (Linux only)--skip-flatpak- Skip Flatpak updates (Linux only)--skip-pip- Skip pip package updates--skip-vim- Skip Vim plugin updates (both Vundle and vim-plug)--skip-tmux- Skip tmux plugin updates (TPM)--skip-omz- Skip Oh My Zsh update (macOS only)--skip-firmware- Skip firmware updates (Linux only)--skip-docker-pull- Skip docker-compose pull--skip-docker-prune- Skip docker system prune--apply-firmware- Automatically apply firmware updates when detected (runs a forced refresh and applies updates). Use with caution on servers.--service-restart- Fedora/RHEL only. Automatically restart services detected bydnf needs-restartingwithout confirmation. If not set, you will be prompted to confirm service restarts (y/n).--print-config- Print the effective configuration (config file merged with CLI flags) and exit.--configure- Launch the interactive configurator to create or update~/.config/system-updater/config.jsonand exit.
Failure summary behavior: The script now collects failures and issues encountered during individual tasks and prints an "ISSUES / FAILURES" section at the end of the run. If any failures were recorded the script will exit with a non-zero exit code so you can detect problems in automation.
# Update everything (auto-yes by default)
system-updater# Update only development tools
system-updater --skip-os-updates --skip-firmware# Update system packages and Docker, skip desktop apps
system-updater --skip-snap --skip-flatpak- Service Restart Confirmation: Always prompts before restarting services (unless
--service-restartis used) - System Reboot Safety: Requires manual confirmation even with
-yflag - Pending Actions Summary: Summarizes all required manual steps (like reboots) at the end, so you don't miss anything.
- Graceful Failures: Continues operation if individual package managers fail, and records failures for later review.
- Update Detection: Only restarts Docker containers when actual updates occur
- Comprehensive Logging: Clear output showing what's being updated and why
π Starting system update process...
Mode: Auto-yes
Detected OS: macos
==================================================
Running: Updating Homebrew
Command: brew update
==================================================
β
Updating Homebrew completed successfully
...
(other updates)
...
==================================================
π PENDING ACTIONS
==================================================
- A restart is required to complete the installation of some macOS updates.
==================================================
π§ ISSUES / FAILURES
==================================================
- Docker-compose pull failed in /home/user/projects/app with exit code 1 (command: docker-compose pull)
π SUMMARY
==================================================
Tasks completed successfully: 10/11
β οΈ Some tasks failed or need attention. Check the 'ISSUES / FAILURES' and output above for details.
- Python 3.6+
- sudo privileges (for system package updates)
- Docker (optional, for Docker operations)
- PyYAML (required for docker-compose operations, automatically installed with Homebrew)
- Searches ~/ directory for compose.yml (optional, for Docker compose operations)
This script is provided as-is for system maintenance purposes. Use at your own discretion and always test in a safe environment first.
- Added: Hard-coded vim-plug parallelism to 4;
PlugUpdatenow runs with--sync 4to limit parallelism during plugin updates. - Changed: Vim-plug update command uses the interactive form to show progress:
+PlugUpgrade +PlugUpdate --sync 4 +qall. - Docs: README updated to document the new vim-plug invocation.
- Note: Vundle behavior unchanged.
- Added: Support for vim-plug alongside Vundle. The script now detects common vim-plug locations for Vim and Neovim and updates plugins when found.
- Changed: Vim update functions renamed for clarity:
update_vim_plugins_vundle(Vundle) andupdate_vim_plugins_vimplug(vim-plug). - Changed: Both Vundle and vim-plug now stream vim's output to the terminal (via the same
run_commandpath) so plugin update progress is visible and consistent. - Changed:
--skip-vimnow skips both Vundle and vim-plug update steps. - Improved: macOS softwareupdate step now announces "Checking for macOS system updates" and only runs the installer if updates are detected; prints a no-updates message when none are available and records restart requirements as pending actions.
- Changed: Vim update steps now prefer the interactive +commands (e.g.
+PlugUpdate,+PluginUpdate) so progress is shown when running from a shell.
- Fixed: Service detection now correctly prioritizes system services over user services, preventing attempts to restart system services with
systemctl --user - Fixed: Service restart now checks if services are enabled and/or running, skipping disabled/inactive services that don't need restart
- Fixed: Services configured to refuse manual start/stop (dependency-only services like systemd-tmpfiles-setup.service) are now automatically detected and skipped to prevent restart failures
- Improved: Added detailed status information when skipping services (disabled/inactive vs. dependency-only)
- Added: Progress bar (spinner) for docker-compose up -d operations to show activity during container updates
- Improved: Service restart logic now detects user services (systemctl --user) vs. system services (sudo systemctl) and uses the appropriate command
- Fixed: Docker-compose updates now properly remove stopped dependent containers before recreating providers, preventing "container already exists" errors during sidecar dependant container updates
- Changed: PyYAML is now a required dependency instead of optional, simplifying installation and removing runtime error handling
- Removed: MacUpdater support completely removed from the script and documentation
- Homebrew: Updated to use system Python instead of requiring Homebrew's Python version, and always install PyYAML>=6.0 in the virtualenv
- Simplified: Removed optional dependency checks and error handling for missing PyYAML
- Fixed:
:latesttag stripped from image names in digest comparison to match compose file references that omit the tag, ensuring correct restart target identification
- Added:
import yamlto handle docker-compose parsing correctly - Fixed: Improved targeted restart logic β now only explicitly stops network-dependent sidecars (
network_mode: service:ornetwork_mode: container:) of updated services - Fixed: Reliably use
docker-compose up -dto handle recreation of updated services and restart of dependents in the correct order, avoiding unnecessary full-stack restarts - Improved: Code cleanup β removed unused variables and redundant function parameters
- Fixed: Removed debug output lines for cleaner production release
- Fixed: Prevented duplicate image entries in updated containers summary by adding deduplication logic
- Fixed: Fixed detect when sidecar containers in podman are in the restart targets and fall back to a full docker-compose down/up to fix dependant containers error
- Fixed: Digest-pinned images with
@sha256:references (e.g. immich-app/postgres) no longer appear as<none>in the updated containers list or trigger false restarts
- Fixed: Image update detection completely reworked β now uses pre/post pull image ID comparison via
docker imagesinstead of parsing Podman pull output, eliminating false positives on every run - Fixed: Digest-pinned images (e.g. containers with
@sha256:tag) now tracked correctly by stripping<none>tags and using repository name as key - Fixed:
docker.io/library/prefix normalised so official Docker Hub images (nginx, redis, postgres etc.) are compared correctly - Fixed: Removed unreliable
Pulling fs layerbased live progress tracking which caused duplicate download messages due to parallel pull interleaving - Simplified:
stream_readerandplain_readermerged into single function since progress tracking moved to digest comparison - Simplified:
downloading_imagesandcurrent_imagetracking removed as no longer needed
- Added: Targeted container restart β only stops containers affected by updates and their dependents, leaving unrelated containers running
- Added: Compose file dependency graph parser using PyYAML to determine restart scope
- Added:
network_mode: container:awareness in dependency graph. podman-docker comptibility doesn't handle this well - Added: Live pull progress output β shows which images are downloading with spinner between updates
- Fixed: Image update detection now tracks layer-level activity (
Pulling fs layer) rather than matchingImage ... Pulledwhich fired for all checked images - Fixed: Duplicate image entries in updated containers summary caused by thread race condition
- Fixed: Garbled Podman terminal output during pull by setting
TERM=dumbandNO_COLOR=1 - Fixed: stdout and stderr both being parsed for pull progress causing duplicate detections β stdout now uses a plain reader
- Changed:
docker system prune -areplaced withdocker system pruneto preserve image layer cache and prevent false positive update detection on consecutive runs - Changed: Restart logic no longer requires full
docker-compose downβ targeted stop followed bydocker-compose up -dhandles all runtimes including Podman