Skip to content

Commit 3ce0c03

Browse files
committed
Prepare systemdkit 0.1.0 release
1 parent e3f50a5 commit 3ce0c03

4 files changed

Lines changed: 137 additions & 52 deletions

File tree

CHANGELOG.md

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,18 @@
11
# Changelog
22

3+
## v0.1.0
4+
5+
First stable release.
6+
7+
- Added D-Bus-backed systemd manager APIs for unit lifecycle, unit-file operations, jobs, and signals.
8+
- Added loss-aware systemd unit-file parsing and rendering.
9+
- Added builders for service, socket, timer, mount, path, and target units.
10+
- Added parser-backed validation for common unit-file directives, including resource-control and sandboxing settings.
11+
- Added typed structs for units, jobs, unit-file operations, unit-file status, and unit/service/socket/timer state.
12+
- Added transient-unit helpers, including resource-control properties.
13+
- Added structured errors with permission/polkit classification.
14+
- Added real Linux/systemd integration tests and documentation guides.
15+
316
## v0.1.0-pre.1
417

518
- Removed VibeKit/Igniter scaffold dependencies from runtime package dependencies.

CONTRIBUTING.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# Contributing
22

3-
Before opening release-oriented changes or publishing a pre-release/Hex package:
3+
Before opening release-oriented changes or publishing a Hex package:
44

55
- Run `mix ci` locally.
66
- Run `mix docs` and inspect generated module examples.

README.md

Lines changed: 78 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -1,88 +1,122 @@
1-
# Systemd
1+
# systemdkit
22

3-
Pure Elixir tools for working with systemd.
3+
Pure Elixir tools for systemd unit files and D-Bus manager control.
44

5-
Hex package name: `systemdkit`. The Mix application and public modules remain `:systemd` / `Systemd`.
5+
`systemdkit` is for Elixir applications and deployment tools that need to generate unit files, inspect systemd state, or control systemd directly over D-Bus without shelling out to `systemctl`.
66

7-
```elixir
8-
{:systemdkit, "~> 0.1.0-pre"}
9-
```
7+
## Installation
108

11-
The package exposes a small D-Bus backed manager client:
9+
The Hex package is named `systemdkit`; the Mix application and modules are `:systemd` / `Systemd`.
1210

1311
```elixir
14-
{:ok, conn} = Systemd.Manager.connect()
15-
{:ok, units} = Systemd.Manager.list_units(conn)
16-
{:ok, unit} = Systemd.Manager.get_unit(conn, "dbus.service")
17-
{:ok, state} = Systemd.UnitObject.state(conn, unit)
18-
{:ok, service_state} = Systemd.UnitObject.service_state(conn, unit)
12+
def deps do
13+
[
14+
{:systemd, "~> 0.1.0", hex: :systemdkit}
15+
]
16+
end
1917
```
2018

21-
It also includes a NimbleParsec-backed unit file parser/generator:
19+
## Unit files
2220

