Offloading System Updates to my NAS

Linux Programming unRAID

One of the first things I do after booting my Arch Linux system is run a system update. However, this completely hogs my internet connection. All network resources are allocated to downloading packages, meaning web pages can take several minutes to load. A simple HTTP request to a static website that normally takes ~50ms suddenly takes over 1.3 seconds.

I like to run updates in the background in a detached tmux session, but I don't want it to interfere with my normal computer usage. So, I came up with a plan: each time I start up my system, it should report to my NAS server exactly which packages need updating. At 6 PM, when I'm not actively using my computer, the NAS should download all the required updates in the background. The next day, I can install them locally in seconds.

Sounds simple, right?

I created a network share on my unRAID NAS to hold a text file with the download URLs and a directory for the downloaded .pkg.tar.zst files.

I started with a bash script that parses pending updates using the checkupdates utility (from the pacman-contrib package). I configured this script to run via a systemd service, ensuring it only executes after the network share is successfully mounted.

To download the packages onto the NAS, I initially wrote a script that spun up a temporary Docker container using the official archlinux image. Inside the container, it would run pacman -Sw --noconfirm package-name to download the package, and then copy the files to the mounted network share.

But this initial approach had a major flaw. My workstation is a full desktop system with a complex web of dependencies. If firefox needed an update, the barebones Docker container didn't have the underlying desktop libraries installed. pacman -Sw would happily download firefox and pull in all of its missing dependencies (like gtk3 and potentially half of GNOME). I would end up downloading gigabytes of packages I didn't actually need updates for, completely defeating the purpose of a lightweight background task.

I realized I could print the direct download URL of a package using pacman -Sp package-name. I updated my script to grab these URLs and send them to the NAS. But immediately, I hit a wall: if I already had an older version of the package in my local /var/cache/pacman/pkg/ directory, pacman would "helpfully" print a file:///var/cache/... URL instead of the remote HTTP mirror URL.

I tried forcing pacman to use an empty temporary folder via pacman --cachedir /tmp/empty_dir -Sp package-name. Apparently, --cachedir appends to the search list rather than replacing the default cache. Pacman still found the old file locally and returned the file:// URL.

The root cause was that my local system database knew I already had an older version installed, and it was matching that against the local cache. I fixed this by replicating what checkupdates does under the hood: I created a fresh, temporary database.

The final, bulletproof command looks like this: pacman --nodeps --dbpath "$tmp_db" --cachedir "$tmp_db" -Sp "$package"

Here is why this works perfectly:

  1. --dbpath "$tmp_db" forces pacman to use a freshly synced database, so it looks for the new version's filename.
  2. --cachedir "$tmp_db" points to a guaranteed-empty directory, preventing any local file:// fallback.
  3. --nodeps prevents pacman from aborting the query with a "breaks dependency" error (which commonly happens with split packages like gdb/gdb-common during transitions).

With the Arch side finally outputting clean HTTP URLs, the unRAID side is beautifully simple: just a wget -c -i command reading the text file.

To run it at 6 PM, I installed the User Scripts plugin to create a cron job. I pasted the script in, wrote a cron schedule expression, and... it didn't work.

After a long debugging session, I realized the issue wasn't my script, my network, or the stars aligning wrong. It was me. My server was in the wrong time zone. I had recently reinstalled unRAID. I bought my license back in 2024 with one year of updates. Outside my license window, version 7.3.0 released, which added an Onboarding Wizard that asks for your time zone. Because I didn't want to pay $36 to extend my license for another year just to access that update, I missed the time zone setup. A quick manual fix in the unRAID GUI sorted it out, and the cron job started firing perfectly.

As a final experiment, I had the idea to add my NAS package share to the CacheDir list in my pacman.conf. My hope was that when I ran pacman -Syu, it would see the new packages on the NAS and install them directly from there. Instead, it ignored them, redownloaded the packages anyway, and the total progress bar did weird things (like going beyond 100%).

So, I stuck to the original plan: a simple sudo pacman -U /mnt/arch-pkg-cache/pkgs/*.pkg.tar.zst the next day. It installs instantly, uses zero internet, and keeps my daytime browsing perfectly smooth.