aboutsummaryrefslogtreecommitdiffstats
path: root/desktop/modules/updates/updatesctl
diff options
context:
space:
mode:
Diffstat (limited to 'desktop/modules/updates/updatesctl')
-rwxr-xr-xdesktop/modules/updates/updatesctl75
1 files changed, 75 insertions, 0 deletions
diff --git a/desktop/modules/updates/updatesctl b/desktop/modules/updates/updatesctl
new file mode 100755
index 0000000..c1e69ed
--- /dev/null
+++ b/desktop/modules/updates/updatesctl
@@ -0,0 +1,75 @@
+#!/usr/bin/python3
+# Copyright (C) 2026 Danilo M. <danix@danix.xyz>
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License version 2 as
+# published by the Free Software Foundation.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+"""updatesctl - what to do next to bring this machine up to date.
+
+Prints one JSON document for the drawer's updates module, with every pending
+update already sorted into the action it needs. The sections and the reasons
+for them are in docs/superpowers/specs/2026-10-15-updates-design.md.
+
+ updatesctl from the last nvchecker results
+ updatesctl --refresh run nvchecker first
+
+UPDATESCTL_ROOT prefixes every system path and HOME roots the user's, so the
+test can point both at a fixture tree.
+"""
+
+import json
+import os
+import re
+import shutil
+import subprocess
+import sys
+import time
+from functools import cmp_to_key
+from pathlib import Path
+
+ROOT = os.environ.get("UPDATESCTL_ROOT", "")
+HOME = Path(os.environ["HOME"])
+NFS_TIMEOUT = float(os.environ.get("UPDATESCTL_NFS_TIMEOUT", "5"))
+
+
+def sysp(p):
+ return Path(ROOT + p)
+
+
+def vercmp(a, b):
+ """Version order: numeric chunks as numbers, numbers after letters.
+
+ When one version runs out, the longer one is newer unless its next chunk
+ is alphabetic, which makes 0.12.3_beta.4 older than 0.12.3 and a kernel
+ suffix or git hash (9.018.00_6.18.41) newer than its base, never behind.
+ """
+ def chunks(v):
+ v = v.lower()
+ return re.findall(r"\d+|[a-z]+", v[1:] if v.startswith("v") else v)
+
+ x, y = chunks(a), chunks(b)
+ for p, q in zip(x, y):
+ if p == q:
+ continue
+ if p.isdigit() and q.isdigit():
+ return (int(p) > int(q)) - (int(p) < int(q))
+ if p.isdigit() != q.isdigit():
+ return 1 if p.isdigit() else -1
+ return (p > q) - (p < q)
+ if len(x) == len(y):
+ return 0
+ longer, sign = (x, 1) if len(x) > len(y) else (y, -1)
+ return sign if longer[min(len(x), len(y))].isdigit() else -sign
+
+
+def main():
+ pass
+
+
+if __name__ == "__main__":
+ main()