One update and cleanup script for Debian and Ubuntu (and other apt-based derivatives).
Version: VERSION file, or ./update-clean.sh --version. See CHANGELOG.md for history.
For Ubuntu AI / GPU compute blades, see ai_blade_ubuntu_update_clean.
Update: fix interrupted installs, then apt-get update / upgrade / full-upgrade and apt-get check. Mutating work uses apt-get, not apt(8).
Cleanup: purge autoremove, autoclean, residual configs, old kernels (running + the KERNEL_KEEP newest extras), Snap/Flatpak when present, fwupd when installed, journal vacuum (JOURNAL_VACUUM_TIME, default 30d), optional regenerable pip/go/uv caches (CLEAN_DEV_CACHES, default off), partial apt lists, man/locate DBs, GRUB after kernel changes.
Also: Debian vs Ubuntu archive host for connectivity, disk change on / /var /boot, log rotation, last-run record, root / disk / APT-lock checks.
sudo ./update-clean.shsudo ./update-clean.sh --dry-run
sudo ./update-clean.sh --check
sudo ./update-clean.sh --last
sudo ./update-clean.sh --no-kernel
sudo ./update-clean.sh --reboot-if-required
sudo ./update-clean.sh --offline--dry-run skips apt-get update and logs planned apt commands. It may still use the network for read-only listings.
Kernel keep count, log retention, and similar knobs live in the config file, not as extra flags. See update-clean.conf.example.
Requires Bash 4+ (#!/usr/bin/env bash). Do not run under /bin/sh.
From the latest GitHub Release (no version pin):
curl -fsSL -o debian_ubuntu_update_clean.tar.gz \
https://github.com/wbharris/debian_ubuntu_update_clean/releases/latest/download/debian_ubuntu_update_clean.tar.gz
tar -xzf debian_ubuntu_update_clean.tar.gz
cd debian_ubuntu_update_clean
sudo install -m 755 update-clean.sh /usr/local/sbin/update-clean.sh
sudo cp systemd/update-clean.service systemd/update-clean.timer /etc/systemd/system/
sudo systemctl daemon-reload
sudo systemctl enable --now update-clean.timerFrom git (development):
git clone https://github.com/wbharris/debian_ubuntu_update_clean.git
cd debian_ubuntu_update_clean
sudo install -m 755 update-clean.sh /usr/local/sbin/update-clean.shRun weekly. Prefer a maintenance window if you use --reboot-if-required.
| Code | Meaning |
|---|---|
0 |
Success |
1 |
One or more steps failed (FAILURES in the last-run record) |
Inspect modes (--check, --last, --version) do not take the instance lock.
Sourced in order if present:
/etc/update-clean.conf(must be root-owned)- As root:
/root/.config/update-clean.conf,/root/.update-clean.conf - Via
sudo: also the invoking user's~/.config/update-clean.confand~/.update-clean.conf - As a normal user:
$HOME/.config/update-clean.conf,$HOME/.update-clean.conf
KERNEL_KEEP=2
BACKUP_MODE=false
REBOOT_IF_REQUIRED=false/etc/update-clean.conf must be owned by root. World-writable configs are skipped. User-level configs are still only as trustworthy as the account that wrote them.
Config loads after CLI parsing; explicit flags win.
| Variable | Default | Meaning |
|---|---|---|
KERNEL_KEEP |
2 |
Newest extra kernels besides the running one (oldest extras are purged) |
BACKUP_MODE |
false |
Tar /etc before purging residual configs (excludes /etc/ssl/private) |
REBOOT_IF_REQUIRED |
false |
Auto-reboot when /var/run/reboot-required is set |
LOG_RETENTION |
3 |
Runs to keep under /var/log/update-clean (log and its .apt-warnings sidecar) |
JOURNAL_VACUUM_TIME |
30d |
journalctl --vacuum-time window |
CLEAN_DEV_CACHES |
false |
If true, remove /root (and sudo user) .cache/{pip,go-build,uv}. Off by default so servers do not rebuild package caches every week. |
Further keys (LOG_DIR, LOCKFILE, APT_LOCK_WAIT_SECS, ADMIN_EMAIL, CRITICAL_PACKAGES, …) are in update-clean.conf.example.
Recommended packages: psmisc or lsof (APT lock holders), jq (last-run JSON and snap cleanup).
- Logs:
/var/log/update-clean/(retention viaLOG_RETENTION). A run's.apt-warningssidecar lists apt/dpkg warnings and errors only; routine apt output stays in the main log. - Last run:
/var/lib/update-clean/last-run - JSON (when
jqis installed):/var/lib/update-clean/last-run.json sudo ./update-clean.sh --lastprints the record and the last 80 log lines- Disk change is
dfon/,/var,/bootonly. If usage grew, the summary says it increased (not a negative “freed”) needrestart(if installed) only lists services that need a restart; this script does not restart them
- Root required except
--check/--version/--last/--help - Needs at least 2 GB free on
/and/var;/boothard abort is 100 MB - Keeps the running kernel plus the
KERNEL_KEEPnewest other images. Recovery: GRUB → previous kernel --reboot-if-requiredreboots if/var/run/reboot-requiredexists (including from a prior run)- Non-critical steps do not abort the run
- The bundled timer does not pass
--reboot-if-required
Weekly, Sunday 04:00:
0 4 * * 0 /usr/local/sbin/update-clean.shOr the bundled timer:
sudo install -m 755 update-clean.sh /usr/local/sbin/update-clean.sh
sudo cp systemd/update-clean.service systemd/update-clean.timer /etc/systemd/system/
sudo systemctl daemon-reload
sudo systemctl enable --now update-clean.timerDebian (stable, testing, unstable), Ubuntu (LTS and interim), and other apt-based derivatives (Kali, Mint, Pop!_OS, …). Not all derivatives are tested.
| Repo | Focus |
|---|---|
update_clean |
Kali |
debian_ubuntu_update_clean |
General Debian/Ubuntu |
ai_blade_ubuntu_update_clean |
Ubuntu AI / GPU blades |
update-clean.sh
VERSION / CHANGELOG.md / README.md / LICENSE
update-clean.conf.example
systemd/ # optional weekly timer
.github/workflows/ # ShellCheck + release
scripts/package-release.sh
tests/ # Ubuntu 22.04 / 24.04 / 26.04 / Debian 12 harness
sudo ./tests/simulate_ubuntu.sh
sudo ./tests/run_real_host.shsimulate_ubuntu.sh bind-mounts Ubuntu 22.04, Ubuntu 24.04, Ubuntu 26.04, and Debian 12 os-release files in a private mount namespace (no Docker). It sources update-clean.sh (CLI skipped) so held-kernel tests use DPKG_STATUS_INSTALLED_RE from the script. HTML report: tests/last-results.html.
run_real_host.sh is a live root harness on this machine: --check, a 90s --dry-run (no dpkg --configure / no apt-mark hold / no unbounded sync), held-kernel listing, and EFI grub-pc skip. It isolates logs and the instance lock under $TMPDIR.
Copyright (C) 2026 wbharris
GNU General Public License v3.0 or later (GPL-3.0-or-later).