pixie-lab-dibs 0.1.0__tar.gz → 0.1.1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (29) hide show
  1. {pixie_lab_dibs-0.1.0 → pixie_lab_dibs-0.1.1}/.github/workflows/test.yml +21 -0
  2. {pixie_lab_dibs-0.1.0 → pixie_lab_dibs-0.1.1}/CHANGELOG.md +13 -0
  3. {pixie_lab_dibs-0.1.0 → pixie_lab_dibs-0.1.1}/Makefile +4 -1
  4. {pixie_lab_dibs-0.1.0 → pixie_lab_dibs-0.1.1}/PKG-INFO +4 -3
  5. {pixie_lab_dibs-0.1.0 → pixie_lab_dibs-0.1.1}/README.md +3 -2
  6. pixie_lab_dibs-0.1.1/scripts/service-smoke.sh +42 -0
  7. {pixie_lab_dibs-0.1.0 → pixie_lab_dibs-0.1.1}/src/dibs/__init__.py +1 -1
  8. {pixie_lab_dibs-0.1.0 → pixie_lab_dibs-0.1.1}/src/dibs/cli.py +31 -4
  9. {pixie_lab_dibs-0.1.0 → pixie_lab_dibs-0.1.1}/tests/test_cli.py +26 -2
  10. {pixie_lab_dibs-0.1.0 → pixie_lab_dibs-0.1.1}/.github/workflows/format.yml +0 -0
  11. {pixie_lab_dibs-0.1.0 → pixie_lab_dibs-0.1.1}/.github/workflows/publish.yml +0 -0
  12. {pixie_lab_dibs-0.1.0 → pixie_lab_dibs-0.1.1}/.gitignore +0 -0
  13. {pixie_lab_dibs-0.1.0 → pixie_lab_dibs-0.1.1}/.pre-commit-config.yaml +0 -0
  14. {pixie_lab_dibs-0.1.0 → pixie_lab_dibs-0.1.1}/LICENSE +0 -0
  15. {pixie_lab_dibs-0.1.0 → pixie_lab_dibs-0.1.1}/LICENSES/BSD-3-Clause.txt +0 -0
  16. {pixie_lab_dibs-0.1.0 → pixie_lab_dibs-0.1.1}/REUSE.toml +0 -0
  17. {pixie_lab_dibs-0.1.0 → pixie_lab_dibs-0.1.1}/pyproject.toml +0 -0
  18. {pixie_lab_dibs-0.1.0 → pixie_lab_dibs-0.1.1}/src/dibs/_sources.py +0 -0
  19. {pixie_lab_dibs-0.1.0 → pixie_lab_dibs-0.1.1}/src/dibs/_sshsig.py +0 -0
  20. {pixie_lab_dibs-0.1.0 → pixie_lab_dibs-0.1.1}/src/dibs/_store.py +0 -0
  21. {pixie_lab_dibs-0.1.0 → pixie_lab_dibs-0.1.1}/src/dibs/_users.py +0 -0
  22. {pixie_lab_dibs-0.1.0 → pixie_lab_dibs-0.1.1}/src/dibs/app.py +0 -0
  23. {pixie_lab_dibs-0.1.0 → pixie_lab_dibs-0.1.1}/tests/__init__.py +0 -0
  24. {pixie_lab_dibs-0.1.0 → pixie_lab_dibs-0.1.1}/tests/conftest.py +0 -0
  25. {pixie_lab_dibs-0.1.0 → pixie_lab_dibs-0.1.1}/tests/test_app.py +0 -0
  26. {pixie_lab_dibs-0.1.0 → pixie_lab_dibs-0.1.1}/tests/test_sources.py +0 -0
  27. {pixie_lab_dibs-0.1.0 → pixie_lab_dibs-0.1.1}/tests/test_store.py +0 -0
  28. {pixie_lab_dibs-0.1.0 → pixie_lab_dibs-0.1.1}/tests/test_users.py +0 -0
  29. {pixie_lab_dibs-0.1.0 → pixie_lab_dibs-0.1.1}/uv.lock +0 -0
@@ -40,3 +40,24 @@ jobs:
40
40
 
41
41
  - name: make build
42
42
  run: make build