23-
```elixir
24-
{:ok, unit_file} = Systemd.UnitFile.parse("[Service]\nExecStart=/bin/app start\n")
25-
Systemd.UnitFile.to_string(unit_file)
21+
Build systemd units with typed helpers, render them, and validate them before installation:
2622

23+
```elixir
2724
unit_file =
2825
Systemd.UnitFile.service(
29-
unit: [description: "My app"],
30-
service: [exec_start: "/bin/app start", restart: :always],
26+
unit: [description: "My app", after: "network.target"],
27+
service: [
28+
type: :exec,
29+
user: "deploy",
30+
working_directory: "/opt/my-app/current",
31+
exec_start: "/opt/my-app/current/bin/my_app start",
32+
restart: "on-failure",
33+
memory_max: "512M",
34+
tasks_max: 512,
35+
no_new_privileges: true,
36+
protect_system: :strict
37+
],
3138
install: [wanted_by: "multi-user.target"]
3239
)
40+
41+
:ok = Systemd.UnitFile.validate(unit_file, :service)
42+
Systemd.UnitFile.to_string(unit_file)
3343
```
3444

35-
The package depends on [`rebus`](https://hex.pm/packages/rebus) for the D-Bus wire protocol instead of shelling out to `systemctl`.
45+
Parsing is loss-aware: comments, blank lines, duplicate directives, reset directives, and source spans are preserved.
3646

37-
APIs return idiomatic `{:ok, value}` / `{:error, %Systemd.Error{}}` tuples. Permission and polkit failures are classified with `category: :permission` and can be checked with `Systemd.Error.permission?/1`.
47+
```elixir
48+
{:ok, unit_file} = Systemd.UnitFile.parse("[Service]\nExecStart=/bin/true\n")
49+
Systemd.UnitFile.get_all(unit_file, "Service", "ExecStart")
50+
```
3851

39-
See `examples/` for service, timer, and user-bus snippets, `guides/dbus-manager.md` for D-Bus manager operations, and `guides/xamal-style-deployment.md` for a deployment-oriented template unit example.
52+
Builders are available for service, socket, timer, mount, path, and target units.
4053

41-
## Permissions
54+
## D-Bus manager control
4255

43-
Systemd control happens over D-Bus. Read-only calls such as listing units usually work as an unprivileged user. Mutating calls such as daemon reload, starting system units, enabling units, or writing to `/etc/systemd/system` may require root or a polkit rule for the caller. The package returns structured `Systemd.Error` values for D-Bus policy failures instead of retrying through `sudo`.
56+
Use the top-level API for short-lived D-Bus operations:
4457

45-
For user units, pass `bus: :session` when a systemd user session bus is available:
58+
```elixir
59+
{:ok, units} = Systemd.list_units()
60+
{:ok, unit_files} = Systemd.list_unit_files()
61+
{:ok, state} = Systemd.unit_state("dbus.service")
62+
63+
:ok = Systemd.reload()
64+
:ok = Systemd.start_unit("my_app.service")
65+
:ok = Systemd.restart_unit("my_app.service")
66+
```
67+
68+
Use `Systemd.Manager` when you want to reuse a connection or inspect jobs:
4669

4770
```elixir
48-
Systemd.list_units(bus: :session)
71+
Systemd.with_connection([], fn conn ->
72+
with {:ok, job} <- Systemd.Manager.restart_unit(conn, "my_app.service"),
73+
:ok <- Systemd.Job.await_signal(conn, job, timeout: 10_000) do
74+
:ok
75+
end
76+
end)
4977
```
5078

51-
## Unit files
79+
## Errors and permissions
5280

53-
`Systemd.UnitFile` preserves comments, blank lines, duplicate directives, reset directives, and source spans. Validation is intentionally separate from parsing and includes directive-specific value checks for common service, socket, timer, and install keys:
81+
APIs return `{:ok, value}` or `{:error, %Systemd.Error{}}`. Permission and polkit failures are classified as `:permission`:
5482

5583
```elixir
56-
unit_file = Systemd.UnitFile.parse!("[Service]\nExecStart=/bin/true\n")
57-
:ok = Systemd.UnitFile.validate(unit_file, :service)
84+
case Systemd.start_unit("my_app.service") do
85+
:ok -> :ok
86+
{:error, error} ->
87+
if Systemd.Error.permission?(error), do: {:error, :permission_denied}, else: {:error, error}
88+
end
5889
```
5990

60-
## Development
91+
Read-only calls often work unprivileged. Mutating system units typically require root or appropriate polkit rules. `systemdkit` reports those D-Bus errors directly; it does not retry through `sudo`.
6192

62-
```sh
63-
mix deps.get
64-
mix ci
93+
For user units, pass `bus: :session` when a systemd user session bus is available:
94+
95+
```elixir
96+
Systemd.list_units(bus: :session)
6597
```
6698

67-
Integration tests are excluded by default because they require Linux with systemd and a system bus. For local development, run them inside the Lima Debian VM named `systemd-test`:
99+
## Guides
100+
101+
- [D-Bus manager operations](guides/dbus-manager.md)
102+
- [Xamal-style deployment units](guides/xamal-style-deployment.md)
103+
104+
## Integration testing
105+
106+
The test suite includes optional integration tests against a real Linux systemd manager. They are excluded by default:
68107

69108
```sh
70-
~/.local/bin/limactl shell systemd-test
71-
cd /Users/dannote/Development/systemd
72-
SYSTEMD_INTEGRATION=1 mix test
109+
mix test
73110
```
74111

