1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
|
# slackware-changelog
A widget for a [Homepage](https://gethomepage.dev) dashboard, one of the
modules under `homepage-modules/`. Siblings share the shape described below.
## Shape of a module
Homepage's `customapi` widget fetches JSON and maps fields to labels. It
cannot parse text, cannot do arithmetic, and refetches on every refresh
interval. So each module is a small stdlib Python HTTP service on localhost
that does the parsing and caching, plus:
- `<name>.py` - the service, single file, stdlib only, executable
- `test_<name>.py` - self-check, fakes the upstream, prints `ok`
- `<name>.service` - systemd unit, `DynamicUser=yes`, config via `Environment=`
- `homepage-services.yaml` / `homepage-settings.yaml` - snippets to merge
No dependencies. No framework. `ThreadingHTTPServer` and `urllib`.
## This module
Serves the latest Slackware -current ChangeLog entry on `127.0.0.1:8098`.
Endpoints: `/latest` (counts and summary), `/top` (the first `TOP_N` packages
as flat fields), `/packages` (every package with its action), `/entry` (HTML
page of the entry), `/refresh` (bypass the cache).
### Why /top exists
`customapi` maps a fixed list of field names to labels. It cannot iterate an
array and cannot scroll, so a JSON list of packages renders as nothing. `/top`
flattens the first `TOP_N` into `pkg1`, `pkg2`, ... which the widget can map
one per row, and appends a `... and N more` row in the next slot when the entry
is longer. The card links to the ChangeLog for the rest.
Mapping more `pkgN` fields than the entry has packages is harmless: Homepage
skips a field that is absent. So the snippet maps a fixed 9 rows regardless of
how many that day's entry holds.
### ChangeLog format
The file is reverse-chronological. Entries are separated by a `+--------+`
line, so the latest entry is everything before the first separator.
Inside an entry:
```
Mon Sep 7 22:56:39 UTC 2026
a/util-linux-2.42.3-x86_64-1.txz: Upgraded.
This update fixes bugs and security issues.
(* Security fix *)
l/cryptopp-8.9.0-x86_64-3.txz: Rebuilt.
Fixed library location. Thanks to Petri Kaukasoina.
```
Gotcha: a package line is identified by **starting at column 0**, not by
ending in a period. Note lines are indented and frequently end in a period
too ("Thanks to Petri Kaukasoina."), so a period-based regex counts prose as
packages. The test asserts this specific case.
Actions seen: `Upgraded`, `Rebuilt`, `Added`, `Removed`. The parser counts
whatever word it finds rather than matching a fixed list, so a new action
verb shows up in `summary` instead of vanishing.
Security fixes are marked by the literal `(* Security fix *)` on its own
indented line. CVE ids appear in the notes as cve.org URLs.
### Caching
`CACHE_TTL` (default 1800s) governs upstream fetches; the widget's
`refreshInterval` only redraws. A failed refresh keeps the previous entry and
adds a `stale` field with the error, so an unreachable mirror degrades to an
old entry rather than a blank card. `/refresh` forces a fetch but obeys the
same fallback.
### Rendering
`/entry` escapes everything from the ChangeLog with `html.escape` before it
reaches the page. Upstream text is not trusted markup; the test asserts no
`<script` survives.
## Conventions
- GPLv2 only. Header notice in every source file.
- Snippet YAML files are merged into Homepage's config by hand, never copied
over it; the comments at the top of each say so.
- Layout groups: any group omitted from `layout:` falls to the bottom of the
page, so the snippet reminds the reader to list their existing groups too.
|