43
+
44
+ service:
45
+ runs-on: ubuntu-latest
46
+ steps:
47
+ - uses: actions/checkout@v7
48
+
49
+ - uses: astral-sh/setup-uv@v7.2.1
50
+
51
+ # The pixie server dibs first ran on resolves users from files only,
52
+ # without nss-systemd. Match it, so a unit that relies on systemd's
53
+ # dynamic users fails here rather than there.
54
+ - name: Resolve users from /etc/passwd only
55
+ run: |
56
+ sudo sed -i 's/^passwd:.*/passwd: files/' /etc/nsswitch.conf
57
+ grep '^passwd' /etc/nsswitch.conf
58
+
59
+ - name: make build
60
+ run: make build
61
+
62
+ - name: make service-smoke
63
+ run: make service-smoke
@@ -10,6 +10,19 @@ Follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
10
10
 
11
11
  ## [Unreleased]
12
12
 
13
+ ## [0.1.1] - 2026-10-27
14
+
15
+ ### Fixed
16
+
17
+ - `dibs server install` over a running service now restarts it, so
18
+ installing again after an upgrade runs the new version. It used to
19
+ write the new unit and leave the old process running.
20
+ - The system service refused every key at `dibs register` on hosts that
21
+ resolve users without nss-systemd, plain Debian among them: under
22
+ `DynamicUser=`, `ssh-keygen` could not look its own user up. `dibs server install` now creates a `dibs` system user and runs the service
23
+ as it. Run `sudo dibs server install` again to update an installed
24
+ service.
25
+
13
26
  ## [0.1.0] - 2026-10-27
14
27
 
15
28
  ### Added
@@ -9,7 +9,7 @@ UV ?= uv
9
9
  PRECOMMIT ?= pre-commit
10
10
 
11
11
  .DEFAULT_GOAL := help
12
- .PHONY: help format lint typecheck test build check
12
+ .PHONY: help format lint typecheck test build check service-smoke
13
13
 
14
14
  help: ## Show this help
15
15
  @grep -hE '^[a-z-]+:.*?## ' $(MAKEFILE_LIST) \
@@ -35,3 +35,6 @@ build: ## Build the sdist and wheel, and check every command the wheel installs
35
35
  done
36
36
 
37
37
  check: lint typecheck test ## Everything but format
38
+
39
+ service-smoke: ## Install the built wheel as the real service and claim against it (sudo, systemd)
40
+ ./scripts/service-smoke.sh
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: pixie-lab-dibs
3
- Version: 0.1.0
3
+ Version: 0.1.1
4
4
  Summary: Call dibs on shared lab machines: see who is using what, claim it, release it
5
5
  Project-URL: Homepage, https://github.com/safl/pixie-lab-dibs
6
6
  Author-email: "Simon A. F. Lund" <os@safl.dk>
@@ -81,8 +81,9 @@ sudo dibs server install
81
81
 
82
82
  The command writes a systemd unit for the `dibs` you ran, enables it
83
83
  and starts it. `dibs server start`, `stop`, `restart`, `status` and
84
- `logs` manage it from then on. The service runs as a throwaway system
85
- user that owns only `/var/lib/dibs`. Without root, `dibs server install --user` installs a per-user service instead. For a quick try,
84
+ `logs` manage it from then on. The service runs as a `dibs` system
85
+ user, which the install creates, with write access only to
86
+ `/var/lib/dibs`. Without root, `dibs server install --user` installs a per-user service instead. For a quick try,
86
87
  `dibs serve` runs the server in the foreground.
87
88
 
88
89
  The system service can only run a `dibs` installed outside your home
@@ -59,8 +59,9 @@ sudo dibs server install
59
59
 
60
60
  The command writes a systemd unit for the `dibs` you ran, enables it
61
61
  and starts it. `dibs server start`, `stop`, `restart`, `status` and
62
- `logs` manage it from then on. The service runs as a throwaway system
63
- user that owns only `/var/lib/dibs`. Without root, `dibs server install --user` installs a per-user service instead. For a quick try,
62
+ `logs` manage it from then on. The service runs as a `dibs` system
63
+ user, which the install creates, with write access only to
64
+ `/var/lib/dibs`. Without root, `dibs server install --user` installs a per-user service instead. For a quick try,
64
65
  `dibs serve` runs the server in the foreground.
65
66
 
66
67
  The system service can only run a `dibs` installed outside your home
