-
-
Save lpar/1036034 to your computer and use it in GitHub Desktop.
jay - a utility for removing excess kernel files from Ubuntu systems
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env ruby | |
# encoding: UTF-8 | |
'di ' | |
'ig00 ' | |
# This is both a Ruby script and a man page; you can symlink it into your | |
# man directory as /usr/local/man/man8/jay.1 or run man -l on this file. | |
# The package which contains the kernel image itself | |
KERNEL_PACKAGE = 'linux-image' | |
# Packages associated with the kernel that should be cleaned up to match | |
PACKAGES = ['linux-headers', 'linux-restricted-modules', | |
'linux-backports-modules'] | |
# Implements a class to make it easy to handle version strings. | |
# Assumes that 2.6 == 2.6.0, and hence that 2.6 < 2.6.1. | |
# Lets you do stuff like: | |
# a = [ Version.new('2.6.3'), | |
# Version.new('2.6.12'), | |
# Version.new('2.12.1'), | |
# Version.new('2.4'), | |
# Version.new('2.13'), | |
# Version.new('1.22.22')] | |
# puts a.sort | |
# and get the right answer. | |
class Version | |
include Comparable | |
def initialize(s) | |
@string = s | |
@numbers = s.split(/[.-]+/).map {|x| Integer(x)} | |
end | |
def Version.extract(s) | |
vs = s.match(/(\d[\d\.-]+\d)/) | |
if vs | |
return Version.new(vs[1]) | |
else | |
return nil | |
end | |
end | |
def [](n) | |
return @numbers[n] | |
end | |
def length | |
return @numbers.length | |
end | |
def append(x) @numbers << x end | |
def <=>(other) | |
# For ease of comparison, we make sure both versions have the same number | |
# of punctuation-separated elements, by adding as many .0s as necessary | |
# to whichever is shorter. This means that eventually your Version objects | |
# will all grow to have as many elements as the longest, but that isn't | |
# an issue for our use here as the elements are only used for sorting; we | |
# only ever output the original string. | |
while @numbers.length < other.length | |
@numbers << 0 | |
end | |
while @numbers.length > other.length | |
other.append(0) | |
end | |
n = 0 | |
result = 0 | |
while result == 0 and n < @numbers.length | |
result = self[n] <=> other[n] | |
n += 1 | |
end | |
return result | |
end | |
def to_s | |
return @string | |
end | |
end | |
# Now we understand versions, work out what we're running right now | |
RUNNING_KERNEL = Version.extract(`uname -r`) | |
# Class to represent a single installed version of a single package | |
# i.e. the contents of one .deb file. | |
class Deb | |
include Comparable | |
attr_accessor :version | |
attr_accessor :name | |
# Initialize from a Version and a package name | |
# e.g. Version.new(2.6.5), 'linux-image' | |
def initialize(version, name) | |
@version = version | |
@name = name | |
end | |
def <=>(other) | |
return @version <=> other.version | |
end | |
def to_s | |
return @name | |
end | |
end | |
# Class to represent all installed versions of a package, | |
# i.e. the package as a whole. | |
class Package | |
attr_accessor :keep # List of Debs to keep | |
attr_accessor :remove # List of Debs to delete | |
# Initialize from a package name (with no appended versions) | |
# e.g. 'linux-image' | |
def initialize(pkgname) | |
@name = pkgname | |
# Ask dpkg for all the installed versions | |
dpkgoutput = `dpkg --get-selections #{pkgname}*` | |
@versions = Array.new | |
dpkgoutput.each_line do |line| | |
if line.match(/\sinstall$/) | |
pkgname = line.strip.sub!(/\s.*$/, '') | |
v = Version.extract(pkgname) | |
if v | |
@versions << Deb.new(v, pkgname) | |
end | |
end | |
end | |
@versions.sort! {|a,b| b <=> a} | |
end | |
# Return the list of installed versions, as a list of Versions | |
def installed(package) | |
return @versions.map {|x| x.version} | |
end | |
# Return the two most recent versions as Version objects; plus the running | |
# kernel version, if that's not one of the two most recent. | |
def recent | |
result = @versions[0,2] | |
if result[0] != RUNNING_KERNEL and result[1] != RUNNING_KERNEL | |
result << RUNNING_KERNEL | |
end | |
return result | |
end | |
# Filter the installed packages according to the argument, an array | |
# of Versions to keep. | |
# After calling this method, @keep and @delete are available, and contain | |
# lists of full package names with version numbers to keep and to delete. | |
def filter!(verstokeep) | |
@keep = Array.new | |
@remove = Array.new | |
for v in @versions | |
if verstokeep.member?(v) | |
@keep << v | |
else | |
@remove << v | |
end | |
end | |
end | |
end | |
# Now the actual cleanup task... | |
# Find out the most recent 2 kernel versions (and running version) | |
kernel = Package.new(KERNEL_PACKAGE) | |
keepers = kernel.recent | |
kernel.filter!(keepers) | |
# Start assembling lists of packages we'll keep or delete | |
grand_keep_list = kernel.keep | |
grand_remove_list = kernel.remove | |
# Run through kernel-related packages and work out what versions we have. | |
# Keep versions which go with the kernels we intend to keep; delete all the | |
# others. | |
for package in (PACKAGES) | |
pkg = Package.new(package) | |
pkg.filter!(keepers) | |
grand_keep_list.concat(pkg.keep) | |
grand_remove_list.concat(pkg.remove) | |
end | |
# Prompt the user, then call APT to do the work. | |
puts "Packages to keep:" | |
grand_keep_list.each {|x| puts " #{x}"} | |
puts "\nPackages to remove:" | |
grand_remove_list.each {|x| puts " #{x}"} | |
puts "\nDo you want me to remove the packages listed for removal above?" | |
puts "If you are absolutely sure, type 'yes' (without the quotes) and hit enter:" | |
print "> " | |
line = STDIN.gets | |
if line.strip != 'yes' | |
puts "Leaving packages untouched." | |
exit 0 | |
end | |
cmd = ['/usr/bin/apt-get', 'purge'] | |
cmd.concat(grand_remove_list.map {|x| x.to_s }) | |
exec(*cmd) | |
__END__ | |
.00 | |
'di | |
.TH jay 8 "20 Jun 2011" | |
.SH NAME | |
jay \- Remove excess kernels | |
.SH SYNOPSIS | |
.B jay | |
.SH DESCRIPTION | |
.PP | |
This utility scans a Ubuntu system and removes all but the two most | |
recent kernels, plus whichever kernel is running. The running kernel will | |
typically be one of the two most recent, but might not be. | |
.PP | |
It should work on any Ubuntu variant. It should also be fairly easy to make | |
it work on Debian by adjusting the constants at the top of the code. | |
.PP | |
While old kernel cleanup is performed automatically in recent releases of | |
Ubuntu, this script is still useful for older LTS releases which are still | |
supported as of 2011. | |
.PP | |
Before doing anything, the program prompts the user with a full list of | |
packages to be kept and packages to be removed, and asks for confirmation | |
that it has worked out the lists appropriately. It then calls apt\-get, which | |
will also ask for a final confirmation before purging anything. So if you | |
hose your system, please do not blame anyone but yourself, because you will | |
have been asked twice to confirm that the changes look sensible. | |
.PP | |
Note that apt\-get removes kernel packages one at a time, and rebuilds the | |
boot menu after each removal, so if you have multiple kernels to remove it | |
can take a long time. | |
.SH BUGS | |
You may get scary looking warnings that say "Status: Before uninstall, this | |
module version was ACTIVE on this kernel." The kernel the error message refers | |
to the one it is uninstalling, not the one you are running, so don't worry. | |
.SH AUTHOR | |
mathew <meta@pobox.com> | |
.SH COPYRIGHT | |
Copyright (c) 2010. Licensed under the GNU Public Licenses version 3.0. | |
.PP | |
Two important clauses from that license are excerpted here: | |
.PP | |
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE | |
LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR | |
OTHER PARTIES PROVIDE THE PROGRAM “AS IS” WITHOUT WARRANTY OF ANY KIND, EITHER | |
EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF | |
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO | |
THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM | |
PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR | |
CORRECTION. | |
.PP | |
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY | |
COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS THE PROGRAM | |
AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, | |
SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR | |
INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR | |
DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR | |
A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH | |
HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment