Remediate the recurring vulnerability-scanner finding "the OS is End-of-Life and has received no security patches" on EOL Debian and Ubuntu servers — safely, idempotently, and with audit evidence a reviewer can accept.
When a Debian/Ubuntu release reaches End-of-Life it is removed from the normal mirrors and archived (archive.debian.org / old-releases.ubuntu.com). apt-get update then fails (404s, expired Release files), so the box stops receiving even the last patches published before EOL. This tool repoints APT to the archive, installs everything available up to EOL, and packages the proof.
Warning
This does not make an EOL release supported. It installs every patch that existed up to EOL. Vulnerabilities disclosed afterwards cannot be fixed by patching — the finding is only durably closed by a dist-upgrade to a supported release or a rebuild, optionally bridged by paid extended support (Freexian ELTS for Debian, Ubuntu Pro / ESM for Ubuntu). The migrate-plan command helps scope that.
The CentOS/RHEL counterpart lives in a sibling repo, centos-eol-patch.
Auto-detected from /etc/os-release:
| Distro | Archive | Security suite |
|---|---|---|
| Debian ≤ 10 (buster and older) | archive.debian.org |
<codename>/updates |
| Debian ≥ 11 (bullseye and newer) | archive.debian.org |
<codename>-security |
| Ubuntu | old-releases.ubuntu.com |
<codename>-security (+ -updates) |
| Debian/Ubuntu derivatives | via ID_LIKE, with a warning |
as above |
The init system is auto-detected (systemd vs sysvinit) from /proc/1/comm, and service/reboot handling adapts accordingly — older Debian boxes upgraded up from wheezy/jessie are frequently still on sysvinit, where systemctl does not apply.
| Command | Effect | Changes the system? |
|---|---|---|
diagnose |
Distro/codename/arch/init, archive reachability, current sources, pending upgrades, web listeners | No — safe for an SSH loop |
migrate-plan |
Per-host migration-assessment report (/root/migrate-plan-<host>.md) |
No — read-only |
patch [--yes] [--dist-upgrade] [--with-thirdparty] [--trust-archive] |
Snapshot gate → repoint to archive → apt-get update → upgrade → validate → interactive reboot prompt. --trust-archive accepts the archive's expired signing keys (see below) |
Yes |
cleanup-kernels |
apt-get autoremove --purge old kernels, with a dry-run preview |
Yes (prompted) |
verify [URL] [--reset-failed] |
Post-reboot checks + optional functional test. On systemd, splits new failed units from pre-existing | No (unless --reset-failed) |
bundle |
tar.gz the evidence directory for the ticket |
No |
scp apt-patch.sh root@server:
ssh -t root@server './apt-patch.sh diagnose' # look before you leap
ssh -t root@server './apt-patch.sh patch' # interactive: snapshot gate + reboot prompt
# after it reboots:
ssh root@server './apt-patch.sh verify https://the-portal.example'# 1) Triage (safe, read-only) — which hosts are behind / on which archive / which init
for h in host1 host2 host3; do ssh root@$h 'bash -s' < apt-patch.sh diagnose; done
# 2) Assess migration effort (safe, read-only); collect the reports
mkdir -p plans
for h in host1 host2 host3; do
ssh root@$h 'bash -s' < apt-patch.sh migrate-plan
scp root@$h:/root/migrate-plan-*.md ./plans/
done
# 3) Patch each host individually (snapshot gate + reboot are per-host, interactive)
for h in host1 host2 host3; do
scp apt-patch.sh root@$h: && ssh -t root@$h './apt-patch.sh patch'
done
# 4) Verify after reboot
for h in host1 host2 host3; do ssh root@$h './apt-patch.sh verify'; done- Snapshot gate.
patchrefuses to proceed until you confirm a VMware/vCenter snapshot exists. - Idempotent. Re-running is safe.
sources.listis rewritten to the archive (original backed up into the evidence dir), and third-party sources undersources.list.d/are set aside for the transaction and restored afterwards — so a broken third-party key (e.g. a Nagios repo withNO_PUBKEY) can't derail the OS upgrade. - Expired releases handled.
Acquire::Check-Valid-Until=falselets APT use the archived, past-Valid-UntilReleasefiles. - Expired signing keys are a conscious choice. EOL archives (notably
archive.debian.org) are signed with keys that have expired by policy — updating the keyring does not fix this.patchstops and explains rather than silently bypassing verification; re-run with--trust-archiveto mark the OS sources[trusted=yes]and accept the official archive over HTTP. This is required for essentially every EOL Debian host. - Conservative upgrade. Default is
apt-get upgrade(won't remove packages). If APT reports packages kept back, re-run with--dist-upgrade. - Init-aware. systemd and sysvinit are both handled;
--failed/reset-failedlogic only runs where systemd exists. - Evidence. Every run writes a timestamped bundle under
/root/apt-patch-evidence/(dpkg lists before/after, pending-upgrade counts, listening-port diffs, apache configtest, apt logs,SUMMARY.txt).
The line most scanners want — "no outstanding security updates remain" — is the post-upgrade pending count reaching zero (apt list --upgradable empty). Attach SUMMARY.txt plus the bundle tarball. See docs/EOL-NOTES.md for ticket wording and why patching alone cannot permanently close an EOL finding.
bash, apt, and curl or wget. Access to the vendor archive over HTTP.
Syntax-validated. Because archive URLs, security-suite naming, and init handling vary, run diagnose on a representative host of each kind (Debian sysvinit, Debian systemd, Ubuntu) before patching, and test patch on a snapshotted non-production host first.
MIT — see LICENSE.
Provided as-is. Patching an EOL OS is a stopgap, not a substitute for upgrading or migrating.