@@ -0,0 +1,42 @@
1
+ #!/bin/sh
2
+ # SPDX-FileCopyrightText: Simon A. F. Lund <os@safl.dk>
3
+ #
4
+ # SPDX-License-Identifier: BSD-3-Clause
5
+ #
6
+ # Install the built wheel as the real systemd service, then register, log
7
+ # in and claim against it. This is the path the unit tests stub out, and
8
+ # where dibs once broke: under systemd, ssh-keygen must be able to look its
9
+ # own user up. Needs root through sudo and a running systemd; CI's runner
10
+ # has both. Run from the repository root after "make build".
11
+ set -eu
12
+
13
+ wheel=$(ls dist/*.whl)
14
+ tmp=$(mktemp -d)
15
+ trap 'sudo dibs server uninstall >/dev/null 2>&1 || true; rm -rf "$tmp"' EXIT
16
+
17
+ sudo env "PATH=$PATH" UV_TOOL_DIR=/opt/uv-tools UV_TOOL_BIN_DIR=/usr/local/bin \
18
+ uv tool install --python /usr/bin/python3 "$wheel"
19
+
20
+ sudo mkdir -p /etc/dibs
21
+ printf '[[thing]]\nname = "box"\nmac = "aa:bb:cc:dd:ee:ff"\n' | sudo tee /etc/dibs/dibs.toml >/dev/null
22
+ sudo /usr/local/bin/dibs server install --port 8090
23
+
24
+ export DIBS_URL=http://127.0.0.1:8090 XDG_CONFIG_HOME="$tmp/config"
25
+ for _ in $(seq 1 30); do curl -fsS "$DIBS_URL/healthz" >/dev/null 2>&1 && break; sleep 1; done
26
+
27
+ ssh-keygen -q -t ed25519 -N '' -f "$tmp/key"
28
+ dibs register ci -i "$tmp/key.pub"
29
+ sudo dibs admin approve ci
30
+ dibs login
31
+ dibs claim box --for smoke
32
+ dibs list | grep -qx "'ci' has claimed 'box' for 'smoke' expires in: 'never'"
33
+ dibs release --mine
34
+
35
+ # Installing again, as an upgrade does, must replace the running process.
36
+ before=$(systemctl show -p MainPID --value dibs.service)
37
+ sudo /usr/local/bin/dibs server install --port 8090
38
+ after=$(systemctl show -p MainPID --value dibs.service)
39
+ [ "$before" != "$after" ] || { echo "install did not restart the running service" >&2; exit 1; }
40
+ for _ in $(seq 1 30); do curl -fsS "$DIBS_URL/healthz" >/dev/null 2>&1 && break; sleep 1; done
41
+ dibs list | grep -qx "'box' is free"
42
+ echo "service smoke test passed"
@@ -12,4 +12,4 @@ everyone who is on a machine, and it never stops anyone from logging
12
12
  in.
13
13
  """
14
14
 
15
- __version__ = "0.1.0"
15
+ __version__ = "0.1.1"
@@ -41,6 +41,7 @@ EXIT_ERROR = 2
41
41
 
42
42
  DEFAULT_PORT = 8090
43
43
  UNIT_NAME = "dibs.service"
44
+ SERVICE_USER = "dibs"
44
45
 
45
46
  _DURATION_RE = re.compile(r"^(?:(\d+)d)?(?:(\d+)h)?(?:(\d+)m)?(?:(\d+)s)?$")
46
47
  _DEFAULT_KEYS = ("id_ed25519.pub", "id_ecdsa.pub", "id_rsa.pub")
@@ -405,10 +406,12 @@ ConfigurationDirectory=dibs
405
406
  Restart=on-failure"""
406
407
  target = "default.target"
407
408
  else:
408
- # DynamicUser gives the service a throwaway system user that owns
409
- # only its StateDirectory, so it needs no account set up.
409
+ # A static user rather than DynamicUser=: ssh-keygen looks its own
410
+ # user up, and a dynamic user resolves only where nss-systemd is set
411
+ # up, which plain Debian does not do.
410
412
  service = f"""ExecStart={serve}
411
- DynamicUser=yes
413
+ User={SERVICE_USER}
414
+ Group={SERVICE_USER}
412
415
  StateDirectory=dibs
413
416
  ConfigurationDirectory=dibs
414
417
  Restart=on-failure
@@ -444,6 +447,25 @@ def _systemctl(user: bool, *argv: str) -> None:
444
447
  raise CliError(f"{' '.join(cmd)} failed")
445
448
 
446
449
 
450
+ def ensure_service_user() -> None:
451
+ """Create the ``dibs`` system user the system service runs as, once."""
452
+ if subprocess.run(["getent", "passwd", SERVICE_USER], capture_output=True).returncode == 0:
453
+ return
454
+ cmd = [
455
+ "useradd",
456
+ "--system",
457
+ "--user-group",
458
+ "--home-dir",
459
+ "/var/lib/dibs",
460
+ "--no-create-home",
461
+ "--shell",
462
+ "/usr/sbin/nologin",
463
+ SERVICE_USER,
464
+ ]
465
+ if subprocess.run(cmd).returncode != 0:
466
+ raise CliError(f"{' '.join(cmd)} failed; run dibs server install as root")
467
+
468
+
447
469
  def cmd_server_install(args: argparse.Namespace) -> None:
448
470
  exe = _exe()
449
471
  if not args.user and str(exe).startswith(str(Path.home())):
@@ -455,11 +477,16 @@ def cmd_server_install(args: argparse.Namespace) -> None:
455
477
  if args.print:
456
478
  sys.stdout.write(text)
457
479
  return
480
+ if not args.user:
481
+ ensure_service_user()
458
482
  path = unit_path(args.user)
459
483
  path.parent.mkdir(parents=True, exist_ok=True)
460
484
  path.write_text(text)
461
485
  _systemctl(args.user, "daemon-reload")
462
- _systemctl(args.user, "enable", "--now", UNIT_NAME)
486
+ _systemctl(args.user, "enable", UNIT_NAME)
487
+ # Restart rather than start, so installing over a running service, as
488
+ # an upgrade does, replaces the process with the new code and unit.
489
+ _systemctl(args.user, "restart", UNIT_NAME)
463
490
  print(f"Installed {path} and started it; dibs listens on port {args.port}.")
464
491
  if args.user:
465
492
  print("To keep it running while you are logged out: loginctl enable-linger")
@@ -128,7 +128,7 @@ def test_system_unit_runs_this_dibs_sandboxed() -> None:
128
128
  text = cli.unit_text(Path("/usr/local/bin/dibs"), user=False, host="0.0.0.0", port=8090)
129
129
  assert "ExecStart=/usr/local/bin/dibs serve --host 0.0.0.0 --port 8090\n" in text
130
130
  for line in (
131
- "DynamicUser=yes",
131
+ "User=dibs",
132
132
  "StateDirectory=dibs",
133
133
  "ProtectHome=yes",
134
134
  "WantedBy=multi-user.target",
@@ -155,7 +155,8 @@ def test_server_install_writes_enables_and_controls(
155
155
  assert "--port 9000 --user" in unit.read_text()
156
156
  assert calls == [
157
157
  ["systemctl", "--user", "daemon-reload"],
158
- ["systemctl", "--user", "enable", "--now", "dibs.service"],
158
+ ["systemctl", "--user", "enable", "dibs.service"],
159
+ ["systemctl", "--user", "restart", "dibs.service"],
159
160
  ]
160
161
  calls.clear()
161
162
  cli.main(["server", "stop", "--user"])
@@ -175,6 +176,29 @@ def test_server_install_writes_enables_and_controls(
175
176
  ]
176
177
 
177
178
 
179
+ def test_system_unit_avoids_dynamic_user() -> None:
180
+ """ssh-keygen needs its user to resolve, and a DynamicUser= user does
181
+ not resolve without nss-systemd (plain Debian has none)."""
182
+ text = cli.unit_text(Path("/usr/local/bin/dibs"), user=False, host="0.0.0.0", port=8090)
183
+ assert "DynamicUser" not in text and "User=dibs\nGroup=dibs\n" in text
184
+
185
+
186
+ @pytest.mark.parametrize(("exists", "want_useradd"), [(True, False), (False, True)])
187
+ def test_service_user_is_created_once(
188
+ monkeypatch: pytest.MonkeyPatch, exists: bool, want_useradd: bool
189
+ ) -> None:
190
+ calls: list[list[str]] = []
191
+
192
+ def fake_run(cmd: list[str], *a: Any, **kw: Any) -> subprocess.CompletedProcess[str]:
193
+ calls.append(cmd)
194
+ return subprocess.CompletedProcess(cmd, 0 if exists or cmd[0] != "getent" else 2)
195
+
196
+ monkeypatch.setattr(cli.subprocess, "run", fake_run)
197
+ cli.ensure_service_user()
198
+ assert calls[0] == ["getent", "passwd", "dibs"]
199
+ assert any(c[0] == "useradd" and c[-1] == "dibs" for c in calls) == want_useradd
200
+
201
+
178
202
  def test_system_install_refuses_a_dibs_in_home(monkeypatch: pytest.MonkeyPatch, capsys) -> None:
179
203
  monkeypatch.setattr(cli, "_exe", lambda: Path.home() / ".local" / "bin" / "dibs")
180
204
  assert cli.main(["server", "install"]) == cli.EXIT_ERROR
File without changes
File without changes