In this article, we will be sharing a Python script, written by James Cherti, that can be used to upgrade Arch Linux. It is designed to make the process of upgrading the Arch Linux system as easy and efficient as possible.
The helper script to upgrade Arch Linux can:
- Delete the ‘/var/lib/pacman/db.lck’ when pacman is not running,
- upgrade archlinux-keyring,
- upgrade specific packages,
- download packages,
- upgrade all packages,
- remove from the cache the pacman packages that are no longer installed.
The script provides a variety of options and is perfect for those who want to automate the process of upgrading their Arch Linux system (e.g. execute it from cron) and ensure that their system is always up to date.
Requirements: psutil
Python script name: archlinux-update.py
"""Helper script to upgrade Arch Linux."""
import argparse
import logging
import os
import re
import subprocess
import sys
import time
import psutil
class ArchUpgrade:
"""Upgrade Arch Linux."""
def __init__(self, no_refresh: bool):
self._download_package_db = no_refresh
self._keyring_and_pacman_upgraded = False
self._delete_pacman_db_lck()
@staticmethod
def _delete_pacman_db_lck():
"""Delete '/var/lib/pacman/db.lck' when pacman is not running."""
pacman_running = False
for pid in psutil.pids():
try:
process = psutil.Process(pid)
if process.name() == "pacman":
pacman_running = True
break
except psutil.Error:
pass
if pacman_running:
print("Error: pacman is already running.", file=sys.stderr)
sys.exit(1)
lockfile = "/var/lib/pacman/db.lck"
if os.path.isfile(lockfile):
os.unlink(lockfile)
def upgrade_specific_packages(self, package_list: list) -> list:
"""Upgrade the packages that are in 'package_list'."""
outdated_packages = self._outdated_packages(package_list)
if outdated_packages:
cmd = ["pacman", "--noconfirm", "-S"] + outdated_packages
self.run(cmd)
return outdated_packages
def _outdated_packages(self, package_list: list) -> list:
"""Return the 'package_list' packages that are outdated."""
outdated_packages = []
try:
output = subprocess.check_output(["pacman", "-Qu"])
except subprocess.CalledProcessError:
output = b""
for line in output.splitlines():
line = line.strip()
pkg_match = re.match(r"^([^\s]*)\s", line.decode())
if not pkg_match:
continue
pkg_name = pkg_match.group(1)
if pkg_name in package_list:
outdated_packages += [pkg_name]
return outdated_packages
@staticmethod
def upgrade_all_packages():
"""Upgrade all packages."""
ArchUpgrade.run(["pacman", "--noconfirm", "-Su"])
def download_all_packages(self):
"""Download all packages."""
self.download_package_db()
self.run(["pacman", "--noconfirm", "-Suw"])
def download_package_db(self):
"""Download the package database."""
if self._download_package_db:
return
print("[INFO] Download the package database...")
ArchUpgrade.run(["pacman", "--noconfirm", "-Sy"])
self._download_package_db = True
def upgrade_keyring_and_pacman(self):
self.download_package_db()
if not self._keyring_and_pacman_upgraded:
self.upgrade_specific_packages(["archlinux-keyring"])
self._keyring_and_pacman_upgraded = True
def clean_package_cache(self):
"""Remove packages that are no longer installed from the cache."""
self.run(["pacman", "--noconfirm", "-Scc"])
@staticmethod
def run(cmd, *args, print_command=True, **kwargs):
"""Execute the command 'cmd'."""
if print_command:
print()
print("[RUN] " + subprocess.list2cmdline(cmd))
subprocess.check_call(
cmd,
*args,
**kwargs,
)
def wait_download_package_db(self):
"""Wait until the package database is downloaded."""
successful = False
minutes = 60
hours = 60 * 60
seconds_between_tests = 15 * minutes
for _ in range(int((10 * hours) / seconds_between_tests)):
try:
self.download_package_db()
except subprocess.CalledProcessError:
minutes = int(seconds_between_tests / 60)
print(
f"[INFO] Waiting {minutes} minutes before downloading "
"the package database...",
file=sys.stderr,
)
time.sleep(seconds_between_tests)
continue
else:
successful = True
break
if not successful:
print("Error: failed to download the package database...",
file=sys.stderr)
sys.exit(1)
def parse_args():
"""Parse the command-line arguments."""
usage = "%(prog)s [--option] [args]"
parser = argparse.ArgumentParser(description=__doc__.splitlines()[0],
usage=usage)
parser.add_argument("packages",
metavar="N",
nargs="*",
help="Upgrade specific packages.")
parser.add_argument(
"-u",
"--upgrade-packages",
default=False,
action="store_true",
required=False,
help="Upgrade all packages.",
)
parser.add_argument(
"-d",
"--download-packages",
default=False,
action="store_true",
required=False,
help="Download the packages that need to be upgraded.",
)
parser.add_argument(
"-c",
"--clean",
default=False,
action="store_true",
required=False,
help=("Remove packages that are no longer installed from "
"the cache."),
)
parser.add_argument(
"-n",
"--no-refresh",
default=False,
action="store_true",
required=False,
help=("Do not download the package database (pacman -Sy)."),
)
parser.add_argument(
"-w",
"--wait-refresh",
default=False,
action="store_true",
required=False,
help=("Wait for a successful download of the package database "
"(pacman -Sy)."),
)
return parser.parse_args()
def command_line_interface():
"""The command-line interface."""
logging.basicConfig(level=logging.INFO, stream=sys.stdout,
format="%(asctime)s %(name)s: %(message)s")
if os.getuid() != 0:
print("Error: you cannot perform this operation unless you are root.",
file=sys.stderr)
sys.exit(1)
nothing_to_do = True
args = parse_args()
upgrade = ArchUpgrade(no_refresh=args.no_refresh)
if args.wait_refresh:
upgrade.wait_download_package_db()
nothing_to_do = False
if args.packages:
print("[INFO] Upgrade the packages:", ", ".join(args.packages))
upgrade.upgrade_keyring_and_pacman()
if not upgrade.upgrade_specific_packages(args.packages):
print()
print("[INFO] The following packages are already up-to-date:",
", ".join(args.packages))
nothing_to_do = False
if args.download_packages:
print("[INFO] Download all packages...")
upgrade.download_all_packages()
nothing_to_do = False
if args.upgrade_packages:
print("[INFO] Upgrade all packages...")
upgrade.upgrade_keyring_and_pacman()
upgrade.upgrade_all_packages()
nothing_to_do = False
if args.clean:
print("[INFO] Remove packages that are no longer installed "
"from the cache...")
upgrade.clean_package_cache()
nothing_to_do = False
if nothing_to_do:
print("Nothing to do.")
print()
sys.exit(0)
def main():
try:
command_line_interface()
except subprocess.CalledProcessError as err:
print(f"[ERROR] Error {err.returncode} returned by the command: "
f"{subprocess.list2cmdline(err.cmd)}",
file=sys.stderr)
sys.exit(1)
if __name__ == '__main__':
main()
Code language: Python (python)