Update Any Linux Distribution from One Script

What this does

Detects which Linux distribution is running and runs the matching update commands — apt on Debian/Ubuntu, dnf on Fedora/RHEL, pacman on Arch, zypper on openSUSE. Adds Flatpak and Snap updates if those are installed. Supports a --dry-run mode that prints the commands without running them.

When this is useful

If you maintain a handful of Linux machines that aren't all on the same distribution, you end up running roughly the same sequence of commands on each one. A small wrapper script means you don't have to remember which flag belongs to which package manager. The same script also works inside Ansible's shell module, on a Raspberry Pi, on a desktop and on a server — one mental model, four implementations.

This is not a replacement for unattended-upgrades or dnf-automatic. Those are better for the always-on case. This script is for the times you want to step through updates by hand, or you want to call something from a cron job that produces predictable output.

The script

update-system.sh
#!/usr/bin/env bash
#
# update-system.sh — update the current Linux system, whichever
# distribution it is. Supports apt, dnf, pacman, zypper, plus Flatpak
# and Snap if they are installed.
#
# Usage:
#   ./update-system.sh            # actually run the update
#   ./update-system.sh --dry-run  # print the commands without running them
#   ./update-system.sh --yes      # pass non-interactive flags through
#
set -euo pipefail

DRY_RUN=0
ASSUME_YES=0

for arg in "$@"; do
  case "$arg" in
    --dry-run) DRY_RUN=1 ;;
    --yes|-y)  ASSUME_YES=1 ;;
    -h|--help)
      sed -n '2,12p' "$0"; exit 0 ;;
    *) echo "Unknown argument: $arg" >&2; exit 2 ;;
  esac
done

run() {
  if [ "$DRY_RUN" -eq 1 ]; then
    printf '%s\n' "DRY-RUN: $*"
  else
    printf '\n--> %s\n' "$*"
    eval "$@"
  fi
}

# Identify the distribution family.
. /etc/os-release

YES_FLAG_APT=""
YES_FLAG_DNF=""
YES_FLAG_PACMAN=""
YES_FLAG_ZYPPER=""
if [ "$ASSUME_YES" -eq 1 ]; then
  YES_FLAG_APT="-y"
  YES_FLAG_DNF="-y"
  YES_FLAG_PACMAN="--noconfirm"
  YES_FLAG_ZYPPER="--non-interactive"
fi

# A reusable "use sudo if we are not root" helper.
SUDO=""
if [ "$(id -u)" -ne 0 ]; then
  SUDO="sudo"
fi

case "$ID:${ID_LIKE:-}" in
  *debian*|ubuntu:*|*ubuntu*)
    run "$SUDO apt-get update"
    run "$SUDO apt-get $YES_FLAG_APT upgrade"
    run "$SUDO apt-get $YES_FLAG_APT full-upgrade"
    run "$SUDO apt-get $YES_FLAG_APT autoremove --purge"
    run "$SUDO apt-get autoclean"
    ;;
  fedora:*|*fedora*|rhel:*|*rhel*|centos:*|*centos*|rocky:*|*rocky*|almalinux:*|*almalinux*)
    run "$SUDO dnf $YES_FLAG_DNF upgrade --refresh"
    run "$SUDO dnf $YES_FLAG_DNF autoremove"
    ;;
  arch:*|*arch*|manjaro:*|*manjaro*|endeavouros:*|*endeavouros*)
    run "$SUDO pacman -Syu $YES_FLAG_PACMAN"
    # Optional: clean orphaned packages
    if pacman -Qtdq >/dev/null 2>&1; then
      run "$SUDO pacman -Rns $YES_FLAG_PACMAN \$(pacman -Qtdq)"
    fi
    ;;
  opensuse*|*suse*|sles:*)
    run "$SUDO zypper $YES_FLAG_ZYPPER refresh"
    run "$SUDO zypper $YES_FLAG_ZYPPER update"
    ;;
  *)
    echo "Unrecognised distribution: ID=$ID, ID_LIKE=${ID_LIKE:-}" >&2
    exit 1
    ;;
esac

# Flatpak, if installed.
if command -v flatpak >/dev/null 2>&1; then
  if [ "$ASSUME_YES" -eq 1 ]; then
    run "flatpak update -y"
  else
    run "flatpak update"
  fi
fi

# Snap, if installed.
if command -v snap >/dev/null 2>&1; then
  run "$SUDO snap refresh"
fi

echo
echo "Done. A reboot is required if the kernel or glibc was updated."

How to use it

Save it and make it executable
# Save the script above to update-system.sh, then:
chmod +x update-system.sh

# Run interactively
./update-system.sh

# See what it would do without running it
./update-system.sh --dry-run

# Run non-interactively (good for cron / systemd timers)
./update-system.sh --yes

Worked example

On a freshly installed Debian system the output looks like this:

$ ./update-system.sh --dry-run
DRY-RUN: sudo apt-get update
DRY-RUN: sudo apt-get  upgrade
DRY-RUN: sudo apt-get  full-upgrade
DRY-RUN: sudo apt-get  autoremove --purge
DRY-RUN: sudo apt-get autoclean

On the same physical machine after switching to Fedora, the same script does this instead:

$ ./update-system.sh --dry-run
DRY-RUN: sudo dnf  upgrade --refresh
DRY-RUN: sudo dnf  autoremove

The point is not that the commands are dramatically different (they aren't), it's that you don't have to remember which one applies to which machine.

Scheduling it

If you want this to run on a schedule, the modern way is a systemd timer. The systemd basics page covers the mechanics in more depth, but the short version is:

# /etc/systemd/system/update-system.service
[Unit]
Description=Run the cross-distro update script

[Service]
Type=oneshot
ExecStart=/usr/local/sbin/update-system.sh --yes
# /etc/systemd/system/update-system.timer
[Unit]
Description=Run update-system weekly

[Timer]
OnCalendar=Sun *-*-* 03:00:00
Persistent=true
RandomizedDelaySec=30m

[Install]
WantedBy=timers.target
sudo systemctl daemon-reload
sudo systemctl enable --now update-system.timer
systemctl list-timers

Common mistakes

  • Running --yes on Arch. --noconfirm on pacman is a sharp tool. It will happily accept replacement packages and configuration changes you would normally see. Leave the assume-yes flag off on rolling-release distributions unless you have read the news page first.
  • Forgetting to reboot after a kernel update. The script prints a reminder, but the new kernel does not take effect until the next boot. See the Linux kernel overview for context.
  • Trying to run this from a non-interactive shell without a TTY. Without --yes, the package manager will sit waiting for input. From cron or a systemd timer, pass --yes.
  • Skipping the dry run. The script is short enough to read, but the dry-run output is the easiest way to confirm it identified your distribution correctly.

Related reading