Feature 57 (config and provisioning): modules declare config and provisions, their files rendered from templates; synced with main (issues of feature 57, 874)

This commit is contained in:
2026-10-16 07:37:43 +00:00
parent 1a2c917abf
commit 9652b2d5ce
771 changed files with 52063 additions and 1774 deletions
+3 -2
View File
@@ -35,8 +35,9 @@ resume units, is not a contribution. Its module keeps its own drop-ins on the sl
## The bar's battery and power draw (novox/hq ADR 0255, ADR 0260)
The module holds the machine's power everywhere, so it gives the bar what is generic about power, on
the machines that have it. Both are contributions to `node-bar` in the bar's own neutral terms, so any
bar renders them, and each names the capability the node-engine reports for it:
the machines that have it. Both are items of the collection `contribution:node-bar:block`, each the block text in
i3status-rust's grammar (`templates/bar-battery`, `templates/bar-power-draw`), placed on the bottom bar's status
place, and each names the capability the node-engine reports for it:
| block | where | what it shows |
|---|---|---|
+129
View File
@@ -0,0 +1,129 @@
package main
import (
"encoding/json"
"os"
"path/filepath"
"strings"
"testing"
)
// Each file's text is a template in templates/, the directory the manifest names (novox/hq ADR 0349 §2, issue 731),
// never in files/: files/ is the archive the machine unpacks, and stays exactly what the machine is given.
func TestEveryFileTakesItsTextFromATemplateOutsideTheUnpackedFiles(t *testing.T) {
raw, err := os.ReadFile("../../module.json")
if err != nil {
t.Fatal(err)
}
var m struct {
Templates string `json:"templates"`
Resources []map[string]any `json:"resources"`
Build struct {
Artifacts []map[string]any `json:"artifacts"`
} `json:"build"`
}
if err := json.Unmarshal(raw, &m); err != nil {
t.Fatal(err)
}
if m.Templates != "templates" {
t.Fatalf("the manifest names the templates directory %q, not templates", m.Templates)
}
files := 0
for _, r := range m.Resources {
if _, inline := r["content"]; inline {
t.Errorf("resource %v keeps its text inline: keep it in templates/ and name it as the template", r["id"])
}
if r["type"] != "file" {
continue
}
files++
name, _ := r["template"].(string)
if !strings.HasPrefix(name, "templates/") {
t.Errorf("resource %v names the template %q, not one in templates/", r["id"], name)
continue
}
if st, err := os.Lstat(filepath.Join("../..", name)); err != nil || !st.Mode().IsRegular() {
t.Errorf("resource %v names the template %s, which is not a plain file: %v", r["id"], name, err)
}
}
if files == 0 {
t.Fatal("no file resource")
}
packs := 0
for _, a := range m.Build.Artifacts {
if a["kind"] == "archive" && a["from"] == "templates" {
packs++
if a["name"] != "templates" {
t.Errorf("the templates archive is named %v, not templates", a["name"])
}
}
}
if packs != 1 {
t.Fatalf("the build packs templates/ into %d archives, not one", packs)
}
// The templates archive is read by the controller, never unpacked onto the machine.
for _, r := range m.Resources {
if r["artifact"] == "templates" {
t.Errorf("resource %v places the templates archive on the machine", r["id"])
}
}
// And files/ holds what the machine runs, no template.
err = filepath.WalkDir("../../files", func(at string, d os.DirEntry, err error) error {
if err != nil {
return err
}
if !d.IsDir() && !strings.HasPrefix(filepath.ToSlash(at), "../../files/bin/") {
t.Errorf("files/ holds %s, which is not one of the scripts the machine unpacks", at)
}
return nil
})
if err != nil {
t.Fatal(err)
}
}
// The bar's blocks are two items of the collection the bar places (design 57 L3, issue 781): the text i3status-rust's
// bars hold for the battery and for the power draw, byte for byte as the bar's own template rendered them before, the
// draw's key being the machine's own name.
func TestTheBarBlocksAreTwoItemsOfTheBarsCollection(t *testing.T) {
raw, err := os.ReadFile("../../module.json")
if err != nil {
t.Fatal(err)
}
var m struct {
Contributions []any `json:"contributions"`
Items []struct {
Collection string `json:"collection"`
Name string `json:"name"`
IfCapability string `json:"if-capability"`
Attributes map[string]string `json:"attributes"`
Template string `json:"template"`
} `json:"items"`
}
if err := json.Unmarshal(raw, &m); err != nil {
t.Fatal(err)
}
if len(m.Contributions) != 0 || len(m.Items) != 2 {
t.Fatalf("%d contributions and %d items", len(m.Contributions), len(m.Items))
}
want := []struct{ name, capability, order, template, text string }{
{"battery", "battery", "40", "templates/bar-battery", "[[block]]\nblock = \"battery\"\n" +
"format = \"$icon $percentage{ $time|}\"\ncharging_format = \"$icon $percentage{ $time|}\"\n" +
"not_charging_format = \"$icon $percentage\"\nfull_format = \"$icon $percentage\"\n" +
"empty_format = \"$icon $percentage\"\nmissing_format = \"\"\n\n"},
{"power-draw", "power-meter", "45", "templates/bar-power-draw", "[[block]]\nblock = \"custom\"\n" +
"command = \"~/.local/bin/i3status-value power.draw ${machine:name}\"\ninterval = 5\nhide_when_empty = true\n\n"},
}
for i, w := range want {
it := m.Items[i]
if it.Collection != "contribution:node-bar:block" || it.Name != w.name || it.IfCapability != w.capability ||
it.Template != w.template || it.Attributes["bar"] != "bottom" || it.Attributes["place"] != "status" ||
it.Attributes["order"] != w.order || len(it.Attributes) != 3 {
t.Errorf("item %d is %+v", i, it)
}
text, err := os.ReadFile(filepath.Join("../..", w.template))
if err != nil || string(text) != w.text {
t.Errorf("%s holds %q (%v), want %q", w.template, text, err, w.text)
}
}
}
+120 -34
View File
@@ -1,6 +1,8 @@
{
"module": "power",
"version": "1",
"moved": true,
"templates": "templates",
"capabilities": [
"package-manager",
"service-manager"
@@ -34,6 +36,88 @@
"power_run",
"power_check"
],
"config": {
"handle-power-key": {
"type": "string",
"description": "What logind does when the power key is pressed: one of logind's actions, such as suspend, ignore or poweroff (logind.conf(5)); the operator's choice for this machine, with no default.",
"enum": [
"ignore",
"poweroff",
"reboot",
"halt",
"kexec",
"suspend",
"hibernate",
"hybrid-sleep",
"suspend-then-hibernate",
"lock",
"factory-reset",
"sleep"
],
"trusted": true,
"required": true
},
"handle-lid-switch": {
"type": "string",
"description": "What logind does when the lid is closed on battery: one of logind's actions, such as suspend, ignore or poweroff (logind.conf(5)); the operator's choice for this machine, with no default.",
"enum": [
"ignore",
"poweroff",
"reboot",
"halt",
"kexec",
"suspend",
"hibernate",
"hybrid-sleep",
"suspend-then-hibernate",
"lock",
"factory-reset",
"sleep"
],
"trusted": true,
"required": true
},
"handle-lid-switch-external-power": {
"type": "string",
"description": "What logind does when the lid is closed on external power: one of logind's actions, such as suspend, ignore or poweroff (logind.conf(5)); the operator's choice for this machine, with no default.",
"enum": [
"ignore",
"poweroff",
"reboot",
"halt",
"kexec",
"suspend",
"hibernate",
"hybrid-sleep",
"suspend-then-hibernate",
"lock",
"factory-reset",
"sleep"
],
"trusted": true,
"required": true
},
"handle-lid-switch-docked": {
"type": "string",
"description": "What logind does when the lid is closed while docked or with another screen attached: one of logind's actions, such as suspend, ignore or poweroff (logind.conf(5)); the operator's choice for this machine, with no default.",
"enum": [
"ignore",
"poweroff",
"reboot",
"halt",
"kexec",
"suspend",
"hibernate",
"hybrid-sleep",
"suspend-then-hibernate",
"lock",
"factory-reset",
"sleep"
],
"trusted": true,
"required": true
}
},
"resources": [
{
"id": "polkit",
@@ -63,42 +147,42 @@
"type": "file",
"path": "/etc/mesh-power/moments/after-boot",
"mode": "0644",
"content": "# What every module on this machine runs at the moment after-boot, as the mesh placed it (module power,\n# novox/hq ADR 0211). Replaced on every push: code is added by the module it belongs to, as a\n# contribution for this moment. Each module's piece runs on its own, as root, bounded in time.\n${shell:after-boot:first}${shell:after-boot:normal}${shell:after-boot:last}"
"template": "templates/mesh-power/moments/after-boot"
},
{
"id": "moment-before-sleep",
"type": "file",
"path": "/etc/mesh-power/moments/before-sleep",
"mode": "0644",
"content": "# What every module on this machine runs at the moment before-sleep, as the mesh placed it (module power,\n# novox/hq ADR 0211). Replaced on every push: code is added by the module it belongs to, as a\n# contribution for this moment. Each module's piece runs on its own, as root, bounded in time.\n${shell:before-sleep:first}${shell:before-sleep:normal}${shell:before-sleep:last}"
"template": "templates/mesh-power/moments/before-sleep"
},
{
"id": "moment-after-wake",
"type": "file",
"path": "/etc/mesh-power/moments/after-wake",
"mode": "0644",
"content": "# What every module on this machine runs at the moment after-wake, as the mesh placed it (module power,\n# novox/hq ADR 0211). Replaced on every push: code is added by the module it belongs to, as a\n# contribution for this moment. Each module's piece runs on its own, as root, bounded in time.\n${shell:after-wake:first}${shell:after-wake:normal}${shell:after-wake:last}"
"template": "templates/mesh-power/moments/after-wake"
},
{
"id": "moment-before-shutdown",
"type": "file",
"path": "/etc/mesh-power/moments/before-shutdown",
"mode": "0644",
"content": "# What every module on this machine runs at the moment before-shutdown, as the mesh placed it (module power,\n# novox/hq ADR 0211). Replaced on every push: code is added by the module it belongs to, as a\n# contribution for this moment. Each module's piece runs on its own, as root, bounded in time.\n${shell:before-shutdown:first}${shell:before-shutdown:normal}${shell:before-shutdown:last}"
"template": "templates/mesh-power/moments/before-shutdown"
},
{
"id": "moment-on-mains",
"type": "file",
"path": "/etc/mesh-power/moments/on-mains",
"mode": "0644",
"content": "# What every module on this machine runs at the moment on-mains, as the mesh placed it (module power,\n# novox/hq ADR 0211). Replaced on every push: code is added by the module it belongs to, as a\n# contribution for this moment. Each module's piece runs on its own, as root, bounded in time.\n${shell:on-mains:first}${shell:on-mains:normal}${shell:on-mains:last}"
"template": "templates/mesh-power/moments/on-mains"
},
{
"id": "moment-on-battery",
"type": "file",
"path": "/etc/mesh-power/moments/on-battery",
"mode": "0644",
"content": "# What every module on this machine runs at the moment on-battery, as the mesh placed it (module power,\n# novox/hq ADR 0211). Replaced on every push: code is added by the module it belongs to, as a\n# contribution for this moment. Each module's piece runs on its own, as root, bounded in time.\n${shell:on-battery:first}${shell:on-battery:normal}${shell:on-battery:last}"
"template": "templates/mesh-power/moments/on-battery"
},
{
"id": "logind-drop-ins",
@@ -111,7 +195,7 @@
"type": "file",
"path": "/etc/systemd/logind.conf.d/power.conf",
"mode": "0644",
"content": "# Written by the mesh (module power, novox/hq ADR 0211). Replaced on every push.\n#\n# The power key and the lid, from this module's settings: the mesh's, then this machine's.\n[Login]\nHandlePowerKey=${setting:handle-power-key}\nHandleLidSwitch=${setting:handle-lid-switch}\nHandleLidSwitchExternalPower=${setting:handle-lid-switch-external-power}\nHandleLidSwitchDocked=${setting:handle-lid-switch-docked}\n"
"template": "templates/systemd/logind.conf.d/power.conf"
},
{
"id": "logind",
@@ -126,35 +210,35 @@
"type": "file",
"path": "/etc/systemd/system/mesh-power-after-boot.service",
"mode": "0644",
"content": "# Written by the mesh (module power, novox/hq ADR 0211). Replaced on every push.\n[Unit]\nDescription=Every module's code after boot (mesh power)\nWants=network-online.target\nAfter=network-online.target\n\n[Service]\nType=oneshot\nRemainAfterExit=yes\nExecStart=/usr/local/lib/mesh-power/bin/power-moment after-boot\n\n[Install]\nWantedBy=multi-user.target\n"
"template": "templates/systemd/system/mesh-power-after-boot.service"
},
{
"id": "before-shutdown-unit",
"type": "file",
"path": "/etc/systemd/system/mesh-power-before-shutdown.service",
"mode": "0644",
"content": "# Written by the mesh (module power, novox/hq ADR 0211). Replaced on every push.\n#\n# Started at boot and left active; stopping it is the shutdown. Ordered after the network, so at\n# shutdown it stops, and runs every module's code, while the network is still up.\n[Unit]\nDescription=Every module's code before shutdown (mesh power)\nWants=network-online.target\nAfter=network-online.target\n\n[Service]\nType=oneshot\nRemainAfterExit=yes\nExecStart=/bin/true\nExecStop=/usr/local/lib/mesh-power/bin/power-moment before-shutdown\nTimeoutStopSec=120\n\n[Install]\nWantedBy=multi-user.target\n"
"template": "templates/systemd/system/mesh-power-before-shutdown.service"
},
{
"id": "before-sleep-unit",
"type": "file",
"path": "/etc/systemd/system/mesh-power-before-sleep.service",
"mode": "0644",
"content": "# Written by the mesh (module power, novox/hq ADR 0211). Replaced on every push.\n#\n# Pulled in by sleep.target through this module's drop-in on it, never enabled: a one-shot waiting\n# for a sleep is not a service whose state the mesh can declare.\n[Unit]\nDescription=Every module's code before sleep (mesh power)\nBefore=sleep.target\n\n[Service]\nType=oneshot\nExecStart=/usr/local/lib/mesh-power/bin/power-moment before-sleep\n"
"template": "templates/systemd/system/mesh-power-before-sleep.service"
},
{
"id": "after-wake-unit",
"type": "file",
"path": "/etc/systemd/system/mesh-power-after-wake.service",
"mode": "0644",
"content": "# Written by the mesh (module power, novox/hq ADR 0211). Replaced on every push.\n#\n# Pulled in by the four sleep targets through this module's drop-ins on them, and ordered after\n# them, so it runs once the machine is awake again. Never enabled.\n[Unit]\nDescription=Every module's code after waking (mesh power)\nAfter=suspend.target hibernate.target hybrid-sleep.target suspend-then-hibernate.target\n\n[Service]\nType=oneshot\nExecStart=/usr/local/lib/mesh-power/bin/power-moment after-wake\n"
"template": "templates/systemd/system/mesh-power-after-wake.service"
},
{
"id": "supply-unit",
"type": "file",
"path": "/etc/systemd/system/mesh-power-supply.service",
"mode": "0644",
"content": "# Written by the mesh (module power, novox/hq ADR 0211). Replaced on every push.\n#\n# Started by the module's udev rule when a power supply changes; runs on-mains or on-battery once per\n# change of source.\n[Unit]\nDescription=Every module's code for the power source (mesh power)\n\n[Service]\nType=oneshot\nExecStart=/usr/local/lib/mesh-power/bin/power-moment supply\n"
"template": "templates/systemd/system/mesh-power-supply.service"
},
{
"id": "sleep-drop-ins",
@@ -167,7 +251,7 @@
"type": "file",
"path": "/etc/systemd/system/sleep.target.d/mesh-power.conf",
"mode": "0644",
"content": "# Written by the mesh (module power, novox/hq ADR 0211). Replaced on every push.\n[Unit]\nWants=mesh-power-before-sleep.service\n"
"template": "templates/systemd/system/sleep.target.d/mesh-power.conf"
},
{
"id": "suspend-drop-ins",
@@ -180,7 +264,7 @@
"type": "file",
"path": "/etc/systemd/system/suspend.target.d/mesh-power.conf",
"mode": "0644",
"content": "# Written by the mesh (module power, novox/hq ADR 0211). Replaced on every push.\n[Unit]\nWants=mesh-power-after-wake.service\n"
"template": "templates/systemd/system/suspend.target.d/mesh-power.conf"
},
{
"id": "hibernate-drop-ins",
@@ -193,7 +277,7 @@
"type": "file",
"path": "/etc/systemd/system/hibernate.target.d/mesh-power.conf",
"mode": "0644",
"content": "# Written by the mesh (module power, novox/hq ADR 0211). Replaced on every push.\n[Unit]\nWants=mesh-power-after-wake.service\n"
"template": "templates/systemd/system/hibernate.target.d/mesh-power.conf"
},
{
"id": "hybrid-sleep-drop-ins",
@@ -206,7 +290,7 @@
"type": "file",
"path": "/etc/systemd/system/hybrid-sleep.target.d/mesh-power.conf",
"mode": "0644",
"content": "# Written by the mesh (module power, novox/hq ADR 0211). Replaced on every push.\n[Unit]\nWants=mesh-power-after-wake.service\n"
"template": "templates/systemd/system/hybrid-sleep.target.d/mesh-power.conf"
},
{
"id": "suspend-then-hibernate-drop-ins",
@@ -219,14 +303,14 @@
"type": "file",
"path": "/etc/systemd/system/suspend-then-hibernate.target.d/mesh-power.conf",
"mode": "0644",
"content": "# Written by the mesh (module power, novox/hq ADR 0211). Replaced on every push.\n[Unit]\nWants=mesh-power-after-wake.service\n"
"template": "templates/systemd/system/suspend-then-hibernate.target.d/mesh-power.conf"
},
{
"id": "udev-rule",
"type": "file",
"path": "/etc/udev/rules.d/90-mesh-power.rules",
"mode": "0644",
"content": "# Written by the mesh (module power, novox/hq ADR 0211). Replaced on every push.\n# Only the charger's change can switch the machine between mains and battery. A battery reports its\n# level many times a minute, and USB-C supplies come and go; matching them started the supply unit\n# every second or two on the laptop.\nSUBSYSTEM==\"power_supply\", ACTION==\"change\", ATTR{type}==\"Mains\", RUN+=\"/usr/bin/systemctl --no-block start mesh-power-supply.service\"\n"
"template": "templates/udev/rules.d/90-mesh-power.rules"
},
{
"id": "after-boot",
@@ -279,34 +363,36 @@
"name": "scripts",
"kind": "archive",
"from": "files"
},
{
"name": "templates",
"kind": "archive",
"from": "templates"
}
]
},
"contributions": [
"items": [
{
"seat": "node-bar",
"kind": "block",
"collection": "contribution:node-bar:block",
"name": "battery",
"if-capability": "battery",
"data": {
"attributes": {
"bar": "bottom",
"place": "status",
"order": 40,
"shows": "battery"
}
"order": "40"
},
"template": "templates/bar-battery"
},
{
"seat": "node-bar",
"kind": "block",
"collection": "contribution:node-bar:block",
"name": "power-draw",
"if-capability": "power-meter",
"data": {
"attributes": {
"bar": "bottom",
"place": "status",
"order": 45,
"shows": "state",
"options": {
"state": "power.draw"
}
}
"order": "45"
},
"template": "templates/bar-power-draw"
}
]
}
+8
View File
@@ -0,0 +1,8 @@
[[block]]
block = "battery"
format = "$icon $percentage{ $time|}"
charging_format = "$icon $percentage{ $time|}"
not_charging_format = "$icon $percentage"
full_format = "$icon $percentage"
empty_format = "$icon $percentage"
missing_format = ""
+5
View File
@@ -0,0 +1,5 @@
[[block]]
block = "custom"
command = "~/.local/bin/i3status-value power.draw ${machine:name}"
interval = 5
hide_when_empty = true
@@ -0,0 +1,4 @@
# What every module on this machine runs at the moment after-boot, as the mesh placed it (module power,
# novox/hq ADR 0211). Replaced on every push: code is added by the module it belongs to, as a
# contribution for this moment. Each module's piece runs on its own, as root, bounded in time.
${shell:after-boot:first}${shell:after-boot:normal}${shell:after-boot:last}
@@ -0,0 +1,4 @@
# What every module on this machine runs at the moment after-wake, as the mesh placed it (module power,
# novox/hq ADR 0211). Replaced on every push: code is added by the module it belongs to, as a
# contribution for this moment. Each module's piece runs on its own, as root, bounded in time.
${shell:after-wake:first}${shell:after-wake:normal}${shell:after-wake:last}
@@ -0,0 +1,4 @@
# What every module on this machine runs at the moment before-shutdown, as the mesh placed it (module power,
# novox/hq ADR 0211). Replaced on every push: code is added by the module it belongs to, as a
# contribution for this moment. Each module's piece runs on its own, as root, bounded in time.
${shell:before-shutdown:first}${shell:before-shutdown:normal}${shell:before-shutdown:last}
@@ -0,0 +1,4 @@
# What every module on this machine runs at the moment before-sleep, as the mesh placed it (module power,
# novox/hq ADR 0211). Replaced on every push: code is added by the module it belongs to, as a
# contribution for this moment. Each module's piece runs on its own, as root, bounded in time.
${shell:before-sleep:first}${shell:before-sleep:normal}${shell:before-sleep:last}
@@ -0,0 +1,4 @@
# What every module on this machine runs at the moment on-battery, as the mesh placed it (module power,
# novox/hq ADR 0211). Replaced on every push: code is added by the module it belongs to, as a
# contribution for this moment. Each module's piece runs on its own, as root, bounded in time.
${shell:on-battery:first}${shell:on-battery:normal}${shell:on-battery:last}
@@ -0,0 +1,4 @@
# What every module on this machine runs at the moment on-mains, as the mesh placed it (module power,
# novox/hq ADR 0211). Replaced on every push: code is added by the module it belongs to, as a
# contribution for this moment. Each module's piece runs on its own, as root, bounded in time.
${shell:on-mains:first}${shell:on-mains:normal}${shell:on-mains:last}
@@ -0,0 +1,8 @@
# Written by the mesh (module power, novox/hq ADR 0211). Replaced on every push.
#
# The power key and the lid, from this module's settings: the mesh's, then this machine's.
[Login]
HandlePowerKey=${config:handle-power-key}
HandleLidSwitch=${config:handle-lid-switch}
HandleLidSwitchExternalPower=${config:handle-lid-switch-external-power}
HandleLidSwitchDocked=${config:handle-lid-switch-docked}
@@ -0,0 +1,3 @@
# Written by the mesh (module power, novox/hq ADR 0211). Replaced on every push.
[Unit]
Wants=mesh-power-after-wake.service
@@ -0,0 +1,3 @@
# Written by the mesh (module power, novox/hq ADR 0211). Replaced on every push.
[Unit]
Wants=mesh-power-after-wake.service
@@ -0,0 +1,13 @@
# Written by the mesh (module power, novox/hq ADR 0211). Replaced on every push.
[Unit]
Description=Every module's code after boot (mesh power)
Wants=network-online.target
After=network-online.target
[Service]
Type=oneshot
RemainAfterExit=yes
ExecStart=/usr/local/lib/mesh-power/bin/power-moment after-boot
[Install]
WantedBy=multi-user.target
@@ -0,0 +1,11 @@
# Written by the mesh (module power, novox/hq ADR 0211). Replaced on every push.
#
# Pulled in by the four sleep targets through this module's drop-ins on them, and ordered after
# them, so it runs once the machine is awake again. Never enabled.
[Unit]
Description=Every module's code after waking (mesh power)
After=suspend.target hibernate.target hybrid-sleep.target suspend-then-hibernate.target
[Service]
Type=oneshot
ExecStart=/usr/local/lib/mesh-power/bin/power-moment after-wake
@@ -0,0 +1,18 @@
# Written by the mesh (module power, novox/hq ADR 0211). Replaced on every push.
#
# Started at boot and left active; stopping it is the shutdown. Ordered after the network, so at
# shutdown it stops, and runs every module's code, while the network is still up.
[Unit]
Description=Every module's code before shutdown (mesh power)
Wants=network-online.target
After=network-online.target
[Service]
Type=oneshot
RemainAfterExit=yes
ExecStart=/bin/true
ExecStop=/usr/local/lib/mesh-power/bin/power-moment before-shutdown
TimeoutStopSec=120
[Install]
WantedBy=multi-user.target
@@ -0,0 +1,11 @@
# Written by the mesh (module power, novox/hq ADR 0211). Replaced on every push.
#
# Pulled in by sleep.target through this module's drop-in on it, never enabled: a one-shot waiting
# for a sleep is not a service whose state the mesh can declare.
[Unit]
Description=Every module's code before sleep (mesh power)
Before=sleep.target
[Service]
Type=oneshot
ExecStart=/usr/local/lib/mesh-power/bin/power-moment before-sleep
@@ -0,0 +1,10 @@
# Written by the mesh (module power, novox/hq ADR 0211). Replaced on every push.
#
# Started by the module's udev rule when a power supply changes; runs on-mains or on-battery once per
# change of source.
[Unit]
Description=Every module's code for the power source (mesh power)
[Service]
Type=oneshot
ExecStart=/usr/local/lib/mesh-power/bin/power-moment supply
@@ -0,0 +1,3 @@
# Written by the mesh (module power, novox/hq ADR 0211). Replaced on every push.
[Unit]
Wants=mesh-power-before-sleep.service
@@ -0,0 +1,3 @@
# Written by the mesh (module power, novox/hq ADR 0211). Replaced on every push.
[Unit]
Wants=mesh-power-after-wake.service
@@ -0,0 +1,3 @@
# Written by the mesh (module power, novox/hq ADR 0211). Replaced on every push.
[Unit]
Wants=mesh-power-after-wake.service
@@ -0,0 +1,5 @@
# Written by the mesh (module power, novox/hq ADR 0211). Replaced on every push.
# Only the charger's change can switch the machine between mains and battery. A battery reports its
# level many times a minute, and USB-C supplies come and go; matching them started the supply unit
# every second or two on the laptop.
SUBSYSTEM=="power_supply", ACTION=="change", ATTR{type}=="Mains", RUN+="/usr/bin/systemctl --no-block start mesh-power-supply.service"