75-
Or from macOS, copy the source into the VM and run the full integration suite:
112+
Run them on Linux with systemd and a system bus:
76113

77114
```sh
78-
scripts/integration_test.sh
115+
SYSTEMD_INTEGRATION=1 mix test
79116
```
80117

81-
Quick VM checks:
118+
This repository also includes a Lima helper used by maintainers:
82119

83120
```sh
84-
~/.local/bin/limactl shell systemd-test -- systemctl is-system-running
85-
~/.local/bin/limactl shell systemd-test -- busctl --system list --no-pager
121+
scripts/integration_test.sh
86122
```
87-
88-
See `CONTRIBUTING.md` before publishing a release.

mix.exs

Lines changed: 45 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
defmodule Systemd.MixProject do
22
use Mix.Project
33

4-
@version "0.1.0-pre.1"
4+
@version "0.1.0"
55
@source_url "https://github.com/elixir-vibe/systemd"
66

77
def project do
@@ -18,7 +18,6 @@ defmodule Systemd.MixProject do
1818
]
1919
end
2020

21-
# Run "mix help compile.app" to learn about applications.
2221
def application do
2322
[
2423
extra_applications: [:logger, :rebus]
@@ -31,7 +30,6 @@ defmodule Systemd.MixProject do
3130
]
3231
end
3332

34-
# Run "mix help deps" to learn about dependencies.
3533
defp deps do
3634
[
3735
{:ex_doc, "~> 0.38", only: :dev, runtime: false},
@@ -44,16 +42,15 @@ defmodule Systemd.MixProject do
4442
{:rebus, "~> 0.2.0"},
4543
{:vibe_kit, "== 0.1.2", only: [:dev, :test], runtime: false},
4644
{:igniter, "~> 0.6", only: [:dev, :test], runtime: false}
47-
# {:dep_from_hexpm, "~> 0.3.0"},
48-
# {:dep_from_git, git: "https://github.com/elixir-lang/my_dep.git", tag: "0.1.0"}
4945
]
5046
end
5147

5248
defp package do
5349
[
5450
name: "systemdkit",
5551
licenses: ["MIT"],
56-
links: %{"GitHub" => @source_url}
52+
links: %{"GitHub" => @source_url},
53+
files: ~w(lib guides examples .formatter.exs mix.exs README.md LICENSE CHANGELOG.md)
5754
]
5855
end
5956

@@ -69,7 +66,48 @@ defmodule Systemd.MixProject do
6966
"guides/xamal-style-deployment.md",
7067
"guides/dbus-manager.md"
7168
],
72-
groups_for_extras: [Guides: ~r/guides\//]
69+
groups_for_extras: [Guides: ~r/guides\//],
70+
groups_for_modules: [
71+
"D-Bus": [
72+
Systemd.DBus,
73+
Systemd.DBus.Result,
74+
Systemd.Manager,
75+
Systemd.Signal,
76+
Systemd.Properties
77+
],
78+
"Unit files": [
79+
Systemd.UnitFile,
80+
Systemd.UnitFile.Blank,
81+
Systemd.UnitFile.Builder,
82+
Systemd.UnitFile.Comment,
83+
Systemd.UnitFile.Directive,
84+
Systemd.UnitFile.ParseError,
85+
Systemd.UnitFile.Raw,
86+
Systemd.UnitFile.Section,
87+
Systemd.UnitFile.Span,
88+
Systemd.UnitFile.ValidationError,
89+
Systemd.UnitFile.Value
90+
],
91+
"Runtime structs": [
92+
Systemd.Error,
93+
Systemd.Job,
94+
Systemd.JobStatus,
95+
Systemd.ServiceState,
96+
Systemd.SocketState,
97+
Systemd.TimerState,
98+
Systemd.Unit,
99+
Systemd.UnitFileChange,
100+
Systemd.UnitFileOperation,
101+
Systemd.UnitFileStatus,
102+
Systemd.UnitObject,
103+
Systemd.UnitState
104+
],
105+
"Transient units": [
106+
Systemd.TransientUnit,
107+
Systemd.TransientUnit.AuxUnit,
108+
Systemd.TransientUnit.Property
109+
]
110+
]
73111
]
74112
end
75113

0 commit comments

Comments
 (0)