<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>UPower on 0xMax42 - Flatfile-Purist. Autodidakt. Systemdenker.</title><link>https://0xMax42.io/tags/upower/</link><description>Recent content in UPower on 0xMax42 - Flatfile-Purist. Autodidakt. Systemdenker.</description><generator>Hugo -- gohugo.io</generator><language>de-de</language><lastBuildDate>Tue, 02 Dec 2025 21:30:00 +0000</lastBuildDate><atom:link href="https://0xMax42.io/tags/upower/index.xml" rel="self" type="application/rss+xml"/><item><title>power-state-target: Wie ich den Energiezustand meines Laptops in systemd modelliert habe</title><link>https://0xMax42.io/p/power-state-target-wie-ich-den-energiezustand-meines-laptops-in-systemd-modelliert-habe/</link><pubDate>Tue, 02 Dec 2025 21:30:00 +0000</pubDate><guid>https://0xMax42.io/p/power-state-target-wie-ich-den-energiezustand-meines-laptops-in-systemd-modelliert-habe/</guid><description>&lt;img src="https://0xMax42.io/p/power-state-target-wie-ich-den-energiezustand-meines-laptops-in-systemd-modelliert-habe/cover.webp" alt="Featured image of post power-state-target: Wie ich den Energiezustand meines Laptops in systemd modelliert habe" /&gt;&lt;p&gt;Es gibt diese kleinen Probleme, die einen jahrelang unterschwellig nerven, bis man sie endlich einmal sauber löst. Genau in diese Kategorie fällt bei mir das Thema &lt;strong&gt;„Was macht das System, wenn ich auf Akku bin – und was darf es nur am Netz?“&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Klar, es gibt Tools wie TLP, Powertop &amp;amp; Co., und viele Desktop-Umgebungen haben ihre eigenen Energiespar-Profile. Aber ich wollte etwas anderes:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Ich wollte den aktuellen Power-Zustand (Akku vs. Netz) &lt;strong&gt;als systemd-Target&lt;/strong&gt; haben – sauber integriert, automatisierbar, paketiert und über meine eigene Debian-Repository-Infrastruktur ausrollbar.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Das Ergebnis ist dieses kleine, aber sehr praktische Projekt: &lt;strong&gt;&lt;code&gt;power-state-target&lt;/code&gt;&lt;/strong&gt;.&lt;/p&gt;
&lt;hr&gt;
&lt;h1 id="problem-der-energiezustand-als-unsichtbare-information"&gt;Problem: Der Energiezustand als „unsichtbare“ Information
&lt;/h1&gt;&lt;p&gt;UPower weiß, ob die Maschine auf Akku läuft. Der Desktop weiß es. Der Nutzer sieht irgendwo ein Batteriesymbol. Aber für viele meiner eigenen Dienste, Timer und Skripte ist diese Info &lt;strong&gt;nicht direkt greifbar&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;Typische Fragen:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;„Starte diesen Dienst nur, wenn das Gerät am Netz hängt.“&lt;/li&gt;
&lt;li&gt;„Fahre diese IO-intensiven Jobs bitte nicht auf Akku.“&lt;/li&gt;
&lt;li&gt;„Auf Akku darf etwas weiterlaufen, aber nur in einem Light-Mode.“&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Man kann natürlich überall lokale Checks einbauen – &lt;code&gt;upower&lt;/code&gt;, &lt;code&gt;busctl&lt;/code&gt;, eigene Daemons, irgendwelche kleinen Wrapper. Aber das ist alles &lt;strong&gt;verteilt, unübersichtlich und schlecht testbar&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;Systemd bietet für solche Zustände eigentlich eine perfekte Abstraktion: &lt;strong&gt;Targets&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;Also war der Plan:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Ich möchte ein &lt;strong&gt;&lt;code&gt;power-ac.target&lt;/code&gt;&lt;/strong&gt; und ein &lt;strong&gt;&lt;code&gt;power-battery.target&lt;/code&gt;&lt;/strong&gt; haben.&lt;/li&gt;
&lt;li&gt;Genau &lt;strong&gt;einer&lt;/strong&gt; dieser Targets soll immer „aktiv“ sein – je nachdem, ob das System auf Akku oder am Netz hängt.&lt;/li&gt;
&lt;li&gt;Dienste können sich dann einfach daran „dranhängen“, statt selbst Power-Logik zu implementieren.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h1 id="ziel-systemd-als-single-source-of-truth-für-den-power-state"&gt;Ziel: systemd als Single Source of Truth für den Power-State
&lt;/h1&gt;&lt;p&gt;Die Idee hinter &lt;code&gt;power-state-target&lt;/code&gt; ist bewusst minimalistisch:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Keine eigene fette Daemon-Logik.&lt;/li&gt;
&lt;li&gt;Nutzung von vorhandener Infrastruktur: &lt;strong&gt;UPower&lt;/strong&gt; und &lt;strong&gt;systemd&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Ein kleiner Listener, der den Zustand aus UPower liest und passende systemd-Targets setzt.&lt;/li&gt;
&lt;li&gt;Das Ganze als &lt;strong&gt;Debian-Paket&lt;/strong&gt;, das sich in meine bestehende Build- und Release-Infrastruktur einfügt.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Im Kern besteht das Projekt aus:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;zwei systemd-Targets: &lt;code&gt;power-ac.target&lt;/code&gt; und &lt;code&gt;power-battery.target&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;einem systemd-Service: &lt;code&gt;power-state-listener.service&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;einem kleinen Shell-Skript: &lt;code&gt;/usr/libexec/update-power-state&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Debian-Packaging + automatisierter Build- und Release-Pipeline über Gitea&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h1 id="die-targets-power-ac-und-power-battery"&gt;Die Targets: &lt;code&gt;power-ac&lt;/code&gt; und &lt;code&gt;power-battery&lt;/code&gt;
&lt;/h1&gt;&lt;p&gt;Die beiden Targets sind bewusst extrem simpel gehalten. Sie dienen als &lt;strong&gt;Zustandsmarkierungen&lt;/strong&gt;, nicht als eigene Logik-Einheiten:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;span class="lnt"&gt;4
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-ini" data-lang="ini"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# /usr/lib/systemd/system/power-ac.target&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;[Unit]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="na"&gt;Description&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;AC Power Mode&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="na"&gt;AllowIsolate&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;yes&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;span class="lnt"&gt;4
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-ini" data-lang="ini"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# /usr/lib/systemd/system/power-battery.target&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;[Unit]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="na"&gt;Description&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;Battery Power Mode&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="na"&gt;AllowIsolate&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;yes&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;Ein paar Gedanken dazu:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Sie haben keine eigenen &lt;code&gt;[Install]&lt;/code&gt;-Sektionen – sie werden &lt;strong&gt;nicht direkt aktiviert&lt;/strong&gt;, sondern vom Listener-Service gesetzt.&lt;/li&gt;
&lt;li&gt;Über &lt;code&gt;AllowIsolate=yes&lt;/code&gt; kann man sie theoretisch auch isolieren, falls man das einmal für Experimente nutzen möchte.&lt;/li&gt;
&lt;li&gt;Für andere Units sind sie vor allem als &lt;strong&gt;Anker&lt;/strong&gt; gedacht: &lt;code&gt;Wants=power-ac.target&lt;/code&gt;, &lt;code&gt;BindsTo=power-battery.target&lt;/code&gt;, &lt;code&gt;PartOf=&lt;/code&gt; usw.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Die eigentliche Intelligenz steckt im Listener.&lt;/p&gt;
&lt;hr&gt;
&lt;h1 id="der-listener-power-state-listenerservice"&gt;Der Listener: &lt;code&gt;power-state-listener.service&lt;/code&gt;
&lt;/h1&gt;&lt;p&gt;Der Listener ist ein kleiner systemd-Service, der genau eine Aufgabe hat:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;UPower beobachten und bei Änderungen der Energiequelle das passende Target aktivieren.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Die Unit dazu sieht so aus:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt; 1
&lt;/span&gt;&lt;span class="lnt"&gt; 2
&lt;/span&gt;&lt;span class="lnt"&gt; 3
&lt;/span&gt;&lt;span class="lnt"&gt; 4
&lt;/span&gt;&lt;span class="lnt"&gt; 5
&lt;/span&gt;&lt;span class="lnt"&gt; 6
&lt;/span&gt;&lt;span class="lnt"&gt; 7
&lt;/span&gt;&lt;span class="lnt"&gt; 8
&lt;/span&gt;&lt;span class="lnt"&gt; 9
&lt;/span&gt;&lt;span class="lnt"&gt;10
&lt;/span&gt;&lt;span class="lnt"&gt;11
&lt;/span&gt;&lt;span class="lnt"&gt;12
&lt;/span&gt;&lt;span class="lnt"&gt;13
&lt;/span&gt;&lt;span class="lnt"&gt;14
&lt;/span&gt;&lt;span class="lnt"&gt;15
&lt;/span&gt;&lt;span class="lnt"&gt;16
&lt;/span&gt;&lt;span class="lnt"&gt;17
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-ini" data-lang="ini"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# /usr/lib/systemd/system/power-state-listener.service&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;[Unit]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="na"&gt;Description&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;Wait for UPower power-state change and update targets&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="na"&gt;Wants&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;upower.service&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="na"&gt;After&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;upower.service&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;[Service]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="na"&gt;Type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;simple&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="na"&gt;ExecStartPre&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;/usr/libexec/update-power-state&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="na"&gt;ExecStart&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;/bin/sh -c \
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt; &amp;#39;upower --monitor-detail | head -n 1 &amp;gt; /dev/null; \
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt; /usr/libexec/update-power-state&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="na"&gt;Restart&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;on-success&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="na"&gt;RestartSec&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;0&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;[Install]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="na"&gt;WantedBy&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;multi-user.target&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;Die wichtigen Punkte:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;ExecStartPre=/usr/libexec/update-power-state&lt;/code&gt; sorgt dafür, dass der aktuelle Zustand &lt;strong&gt;sofort beim Start&lt;/strong&gt; des Dienstes gesetzt wird (z.B. nach einem Boot).&lt;/li&gt;
&lt;li&gt;&lt;code&gt;ExecStart=... upower --monitor-detail&lt;/code&gt; hängt sich an die UPower-Monitor-API und wartet auf ein Ereignis (zum Beispiel: Stromkabel gezogen oder eingesteckt).&lt;/li&gt;
&lt;li&gt;Sobald ein Ereignis eintritt, läuft danach erneut &lt;code&gt;update-power-state&lt;/code&gt;, dann beendet sich der Dienst erfolgreich.&lt;/li&gt;
&lt;li&gt;Durch &lt;code&gt;Restart=on-success&lt;/code&gt; wird der Service &lt;strong&gt;automatisch neu gestartet&lt;/strong&gt;. Ergebnis: eine kleine &lt;strong&gt;Ereignis-Schleife&lt;/strong&gt;, die immer reagiert, wenn sich der Power-State ändert, ohne selbst busy zu sein.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Das ist im Grunde ein reaktives Mini-Framework: systemd + UPower erledigen 95 % der Arbeit, der Rest ist eine dünne Klebeschicht.&lt;/p&gt;
&lt;hr&gt;
&lt;h1 id="die-eigentliche-logik-usrlibexecupdate-power-state"&gt;Die eigentliche Logik: &lt;code&gt;/usr/libexec/update-power-state&lt;/code&gt;
&lt;/h1&gt;&lt;p&gt;Das Herzstück ist ein Shell-Skript, das genau zwei Dinge tut:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Den aktuellen UPower-Status („bin ich auf Akku?“) abfragen.&lt;/li&gt;
&lt;li&gt;Die passenden systemd-Targets setzen.&lt;/li&gt;
&lt;/ol&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt; 1
&lt;/span&gt;&lt;span class="lnt"&gt; 2
&lt;/span&gt;&lt;span class="lnt"&gt; 3
&lt;/span&gt;&lt;span class="lnt"&gt; 4
&lt;/span&gt;&lt;span class="lnt"&gt; 5
&lt;/span&gt;&lt;span class="lnt"&gt; 6
&lt;/span&gt;&lt;span class="lnt"&gt; 7
&lt;/span&gt;&lt;span class="lnt"&gt; 8
&lt;/span&gt;&lt;span class="lnt"&gt; 9
&lt;/span&gt;&lt;span class="lnt"&gt;10
&lt;/span&gt;&lt;span class="lnt"&gt;11
&lt;/span&gt;&lt;span class="lnt"&gt;12
&lt;/span&gt;&lt;span class="lnt"&gt;13
&lt;/span&gt;&lt;span class="lnt"&gt;14
&lt;/span&gt;&lt;span class="lnt"&gt;15
&lt;/span&gt;&lt;span class="lnt"&gt;16
&lt;/span&gt;&lt;span class="lnt"&gt;17
&lt;/span&gt;&lt;span class="lnt"&gt;18
&lt;/span&gt;&lt;span class="lnt"&gt;19
&lt;/span&gt;&lt;span class="lnt"&gt;20
&lt;/span&gt;&lt;span class="lnt"&gt;21
&lt;/span&gt;&lt;span class="lnt"&gt;22
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-sh" data-lang="sh"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="cp"&gt;#!/bin/sh
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;get_state&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; busctl get-property org.freedesktop.UPower &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; /org/freedesktop/UPower &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; org.freedesktop.UPower &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; OnBattery &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;|&lt;/span&gt; awk &lt;span class="s1"&gt;&amp;#39;{print $2}&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;apply_state&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nv"&gt;state&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="nv"&gt;$1&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="nv"&gt;$state&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;true&amp;#34;&lt;/span&gt; &lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; systemctl start power-battery.target
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; systemctl stop power-ac.target
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;else&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; systemctl start power-ac.target
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; systemctl stop power-battery.target
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;fi&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;apply_state &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="k"&gt;$(&lt;/span&gt;get_state&lt;span class="k"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;Ein paar Details dazu:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;busctl get-property ... OnBattery&lt;/code&gt; holt sich direkt den &lt;code&gt;OnBattery&lt;/code&gt;-Property von UPower über D-Bus.&lt;/li&gt;
&lt;li&gt;UPower liefert hier ein &lt;code&gt;b true&lt;/code&gt; oder &lt;code&gt;b false&lt;/code&gt; – über &lt;code&gt;awk '{print $2}'&lt;/code&gt; ziehe ich mir einfach den Wahrheitswert heraus.&lt;/li&gt;
&lt;li&gt;Wenn &lt;code&gt;true&lt;/code&gt;, wird &lt;code&gt;power-battery.target&lt;/code&gt; gestartet und &lt;code&gt;power-ac.target&lt;/code&gt; gestoppt.&lt;/li&gt;
&lt;li&gt;Wenn &lt;code&gt;false&lt;/code&gt;, genau umgekehrt.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Damit ist sichergestellt, dass &lt;strong&gt;nie beide Targets gleichzeitig aktiv sind&lt;/strong&gt;, sondern immer genau einer der beiden Zustände gilt.&lt;/p&gt;
&lt;hr&gt;
&lt;h1 id="integration-in-debian-paket-power-state-target"&gt;Integration in Debian: Paket &lt;code&gt;power-state-target&lt;/code&gt;
&lt;/h1&gt;&lt;p&gt;Weil ich praktisch alles, was ich produktiv nutzen will, in &lt;code&gt;.deb&lt;/code&gt;-Pakete verpacke, war von Anfang an klar: Das hier wird ein &lt;strong&gt;normales Debian-Paket&lt;/strong&gt; mit sauberem &lt;code&gt;debian/&lt;/code&gt;-Verzeichnis.&lt;/p&gt;
&lt;p&gt;Die &lt;code&gt;debian/control&lt;/code&gt; ist bewusst minimal:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt; 1
&lt;/span&gt;&lt;span class="lnt"&gt; 2
&lt;/span&gt;&lt;span class="lnt"&gt; 3
&lt;/span&gt;&lt;span class="lnt"&gt; 4
&lt;/span&gt;&lt;span class="lnt"&gt; 5
&lt;/span&gt;&lt;span class="lnt"&gt; 6
&lt;/span&gt;&lt;span class="lnt"&gt; 7
&lt;/span&gt;&lt;span class="lnt"&gt; 8
&lt;/span&gt;&lt;span class="lnt"&gt; 9
&lt;/span&gt;&lt;span class="lnt"&gt;10
&lt;/span&gt;&lt;span class="lnt"&gt;11
&lt;/span&gt;&lt;span class="lnt"&gt;12
&lt;/span&gt;&lt;span class="lnt"&gt;13
&lt;/span&gt;&lt;span class="lnt"&gt;14
&lt;/span&gt;&lt;span class="lnt"&gt;15
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Source: power-state-target
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Section: utils
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Priority: optional
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Maintainer: 0xMax42 &amp;lt;debian@0xMax42.io&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Build-Depends: debhelper-compat (= 13)
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Standards-Version: 4.7.0
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Rules-Requires-Root: no
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Package: power-state-target
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Architecture: all
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Depends: ${misc:Depends}, upower
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Description: Power state target management
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; Introduces a systemd target that reflects the current power state
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; of the system (on battery, on AC power) and updates it
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; dynamically as the power state changes.
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;ul&gt;
&lt;li&gt;Architektur ist &lt;code&gt;all&lt;/code&gt;, weil es sich nur um Skripte und Unit-Files handelt.&lt;/li&gt;
&lt;li&gt;Die einzige echte Laufzeit-Abhängigkeit ist &lt;code&gt;upower&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Rules-Requires-Root: no&lt;/code&gt; – gebaut werden kann also auch ohne Root, sofern die Build-Dependencies vorhanden sind.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Die Installation der Dateien übernimmt &lt;code&gt;debian/install&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;span class="lnt"&gt;4
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;usr/libexec/update-power-state usr/libexec/
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;usr/lib/systemd/system/power-state-listener.service usr/lib/systemd/system/
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;usr/lib/systemd/system/power-ac.target usr/lib/systemd/system/
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;usr/lib/systemd/system/power-battery.target usr/lib/systemd/system/
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;&lt;code&gt;debian/rules&lt;/code&gt; ist dementsprechend trivial:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;span class="lnt"&gt;4
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-make" data-lang="make"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c"&gt;#!/usr/bin/make -f
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nf"&gt;%&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; dh &lt;span class="nv"&gt;$@&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;Das Ganze ist als &lt;strong&gt;native Paketquelle&lt;/strong&gt; (&lt;code&gt;debian/source/format&lt;/code&gt; = &lt;code&gt;3.0 (native)&lt;/code&gt;) angelegt, da Projekt und Paket faktisch identisch sind.&lt;/p&gt;
&lt;hr&gt;
&lt;h1 id="automatisierter-debian-build-mit-buildsh"&gt;Automatisierter Debian-Build mit &lt;code&gt;build.sh&lt;/code&gt;
&lt;/h1&gt;&lt;p&gt;Um das Paket nicht nur lokal, sondern auch automatisiert im CI bauen zu können, gibt es ein eigenes Build-Skript:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt; 1
&lt;/span&gt;&lt;span class="lnt"&gt; 2
&lt;/span&gt;&lt;span class="lnt"&gt; 3
&lt;/span&gt;&lt;span class="lnt"&gt; 4
&lt;/span&gt;&lt;span class="lnt"&gt; 5
&lt;/span&gt;&lt;span class="lnt"&gt; 6
&lt;/span&gt;&lt;span class="lnt"&gt; 7
&lt;/span&gt;&lt;span class="lnt"&gt; 8
&lt;/span&gt;&lt;span class="lnt"&gt; 9
&lt;/span&gt;&lt;span class="lnt"&gt;10
&lt;/span&gt;&lt;span class="lnt"&gt;11
&lt;/span&gt;&lt;span class="lnt"&gt;12
&lt;/span&gt;&lt;span class="lnt"&gt;13
&lt;/span&gt;&lt;span class="lnt"&gt;14
&lt;/span&gt;&lt;span class="lnt"&gt;15
&lt;/span&gt;&lt;span class="lnt"&gt;16
&lt;/span&gt;&lt;span class="lnt"&gt;17
&lt;/span&gt;&lt;span class="lnt"&gt;18
&lt;/span&gt;&lt;span class="lnt"&gt;19
&lt;/span&gt;&lt;span class="lnt"&gt;20
&lt;/span&gt;&lt;span class="lnt"&gt;21
&lt;/span&gt;&lt;span class="lnt"&gt;22
&lt;/span&gt;&lt;span class="lnt"&gt;23
&lt;/span&gt;&lt;span class="lnt"&gt;24
&lt;/span&gt;&lt;span class="lnt"&gt;25
&lt;/span&gt;&lt;span class="lnt"&gt;26
&lt;/span&gt;&lt;span class="lnt"&gt;27
&lt;/span&gt;&lt;span class="lnt"&gt;28
&lt;/span&gt;&lt;span class="lnt"&gt;29
&lt;/span&gt;&lt;span class="lnt"&gt;30
&lt;/span&gt;&lt;span class="lnt"&gt;31
&lt;/span&gt;&lt;span class="lnt"&gt;32
&lt;/span&gt;&lt;span class="lnt"&gt;33
&lt;/span&gt;&lt;span class="lnt"&gt;34
&lt;/span&gt;&lt;span class="lnt"&gt;35
&lt;/span&gt;&lt;span class="lnt"&gt;36
&lt;/span&gt;&lt;span class="lnt"&gt;37
&lt;/span&gt;&lt;span class="lnt"&gt;38
&lt;/span&gt;&lt;span class="lnt"&gt;39
&lt;/span&gt;&lt;span class="lnt"&gt;40
&lt;/span&gt;&lt;span class="lnt"&gt;41
&lt;/span&gt;&lt;span class="lnt"&gt;42
&lt;/span&gt;&lt;span class="lnt"&gt;43
&lt;/span&gt;&lt;span class="lnt"&gt;44
&lt;/span&gt;&lt;span class="lnt"&gt;45
&lt;/span&gt;&lt;span class="lnt"&gt;46
&lt;/span&gt;&lt;span class="lnt"&gt;47
&lt;/span&gt;&lt;span class="lnt"&gt;48
&lt;/span&gt;&lt;span class="lnt"&gt;49
&lt;/span&gt;&lt;span class="lnt"&gt;50
&lt;/span&gt;&lt;span class="lnt"&gt;51
&lt;/span&gt;&lt;span class="lnt"&gt;52
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="cp"&gt;#!/bin/bash
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nb"&gt;set&lt;/span&gt; -euo pipefail
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nv"&gt;REQUIRED_PKGS&lt;/span&gt;&lt;span class="o"&gt;=(&lt;/span&gt;build-essential devscripts dkms debhelper dh-dkms&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nv"&gt;missing&lt;/span&gt;&lt;span class="o"&gt;=()&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;for&lt;/span&gt; pkg in &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;REQUIRED_PKGS&lt;/span&gt;&lt;span class="p"&gt;[@]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; dpkg -s &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="nv"&gt;$pkg&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt; &lt;span class="p"&gt;&amp;amp;&lt;/span&gt;&amp;gt;/dev/null &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nv"&gt;missing&lt;/span&gt;&lt;span class="o"&gt;+=(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="nv"&gt;$pkg&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;done&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;((&lt;/span&gt; &lt;span class="si"&gt;${#&lt;/span&gt;&lt;span class="nv"&gt;missing&lt;/span&gt;&lt;span class="p"&gt;[@]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="o"&gt;))&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;🔧 Installing missing build packages: &lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;missing&lt;/span&gt;&lt;span class="p"&gt;[*]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; sudo apt-get update -qq
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; sudo apt-get install -y --no-install-recommends &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;missing&lt;/span&gt;&lt;span class="p"&gt;[@]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;fi&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nv"&gt;DIST_DIR&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;dist&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nv"&gt;TAG&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;TAG_NAME&lt;/span&gt;&lt;span class="k"&gt;:-$(&lt;/span&gt;&lt;span class="nb"&gt;exit&lt;/span&gt; 1&lt;span class="k"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;🔖 Using tag: &lt;/span&gt;&lt;span class="nv"&gt;$TAG&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;curl -s https://git.0xmax42.io/actions/deb-changelog-action/raw/branch/main/bootstrap.sh &lt;span class="p"&gt;|&lt;/span&gt; bash -s -- &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; --version &lt;span class="s2"&gt;&amp;#34;v0&amp;#34;&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; --tag &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="nv"&gt;$TAG&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; --package_name &lt;span class="s2"&gt;&amp;#34;power-state-target&amp;#34;&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; --author_name &lt;span class="s2"&gt;&amp;#34;0xMax42&amp;#34;&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; --author_email &lt;span class="s2"&gt;&amp;#34;Mail@0xMax42.io&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nv"&gt;PKG_NAME&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="k"&gt;$(&lt;/span&gt;dpkg-parsechangelog --show-field Source&lt;span class="k"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nv"&gt;PKG_VERSION&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="k"&gt;$(&lt;/span&gt;dpkg-parsechangelog --show-field Version&lt;span class="k"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;tar --exclude&lt;span class="o"&gt;=&lt;/span&gt;debian -czf ../power-state-target_&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;PKG_VERSION&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;.orig.tar.gz .
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;Building Debian package...&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;dpkg-buildpackage -us -uc
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;mkdir -p &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="nv"&gt;$DIST_DIR&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;rm -rf &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="nv"&gt;$DIST_DIR&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;/*
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;for&lt;/span&gt; file in ../&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;PKG_NAME&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;*_&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;PKG_VERSION&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;_*.deb &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; ../&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;PKG_NAME&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;*_&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;PKG_VERSION&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;_*.ddeb &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; ../&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;PKG_NAME&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;*_&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;PKG_VERSION&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;_*.buildinfo &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; ../&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;PKG_NAME&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;*_&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;PKG_VERSION&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;_*.changes &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; ../&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;PKG_NAME&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;*_&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;PKG_VERSION&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;.dsc &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; ../&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;PKG_NAME&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;*_&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;PKG_VERSION&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;.*.tar.* &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; ../&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;PKG_NAME&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;*_&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;PKG_VERSION&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;.tar.*&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="o"&gt;[[&lt;/span&gt; -f &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="nv"&gt;$file&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt; &lt;span class="o"&gt;]]&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt; mv &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="nv"&gt;$file&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="nv"&gt;$DIST_DIR&lt;/span&gt;&lt;span class="s2"&gt;/&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;📦 Moved &lt;/span&gt;&lt;span class="k"&gt;$(&lt;/span&gt;basename &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="nv"&gt;$file&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="k"&gt;)&lt;/span&gt;&lt;span class="s2"&gt; → &lt;/span&gt;&lt;span class="nv"&gt;$DIST_DIR&lt;/span&gt;&lt;span class="s2"&gt;/&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="o"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;done&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt;&amp;#34;dpkg-buildpackage&amp;#34;&lt;/span&gt; -T clean
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;Build complete. Output in &lt;/span&gt;&lt;span class="nv"&gt;$DIST_DIR&lt;/span&gt;&lt;span class="s2"&gt;/&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;Wichtige Punkte:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Das Script kümmert sich darum, dass alle benötigten Build-Pakete installiert sind (typische &lt;code&gt;build-essential&lt;/code&gt;/&lt;code&gt;devscripts&lt;/code&gt;/&lt;code&gt;debhelper&lt;/code&gt;-Kette).&lt;/li&gt;
&lt;li&gt;Die Versionierung und der Changelog werden über ein externes Script aus meiner &lt;code&gt;deb-changelog-action&lt;/code&gt; automatisiert.&lt;/li&gt;
&lt;li&gt;Alle entstehenden Artefakte werden in ein zentrales &lt;code&gt;dist/&lt;/code&gt;-Verzeichnis verschoben, sodass der CI-Job sie leicht weiterverarbeiten kann.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;code&gt;TAG_NAME&lt;/code&gt; kommt dabei aus dem CI-Kontext – das ist die Git-Tag-Version, die zum Release gehört.&lt;/p&gt;
&lt;hr&gt;
&lt;h1 id="cicd-vom-commit-zum-debian-paket-in-der-eigenen-repo"&gt;CI/CD: Vom Commit zum Debian-Paket in der eigenen Repo
&lt;/h1&gt;&lt;p&gt;Da das Projekt auf meiner privaten Gitea-Instanz liegt, laufen die CI-Pipelines ebenfalls dort. Es gibt zwei wesentliche Workflows:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;release.yml&lt;/code&gt;&lt;/strong&gt; – erstellt automatisch Releases und Changelogs.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;build.yml&lt;/code&gt;&lt;/strong&gt; – baut aus einem Release ein Debian-Paket und schiebt es in meine &lt;code&gt;debrepo&lt;/code&gt;.&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id="auto-changelog--release"&gt;Auto-Changelog &amp;amp; Release
&lt;/h3&gt;&lt;p&gt;Der Release-Workflow triggert bei Pushes auf &lt;code&gt;main&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt; 1
&lt;/span&gt;&lt;span class="lnt"&gt; 2
&lt;/span&gt;&lt;span class="lnt"&gt; 3
&lt;/span&gt;&lt;span class="lnt"&gt; 4
&lt;/span&gt;&lt;span class="lnt"&gt; 5
&lt;/span&gt;&lt;span class="lnt"&gt; 6
&lt;/span&gt;&lt;span class="lnt"&gt; 7
&lt;/span&gt;&lt;span class="lnt"&gt; 8
&lt;/span&gt;&lt;span class="lnt"&gt; 9
&lt;/span&gt;&lt;span class="lnt"&gt;10
&lt;/span&gt;&lt;span class="lnt"&gt;11
&lt;/span&gt;&lt;span class="lnt"&gt;12
&lt;/span&gt;&lt;span class="lnt"&gt;13
&lt;/span&gt;&lt;span class="lnt"&gt;14
&lt;/span&gt;&lt;span class="lnt"&gt;15
&lt;/span&gt;&lt;span class="lnt"&gt;16
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;on&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;push&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;branches&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="l"&gt;main&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;jobs&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;release&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;runs-on&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;ubuntu-latest&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;steps&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="nt"&gt;uses&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;actions/checkout@v4&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;with&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;fetch-depth&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;Release&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;uses&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;https://git.0xmax42.io/actions/auto-changelog-release-action@v1&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;with&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;token&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;${{ secrets.RELEASE_PUBLISH_TOKEN }}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;Die &lt;code&gt;auto-changelog-release-action&lt;/code&gt; kümmert sich darum, anhand der Commits (per &lt;code&gt;git-cliff&lt;/code&gt;-Konfiguration) einen Changelog zu erzeugen, eine neue Version zu bestimmen, den Tag zu setzen und das Release im Gitea-Repository zu veröffentlichen.&lt;/p&gt;
&lt;h2 id="debian-build-und-push-in-debrepo"&gt;Debian-Build und Push in &lt;code&gt;debrepo&lt;/code&gt;
&lt;/h2&gt;&lt;p&gt;Sobald ein Release veröffentlicht ist, greift der zweite Workflow:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;on&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;release&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;types&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="l"&gt;published]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;Der Job:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;checkt den Quellcode aus,&lt;/li&gt;
&lt;li&gt;ruft &lt;code&gt;build.sh&lt;/code&gt; mit dem Tag aus dem Release auf,&lt;/li&gt;
&lt;li&gt;macht ein &lt;strong&gt;Sparse Checkout&lt;/strong&gt; meiner &lt;code&gt;debrepo&lt;/code&gt; (nur &lt;code&gt;power-state-target/&lt;/code&gt;),&lt;/li&gt;
&lt;li&gt;kopiert die &lt;code&gt;.deb&lt;/code&gt;, &lt;code&gt;.dsc&lt;/code&gt; und Tarballs in dieses Verzeichnis,&lt;/li&gt;
&lt;li&gt;commitet neue Artefakte auf den &lt;code&gt;packages&lt;/code&gt;-Branch.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Damit landet jede neue Version von &lt;code&gt;power-state-target&lt;/code&gt; automatisch in meiner eigenen APT-Repository-Struktur – und kann dann wie gewohnt über &lt;code&gt;apt&lt;/code&gt; installiert werden.&lt;/p&gt;
&lt;hr&gt;
&lt;h1 id="git-cliff-konsistente-changelogs-mit-emojis"&gt;&lt;code&gt;git-cliff&lt;/code&gt;: Konsistente Changelogs mit Emojis
&lt;/h1&gt;&lt;p&gt;Da ich inzwischen sehr viel Wert auf saubere, konsistente Changelogs lege, kommt auch hier wieder &lt;code&gt;git-cliff&lt;/code&gt; zum Einsatz. Die &lt;code&gt;cliff.toml&lt;/code&gt; ist an meinen üblichen Stil angepasst – inklusive Emoji-Gruppierung:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;🚀 Features&lt;/li&gt;
&lt;li&gt;🐛 Bug Fixes&lt;/li&gt;
&lt;li&gt;⚙️ Miscellaneous Tasks&lt;/li&gt;
&lt;li&gt;usw.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Für &lt;code&gt;power-state-target&lt;/code&gt; sieht der erste Release-Eintrag aktuell so aus:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt; 1
&lt;/span&gt;&lt;span class="lnt"&gt; 2
&lt;/span&gt;&lt;span class="lnt"&gt; 3
&lt;/span&gt;&lt;span class="lnt"&gt; 4
&lt;/span&gt;&lt;span class="lnt"&gt; 5
&lt;/span&gt;&lt;span class="lnt"&gt; 6
&lt;/span&gt;&lt;span class="lnt"&gt; 7
&lt;/span&gt;&lt;span class="lnt"&gt; 8
&lt;/span&gt;&lt;span class="lnt"&gt; 9
&lt;/span&gt;&lt;span class="lnt"&gt;10
&lt;/span&gt;&lt;span class="lnt"&gt;11
&lt;/span&gt;&lt;span class="lnt"&gt;12
&lt;/span&gt;&lt;span class="lnt"&gt;13
&lt;/span&gt;&lt;span class="lnt"&gt;14
&lt;/span&gt;&lt;span class="lnt"&gt;15
&lt;/span&gt;&lt;span class="lnt"&gt;16
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-markdown" data-lang="markdown"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gu"&gt;## [0.1.0] - 2025-12-02
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gu"&gt;### 🚀 Features
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;-&lt;/span&gt; Add systemd targets and service for dynamic power state switching - (...)
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gu"&gt;### 🐛 Bug Fixes
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;-&lt;/span&gt; *(config)* Correct systemd installation paths in debian install - (...)
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gu"&gt;### ⚙️ Miscellaneous Tasks
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;-&lt;/span&gt; *(ci)* Add build and release workflows for package automation - (...)
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;-&lt;/span&gt; *(build)* Add script for automated Debian package build - (...)
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;-&lt;/span&gt; Add git-cliff changelog configuration file - (...)
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;-&lt;/span&gt; *(debian)* Add packaging files for power-state-target - (...)
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;Nichts weltbewegendes, aber genau das ist der Punkt: saubere kleine Bausteine, die später zusammenspielen können.&lt;/p&gt;
&lt;hr&gt;
&lt;h1 id="wie-man-power-state-target-praktisch-nutzt"&gt;Wie man &lt;code&gt;power-state-target&lt;/code&gt; praktisch nutzt
&lt;/h1&gt;&lt;p&gt;Der eigentliche Mehrwert entsteht dadurch, dass andere Units auf die Targets reagieren können.&lt;/p&gt;
&lt;p&gt;Ein paar typische Szenarien:&lt;/p&gt;
&lt;h2 id="1-dienste-nur-am-netz-erlauben"&gt;1. Dienste nur am Netz erlauben
&lt;/h2&gt;&lt;p&gt;Angenommen, du hast einen Dienst, der nur laufen soll, wenn das System am Netz hängt, z.B. ein Backup- oder Dedupe-Job.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt; 1
&lt;/span&gt;&lt;span class="lnt"&gt; 2
&lt;/span&gt;&lt;span class="lnt"&gt; 3
&lt;/span&gt;&lt;span class="lnt"&gt; 4
&lt;/span&gt;&lt;span class="lnt"&gt; 5
&lt;/span&gt;&lt;span class="lnt"&gt; 6
&lt;/span&gt;&lt;span class="lnt"&gt; 7
&lt;/span&gt;&lt;span class="lnt"&gt; 8
&lt;/span&gt;&lt;span class="lnt"&gt; 9
&lt;/span&gt;&lt;span class="lnt"&gt;10
&lt;/span&gt;&lt;span class="lnt"&gt;11
&lt;/span&gt;&lt;span class="lnt"&gt;12
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-ini" data-lang="ini"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;[Unit]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="na"&gt;Description&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;Heavy IO Job (AC only)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="na"&gt;Wants&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;power-ac.target&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="na"&gt;After&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;power-ac.target&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;[Service]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="na"&gt;Type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;oneshot&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="na"&gt;ExecStart&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;/usr/local/bin/heavy-job.sh&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;[Install]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="na"&gt;WantedBy&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;multi-user.target&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="na"&gt;WantedBy&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;power-ac.target&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;Hier kann man zusätzlich mit &lt;code&gt;ConditionPathExists&lt;/code&gt;, Timer-Units, &lt;code&gt;StartLimit*&lt;/code&gt; usw. arbeiten – aber der Kern ist: &lt;strong&gt;„nur sinnvoll, wenn AC da ist“&lt;/strong&gt;.&lt;/p&gt;
&lt;h2 id="2-reduzierter-modus-auf-akku"&gt;2. Reduzierter Modus auf Akku
&lt;/h2&gt;&lt;p&gt;Statt Dienste komplett abzuschalten, kann man auch alternative Konfigurationen fahren, z.B. einen „Light-Modus“ auf Akku und „Full-Modus“ am Netz. Man modelliert dann zwei Units, die jeweils an ein anderes Target gekoppelt sind.&lt;/p&gt;
&lt;h2 id="3-timer-die-auf-den-power-state-hören"&gt;3. Timer, die auf den Power-State hören
&lt;/h2&gt;&lt;p&gt;Timer-Units können ebenfalls indirekt über die Targets gesteuert werden, etwa indem nur der eigentliche Service an das passende Target gebunden wird.&lt;/p&gt;
&lt;p&gt;Das Schöne: &lt;strong&gt;Alle Entscheidungen laufen über systemd&lt;/strong&gt;, nicht über wilde Shell-Ifs überall im System.&lt;/p&gt;
&lt;hr&gt;
&lt;h1 id="ausblick-was-man-darauf-noch-aufbauen-kann"&gt;Ausblick: Was man darauf noch aufbauen kann
&lt;/h1&gt;&lt;p&gt;&lt;code&gt;power-state-target&lt;/code&gt; ist bewusst klein gehalten. Aber genau dadurch lässt es sich gut als &lt;strong&gt;Baustein&lt;/strong&gt; für komplexere Setups verwenden:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Dynamisches Umschalten des CPU-Governors über separate Dienste.&lt;/li&gt;
&lt;li&gt;Anpassung von Backup-Strategien (z.B. nur Metadaten auf Akku, Voll-Backups am Netz).&lt;/li&gt;
&lt;li&gt;Steuerung von Container- oder VM-Last abhängig vom Power-State.&lt;/li&gt;
&lt;li&gt;Integration in bestehende Tuning-Tools, indem diese die Targets nur noch als Signalquelle verwenden.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Ich mag solche Projekte, weil sie &lt;strong&gt;zwei Welten verbinden&lt;/strong&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Auf der einen Seite: klassische Desktop-/Laptop-Welt mit Akku, UPower, grafischen Energiemanagern.&lt;/li&gt;
&lt;li&gt;Auf der anderen: systemd- und serverartige Steuerung, bei der Zustände explizit modelliert sind.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;code&gt;power-state-target&lt;/code&gt; macht den Energiezustand zu einem &lt;strong&gt;ersten Bürger in systemd&lt;/strong&gt; – und damit für alles Weitere komponierbar.&lt;/p&gt;
&lt;hr&gt;
&lt;h1 id="fazit"&gt;Fazit
&lt;/h1&gt;&lt;p&gt;Für sich genommen ist &lt;code&gt;power-state-target&lt;/code&gt; ein kleines Projekt:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;ein Shell-Skript,&lt;/li&gt;
&lt;li&gt;zwei Targets,&lt;/li&gt;
&lt;li&gt;ein Service,&lt;/li&gt;
&lt;li&gt;etwas Debian-Packaging und CI.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Aber für mich ist es genau die Art von Infrastruktur, die ich mag:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;klar umrissen,&lt;/li&gt;
&lt;li&gt;gut integrierbar,&lt;/li&gt;
&lt;li&gt;automatisiert gebaut und ausgeliefert,&lt;/li&gt;
&lt;li&gt;und mit einem einfachen mentalen Modell: &lt;em&gt;„Bin ich auf Akku oder am Netz?“&lt;/em&gt; wird zu &lt;em&gt;„Welches systemd-Target ist aktiv?“&lt;/em&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Und ab da kann der Rest des Systems sich darauf verlassen.&lt;/p&gt;</description></item></channel></rss>