Restart active OWE user services after upgrades (#888)

This commit is contained in:
David Heinemeier Hansson authored and GitHub committed 2026-10-09 11:43:56 +02:00
1 parent 3e4cb2308e
commit f3e71f8a13
5 files changed
+134 -3

No files matched your search

+1
View File
@@ -48,6 +48,7 @@ jobs:
archlinux:base-devel bash -lc '
set -euo pipefail
pacman -Syu --noconfirm git jq python libarchive neovim tmux
python tests/owe-package-restart.py
python tests/oma-service-removal.py
python tests/upstream-watch.py
bash tests/ipu7-install.sh
+9
View File
@@ -0,0 +1,9 @@
[Trigger]
Operation = Upgrade
Type = Package
Target = owe
[Action]
Description = Restarting active OWE user services...
When = PostTransaction
Exec = /usr/lib/owe/package-restart
+9 -3
View File
@@ -1,7 +1,7 @@
# Maintainer: owe contributors
pkgname=owe
pkgver=0.2.10
pkgrel=3
pkgrel=4
pkgdesc="High-performance wallpaper engine for Omarchy (mp4, gif, stills)"
arch=('x86_64' 'aarch64')
url="https://github.com/omacom/owe"
@@ -12,9 +12,13 @@ checkdepends=('python')
optdepends=('intel-media-driver: VAAPI hardware decode on Intel GPUs'
'libva-mesa-driver: VAAPI hardware decode on AMD GPUs')
source=("$pkgname-$pkgver.tar.gz::https://github.com/omacom/owe/archive/refs/tags/v$pkgver.tar.gz"
'lock-policy-timestamp.patch')
'lock-policy-timestamp.patch'
'90-owe-restart.hook'
'package-restart')
sha256sums=('b171397c24c0ffd6096781b3f744f674055a57f660a74db07a3b71a111a6980b'
'3ebf413093922b0917bca60c42720c6424c77b1695317364bf73f9fd745376d8')
'3ebf413093922b0917bca60c42720c6424c77b1695317364bf73f9fd745376d8'
'aee4a08132f0528575d3b7364defc070b8af1648cc48559238ba089c09504b62'
'ef1fc780425343623c4a4ddd36cc58cd89ba8a71c528641f4b421db834a349cd')
prepare() {
cd "$srcdir/owe-$pkgver"
@@ -43,6 +47,8 @@ check() {
package() {
DESTDIR="$pkgdir" ninja -C build install
install -Dm644 "$srcdir/90-owe-restart.hook" "$pkgdir/usr/share/libalpm/hooks/90-owe-restart.hook"
install -Dm755 "$srcdir/package-restart" "$pkgdir/usr/lib/owe/package-restart"
install -d "$pkgdir/usr/lib/systemd/user"
sed 's|%h/.local/bin/owed|/usr/bin/owed|' "$srcdir/owe-$pkgver/systemd/owed.service" \
>"$pkgdir/usr/lib/systemd/user/owed.service"
+21
View File
@@ -0,0 +1,21 @@
#!/bin/bash
# Run after systemd's 30-systemd-daemon-reload-user.hook. Image builds and
# offline installations have no running managers and must remain untouched.
if systemd-detect-virt --chroot || ! systemd-notify --booted; then
exit 0
fi
managers=$(systemctl list-units 'user@*.service' --state=running --no-legend --plain) || exit 1
result=0
while read -r unit _; do
[[ $unit =~ ^user@([0-9]+)\.service$ ]] || continue
user_id=${BASH_REMATCH[1]}
# Use the user's manager rather than pacman's root session. try-restart
# never starts a stopped service or changes its enablement.
if ! systemctl --user --machine="$user_id@.host" try-restart owed.service; then
echo ":: Could not restart OWE for user $user_id; restart owed.service in that user session." >&2
result=1
fi
done <<< "$managers"
exit "$result"
+94
View File
@@ -0,0 +1,94 @@
#!/usr/bin/env python3
"""Package restart fixtures; no real systemd managers are contacted."""
import hashlib
import os
from pathlib import Path
import subprocess
import tempfile
import unittest
ROOT = Path(__file__).resolve().parents[1]
PACKAGE = ROOT / "pkgbuilds/owe"
class Restart(unittest.TestCase):
def setUp(self):
self.temp = tempfile.TemporaryDirectory(prefix="owe-restart-")
self.addCleanup(self.temp.cleanup)
self.root = Path(self.temp.name)
self.log = self.root / "calls"
self.env = dict(os.environ, PATH=f"{self.root}:{os.environ['PATH']}", CALLS=str(self.log))
self.command("systemd-detect-virt", 'exit "${CHROOT:-1}"')
self.command("systemd-notify", 'exit "${BOOTED:-0}"')
self.command("systemctl", '''
printf '%s\\n' "$*" >> "$CALLS"
case "$1" in
list-units)
printf '%s\\n' "${MANAGERS:-}"
exit "${LIST_FAILED:-0}"
;;
esac
[[ $* != *"--machine=${FAIL_USER:-none}@.host"* ]]
''')
def command(self, name, body):
path = self.root / name
path.write_text("#!/bin/bash\n" + body + "\n")
path.chmod(0o755)
def run_hook(self, **values):
return subprocess.run(["bash", str(PACKAGE / "package-restart")],
env=self.env | values, capture_output=True, text=True)
def calls(self):
return self.log.read_text().splitlines() if self.log.exists() else []
def test_offline_and_chroot_skip_managers(self):
for values in ({"CHROOT": "0"}, {"BOOTED": "1"}):
with self.subTest(values=values):
self.assertEqual(self.run_hook(**values).returncode, 0)
self.assertEqual(self.calls(), [])
def test_no_running_managers(self):
self.assertEqual(self.run_hook().returncode, 0)
self.assertEqual(len(self.calls()), 1)
def test_all_running_managers_use_conditional_restart(self):
result = self.run_hook(MANAGERS="user@1000.service loaded active running User Manager\nuser@1001.service loaded active running User Manager")
self.assertEqual(result.returncode, 0, result.stderr)
self.assertEqual(self.calls(), [
"list-units user@*.service --state=running --no-legend --plain",
"--user --machine=1000@.host try-restart owed.service",
"--user --machine=1001@.host try-restart owed.service",
])
def test_failure_reports_user_and_continues(self):
result = self.run_hook(MANAGERS="user@1000.service loaded active running\nuser@1001.service loaded active running", FAIL_USER="1000")
self.assertNotEqual(result.returncode, 0)
self.assertIn("Could not restart OWE for user 1000", result.stderr)
self.assertIn("--user --machine=1001@.host try-restart owed.service", self.calls())
def test_list_failure_is_not_silently_successful(self):
self.assertNotEqual(self.run_hook(LIST_FAILED="1").returncode, 0)
self.assertEqual(len(self.calls()), 1)
def test_only_user_manager_units_are_accepted(self):
self.assertEqual(self.run_hook(MANAGERS="invalid.service loaded active running\nuser@oops.service loaded active running").returncode, 0)
self.assertEqual(len(self.calls()), 1)
def test_packaged_hook_and_sources(self):
hook = (PACKAGE / "90-owe-restart.hook").read_text()
self.assertIn("Operation = Upgrade\n", hook)
self.assertNotIn("Operation = Install", hook)
self.assertNotIn("Operation = Remove", hook)
self.assertIn("When = PostTransaction\n", hook)
self.assertIn("Exec = /usr/lib/owe/package-restart\n", hook)
recipe = (PACKAGE / "PKGBUILD").read_text()
for source in ("90-owe-restart.hook", "package-restart"):
self.assertIn(hashlib.sha256((PACKAGE / source).read_bytes()).hexdigest(), recipe)
self.assertIn('"$pkgdir/usr/share/libalpm/hooks/90-owe-restart.hook"', recipe)
self.assertIn('"$pkgdir/usr/lib/owe/package-restart"', recipe)
if __name__ == "__main__":
unittest.main()