pax_global_header00006660000000000000000000000064137632664620014531gustar00rootroot0000000000000052 comment=4f8b56467947d7284d439b8e6a80ccd7f48ed1ed kthresher-1.4.1/000077500000000000000000000000001376326646200135335ustar00rootroot00000000000000kthresher-1.4.1/.gitignore000066400000000000000000000000371376326646200155230ustar00rootroot00000000000000*.pyc build/ dist/ *.egg-info/ kthresher-1.4.1/.travis.yml000066400000000000000000000025271376326646200156520ustar00rootroot00000000000000--- language: generic dist: xenial sudo: enabled branches: only: - master - development matrix: include: - name: "Ubuntu 14.04 (trusty)" env: - DISTRO='ubuntu:14.04' - name: "Ubuntu 16.04 (xenial)" env: - DISTRO='ubuntu:16.04' - name: "Ubuntu 18.04 (bionic)" env: - DISTRO='ubuntu:18.04' - name: "Debian 8 (jessie)" env: - DISTRO='debian:8' - name: "Debian 9 (stretch)" env: - DISTRO='debian:9' - name: "Debian (buster)" env: - DISTRO='debian:buster' before_install: - | docker run \ --detach \ --rm \ --tty \ --privileged \ --network=host \ --name kthresher_on_${DISTRO/:/_} \ --mount type=bind,src="$(pwd)",dst=/kthresher \ ${DISTRO} - | docker exec \ kthresher_on_${DISTRO/:/_} \ bash -c \ 'apt-get update; \ apt-get install -yq --no-install-suggests --no-install-recommends \ python \ python3 \ python-apt \ python3-apt \ python-pip \ python3-pip \ python-setuptools \ python3-setuptools' - | docker exec \ kthresher_on_${DISTRO/:/_} \ bash -c 'pip install tox' script: - > docker exec \ kthresher_on_${DISTRO/:/_} \ bash -c 'cd /kthresher/; tox' ... kthresher-1.4.1/CHANGELOG.md000066400000000000000000000073441376326646200153540ustar00rootroot00000000000000# Change Log All notable changes to this project will be documented in this file. ## [Unreleased] ## [1.4.1] - 2019-06-26 ### Added - Only create a syslog handler if /dev/log exists. - Reference of unattended-updates capability to remove unused kernels. ### Changed - Use package section from `Version` instead of `Package`. ## [1.4.0] - 2019-02-13 ### Added - Add system logs on purge, show and errors. - Python3 Support. - Add systemd timer units. ### Changed - Documented default values. - Make show autoremoval action the default. - Use `version_compare` from `apt` when `LooseVersion` fails. ### Fixed - Make `keep` truly default to 1. - Cron job should not fail if the binary does not exist. ### Removed - Setuptools does not include `data_files` with man page nor default config anymore. ## [1.3.1] - 2018-04-23 ### Added - Removed shell from bash completion. - Added license to bash completion. ## [1.3.0] - 2018-01-30 ### Added - Bash completion ### Changed - Support to most/all kernel/header packages [flavors used in Ubuntu](https://people.canonical.com/~kernel/info/kernel-version-pockets.txt) - Using logging consistently. - PEP8 compliant. ## [1.2.7] - 2017-03-09 ### Added - Better error handling when executing as non-root. - Logos. ### Changed - Regex to include linux-headers ending in -common. - Improved the testing example on the README. ## [1.2.6] - 2017-01-30 ### Added - Support to amd64 kernels. ### Changed - Consistency between `--show` and `--purge`. - Sytle improvements(pep8). - Man page update. ## [1.2.5] - 2016-11-15 ### Added - Support for nested config files through `include` setting. - README info about how a package is marked for autoremoval. - README info to be able to perform tests by installing kernels and headers. ### Changed - Default config file to only include a `include` path for `/etc/kthresher.d/*.conf` ### Removed - Debian dir and drone configs, will not live now with the code, @thebwt will maintain that now. - Config file support for dry-run, this is now only available through command line arguments. ## [1.2.4] - 2016-09-02 ### Added - Drone config. ### Changed - Debian configs for proper building. ### Fixed - Typos on man page. ## [1.2.3] - 2016-07-25 ### Added - Man page. - Changelog. - Debian directory for .debs. ### Changed - Flatten directory structure for .deb. - Cron file to check if script is available prior execution. ## [1.2.2] - 2016-04-19 ### Added - Cron file cron.daily. ## [1.2.1] - 2016-04-18 ### Added - Support for old virtual kernel packages. ### Fixed - Bug when searching for a list of installed kernel images. ## [1.2.0] - 2016-04-18 ### Added - Support to remove headers '-h'. ## [1.1.0] - 2016-04-14 ### Added - Support for config file '-c'. ### Change - Use of '-n' or '--number' changed to '-k' or '--keep' for number of kernels to keep. ## [1.0.1] - 2016-04-11 ### Added - LICENSE. ### Change - README to rst. ## [1.0.0] - 2016-04-06 ### Added - Support to keep a fixed amount of kernels '-n'. - Support to '--dry-run'. ### Changed - Previously '-v' was used for version, it was changed to '-V'. - Use of '-v' or '--verbose' was changed to add verbosity. - Option '-l' or '--list' was changed to '-s' or '--show-autoremoval'. - The use of disutils.LooseVersion broke support for Python3. ### Deprecated - Use of '-l','-f'. ### Fixed - Typos. ## [0.2.3] - 2015-12-14 ### Fixed - README. ## [0.2.2] - 2015-11-09 ### Added - Licencing. ### Fixed - README. ## [0.2.1] - 2015-10-12 ### Added - Released kthresher. --- # Contributors - [delag](https://github.com/delag) - [disengage00](https://github.com/disengage00) - [jamrok](https://github.com/jamrok) - [jkirk](https://github.com/jkirk) - [Jose R. Gonzalez](https://github.com/Komish) - [Tony G.](https://github.com/tonyskapunk) kthresher-1.4.1/CONTRIBUTING.md000066400000000000000000000030151376326646200157630ustar00rootroot00000000000000# Workflow This project uses gitflow, here some documentation about [how gitflow works](https://datasift.github.io/gitflow/IntroducingGitFlow.html) # Issues Bugs, RFE or others should be documented in the [issues](https://github.com/rackerlabs/kthresher/issues/). ## Guidelines 1. Before opening an issue, search [open](https://github.com/rackerlabs/kthresher/issues?q=is%3Aopen+is%3Aissue) and [closed](https://github.com/rackerlabs/kthresher/issues?q=is%3Aissue+is%3Aclosed) issues to ensure the issue has not ben reported previously. 1. Ensure the issue can be reproduced with the latest [`development`](https://github.com/rackerlabs/kthresher/tree/development) version. 1. The issue should contain detailed instructions and when possible logs, as well as the expected result. 1. Include details of the environment where it's running, e.g. Debian Jessie, CentOS 7.3, etc. 1. For RFE explain *Why would this feature be useful?* # Pull Requests Code contributions are greatly appreciated, please make sure you follow the guidelines below. ## Pull requests should be 1. Made against the `development` branch. 1. Made from a git feature/fix branch. 1. Associated to a documented issue. ## Pull requests will not be accepted that 1. Are not made against the `development` branch 1. Are made by editing files via the GitHub website 1. Does not have a documented [issue](https://github.com/rackerlabs/kthresher/issues/) for it. # Coding Guidelines - pep8/pycodestyle - pyflakes # Testing - None at the moment :(, would be nice to have some. kthresher-1.4.1/LICENSE000066400000000000000000000261361376326646200145500ustar00rootroot00000000000000 Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright [yyyy] [name of copyright owner] Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. kthresher-1.4.1/MANIFEST.in000066400000000000000000000001161376326646200152670ustar00rootroot00000000000000include LICENSE include README.rst include kthresher.conf include kthresher.8 kthresher-1.4.1/README.rst000066400000000000000000000452161376326646200152320ustar00rootroot00000000000000|logo0| kthresher ========= .. image:: https://img.shields.io/github/release/rackerlabs/kthresher.svg :target: https://github.com/rackerlabs/kthresher/releases/latest :alt: Github release .. image:: https://img.shields.io/travis/rackerlabs/kthresher/master.svg?logo=travis&label=master :target: https://travis-ci.org/rackerlabs/kthresher :alt: Build Master Status .. image:: https://img.shields.io/travis/rackerlabs/kthresher/master.svg?logo=travis&label=development :target: https://travis-ci.org/rackerlabs/kthresher :alt: Build Development Status .. image:: https://img.shields.io/github/license/rackerlabs/kthresher.svg :target: https://raw.githubusercontent.com/rackerlabs/kthresher/master/LICENSE :alt: License .. image:: https://img.shields.io/twitter/url/https/github.com/rackerlabs/kthresher.svg?style=social :target: https://twitter.com/intent/tweet?text=Check%20this%20out:&url=https%3A%2F%2Fgithub.com%2Frackerlabs%2Fkthresher :alt: Twitter Tool to remove unused kernels that were installed automatically in Debian/Ubuntu. This tool removes those kernel packages marked as candidate for autoremoval. Those packages are generally installed via Unattended upgrade or meta-packages. By default, on apt 1.0 and below, the booted kernel, the latest-installed kernel and the latest kernel are set to "NeverAutoRemove". Or, for apt 1.2 and above, the booted kernel, the latest-installed kernel, the latest kernel and the second-latest kernel are set to "NeverAutoRemove". Ubuntu has multiple suggestions on how to remove kernels: https://help.ubuntu.com/community/RemoveOldKernels A great recommendation is to make use of unattended-upgrades `u-u` (`debian `__, `ubuntu `__). Since version **1.0** (`debian `__, `ubuntu `__) *u-u* removes unused kernel packages by default (*Remove-Unused-Kernel-Packages*). Some distribution versions may not make reference or may comment out that setting in its configuration, but unless it is explicitly disabled, *u-u* will attempt to remove unused kernels. `More info here `__. For scenarios where *u-u* is not available or older than **1.0**, `kthresher` is still a good option. *thresher - A device that first separates the head of a stalk of grain from the straw, and then further separates the kernel from the rest of the head.* ----- |version| |downloads-pypi| |versions| |license| |stars-github| ----- .. contents:: Table of Contents :depth: 1 :backlinks: none ----- How a package is marked for autoremoval? ---------------------------------------- Whenever a package is auto-installed and there is no other dependency for it, the package is marked as a candidate for autoremoval, there is an exception if the *APT* configuration does have the package marked as "NeverAutoRemove". How the kernel image is added into the "APT::NeverAutoRemove::" config? ----------------------------------------------------------------------- When a kernel image is installed the *postinstall* script will issue the *run-parts* on */etc/kernel/postinst.d/* and */etc/kernel/postinst.d/${version}* if any exist. The *run-parts* script will run each one of the scripts located in that directory, e.g. .. code-block:: bash # ls -1 /etc/kernel/postinst.d/ apt-auto-removal initramfs-tools update-notifier x-grub-legacy-ec2 zz-update-grub All the scripts found by *run-parts* are executed on post install of the kernel package and the output of apt-get install/upgrade/dist-upgrade will show them, e.g. .. code-block:: bash run-parts: executing /etc/kernel/postinst.d/apt-auto-removal 3.13.0-96-generic /boot/vmlinuz-3.13.0-96-generic run-parts: executing /etc/kernel/postinst.d/initramfs-tools 3.13.0-96-generic /boot/vmlinuz-3.13.0-96-generic run-parts: executing /etc/kernel/postinst.d/update-notifier 3.13.0-96-generic /boot/vmlinuz-3.13.0-96-generic run-parts: executing /etc/kernel/postinst.d/x-grub-legacy-ec2 3.13.0-96-generic /boot/vmlinuz-3.13.0-96-generic run-parts: executing /etc/kernel/postinst.d/zz-update-grub 3.13.0-96-generic /boot/vmlinuz-3.13.0-96-generic The first script *"apt-auto-removal"* takes care of adding a configuration in /etc/apt/apt.conf.d/01autoremove-kernels this script generates that list based on the logic described above, it means that the NeverAutoRemove may have anything between two to three kernels listed. Supported Operating Systems --------------------------- * Debian (Tested on Version(s)) * `8 `__ * `9 `__ * Ubuntu (Tested on Version(s)) * `12.04 `__ * `14.04 `__ * `16.04 `__ * `17.10 `__ Installation ------------ script ~~~~~~ .. code-block:: bash wget -O kthresher https://raw.githubusercontent.com/rackerlabs/kthresher/master/kthresher.py chmod u+x kthresher pip ~~~ .. code-block:: bash pip install kthresher or .. code-block:: bash pip install git+https://github.com/rackerlabs/kthresher.git Github ~~~~~~ .. code-block:: bash git clone https://github.com/rackerlabs/kthresher.git cd kthresher && python setup.py install Usage ----- .. code-block:: $ kthresher -h usage: kthresher [-h] [-c FILE] [-d] [-H] [-k [N]] [-p] [-s] [-v] [-V] Purge Unused Kernels. optional arguments: -h, --help show this help message and exit -c FILE, --config FILE Config file, default is /etc/kthresher.conf -d, --dry-run List unused kernel images available to purge(dry run). Is always verbose. -H, --headers Include the search for kernel headers. -k [N], --keep [N] Number of kernels to keep, default 1. -p, --purge Purge Unused Kernels. -s, --show-autoremoval Show kernel packages available for autoremoval. -v, --verbose Be verbose. -V, --version Print version. Examples -------- List which kernel images and its dependencies would remove(dry run) ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ .. code-block:: # kthresher -d INFO: Attempting to read /etc/kthresher.conf. INFO: Config file /etc/kthresher.conf is empty or does not exist, ignoring. INFO: Options: {'purge': False, 'verbose': False, 'dry_run': True, 'keep': 1} INFO: ----- DRY RUN ----- INFO: Running kernel is linux-image-3.13.0-83-generic v[3.13.0-83.127] INFO: Attempting to keep 1 kernel package(s) INFO: Found 4 kernel image(s) installed and available for autoremoval INFO: Pre-sorting: ['3.16.0-60.80~14.04.1', '3.13.0-77.121', '3.13.0-63.103', '3.16.0-33.44~14.04.1'] INFO: Post-sorting: ['3.13.0-63.103', '3.13.0-77.121', '3.16.0-33.44~14.04.1', '3.16.0-60.80~14.04.1'] INFO: Purging packages from version: 3.13.0-63.103 INFO: Purging: linux-image-extra-3.13.0-63-generic INFO: Purging: linux-image-3.13.0-63-generic INFO: Purging packages from version: 3.13.0-77.121 INFO: Purging: linux-image-3.13.0-77-generic INFO: Purging: linux-image-extra-3.13.0-77-generic INFO: Purging packages from version: 3.16.0-33.44~14.04.1 INFO: Purging: linux-image-3.16.0-33-generic Show all kernel packages available for autoremoval ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ .. code-block:: # kthresher -s List of kernel packages available for autoremoval: Version Package 3.13.0.83.89 linux-generic 3.13.0-51.84 linux-headers-3.13.0-51 3.13.0-51.84 linux-headers-3.13.0-51-generic 3.13.0-71.114 linux-headers-3.13.0-71 3.13.0-71.114 linux-headers-3.13.0-71-generic 3.13.0-77.121 linux-headers-3.13.0-77 3.13.0-77.121 linux-headers-3.13.0-77-generic 3.13.0-79.123 linux-headers-3.13.0-79 3.13.0-79.123 linux-headers-3.13.0-79-generic 3.13.0-63.103 linux-image-3.13.0-63-generic 3.13.0-77.121 linux-image-3.13.0-77-generic 3.16.0-33.44~14.04.1 linux-image-3.16.0-33-generic 3.16.0-60.80~14.04.1 linux-image-3.16.0-60-generic 3.13.0-63.103 linux-image-extra-3.13.0-63-generic 3.13.0-77.121 linux-image-extra-3.13.0-77-generic 3.13.0.83.89 linux-image-generic Purge Unused Kernels, keep 3 kernels and be verbose ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ .. code-block:: # kthresher -p -k3 -v INFO: Attempting to read /etc/kthresher.conf. INFO: Config file /etc/kthresher.conf is empty or does not exist, ignoring. INFO: Options: {'purge': True, 'verbose': True, 'dry_run': False, 'keep': 3} INFO: Running kernel is linux-image-3.13.0-83-generic v[3.13.0-83.127] INFO: Attempting to keep 3 kernel package(s) INFO: Found 4 kernel image(s) installed and available for autoremoval INFO: Pre-sorting: ['3.16.0-60.80~14.04.1', '3.13.0-77.121', '3.13.0-63.103', '3.16.0-33.44~14.04.1'] INFO: Post-sorting: ['3.13.0-63.103', '3.13.0-77.121', '3.16.0-33.44~14.04.1', '3.16.0-60.80~14.04.1'] INFO: Purging packages from version: 3.13.0-63.103 INFO: Purging: linux-image-extra-3.13.0-63-generic INFO: Purging: linux-image-3.13.0-63-generic Fetched 0 B in 0s (0 B/s) (Reading database ... 169514 files and directories currently installed.) Removing linux-image-extra-3.13.0-63-generic (3.13.0-63.103) ... run-parts: executing /etc/kernel/postinst.d/apt-auto-removal 3.13.0-63-generic /boot/vmlinuz-3.13.0-63-generic run-parts: executing /etc/kernel/postinst.d/initramfs-tools 3.13.0-63-generic /boot/vmlinuz-3.13.0-63-generic update-initramfs: Generating /boot/initrd.img-3.13.0-63-generic run-parts: executing /etc/kernel/postinst.d/zz-update-grub 3.13.0-63-generic /boot/vmlinuz-3.13.0-63-generic Generating grub configuration file ... Found linux image: /boot/vmlinuz-3.16.0-60-generic Found initrd image: /boot/initrd.img-3.16.0-60-generic Found linux image: /boot/vmlinuz-3.16.0-33-generic Found initrd image: /boot/initrd.img-3.16.0-33-generic Found linux image: /boot/vmlinuz-3.13.0-83-generic Found initrd image: /boot/initrd.img-3.13.0-83-generic Found linux image: /boot/vmlinuz-3.13.0-77-generic Found initrd image: /boot/initrd.img-3.13.0-77-generic Found linux image: /boot/vmlinuz-3.13.0-63-generic Found initrd image: /boot/initrd.img-3.13.0-63-generic done Purging configuration files for linux-image-extra-3.13.0-63-generic (3.13.0-63.103) ... Removing linux-image-3.13.0-63-generic (3.13.0-63.103) ... Examining /etc/kernel/postrm.d . run-parts: executing /etc/kernel/postrm.d/initramfs-tools 3.13.0-63-generic /boot/vmlinuz-3.13.0-63-generic update-initramfs: Deleting /boot/initrd.img-3.13.0-63-generic run-parts: executing /etc/kernel/postrm.d/zz-update-grub 3.13.0-63-generic /boot/vmlinuz-3.13.0-63-generic Generating grub configuration file ... Found linux image: /boot/vmlinuz-3.16.0-60-generic Found initrd image: /boot/initrd.img-3.16.0-60-generic Found linux image: /boot/vmlinuz-3.16.0-33-generic Found initrd image: /boot/initrd.img-3.16.0-33-generic Found linux image: /boot/vmlinuz-3.13.0-83-generic Found initrd image: /boot/initrd.img-3.13.0-83-generic Found linux image: /boot/vmlinuz-3.13.0-77-generic Found initrd image: /boot/initrd.img-3.13.0-77-generic done Purging configuration files for linux-image-3.13.0-63-generic (3.13.0-63.103) ... Examining /etc/kernel/postrm.d . run-parts: executing /etc/kernel/postrm.d/initramfs-tools 3.13.0-63-generic /boot/vmlinuz-3.13.0-63-generic run-parts: executing /etc/kernel/postrm.d/zz-update-grub 3.13.0-63-generic /boot/vmlinuz-3.13.0-63-generic Verbose run using a non-default config file ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ .. code-block:: # kthresher -c myconf.conf INFO: Attempting to read myconf.conf. INFO: Options found: ['keep', 'dry_run']. INFO: Valid setting found "keep" INFO: keep = 1 INFO: Valid setting found "dry_run" INFO: dry_run = True INFO: Options: {'purge': False, 'verbose': True, 'dry_run': True, 'keep': 1} INFO: ----- DRY RUN ----- INFO: Running kernel is linux-image-3.13.0-83-generic v[3.13.0-83.127] INFO: Attempting to keep 1 kernel package(s) INFO: Found 2 kernel image(s) installed and available for autoremoval INFO: Pre-sorting: ['3.16.0-60.80~14.04.1', '3.16.0-33.44~14.04.1'] INFO: Post-sorting: ['3.16.0-33.44~14.04.1', '3.16.0-60.80~14.04.1'] INFO: Purging packages from version: 3.16.0-33.44~14.04.1 INFO: Purging: linux-image-3.16.0-33-generic Content of myconf.conf is: .. code-block:: [main] keep = 1 dry_run = yes #purge = yes Dry run including headers ~~~~~~~~~~~~~~~~~~~~~~~~~ .. code-block:: # kthresher -v -d -H INFO: Attempting to read /etc/kthresher.conf. INFO: Options found: ['keep', 'dry_run', 'purge', 'verbose']. INFO: Valid setting found "keep" INFO: keep = 2 INFO: Valid setting found "dry_run" INFO: dry_run = False INFO: Valid setting found "purge" INFO: purge = True INFO: Valid setting found "verbose" INFO: verbose = True INFO: Options: {'verbose': True, 'dry_run': True, 'keep': 2, 'purge': True, 'headers': True} INFO: ----- DRY RUN ----- INFO: Running kernel is linux-image-3.13.0-83-generic v[3.13.0-83.127] INFO: Attempting to keep 2 kernel package(s) INFO: Found 4 kernel image(s) installed and available for autoremoval INFO: Pre-sorting: ['3.16.0-60.80~14.04.1', '3.16.0-33.44~14.04.1', '3.13.0-85.129', '3.13.0-79.123'] INFO: Post-sorting: ['3.13.0-79.123', '3.13.0-85.129', '3.16.0-33.44~14.04.1', '3.16.0-60.80~14.04.1'] INFO: Purging packages from version: 3.13.0-79.123 INFO: Purging: linux-image-3.13.0-79-generic INFO: Purging: linux-headers-3.13.0-79-generic INFO: Purging: linux-headers-3.13.0-79 INFO: Purging packages from version: 3.13.0-85.129 INFO: Purging: linux-image-3.13.0-85-generic INFO: Purging: linux-headers-3.13.0-85 INFO: Purging: linux-headers-3.13.0-85-generic Testing ------- The below code can be used to install up to a fixed amount of kernels and headers if available of the form "linux-(image|headers)-[0-9].*-(generic|amd64)" at the end it should end up with two or three kernels in the NeverAutoRemove list, including the latest, the prior to latest and the running kernel. .. code-block:: python #!/usr/bin/env python '''Installs available linux-image-* and linux-headers-* And set them for autoremoval, so kthresher can be used for testing. ''' import re import apt import sys from platform import uname def autorm_install(pkgs): '''Install a list of packages and set them autoremovable. ''' latest_kernel = '' ac = apt.Cache() for pkg in pkgs: latest_kernel = pkg k = ac[pkg] if not k.is_installed: k.mark_install(from_user=False) try: ac.commit(install_progress=None) except apt.cache.LockFailedException as lfe: print('{}, are you root?'.format(lfe)) sys.exit(1) except SystemError: print('Something failed') sys.exit(1) def get_pkg(regex): '''Get a list of packages available that match the regex. ''' pkgs = [] ac = apt.Cache() ac.update() for pkg in ac: if re.match(regex, pkg.name): # ignore running kernel if pkg.name == 'linux-image-{0}'.format(uname()[2]): continue pkgs.append(pkg.name) return pkgs def main(): limit = 5 if len(sys.argv) > 1: try: limit = int(sys.argv[1]) except: print("Use an integer as the limit of pkgs to install.") sys.exit(1) print("Installing {} kernels/headers if available...".format(limit)) kernel_regex = "^linux-image-\d\..*-(generic|amd64)$" header_regex = "^linux-headers-\d\..*-(generic|amd64)$" kernels = get_pkg(kernel_regex) headers = get_pkg(header_regex) pkgs = kernels[0:limit] + headers[0:limit] print("Installing {} packages total\n\tkernels: {}\n\theaders: {}" .format(len(pkgs), kernels[0:limit], headers[0:limit])) autorm_install(pkgs) if __name__ == "__main__": main() Bugs ---- Submit Bug reports, feature requests via `issues `__. Logos ----- The art was created by `Carlos Garcia `__ and released under CC BY-SA 4.0 +---------+---------+ | |logo0| | |logo1| | +---------+---------+ | |logo2| | |logo3| | +---------+---------+ .. image:: https://i.creativecommons.org/l/by-sa/4.0/88x31.png :target: http://creativecommons.org/licenses/by-sa/4.0/ :alt: Creative Commons License ----- .. |version| image:: https://img.shields.io/pypi/v/kthresher.svg :target: https://github.com/rackerlabs/kthresher/releases/latest :alt: Latest Version .. |downloads-pypi| image:: https://img.shields.io/pypi/dm/kthresher.svg :target: https://pypi.python.org/pypi/kthresher :alt: PyPi Downloads .. |stars-github| image:: https://img.shields.io/github/stars/rackerlabs/kthresher.svg :target: https://github.com/rackerlabs/kthresher :alt: Github Stars .. |versions| image:: https://img.shields.io/pypi/pyversions/kthresher.svg :target: https://github.com/rackerlabs/kthresher/releases :alt: Versions .. |license| image:: https://img.shields.io/pypi/l/kthresher.svg :target: https://github.com/rackerlabs/kthresher/blob/master/LICENSE :alt: License .. |logo0| image:: https://github.com/rackerlabs/kthresher/wiki/img/kthresher.png .. |logo1| image:: https://github.com/rackerlabs/kthresher/wiki/img/kthresher_horiz.png .. |logo2| image:: https://github.com/rackerlabs/kthresher/wiki/img/kthresher_circ.png .. |logo3| image:: https://github.com/rackerlabs/kthresher/wiki/img/kthresher_half.png kthresher-1.4.1/kthresher.8000066400000000000000000000047701376326646200156330ustar00rootroot00000000000000.TH kthresher 8 "November 2016" "kthresher" .SH "NAME" kthresher \- purge unused kernels .SH "SYNOPSIS" .B kthresher [\-h] [\-c .I FILE .B ] [\-d] [\-H] [\-k .I [N] .B ] [\-p] [\-s] [\-v] [\-V] .SH "DESCRIPTION" .I kthresher is a utility to remove kernel packages that are marked as a candidate for autoremoval, such as those installed via unattended\-upgrades or meta packages. .PP By default the running kernel, the latest kernel and manual installations are marked to 'Never Auto Remove'. .IR "kthresher " "then will not attempt to remove those packages." .PP When no option is passed to kthresher the list of available kernels for autoremoval will be printed. .SH "OPTIONS" .TP .B "\-h, \-\-help" show a help message and exits .TP .BI "\-c " "FILE" ", \-\-config " "FILE" Config file, default is .I /etc/kthresher.conf .TP .B "\-d, \-\-dry\-run" List what packages would be purged but do not perform the action, by default does not perform a dry-run. .TP .B "\-H, \-\-headers" Include the search for kernel headers, by default does not include headers. .TP .BI "\-k " "N" ", \-\-keep " "N" Number of autoremovable kernels to keep, default is 1. .TP .B "\-p, \-\-purge" Purge unused kernels, by default does not purge packages. .TP .B "\-s, \-\-show\-autoremoval" Show kernel packages available for autoremoval. When defined, overrides any other action coming from the configuration file(s). This is the default action when no other action is defined. .TP .B "\-v, \-\-verbose" Be verbose, by default is not verbose. .TP .B "\-V, \-\-version" Print version .SH FILES .TP .B \fBMain Configuration\fP /etc/kthresher.conf .TP .B \fBOverriding configs\fP /etc/kthresher.d .SH "REPORTING BUGS" Bugs and issues to be submitted via github . .SH "AUTHOR" Written by Tony Garcia . .SH "SEE ALSO" Additional documentation to be found via github . .SH "LICENSE" This software is Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at . .PP Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. kthresher-1.4.1/kthresher.bash_completion000066400000000000000000000032611376326646200206240ustar00rootroot00000000000000# bash completion for kthresher -*- shell-script -*- # # Copyright 2015-2018 Tony Garcia # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. containsElement() { # 0 if string in $1 is in the array # 1 if not local e for e in "${@:2}"; do if [[ "${e}" == "${1}" ]]; then return 0 fi done return 1 } genOpts() { local OLDIFS local IFS local not_used="" # options in pairs local opts="--help,-h --config,-c --dry-run,-d --headers,-H --keep,-k --purge,-p " opts+="--show-autoremoval,-s --verbose,-v --version,-V" for i in ${opts} ; do OLDIFS=${IFS} # set IFS to split our pairs IFS="," set -- ${i}`` # reset IFS to original value IFS=${OLDIFS} if ! $( containsElement "${1}" "${COMP_WORDS[@]}" ) && \ ! $( containsElement "${2}" "${COMP_WORDS[@]}" ); then not_used="${not_used} ${1}" not_used="${not_used} ${2}" fi done echo "${not_used}" } _kthresher() { local cur=${COMP_WORDS[COMP_CWORD]} local prev=${COMP_WORDS[COMP_CWORD-1]} #echo "Genopts $(genOpts) COMP_WORDS ${COMP_WORDS[@]}" COMPREPLY=( $(compgen -W "$(genOpts)" -- ${cur} ) ) } complete -F _kthresher kthresher kthresher-1.4.1/kthresher.conf000066400000000000000000000014401376326646200164000ustar00rootroot00000000000000[main] include = /etc/kthresher.d/*.conf # Is recommended to put the configuration in the included directory instead # of making changes in here. # # The options are: # headers - (boolean) Include or not the headers, default is false. # include - [ /path/to/file ] Suports globbing, e.g. /path/to/dir/*.conf # keep - (int) Number of kernel images to keep (0-9), default is 1. # purge - (boolean) Commit changes, default is false. # verbose - (boolean) Be verbose, default is false. # # The accepted values for booleans are: # - yes, on, true # - no, off, false # # NOTES: # 1) The --dry-run option is only allowed through CLI. # When purge is defined in the config file and --dry-run is used, the latter has precedence. # 2) Only the non-boolean options can be overriden from CLI. kthresher-1.4.1/kthresher.cron.daily000066400000000000000000000001171376326646200175150ustar00rootroot00000000000000#!/bin/sh [ -x /usr/bin/kthresher ] || exit 0 /usr/bin/kthresher &>/dev/null kthresher-1.4.1/kthresher.py000077500000000000000000000413141376326646200161120ustar00rootroot00000000000000#!/usr/bin/env python from __future__ import print_function # Copyright 2015 Tony Garcia # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. """Tool to Purge Unused Kernels Removes those kernel packages marked as candidate for autoremoval. Those packages are generally installed via Unattended upgrade or meta-packages By default the latest kernel and manual installations are marked to Never Auto Remove. Ubuntu has multiple suggestions on how to remove kernels: https://help.ubuntu.com/community/RemoveOldKernels A great recommendation is to make use of unattended-upgrades (u-u). Since version 1.0 u-u is capable of removing unused kernel packages. thresher - A device that first separates the head of a stalk of grain from the straw, and then further separates the kernel from the rest of the head. """ import re import sys import logging import argparse from glob import iglob from os import path from platform import uname from logging.handlers import SysLogHandler from distutils.version import LooseVersion try: import configparser except ImportError: import ConfigParser as configparser try: Parser = configparser.ConfigParser except AttributeError: Parser = configparser.SafeConfigParser try: import apt except ImportError: try: import distro except ImportError: from platform import dist DISTRO = dist()[0] else: DISTRO = distro.linux_distribution(False)[0] if DISTRO == "debian" or DISTRO == "Ubuntu": print( "Error: python apt library was not found\n" "python-apt and/or python3-apt packages provide it.", file=sys.stderr, ) else: print("Error: {0} distro not supported".format(DISTRO), file=sys.stderr) sys.exit(1) __version__ = "1.4.1" # Loggers logger = logging.getLogger("kthresher") def cmp_to_key(mycmp): """Convert a cmp= function into a key= function Wrapper to leverage the use of apt_pkg.version_compare. As documented in: https://docs.python.org/3/howto/sorting.html#the-old-way-using-the-cmp-parameter """ class K: def __init__(self, obj, *args): self.obj = obj def __lt__(self, other): return mycmp(self.obj, other.obj) < 0 def __gt__(self, other): return mycmp(self.obj, other.obj) > 0 def __eq__(self, other): return mycmp(self.obj, other.obj) == 0 def __le__(self, other): return mycmp(self.obj, other.obj) <= 0 def __ge__(self, other): return mycmp(self.obj, other.obj) >= 0 def __ne__(self, other): return mycmp(self.obj, other.obj) != 0 return K def get_configs(conf_file, section): """Obtains the configs from a file. Config file format: INI Valid sections: main Valid options: headers, include, keep, purge, verbose Example: [main] headers=(yes|on|true|no|off|false) include=/path/to/dir/ keep=[0-9] purge=(yes|on|true|no|off|false) verbose=(yes|on|true|no|off|false) """ valid_configs = { "headers": "boolean", "include": "str", "keep": "int", "purge": "boolean", "verbose": "boolean", } configs = {} def_conf = Parser() logger.info("Attempting to read {0}.".format(conf_file)) try: def_conf.read(conf_file) except configparser.ParsingError: logger.error("Config file contains errors: {0}".format(conf_file)) sys.exit(1) if not def_conf.read(conf_file): logger.info( "Config file {0} is empty or does not exist, ignoring.".format(conf_file) ) return configs if not def_conf.has_section(section): logger.info("Unable to find section [{0}].".format(section)) return configs if len(def_conf.options(section)) < 1: logger.info("No options found in section [{0}].".format(section)) return configs logger.info("Options found: {0}.".format(def_conf.options(section))) # Validation of the options found for option in def_conf.options(section): if option not in valid_configs.keys(): logger.info('Invalid setting "{0}", ignoring'.format(option)) else: logger.info('Valid setting found "{0}"'.format(option)) if valid_configs[option] == "int": try: configs[option] = def_conf.getint(section, option) except configparser.NoOptionError: logger.error('Unable to get value from "{0}".'.format(option)) sys.exit(1) except ValueError: logger.error( 'Invalid "{0}" value, an integer is required.'.format(option) ) sys.exit(1) if option == "keep": if configs[option] > 9: logger.error("keep value should be between 0-9.") sys.exit(1) elif valid_configs[option] == "boolean": try: configs[option] = def_conf.getboolean(section, option) except configparser.NoOptionError: logger.error('Unable to get value from "{0}".'.format(option)) sys.exit(1) except ValueError: logger.error( 'Invalid "{0}" value, a boolean is required.'.format(option) ) sys.exit(1) elif valid_configs[option] == "str": try: configs[option] = def_conf.get(section, option) except configparser.NoOptionError: logger.error('Unable to get value from "{0}".'.format(option)) logger.info("\t{0} = {1}".format(option, configs[option])) if "include" in configs.keys(): # Obtain the configs on each nested config file. for nested_file in sorted(iglob(configs["include"])): # Aborting if importing the same config file. # Won't prevent indirect loops. if nested_file == conf_file: logger.error("Looping config files, aborting...") sys.exit(1) nested_configs = get_configs(nested_file, section) # Override any option coming from the nested configs. for nested_config in nested_configs: configs[nested_config] = nested_configs[nested_config] return configs def show_autoremovable_pkgs(): """List all the kernel related packages available for autoremoval. """ packages = {} ver_max_len = 0 try: apt_cache = apt.Cache() except SystemError: logger.error("Unable to obtain the cache!") sys.exit(1) for pkg_name in apt_cache.keys(): pkg = apt_cache[pkg_name] if (pkg.is_installed and pkg.is_auto_removable) and re.match( r"^linux-(image|(\w+-)?headers)-.*$", pkg_name ): packages[pkg_name] = pkg.installed.version if ver_max_len < len(pkg.installed.version): ver_max_len = len(pkg.installed.version) if packages: logger.info("List of kernel packages available for autoremoval:") logger.info( "{0:>{width}} {1:<{width}}".format( "Version", "Package", width=ver_max_len + 2 ) ) for package in sorted(packages.keys()): logger.info( "{0:>{width}} {1:<{width}}".format( packages[package], package, width=ver_max_len + 2 ) ) logger.log( 42, "kernel packages available for autoremoval: {0}".format( sorted(packages.keys()) ), ) else: logger.log(42, "No kernel packages available for autoremoval.") def kthreshing(purge=None, headers=None, keep=1): """Purge or list the unused kernels. By default keeps 1. The running kernel, the kernels marked as NeverAutoRemove and Manually installed kernels are nevertouched by kthresher. """ kernels = {} ver_max_len = 0 kernel_image_regex = r"^linux-image-.*$" kernel_header_regex = r"^linux-(\w+-)?headers-.*$" try: apt_cache = apt.Cache() except SystemError: logger.error("Unable to obtain the cache!") sys.exit(1) current_kernel_ver = uname()[2] kernel_pkg = apt_cache["linux-image-%s" % current_kernel_ver] logger.info( "Running kernel is {0} v[{1}]".format( kernel_pkg.name, kernel_pkg.installed.version ) ) for pkg_name in apt_cache.keys(): pkg = apt_cache[pkg_name] section = pkg.candidate.section or '' if (pkg.is_installed and pkg.is_auto_removable) and ( "kernel" in section and re.match(kernel_image_regex, pkg_name) ): if ver_max_len < len(pkg.installed.version): ver_max_len = len(pkg.installed.version) kernels.setdefault(pkg.installed.version, []).append(pkg.name) if headers: for pkg_name in apt_cache.keys(): pkg = apt_cache[pkg_name] if (pkg.is_installed and pkg.is_auto_removable) and re.match( kernel_header_regex, pkg_name ): if pkg.installed.version in kernels.keys(): kernels[pkg.installed.version].append(pkg.name) else: kernels[pkg.installed.version] = [pkg.name] if kernels: logger.info("Attempting to keep {0} kernel package(s)".format(keep)) kernel_versions = list(kernels.copy().keys()) logger.info( "Found {0} kernel image(s) installed and available for " "autoremoval".format(len(kernel_versions)) ) logger.info("Pre-sorting: {0}".format(kernel_versions)) try: # Sadly this is broken in python3, https://bugs.python.org/issue14894 sorted_kernel_list = sorted(kernel_versions, key=LooseVersion) except TypeError: # Using apt_pkg.version_compare # https://github.com/rackerlabs/kthresher/pull/61 sorted_kernel_list = sorted( kernel_versions, key=cmp_to_key(apt.apt_pkg.version_compare) ) logger.info("Post-sorting: {0}".format(sorted_kernel_list)) if keep >= len(kernel_versions): logger.log( 42, "Nothing to do, attempting to keep {0} out of {1} " "kernel images.".format(keep, len(kernel_versions)), ) sys.exit(0) else: purged_pkgs = [] for index in range(0, len(sorted_kernel_list) - keep): kernel_version = sorted_kernel_list[index] logger.info( "\tPurging packages from version: {0}".format(kernel_version) ) for pkg_name in kernels[kernel_version]: logger.info("\t\tPurging: {0}".format(pkg_name)) if purge: pkg = apt_cache[pkg_name] pkg.mark_delete(purge=True) purged_pkgs.append(pkg_name) if purge: try: apt_cache.commit( fetch_progress=apt.progress.text.AcquireProgress(), install_progress=apt.progress.base.InstallProgress(), ) except apt.cache.LockFailedException as lfe: logger.error("{}, are you root?".format(lfe)) sys.exit(1) except SystemError: logger.error("Unable to commit the changes") sys.exit(1) logger.log( 42, "kernel packages purged: {} - {}".format( len(purged_pkgs), purged_pkgs ), ) else: logger.info("No packages available for autoremoval.") def main(): """The main function. """ defaults = { "config": {"file": "/etc/kthresher.conf", "section": "main"}, "options": { "dry_run": False, "headers": False, "keep": 1, "purge": False, "verbose": False, }, } options = defaults["options"].copy() conf_options = {} parser = argparse.ArgumentParser( description="Purge Unused Kernels.", prog="kthresher" ) parser.add_argument( "-c", "--config", type=str, metavar="FILE", help="Config file, default is /etc/kthresher.conf", ) parser.add_argument( "-d", "--dry-run", action="store_true", help="List unused kernel images available to purge" " (dry run). Is always verbose.", ) parser.add_argument( "-H", "--headers", action="store_true", help="Include the search for kernel headers.", ) parser.add_argument( "-k", "--keep", nargs="?", type=int, const=1, metavar="N", choices=range(0, 10), help="Number of kernels to keep, default 1.", ) parser.add_argument( "-p", "--purge", help="Purge Unused Kernels.", action="store_true" ) parser.add_argument( "-s", "--show-autoremoval", action="store_true", help="Show kernel packages available for autoremoval. This is the " "default action", ) parser.add_argument("-v", "--verbose", action="store_true", help="Be verbose.") parser.add_argument( "-V", "--version", action="version", version="%(prog)s v{0}".format(__version__), help="Print version.", ) args = parser.parse_args() # Default logger logger.setLevel(logging.ERROR) # Configure console handler ch = logging.StreamHandler() cf = logging.Formatter("%(levelname)s: %(message)s") ch.setFormatter(cf) logger.addHandler(ch) # Console logging override if args.verbose or args.dry_run: logger.setLevel(logging.INFO) # Configure syslog handler if path.exists("/dev/log"): sh = SysLogHandler("/dev/log") sf = logging.Formatter("%(name)s[%(process)d]: %(message)s") sh.setLevel(logging.ERROR) sh.setFormatter(sf) logger.addHandler(sh) # Create new logging level to be used on syslog logging.addLevelName(42, "INFO") # Read config files if args.config: conf_options = get_configs(args.config, defaults["config"]["section"]) else: conf_options = get_configs( defaults["config"]["file"], defaults["config"]["section"] ) # Overriding options as follows: # defaults -> default config file or custom config file -> included config # -> cli arguments # First overriding default configs from a file if available: if conf_options: options.update(conf_options) # Override the verbosity if set through configuration if options["verbose"]: logger.setLevel(logging.INFO) # Now overriding the result options with cli arguments if args.dry_run: options["dry_run"] = args.dry_run if args.headers: options["headers"] = args.headers if args.keep is not None: options["keep"] = args.keep if args.purge: options["purge"] = args.purge if args.verbose: options["verbose"] = args.verbose logger.info("Options: {0}".format(options)) # Show auto-removable, this is only available via explicit argument # Overrides actions defined in the configuration file(s). if args.show_autoremoval: logger.setLevel(logging.INFO) show_autoremovable_pkgs() sys.exit(0) if options["dry_run"]: logger.info("----- DRY RUN -----") kthreshing(purge=False, headers=options["headers"], keep=options["keep"]) sys.exit(0) if options["purge"]: kthreshing(purge=True, headers=options["headers"], keep=options["keep"]) sys.exit(0) if not sys.stdout.isatty(): sys.exit(0) else: # Show auto-remove is also a default option if no other action is # defined. logger.setLevel(logging.INFO) show_autoremovable_pkgs() sys.exit(0) if __name__ == "__main__": main() kthresher-1.4.1/kthresher.systemd.service000066400000000000000000000001741376326646200206050ustar00rootroot00000000000000[Unit] Description=Purge unused kernels Documentation=man:kthresher(8) [Service] Type=oneshot ExecStart=/usr/bin/kthresher kthresher-1.4.1/kthresher.systemd.timer000066400000000000000000000001631376326646200202630ustar00rootroot00000000000000[Unit] Description=Run kthresher daily [Timer] OnCalendar=daily Persistent=True [Install] WantedBy=timers.target kthresher-1.4.1/setup.py000066400000000000000000000035561376326646200152560ustar00rootroot00000000000000#!/usr/bin/env python # Copyright 2015 Tony Garcia # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. import setuptools def get_version(): with open("kthresher.py") as f: for line in f: if line.startswith("__version__"): return eval(line.split("=")[-1]) setuptools.setup( name="kthresher", version=get_version(), description=("Purge Unused Kernels."), long_description=( "Tool to remove kernel image packages marked as candidates for autoremoval." ), url="https://github.com/rackerlabs/kthresher", author="Tony Garcia", author_email="tony.garcia@rackspace.com", license="Apache License, Version 2.0", entry_points={"console_scripts": ["kthresher=kthresher:main"]}, py_modules=["kthresher"], download_url="https://github.com/rackerlabs/kthresher/tarball/{0}".format( get_version() ), classifiers=[ "Development Status :: 5 - Production/Stable", "Programming Language :: Python", "Environment :: Console", "Operating System :: POSIX :: Linux", "License :: OSI Approved :: Apache Software License", "Programming Language :: Python :: 2.7", "Programming Language :: Python :: 3.4", "Programming Language :: Python :: 3.5", "Programming Language :: Python :: 3.6", "Programming Language :: Python :: 3.7", ], ) kthresher-1.4.1/tox.ini000066400000000000000000000005411376326646200150460ustar00rootroot00000000000000[tox] skipsdist = True skip_missing_interpreters = True envlist = py2, py3, pycodestyle [testenv] sitepackages = True commands = {envpython} -V {envpython} setup.py install kthresher --version [testenv:pycodestyle] deps = pycodestyle basepython = python commands = {envpython} -V pycodestyle --max-line-length=90 kthresher.py