{"id":114,"date":"2025-10-09T10:05:12","date_gmt":"2025-10-09T06:05:12","guid":{"rendered":"https:\/\/www.kerloys.com\/?p=114"},"modified":"2025-10-09T10:05:12","modified_gmt":"2025-10-09T06:05:12","slug":"automating-daily-updates-on-pop_os-using-systemd-timer","status":"publish","type":"post","link":"https:\/\/www.kerloys.com\/index.php\/2025\/10\/09\/automating-daily-updates-on-pop_os-using-systemd-timer\/","title":{"rendered":"Automating Daily Updates on Pop!_OS Using systemd Timer"},"content":{"rendered":"\n<h1 class=\"wp-block-heading\"><\/h1>\n\n\n\n<p>Keeping your Linux system up-to-date is crucial for stability, performance, and especially <strong>security<\/strong>.<br>But running <code>sudo apt update &amp;&amp; sudo apt upgrade<\/code> manually every morning gets repetitive.<\/p>\n\n\n\n<p>Let\u2019s automate it so your <strong>Pop!_OS<\/strong> machine updates itself <strong>once per day<\/strong>, only on the first boot of the day \u2014 not on every restart.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">\ud83e\udde0 Goal<\/h2>\n\n\n\n<p>We\u2019ll build a lightweight automation that:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Runs system and Flatpak updates automatically once per day<\/li>\n\n\n\n<li>Ensures updates happen <em>after<\/em> the network is online<\/li>\n\n\n\n<li>Logs everything to <code>journalctl<\/code> for easy review<\/li>\n\n\n\n<li>Works quietly in the background using <strong>systemd timers<\/strong><\/li>\n<\/ul>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">\ud83e\uddf0 Overview<\/h2>\n\n\n\n<p>You\u2019ll create three small components:<\/p>\n\n\n\n<ol class=\"wp-block-list\">\n<li>A Bash script that performs the updates<\/li>\n\n\n\n<li>A <code>systemd<\/code> service to define how the script runs<\/li>\n\n\n\n<li>A <code>systemd<\/code> timer to define when it runs (once per day)<\/li>\n<\/ol>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">\ud83e\ude84 Step 1 \u2014 Create the Update Script<\/h2>\n\n\n\n<p>File: <strong><code>\/usr\/local\/sbin\/daily-update.sh<\/code><\/strong><\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>sudo tee \/usr\/local\/sbin\/daily-update.sh &gt;\/dev\/null &lt;&lt;'EOF'\n#!\/usr\/bin\/env bash\nset -euo pipefail\n\n# Log everything to the system journal (view with: journalctl -t daily-update)\nexec 1&gt; &gt;(logger -t daily-update) 2&gt;&amp;1\n\nexport DEBIAN_FRONTEND=noninteractive\n\n# Skip if APT is already running\nif pidof apt apt-get &gt;\/dev\/null; then\n  echo \"APT already running, skipping update.\"\n  exit 0\nfi\n\n# Refresh package lists\napt-get update\n\n# Upgrade packages safely, keeping existing config files\napt-get -o Dpkg::Options::=--force-confdef \\\n        -o Dpkg::Options::=--force-confold \\\n        -y dist-upgrade\n\n# Clean out old dependencies\napt-get -y autoremove\n\n# Update Flatpak apps if installed\nflatpak update -y || true\nEOF\n\nsudo chmod +x \/usr\/local\/sbin\/daily-update.sh\n<\/code><\/pre>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h3 class=\"wp-block-heading\">\ud83d\udd0d Explanation<\/h3>\n\n\n\n<figure class=\"wp-block-table\"><table class=\"has-fixed-layout\"><thead><tr><th>Line<\/th><th>Purpose<\/th><\/tr><\/thead><tbody><tr><td><code>set -euo pipefail<\/code><\/td><td>Exits on any error, undefined variable, or broken pipe<\/td><\/tr><tr><td><code>logger -t daily-update<\/code><\/td><td>Sends logs to <code>journalctl<\/code> under the tag <em>daily-update<\/em><\/td><\/tr><tr><td><code>DEBIAN_FRONTEND=noninteractive<\/code><\/td><td>Prevents interactive prompts during unattended upgrades<\/td><\/tr><tr><td><code>apt-get update<\/code><\/td><td>Fetches the latest package lists<\/td><\/tr><tr><td><code>dist-upgrade<\/code><\/td><td>Installs all available upgrades, resolving dependencies<\/td><\/tr><tr><td><code>autoremove<\/code><\/td><td>Cleans unused packages automatically<\/td><\/tr><tr><td><code>flatpak update<\/code><\/td><td>Updates any Flatpak apps, if present<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<p>Check logs anytime with:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>journalctl -t daily-update -n 200 --no-pager\n<\/code><\/pre>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">\u2699\ufe0f Step 2 \u2014 Create the systemd Service<\/h2>\n\n\n\n<p>File: <strong><code>\/etc\/systemd\/system\/daily-update.service<\/code><\/strong><\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>&#91;Unit]\nDescription=Daily system + Flatpak update\nWants=network-online.target\nAfter=network-online.target\n\n&#91;Service]\nType=oneshot\nExecStart=\/usr\/local\/sbin\/daily-update.sh\nNice=10\nIOSchedulingClass=best-effort\nIOSchedulingPriority=7\n<\/code><\/pre>\n\n\n\n<h3 class=\"wp-block-heading\">\ud83e\udde9 What Each Setting Does<\/h3>\n\n\n\n<ul class=\"wp-block-list\">\n<li><strong>Wants=network-online.target<\/strong><br>Ensures network is initialized before running (important for APT).<\/li>\n\n\n\n<li><strong>After=network-online.target<\/strong><br>Prevents the update from starting too early at boot.<\/li>\n\n\n\n<li><strong>Type=oneshot<\/strong><br>Runs once and exits; not a background service.<\/li>\n\n\n\n<li><strong>Nice \/ IOSchedulingClass \/ IOSchedulingPriority<\/strong><br>Lowers CPU and disk priority so updates don\u2019t slow your desktop.<\/li>\n<\/ul>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">\u23f0 Step 3 \u2014 Create the Timer<\/h2>\n\n\n\n<p>File: <strong><code>\/etc\/systemd\/system\/daily-update.timer<\/code><\/strong><\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>&#91;Unit]\nDescription=Run daily-update once per day\n\n&#91;Timer]\nOnCalendar=daily\nRandomizedDelaySec=1h\nPersistent=true\nUnit=daily-update.service\n\n&#91;Install]\nWantedBy=timers.target\n<\/code><\/pre>\n\n\n\n<h3 class=\"wp-block-heading\">\ud83d\udd0d Explanation<\/h3>\n\n\n\n<figure class=\"wp-block-table\"><table class=\"has-fixed-layout\"><thead><tr><th>Setting<\/th><th>Meaning<\/th><\/tr><\/thead><tbody><tr><td><code>OnCalendar=daily<\/code><\/td><td>Runs once every 24 hours<\/td><\/tr><tr><td><code>RandomizedDelaySec=1h<\/code><\/td><td>Adds a random 0\u20131 hour delay to avoid network congestion<\/td><\/tr><tr><td><code>Persistent=true<\/code><\/td><td>Runs automatically after next boot if missed<\/td><\/tr><tr><td><code>Unit=<\/code><\/td><td>Tells the timer which service to trigger<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">\ud83d\ude80 Step 4 \u2014 Enable and Test<\/h2>\n\n\n\n<p>Reload and enable the timer:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>sudo systemctl daemon-reload\nsudo systemctl enable --now daily-update.timer\n<\/code><\/pre>\n\n\n\n<p>Check scheduled timers:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>systemctl list-timers | grep daily-update\n<\/code><\/pre>\n\n\n\n<p>Run manually (for testing):<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>sudo systemctl start daily-update.service\n<\/code><\/pre>\n\n\n\n<p>View logs:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>journalctl -t daily-update -n 50 --no-pager\n<\/code><\/pre>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">\ud83e\uddef Common Error: Permission Denied on Lock File<\/h2>\n\n\n\n<p>If you see:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>could not open lock file \/var\/lib\/apt\/lists\/lock - open (13: Permission denied)\n<\/code><\/pre>\n\n\n\n<p>it means the script wasn\u2019t run as root.<br>Use:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>sudo \/usr\/local\/sbin\/daily-update.sh\n<\/code><\/pre>\n\n\n\n<p>When triggered by <code>systemd<\/code>, it runs as root automatically.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">\ud83e\uddfe Verify It\u2019s Working<\/h2>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Show next scheduled run <code>systemctl list-timers --all | grep daily-update<\/code><\/li>\n\n\n\n<li>View last run <code>systemctl status daily-update.service<\/code><\/li>\n\n\n\n<li>Check logs for today <code>journalctl -t daily-update --since today<\/code><\/li>\n<\/ul>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">\ud83d\udcac Final Thoughts<\/h2>\n\n\n\n<p>This lightweight automation makes your Pop!_OS machine self-maintaining.<br>It runs quietly once a day, applies updates, and cleans old packages \u2014 all while you focus on your work.<\/p>\n\n\n\n<p>For an optional desktop notification, add this to the end of your script:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>notify-send \"Daily Update\" \"System packages and Flatpaks are up to date!\"\n<\/code><\/pre>\n\n\n\n<p>That\u2019s it \u2014 your Pop!_OS now keeps itself secure and current every day \u2728<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<p>\u2705 <em>Written and tested on Pop!_OS 22.04 LTS (Ubuntu base). Works on any systemd-based distro: Ubuntu, Debian, Fedora, etc.<\/em><\/p>\n","protected":false},"excerpt":{"rendered":"<p>Keeping your Linux system up-to-date is crucial for stability, performance, and especially security.But running sudo apt update &amp;&amp; sudo apt upgrade manually every morning gets repetitive. Let\u2019s automate it so your Pop!_OS machine updates itself once per day, only on the first boot of the day \u2014 not on every restart. \ud83e\udde0 Goal We\u2019ll build &hellip; <\/p>\n<p class=\"link-more\"><a href=\"https:\/\/www.kerloys.com\/index.php\/2025\/10\/09\/automating-daily-updates-on-pop_os-using-systemd-timer\/\" class=\"more-link\">Continue reading<span class=\"screen-reader-text\"> &#8220;Automating Daily Updates on Pop!_OS Using systemd Timer&#8221;<\/span><\/a><\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[1],"tags":[34,33,35],"class_list":["post-114","post","type-post","status-publish","format-standard","hentry","category-uncategorised","tag-linux","tag-pop-os","tag-ubuntu"],"_links":{"self":[{"href":"https:\/\/www.kerloys.com\/index.php\/wp-json\/wp\/v2\/posts\/114","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.kerloys.com\/index.php\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.kerloys.com\/index.php\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.kerloys.com\/index.php\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/www.kerloys.com\/index.php\/wp-json\/wp\/v2\/comments?post=114"}],"version-history":[{"count":1,"href":"https:\/\/www.kerloys.com\/index.php\/wp-json\/wp\/v2\/posts\/114\/revisions"}],"predecessor-version":[{"id":115,"href":"https:\/\/www.kerloys.com\/index.php\/wp-json\/wp\/v2\/posts\/114\/revisions\/115"}],"wp:attachment":[{"href":"https:\/\/www.kerloys.com\/index.php\/wp-json\/wp\/v2\/media?parent=114"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.kerloys.com\/index.php\/wp-json\/wp\/v2\/categories?post=114"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.kerloys.com\/index.php\/wp-json\/wp\/v2\/tags?post=114"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}