about summary refs log tree commit diff
path: root/items/svc_s6rc.py
diff options
context:
space:
mode:
authorRobert Günzler <r@gnzler.io>2025-09-07 17:32:13 +0200
committerRobert Günzler <r@gnzler.io>2025-09-07 17:42:42 +0200
commit38be6c13f76e893cf47636257071b440dec0c5b2 (patch)
treef7ded3235a7e3362cbd1ed3d91523968fe0faf90 /items/svc_s6rc.py
initial commit
Signed-off-by: Robert Günzler <r@gnzler.io>
Diffstat (limited to 'items/svc_s6rc.py')
-rw-r--r--items/svc_s6rc.py140
1 files changed, 140 insertions, 0 deletions
diff --git a/items/svc_s6rc.py b/items/svc_s6rc.py
new file mode 100644
index 0000000..429c04a
--- /dev/null
+++ b/items/svc_s6rc.py
@@ -0,0 +1,140 @@
+from shlex import quote
+from pathlib import Path
+
+from bundlewrap.items import Item
+
+S6RC_ROOT = "/etc/s6-rc"
+
+def generate_request_path(template, service) -> str:
+    return Path(S6RC_ROOT, "generate", f"{template}.{service}").as_posix()
+
+def service_dependency_path(bundle, service) -> str:
+    return Path(S6RC_ROOT, "services", bundle, "contents.d", service).as_posix()
+
+def have_generate_template(node, template) -> bool:
+    template_path = Path(S6RC_ROOT, "templates", template)
+    try:
+        result = node.run(
+            f"test -d {quote(template_path.as_posix())}",
+        )
+        return result.return_code == 0
+    except:
+        return False
+
+def is_service_generated(node, template, service) -> bool:
+    try:
+        result = node.run(
+            f"test -f {quote(generate_request_path(template, service))}",
+        )
+        return result.return_code == 0
+    except:
+        return False
+
+def generate_service(node, template, service) -> bool:
+    """
+    This doesn't really generate the service from the template. Rather it
+    requests the s6-rc-generate target to do it on boot.
+    """
+    result = node.run(
+        f"touch {quote(generate_request_path(template, service))}",
+    )
+    return result.return_code == 0
+
+def is_service_registered(node, bundle, service) -> bool:
+    try:
+        result = node.run(
+            f"test -f {quote(service_dependency_path(bundle, service))}",
+        )
+        return result.return_code == 0
+    except:
+        return False
+
+def register_service(node, bundle, service) -> bool:
+    result = node.run(
+        f"touch {quote(service_dependency_path(bundle, service))}",
+    )
+    return result.return_code == 0
+
+def is_service_running(node, service) -> bool:
+    try:
+        result = node.run(
+            f"s6-rc -v2 -bt 30000 -a list | grep -q {quote(service)}",
+        )
+        return result.return_code == 0
+    except:
+        return False
+
+def run_service(node, service) -> bool:
+    result = node.run(
+        f"s6-rc -v2 -bt 30000 start {quote(service)}",
+    )
+    return result.return_code == 0
+
+class SvcS6RC(Item):
+    """
+    A service managed by s6-rc.
+    """
+
+    BUNDLE_ATTRIBUTE_NAME = "svc_s6rc"
+    ITEM_ATTRIBUTES = {
+        "bundle": "default",
+        "generate": None,
+        "running": True,
+    }
+    ITEM_TYPE_NAME = "svc_s6rc"
+    REQUIRED_ATTRIBUTES = []
+
+    def __repr__(self):
+        return "<SvcS6RC name:{} running:{} bundle:{} generate:{}>".format(
+            self.name,
+            self.attributes["running"],
+            self.attributes["bundle"],
+            self.attributes["generate"],
+        )
+
+    def get_canned_actions(self):
+        return {
+            "stop": {
+                "command": f"s6-rc -v2 -b -t 30000 stop {quote(self.name)}",
+                "needed_by": {self.id},
+            },
+            "restart": {
+                "command": f"s6-rc -v2 -b -t 30000 stop {quote(self.name)} && s6-rc -v2 -b -t 30000 start {quote(self.name)}",
+                "needs": {self.id},
+            },
+        }
+
+    def cdict(self):
+        return {
+            "running": self.attributes["running"],
+            "generated": self.attributes["generate"] != None,
+            "registered": self.attributes["bundle"] != None,
+        }
+
+    def sdict(self):
+        return {
+            "running": is_service_running(self.node, self.name),
+            "generated": is_service_generated(self.node,
+                                              self.attributes["generate"],
+                                              self.name) if
+                self.attributes["generate"] != None else False,
+            "registered": is_service_registered(self.node,self.attributes["bundle"], self.name),
+        }
+
+    def fix(self, status):
+        if status.correct:
+            return
+
+        if self.attributes["generate"]:
+            if not have_generate_template(self.node, self.attributes["generate"]):
+                raise Exception(f"No template provides `{self.attributes["generate"]}`")
+
+        if status.cdict["generated"] != status.sdict["generated"]:
+            generate_service(self.node, self.attributes["generate"], self.name)
+
+        if status.cdict["registered"] != status.sdict["registered"]:
+            register_service(self.node, self.attributes["bundle"], self.name)
+
+        if status.cdict["running"] != status.sdict["running"]:
+            run_service(self.node, self.name)
+