rules_systemd
Bazel-idiomatic systemd unit provisioning: typed unit rules + providers + an aspect that emits the /etc/systemd/system layer with enable-symlinks
| Latest | 0.0.1 |
|---|---|
| Versions | 1 |
| Category | Bazel rules |
| Maintainers | Matt Marshall |
| Registry | https://registry.tbzl.dev/modules/rules_systemd/ |
| Source | github.com/tomato-bazel/rules_systemd |
bazel_dep(name = "rules_systemd", version = "0.0.1")
View source & releases on GitHub ↗
Bazel-idiomatic systemd unit provisioning: typed rules that render unit
files from attrs, providers carrying unit metadata, an aspect that
collects units across a deps graph, and a systemd_layer rule that
packs them into an /etc/systemd/system tar — computing the
*.target.wants/ enable-symlinks itself, since a build action never
runs systemctl enable.
The tar drops straight into oci_image(tars = [...]), so you can build a
systemd-PID-1 image entirely from Bazel without hand-writing .service
text into a pkg_tar.
Status: v0.0.1
Public rules: systemd_service, systemd_oneshot, systemd_target,
systemd_socket, systemd_timer, systemd_tmpfiles, systemd_dropin,
systemd_layer. Rendering is pure Starlark (ctx.actions.write); the
layer tar is built by a small deterministic tool (sorted entries,
mtime=0, uid/gid=0) so it is reproducible and golden-testable.
Install
.bazelrc:
common --registry=https://registry.fastverk.com/
common --registry=https://bcr.bazel.build/
MODULE.bazel:
bazel_dep(name = "rules_systemd", version = "0.0.1")
Usage
load("@rules_systemd//systemd:defs.bzl",
"systemd_service", "systemd_oneshot", "systemd_tmpfiles", "systemd_layer")
systemd_oneshot(
name = "app-init",
description = "First-boot init",
exec_start = "/usr/local/bin/app-init",
before = ["app.service"],
)
systemd_tmpfiles(
name = "app-tmpfiles",
unit_name = "app.conf",
lines = ["d /var/lib/app 0750 app app -"],
)
systemd_service(
name = "app",
description = "App daemon",
requires = ["app-init.service"],
after = ["network.target", "app-init.service"],
exec_start = "/usr/local/bin/app --config /etc/app/config.yaml",
user = "app",
restart = "on-failure",
wanted_by = ["multi-user.target"], # systemd_layer writes the enable-symlink
)
systemd_layer(
name = "units",
deps = [":app", ":app-init", ":app-tmpfiles"],
)
Then, in an image:
load("@rules_oci//oci:defs.bzl", "oci_image")
oci_image(
name = "app_image",
base = "//images/systemd-base", # a base whose ENTRYPOINT is /sbin/init
entrypoint = ["/sbin/init"],
tars = [":units", "//toolkits/app:layer"],
)
bazel build //…:units emits the tar; its deterministic path/symlink
listing is in output group listing (used for golden tests — see
examples/smoke). Run it under podman with --systemd=always.
Layout
systemd/defs.bzl— the public rules + the_systemd_unitsaspect.systemd/providers.bzl—SystemdUnitInfo,SystemdTransitiveInfo,SystemdLayerInfo.systemd/private/render.bzl— INI rendering helpers.systemd/private/mklayer.py— the deterministic tar builder.examples/smoke— a worked example with golden tests.
Usage#
Real usage, taken from the module’s examples/.
examples/smoke/BUILD.bazel
load("@bazel_skylib//rules:diff_test.bzl", "diff_test")
load(
"//systemd:defs.bzl",
"systemd_layer",
"systemd_oneshot",
"systemd_service",
"systemd_target",
"systemd_tmpfiles",
)
package(default_visibility = ["//visibility:private"])
# A one-shot first-boot init that the daemon orders After=.
systemd_oneshot(
name = "demo-init",
before = ["demo.service"],
description = "Demo first-boot init",
exec_start = "/usr/local/bin/demo-init",
)
# tmpfiles establishing ownership of the state dir before the daemon runs.
systemd_tmpfiles(
name = "demo-tmpfiles",
lines = ["d /var/lib/demo 0750 demo demo -"],
unit_name = "demo.conf",
)
# The long-running daemon, enabled into multi-user.target.
systemd_service(
name = "demo",
after = [
"network.target",
"demo-init.service",
],
description = "Demo daemon",
environment = [
"DEMO_ENV=local",
"DEMO_PORT=8080",
],
exec_start = "/usr/local/bin/demo --config /etc/demo/config.yaml",
requires = ["demo-init.service"],
restart = "on-failure",
restart_sec = "2s",
user = "demo",
wanted_by = ["multi-user.target"],
)
# A synchronization target with no [Install] (not enabled itself).
systemd_target(
name = "demo-stack",
description = "Demo stack target",
wants = ["demo.service"],
)
# Collect everything into one /etc/systemd/system tar layer. The
# demo.service enable-symlink under multi-user.target.wants/ is computed
# automatically from its wanted_by.
systemd_layer(
name = "units",
deps = [
":demo",
":demo-init",
":demo-stack",
":demo-tmpfiles",
],
)
filegroup(
name = "units_listing",
srcs = [":units"],
output_group = "listing",
)
# Golden tests: the rendered daemon unit + the layer's path/symlink listing.
diff_test(
name = "demo_service_golden",
file1 = "demo.service.golden",
file2 = ":demo",
)
diff_test(
name = "units_listing_golden",
file1 = "units.listing.golden",
file2 = ":units_listing",
)Conformance#
No gate findings. 8 contested atoms. See how gating works or the full report.
Contested atoms
Third-party modules where this module resolves a different version than others do. Not a violation of anything this module did — it is the actionable form of a registry-level convergence finding, and the sentence a maintainer can act on.
| Atom | Resolved here | Elsewhere |
|---|---|---|
apple_support | 1.24.2 | 2.2.0 ×1 |
bazel_skylib | 1.8.2 | 1.9.0 ×2 |
nlohmann_json | 3.6.1 | 3.12.0.bcr.1 ×1 |
protobuf | 33.4 | 34.0.bcr.1 ×2 |
rules_jvm_external | 6.7 | 6.8 ×4 |
rules_python | 1.7.0 | 2.0.1 ×1 |
rules_swift | 3.1.2 | 3.6.1 ×1 |
upb | 0.0.0-20220923-a547704 | 0.0.0-20230516-61a97ef ×1 |
Dependencies#
Depends on
Versions#
1 published version, newest first. Each resolves to an immutable, integrity-checked archive.
| Version | Integrity (sha256) | Source archive |
|---|---|---|
0.0.1 latest | LDLKb6d5pHOF8vxt… | tag archive ↗ |
Changelog#
All notable changes to rules_systemd. The format is loosely Keep a Changelog — version headers mirror the published bazel-registry entries.
0.0.1
- Initial scaffold via
rels scaffold. - Typed unit rules:
systemd_service,systemd_oneshot,systemd_target,systemd_socket,systemd_timer,systemd_tmpfiles,systemd_dropin— pure-Starlark INI rendering. - Providers:
SystemdUnitInfo,SystemdTransitiveInfo,SystemdLayerInfo; a_systemd_unitsaspect overdeps. systemd_layer: packs transitive units into a reproducible/etc/systemd/system(+/etc/tmpfiles.d, drop-ins) tar foroci_image(tars = [...]), auto-writing*.target.wants/enable symlinks from each unit’swanted_by.examples/smokewith golden tests over a rendered unit + the layer listing.