<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Linux on 0xMax42 - Flatfile-Purist. Autodidakt. Systemdenker.</title><link>https://0xMax42.io/tags/linux/</link><description>Recent content in Linux on 0xMax42 - Flatfile-Purist. Autodidakt. Systemdenker.</description><generator>Hugo -- gohugo.io</generator><language>de-de</language><lastBuildDate>Thu, 18 Dec 2025 00:00:00 +0000</lastBuildDate><atom:link href="https://0xMax42.io/tags/linux/index.xml" rel="self" type="application/rss+xml"/><item><title>Reaktive lokale Dienste mit systemd, UNIX-Sockets und rootlosen Containern</title><link>https://0xMax42.io/p/reaktive-lokale-dienste-mit-systemd-unix-sockets-und-rootlosen-containern/</link><pubDate>Thu, 18 Dec 2025 00:00:00 +0000</pubDate><guid>https://0xMax42.io/p/reaktive-lokale-dienste-mit-systemd-unix-sockets-und-rootlosen-containern/</guid><description>&lt;img src="https://0xMax42.io/p/reaktive-lokale-dienste-mit-systemd-unix-sockets-und-rootlosen-containern/reactive-services.webp" alt="Featured image of post Reaktive lokale Dienste mit systemd, UNIX-Sockets und rootlosen Containern" /&gt;&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Status:&lt;/strong&gt; Informelles Referenzdokument / Pattern-Beschreibung&lt;br&gt;
&lt;strong&gt;Zielgruppe:&lt;/strong&gt; Linux-Administratoren, DevOps-/Platform-Engineers, systemd- und Podman-Anwender&lt;br&gt;
&lt;strong&gt;Beispiel-Implementierung:&lt;/strong&gt; &lt;a class="link" href="https://github.com/0xMax42/Reactive-PlantUML" target="_blank" rel="noopener"
&gt;Reactive-PlantUML auf GitHub&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id="motivation"&gt;Motivation
&lt;/h2&gt;&lt;p&gt;Moderne Entwickler- und Administrationsumgebungen benötigen zunehmend lokale Dienste, die:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;reproduzierbar konfiguriert sind,&lt;/li&gt;
&lt;li&gt;keine permanent laufenden Hintergrundprozesse erfordern,&lt;/li&gt;
&lt;li&gt;ohne Root-Rechte betrieben werden können,&lt;/li&gt;
&lt;li&gt;und dennoch sauber in bestehende Systemmechanismen integriert sind.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Typische Beispiele sind lokale Vorschau‑Server, Dokumentationsdienste oder Hilfsdienste für IDEs.&lt;/p&gt;
&lt;p&gt;Reactive-PlantUML entstand aus der Beobachtung, dass &lt;strong&gt;systemd&lt;/strong&gt;, &lt;strong&gt;UNIX-Domain-Sockets&lt;/strong&gt; und &lt;strong&gt;rootlose Container&lt;/strong&gt; bereits alle notwendigen Bausteine bereitstellen, um solche Dienste &lt;em&gt;reaktiv&lt;/em&gt; und &lt;em&gt;bedarfsorientiert&lt;/em&gt; zu betreiben – ohne zusätzliche Infrastruktur oder Spezialframeworks.&lt;/p&gt;
&lt;p&gt;Dieses Dokument beschreibt das daraus abgeleitete &lt;strong&gt;Betriebsmuster&lt;/strong&gt;, nicht die konkrete PlantUML-Implementierung.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="grundprinzipien-des-musters"&gt;Grundprinzipien des Musters
&lt;/h2&gt;&lt;p&gt;Das hier beschriebene Muster basiert auf folgenden Invarianten:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Rootless by default&lt;/strong&gt;
Alle Komponenten laufen als &lt;em&gt;systemd user services&lt;/em&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Socket-Aktivierung statt Daemons&lt;/strong&gt;
Dienste starten ausschließlich bei tatsächlicher Nutzung.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Explizite Port-Verantwortung&lt;/strong&gt;
Die Zuordnung von TCP-Ports erfolgt bewusst durch den Benutzer.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Keine internen TCP-Ports&lt;/strong&gt;
Interne Kommunikation erfolgt ausschließlich über UNIX-Domain-Sockets.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Automatisches Idle-Shutdown&lt;/strong&gt;
Dienste beenden sich nach Inaktivität selbstständig.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Reproduzierbarkeit vor Magie&lt;/strong&gt;
Alle Abhängigkeiten und Artefakte sind versioniert und überprüfbar.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;hr&gt;
&lt;h2 id="architekturüberblick"&gt;Architekturüberblick
&lt;/h2&gt;&lt;p&gt;Der Datenfluss folgt einem strikt linearen Modell:&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-fallback" data-lang="fallback"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Client (Browser, IDE, CLI)
&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; │ TCP (lokaler, benutzerdefinierter Port)
&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;systemd .socket (User-Unit)
&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&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;systemd-socket-proxyd
&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; │ UNIX-Domain-Socket (%t/plantuml/&amp;lt;port&amp;gt;.sock)
&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;rootloser Podman-Container
&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&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Applikation (z. B. PlantUML HTTP-Server)
&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;Wesentliche Beobachtung:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Kein Bestandteil kennt mehr Kontext als notwendig.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr&gt;
&lt;h2 id="rolle-der-einzelnen-komponenten"&gt;Rolle der einzelnen Komponenten
&lt;/h2&gt;&lt;h3 id="systemd-socket-units-user"&gt;systemd Socket Units (User)
&lt;/h3&gt;&lt;ul&gt;
&lt;li&gt;Definieren ausschließlich den extern sichtbaren TCP-Port&lt;/li&gt;
&lt;li&gt;Starten keinerlei Logik&lt;/li&gt;
&lt;li&gt;Der Instanzname entspricht direkt dem Port (&lt;code&gt;plantuml@8080.socket&lt;/code&gt;)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Dies erzwingt eine explizite, konfliktfreie Portwahl durch den Benutzer.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id="systemd-socket-proxyd"&gt;systemd-socket-proxyd
&lt;/h3&gt;&lt;ul&gt;
&lt;li&gt;Übernimmt die Brückenfunktion zwischen TCP und UNIX-Socket&lt;/li&gt;
&lt;li&gt;Wartet implizit auf die Verfügbarkeit des Ziel-Sockets&lt;/li&gt;
&lt;li&gt;Beendet sich automatisch nach Inaktivität (&lt;code&gt;--exit-idle-time&lt;/code&gt;)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Wichtig:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;code&gt;systemd-socket-proxyd&lt;/code&gt; ist zustandslos und enthält keinerlei Anwendungslogik.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr&gt;
&lt;h3 id="podman-container-quadlet"&gt;Podman-Container (Quadlet)
&lt;/h3&gt;&lt;p&gt;Der Container:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;läuft rootlos&lt;/li&gt;
&lt;li&gt;besitzt &lt;strong&gt;keinen exponierten TCP-Port&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;erzeugt und verwaltet ausschließlich einen UNIX-Domain-Socket&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Der Socket wird über ein &lt;code&gt;RuntimeDirectory&lt;/code&gt; bereitgestellt und per Volume in den Container gemountet.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id="interne-socket-brücke-socat"&gt;Interne Socket-Brücke (socat)
&lt;/h3&gt;&lt;p&gt;Innerhalb des Containers:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;stellt die Applikation lokal einen TCP-Port bereit&lt;/li&gt;
&lt;li&gt;&lt;code&gt;socat&lt;/code&gt; bridged diesen Port zu einem UNIX-Socket&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Dieses Detail ist austauschbar und &lt;strong&gt;kein Bestandteil des Musters&lt;/strong&gt; – entscheidend ist lediglich, dass:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;die Container-Grenze ausschließlich über einen UNIX-Socket überschritten wird.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr&gt;
&lt;h2 id="health-checks-und-zustandsmodell"&gt;Health Checks und Zustandsmodell
&lt;/h2&gt;&lt;p&gt;Health Checks erfolgen:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;über den UNIX-Socket&lt;/li&gt;
&lt;li&gt;ohne Nutzung externer Ports&lt;/li&gt;
&lt;li&gt;sowohl während des Starts als auch im laufenden Betrieb&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Der Container meldet seine Bereitschaft explizit an systemd (&lt;code&gt;Notify=healthy&lt;/code&gt;).&lt;/p&gt;
&lt;p&gt;Damit entsteht ein klares Zustandsmodell:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;em&gt;Socket offen&lt;/em&gt; → Anfrage möglich&lt;/li&gt;
&lt;li&gt;&lt;em&gt;Container healthy&lt;/em&gt; → Dienst verfügbar&lt;/li&gt;
&lt;li&gt;&lt;em&gt;Idle&lt;/em&gt; → automatisches Herunterfahren&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2 id="installation-und-betrieb"&gt;Installation und Betrieb
&lt;/h2&gt;&lt;p&gt;Das Muster ist distributionsneutral, lässt sich aber besonders gut über Debian-Pakete ausrollen:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;systemd-User-Units&lt;/li&gt;
&lt;li&gt;Podman-Quadlets&lt;/li&gt;
&lt;li&gt;Container-Build-Dateien&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Nach Installation ist &lt;strong&gt;keine Aktivität erforderlich&lt;/strong&gt;, bis der Benutzer bewusst einen Socket aktiviert:&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;/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;systemctl --user &lt;span class="nb"&gt;enable&lt;/span&gt; --now plantuml@8080.socket
&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;hr&gt;
&lt;h2 id="abgrenzung-und-bewusste-nicht-ziele"&gt;Abgrenzung und bewusste Nicht-Ziele
&lt;/h2&gt;&lt;p&gt;Dieses Muster versucht ausdrücklich &lt;strong&gt;nicht&lt;/strong&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;klassische Service-Orchestrierung zu ersetzen&lt;/li&gt;
&lt;li&gt;Netzwerk-Namensräume zu abstrahieren&lt;/li&gt;
&lt;li&gt;bestehende Init-Systeme zu umgehen&lt;/li&gt;
&lt;li&gt;neue Sicherheitsmodelle einzuführen&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Es nutzt ausschließlich:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;bestehende systemd-Mechanismen&lt;/li&gt;
&lt;li&gt;etablierte UNIX-Prinzipien&lt;/li&gt;
&lt;li&gt;klar definierte Verantwortlichkeiten&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2 id="übertragbarkeit-des-musters"&gt;Übertragbarkeit des Musters
&lt;/h2&gt;&lt;p&gt;Obwohl Reactive-PlantUML der konkrete Anwendungsfall ist, eignet sich das Muster u. a. für:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;lokale Preview-Server&lt;/li&gt;
&lt;li&gt;Dokumentationsdienste&lt;/li&gt;
&lt;li&gt;Entwicklungs-Backends&lt;/li&gt;
&lt;li&gt;Hilfsdienste für IDEs&lt;/li&gt;
&lt;li&gt;kurzlebige Werkzeuge mit Netzwerkbedarf&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Die konkrete Applikation ist austauschbar.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="fazit"&gt;Fazit
&lt;/h2&gt;&lt;p&gt;Reactive-PlantUML zeigt kein neues Werkzeug, sondern ein &lt;strong&gt;ruhiges Zusammenspiel bestehender Mechanismen&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;Das Muster ist:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;transparent&lt;/li&gt;
&lt;li&gt;reproduzierbar&lt;/li&gt;
&lt;li&gt;wartbar&lt;/li&gt;
&lt;li&gt;und bewusst unspektakulär&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Oder anders formuliert:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;Es passiert nur genau das, was passieren muss – und nichts darüber hinaus.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;</description></item><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><item><title>Systemd-Timer per CLI erstellen – einfach, schnell, nervenschonend</title><link>https://0xMax42.io/p/systemd-timer-per-cli-erstellen-einfach-schnell-nervenschonend/</link><pubDate>Sat, 24 May 2025 21:30:00 +0000</pubDate><guid>https://0xMax42.io/p/systemd-timer-per-cli-erstellen-einfach-schnell-nervenschonend/</guid><description>&lt;img src="https://0xMax42.io/p/systemd-timer-per-cli-erstellen-einfach-schnell-nervenschonend/systemd-timer-cover.webp" alt="Featured image of post Systemd-Timer per CLI erstellen – einfach, schnell, nervenschonend" /&gt;&lt;p&gt;Wer schon mal versucht hat, systemd-Timer von Hand zu schreiben, weiß: So richtig Spaß macht das nicht. Zwischen &lt;code&gt;.service&lt;/code&gt;- und &lt;code&gt;.timer&lt;/code&gt;-Dateien, Pfadangaben, und &lt;code&gt;OnCalendar&lt;/code&gt;-Syntax kann man sich schnell vertun. Genau dafür habe ich mir ein kleines Tool gebaut: &lt;code&gt;systemd-timer&lt;/code&gt;.&lt;/p&gt;
&lt;h2 id="was-macht-das-ding"&gt;Was macht das Ding?
&lt;/h2&gt;&lt;p&gt;Ganz einfach: Du rufst ein CLI auf, gibst ein paar Parameter an, und das Tool schreibt dir die passenden &lt;code&gt;.service&lt;/code&gt;- und &lt;code&gt;.timer&lt;/code&gt;-Units – inklusive Logging, Abhängigkeiten und allem, was systemd eben braucht. Keine Vorlage kopieren, keine Tippfehler mehr, kein Nachdenken darüber, ob das jetzt &lt;code&gt;WantedBy=timers.target&lt;/code&gt; heißen muss oder nicht.&lt;/p&gt;
&lt;h2 id="warum-nicht-einfach-cron"&gt;Warum nicht einfach &lt;code&gt;cron&lt;/code&gt;?
&lt;/h2&gt;&lt;p&gt;Klar, &lt;code&gt;cron&lt;/code&gt; funktioniert. Aber systemd hat ein paar Vorteile:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Besseres Logging (auch in Datei)&lt;/li&gt;
&lt;li&gt;Einfache Integration mit anderen Diensten (z. B. Netzwerkwarten)&lt;/li&gt;
&lt;li&gt;Funktioniert auch pro Benutzer (ohne root-Rechte)&lt;/li&gt;
&lt;li&gt;Alles bleibt in einer Sprache: systemd&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Das CLI hilft dabei, die Einstiegshürde zu senken und trotzdem &amp;ldquo;richtig&amp;rdquo; mit systemd zu arbeiten.&lt;/p&gt;
&lt;h2 id="was-kann-systemd-timer"&gt;Was kann &lt;code&gt;systemd-timer&lt;/code&gt;?
&lt;/h2&gt;&lt;ul&gt;
&lt;li&gt;&lt;code&gt;.service&lt;/code&gt; + &lt;code&gt;.timer&lt;/code&gt;-Dateien erstellen&lt;/li&gt;
&lt;li&gt;&lt;code&gt;--user&lt;/code&gt;-Timer (für Desktop- oder Container-Umgebungen)&lt;/li&gt;
&lt;li&gt;Logging per &lt;code&gt;--logfile&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Standard-Optionen wie:
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;--exec&lt;/code&gt;, &lt;code&gt;--calendar&lt;/code&gt;, &lt;code&gt;--after&lt;/code&gt;, &lt;code&gt;--environment&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;--description&lt;/code&gt;, &lt;code&gt;--output&lt;/code&gt;, &lt;code&gt;--dry-run&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;CLI basiert auf &lt;code&gt;Cliffy&lt;/code&gt;, getippt mit Deno&lt;/li&gt;
&lt;li&gt;Plattformunabhängige Installation per Shell-Skript&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="ein-beispiel-das-du-sofort-nutzen-kannst"&gt;Ein Beispiel, das du sofort nutzen kannst
&lt;/h2&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;/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;systemd-timer create &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; --exec &lt;span class="s2"&gt;&amp;#34;/usr/local/bin/backup.sh&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; --calendar &lt;span class="s2"&gt;&amp;#34;Mon..Fri 02:00&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; --description &lt;span class="s2"&gt;&amp;#34;Backup Job&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; --user &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; --logfile &lt;span class="s2"&gt;&amp;#34;/var/log/backup.log&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;Ergebnis:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;~/.config/systemd/user/backup.service&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;~/.config/systemd/user/backup.timer&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Aktivieren:&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;/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;systemctl --user daemon-reload
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;systemctl --user &lt;span class="nb"&gt;enable&lt;/span&gt; --now backup.timer
&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;h2 id="installation-ein-einzeiler"&gt;Installation: Ein Einzeiler
&lt;/h2&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;/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;curl -fsSL https://git.0xmax42.io/maxp/systemd-timer/raw/branch/main/scripts/install.sh &lt;span class="p"&gt;|&lt;/span&gt; sh
&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 Skript erkennt automatisch deine Architektur (amd64/arm64), lädt die passende Binary und prüft sie mit SHA256. Und ja, du kannst dir den Code vorher anschauen, bevor du ihn durch die Leitung jagst.&lt;/p&gt;
&lt;h2 id="entwicklung--test"&gt;Entwicklung &amp;amp; Test
&lt;/h2&gt;&lt;p&gt;Das Tool ist komplett mit TypeScript/Deno geschrieben, ordentlich typisiert und testgetrieben. Alles Wichtige ist modular aufgebaut, damit man es auch als Bibliothek verwenden könnte.&lt;/p&gt;
&lt;p&gt;Tests laufen einfach per:&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;/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;deno task &lt;span class="nb"&gt;test&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;h2 id="fazit"&gt;Fazit
&lt;/h2&gt;&lt;p&gt;&lt;code&gt;systemd-timer&lt;/code&gt; ist kein Monster-Tool mit 1000 Optionen – sondern genau das, was man sich wünscht, wenn man öfter mal einfache Tasks zeitgesteuert per systemd laufen lassen will, ohne sich jedes Mal die Finger wundzuschreiben. Es richtet sich an Leute, die lieber deklarativ als improvisiert arbeiten.&lt;/p&gt;
&lt;p&gt;Quellcode, Releases und alles Weitere findest du hier:
👉 &lt;a class="link" href="https://git.0xmax42.io/maxp/systemd-timer" target="_blank" rel="noopener"
&gt;git.0xmax42.io/maxp/systemd-timer&lt;/a&gt;&lt;/p&gt;</description></item></channel></rss>