pixie-lab-dibs 0.1.0__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.
@@ -0,0 +1,46 @@
1
+ # SPDX-FileCopyrightText: Simon A. F. Lund <os@safl.dk>
2
+ #
3
+ # SPDX-License-Identifier: BSD-3-Clause
4
+
5
+ name: format
6
+
7
+ # Only the default branch on push. Every other branch is covered by the pull
8
+ # request event, and running both would check each commit twice.
9
+ on:
10
+ push:
11
+ branches: [main]
12
+ pull_request:
13
+
14
+ permissions:
15
+ contents: read
16
+
17
+ jobs:
18
+ format:
19
+ runs-on: ubuntu-latest
20
+ steps:
21
+ - uses: actions/checkout@v7
22
+
23
+ - uses: actions/setup-python@v7
24
+ with:
25
+ python-version: "3.x"
26
+
27
+ - name: Cache pre-commit environments
28
+ uses: actions/cache@v6
29
+ with:
30
+ path: ~/.cache/pre-commit
31
+ key: pre-commit-${{ hashFiles('.pre-commit-config.yaml') }}
32
+
33
+ - name: Install pre-commit
34
+ run: pip install pre-commit
35
+
36
+ - name: make format
37
+ run: make format
38
+
39
+ # Most hooks fail when they rewrite a file, so the step above catches
40
+ # them. This catches the ones that would fix something and exit zero.
41
+ - name: Verify nothing changed
42
+ run: git diff --exit-code
43
+
44
+ - name: Show what changed
45
+ if: failure()
46
+ run: git --no-pager diff
@@ -0,0 +1,62 @@
1
+ # SPDX-FileCopyrightText: Simon A. F. Lund <os@safl.dk>
2
+ #
3
+ # SPDX-License-Identifier: BSD-3-Clause
4
+
5
+ name: publish
6
+
7
+ # A version tag publishes that version to PyPI, as pixie-lab-dibs. The upload uses
8
+ # PyPI's trusted publishing, so no token is stored here: PyPI accepts it
9
+ # because it comes from this workflow in this repository.
10
+ on:
11
+ push:
12
+ tags: ["v*"]
13
+
14
+ permissions:
15
+ contents: read
16
+
17
+ jobs:
18
+ test:
19
+ strategy:
20
+ fail-fast: false
21
+ matrix:
22
+ python: ["3.11", "3.13"]
23
+ runs-on: ubuntu-latest
24
+ steps:
25
+ - uses: actions/checkout@v7
26
+
27
+ - uses: astral-sh/setup-uv@v7.2.1
28
+ with:
29
+ python-version: ${{ matrix.python }}
30
+
31
+ - name: make format
32
+ run: make format PRECOMMIT="uvx pre-commit"
33
+
34
+ - name: make check
35
+ run: make check
36
+
37
+ publish:
38
+ needs: test
39
+ runs-on: ubuntu-latest
40
+ permissions:
41
+ id-token: write
42
+ steps:
43
+ - uses: actions/checkout@v7
44
+
45
+ - uses: astral-sh/setup-uv@v7.2.1
46
+ with:
47
+ python-version: "3.13"
48
+
49
+ # Without this, a tag that does not match __version__ would publish
50
+ # the version in the code under a release named after the tag.
51
+ - name: Check the tag matches the version
52
+ run: |
53
+ version=$(sed -n 's/^__version__ = "\(.*\)"/\1/p' src/dibs/__init__.py)
54
+ if [ "v$version" != "$GITHUB_REF_NAME" ]; then
55
+ echo "tag $GITHUB_REF_NAME does not match __version__ $version" >&2
56
+ exit 1
57
+ fi
58
+
59
+ - name: make build
60
+ run: make build
61
+
62
+ - uses: pypa/gh-action-pypi-publish@release/v1
@@ -0,0 +1,42 @@
1
+ # SPDX-FileCopyrightText: Simon A. F. Lund <os@safl.dk>
2
+ #
3
+ # SPDX-License-Identifier: BSD-3-Clause
4
+
5
+ name: test
6
+
7
+ # Only the default branch on push. Every other branch is covered by the pull
8
+ # request event, and running both would check each commit twice.
9
+ on:
10
+ push:
11
+ branches: [main]
12
+ pull_request:
13
+
14
+ permissions:
15
+ contents: read
16
+
17
+ jobs:
18
+ test:
19
+ strategy:
20
+ fail-fast: false
21
+ matrix:
22
+ # The oldest and newest Python dibs supports.
23
+ python: ["3.11", "3.13"]
24
+ runs-on: ubuntu-latest
25
+ steps:
26
+ - uses: actions/checkout@v7
27
+
28
+ - uses: astral-sh/setup-uv@v7.2.1
29
+ with:
30
+ python-version: ${{ matrix.python }}
31
+
32
+ # The login tests sign and verify with ssh-keygen, which the runner
33
+ # image ships; they skip rather than pass if it is ever missing, so
34
+ # fail here instead.
35
+ - name: Check ssh-keygen is present
36
+ run: ssh-keygen -Y 2>&1 | grep -q sign
37
+
38
+ - name: make check
39
+ run: make check
40
+
41
+ - name: make build
42
+ run: make build
@@ -0,0 +1,13 @@
1
+ # SPDX-FileCopyrightText: Simon A. F. Lund <os@safl.dk>
2
+ #
3
+ # SPDX-License-Identifier: BSD-3-Clause
4
+
5
+ __pycache__/
6
+ *.pyc
7
+ *.egg-info/
8
+ .venv/
9
+ dist/
10
+ build/
11
+ .pytest_cache/
12
+ .mypy_cache/
13
+ .ruff_cache/
@@ -0,0 +1,53 @@
1
+ # SPDX-FileCopyrightText: Simon A. F. Lund <os@safl.dk>
2
+ #
3
+ # SPDX-License-Identifier: BSD-3-Clause
4
+
5
+ # The license texts under LICENSES/ are kept away from the hooks that rewrite
6
+ # files. Editing a license to suit a formatter is the one thing that must never
7
+ # happen to one, whatever the formatter thinks of its whitespace.
8
+ exclude: ^LICENSES/
9
+
10
+ repos:
11
+ - repo: https://github.com/pre-commit/pre-commit-hooks
12
+ rev: v6.0.0
13
+ hooks:
14
+ - id: trailing-whitespace
15
+ - id: end-of-file-fixer
16
+ - id: mixed-line-ending
17
+ - id: check-merge-conflict
18
+ - id: check-yaml
19
+ - id: check-added-large-files
20
+
21
+ - repo: https://github.com/astral-sh/ruff-pre-commit
22
+ rev: v0.16.2
23
+ hooks:
24
+ - id: ruff-check
25
+ args: [--fix]
26
+ - id: ruff-format
27
+
28
+ - repo: https://github.com/hukkin/mdformat
29
+ rev: 1.0.0
30
+ hooks:
31
+ - id: mdformat
32
+ # Pinned because `pre-commit autoupdate` does not touch these, so an
33
+ # unpinned release would change formatting on an unrelated commit and
34
+ # fail CI there.
35
+ additional_dependencies:
36
+ - mdformat-gfm==1.0.0
37
+
38
+ - repo: https://github.com/fsfe/reuse-tool
39
+ rev: v6.2.0
40
+ hooks:
41
+ - id: reuse
42
+
43
+ - repo: local
44
+ hooks:
45
+ # Alternation, not a character class: pygrep matches on bytes, so a class
46
+ # of multi-byte characters becomes a class of their individual bytes and
47
+ # then flags unrelated letters. "æ" shares a byte with "…".
48
+ - id: ascii-punctuation
49
+ name: ASCII punctuation only
50
+ language: pygrep
51
+ entry: '(—|–|‘|’|“|”|…|→|←|•)'
52
+ types: [text]
53
+ exclude: ^\.pre-commit-config\.yaml$
@@ -0,0 +1,23 @@
1
+ <!--
2
+ SPDX-FileCopyrightText: Simon A. F. Lund <os@safl.dk>
3
+
4
+ SPDX-License-Identifier: BSD-3-Clause
5
+ -->
6
+
7
+ # Changelog
8
+
9
+ Follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
10
+
11
+ ## [Unreleased]
12
+
13
+ ## [0.1.0] - 2026-10-27
14
+
15
+ ### Added
16
+
17
+ - The first version: things to claim, fed from a config file and from
18
+ pixie servers; claims that are all or nothing, never expire unless
19
+ asked to, and say when an agent made them; users who register an SSH
20
+ public key, are approved by the admin, and log in by signing a
21
+ one-time challenge; a login hook that shows a machine's claim; and
22
+ one `dibs` command for the server, its systemd service, the admin, and
23
+ everyone claiming.
@@ -0,0 +1,28 @@
1
+ BSD 3-Clause License
2
+
3
+ Copyright (c) 2026, Simon A. F. Lund
4
+
5
+ Redistribution and use in source and binary forms, with or without
6
+ modification, are permitted provided that the following conditions are met:
7
+
8
+ 1. Redistributions of source code must retain the above copyright notice, this
9
+ list of conditions and the following disclaimer.
10
+
11
+ 2. Redistributions in binary form must reproduce the above copyright notice,
12
+ this list of conditions and the following disclaimer in the documentation
13
+ and/or other materials provided with the distribution.
14
+
15
+ 3. Neither the name of the copyright holder nor the names of its
16
+ contributors may be used to endorse or promote products derived from
17
+ this software without specific prior written permission.
18
+
19
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
20
+ AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
21
+ IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
22
+ DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
23
+ FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
24
+ DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
25
+ SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
26
+ CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
27
+ OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
28
+ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
@@ -0,0 +1,11 @@
1
+ Copyright (c) Simon A. F. Lund <os@safl.dk>
2
+
3
+ Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
4
+
5
+ 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
6
+
7
+ 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
8
+
9
+ 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
10
+
11
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
@@ -0,0 +1,37 @@
1
+ # SPDX-FileCopyrightText: Simon A. F. Lund <os@safl.dk>
2
+ #
3
+ # SPDX-License-Identifier: BSD-3-Clause
4
+ #
5
+ # Common tasks, and the home of the CI logic: the workflows call these
6
+ # targets, so everything CI does can be run locally. Run "make" for the list.
7
+
8
+ UV ?= uv
9
+ PRECOMMIT ?= pre-commit
10
+
11
+ .DEFAULT_GOAL := help
12
+ .PHONY: help format lint typecheck test build check
13
+
14
+ help: ## Show this help
15
+ @grep -hE '^[a-z-]+:.*?## ' $(MAKEFILE_LIST) \
16
+ | awk 'BEGIN{FS=":.*?## "}{printf " %-10s %s\n", $$1, $$2}'
17
+
18
+ format: ## Run every pre-commit hook: ruff, mdformat, reuse, ASCII punctuation
19
+ $(PRECOMMIT) run --all-files
20
+
21
+ lint: ## ruff check
22
+ $(UV) run ruff check .
23
+
24
+ typecheck: ## mypy --strict over src
25
+ $(UV) run mypy
26
+
27
+ test: ## pytest
28
+ $(UV) run pytest -q
29
+
30
+ build: ## Build the sdist and wheel, and check every command the wheel installs runs
31
+ rm -rf dist
32
+ $(UV) build
33
+ for cmd in $$(unzip -p dist/*.whl '*/entry_points.txt' | sed -n 's/ *=.*//p' | grep -v '^\['); do \
34
+ $(UV) tool run --from "$$(ls dist/*.whl)" "$$cmd" --help >/dev/null || exit 1; \
35
+ done
36
+
37
+ check: lint typecheck test ## Everything but format
@@ -0,0 +1,253 @@
1
+ Metadata-Version: 2.5
2
+ Name: pixie-lab-dibs
3
+ Version: 0.1.0
4
+ Summary: Call dibs on shared lab machines: see who is using what, claim it, release it
5
+ Project-URL: Homepage, https://github.com/safl/pixie-lab-dibs
6
+ Author-email: "Simon A. F. Lund" <os@safl.dk>
7
+ License: BSD-3-Clause
8
+ License-File: LICENSE
9
+ Keywords: claims,coordination,lab,pixie,reservation
10
+ Classifier: Development Status :: 3 - Alpha
11
+ Classifier: Environment :: Console
12
+ Classifier: Intended Audience :: System Administrators
13
+ Classifier: License :: OSI Approved :: BSD License
14
+ Classifier: Programming Language :: Python :: 3
15
+ Requires-Python: >=3.11
16
+ Requires-Dist: fastapi>=0.115
17
+ Requires-Dist: httpx>=0.27
18
+ Requires-Dist: pyyaml>=6
19
+ Requires-Dist: tomlkit>=0.12
20
+ Requires-Dist: uvicorn[standard]>=0.30
21
+ Description-Content-Type: text/markdown
22
+
23
+ <!--
24
+ SPDX-FileCopyrightText: Simon A. F. Lund <os@safl.dk>
25
+
26
+ SPDX-License-Identifier: BSD-3-Clause
27
+ -->
28
+
29
+ # dibs
30
+
31
+ [![test](https://github.com/safl/pixie-lab-dibs/actions/workflows/test.yml/badge.svg)](https://github.com/safl/pixie-lab-dibs/actions/workflows/test.yml)
32
+ [![format](https://github.com/safl/pixie-lab-dibs/actions/workflows/format.yml/badge.svg)](https://github.com/safl/pixie-lab-dibs/actions/workflows/format.yml)
33
+ [![PyPI](https://img.shields.io/pypi/v/pixie-lab-dibs)](https://pypi.org/project/pixie-lab-dibs/)
34
+ [![Python](https://img.shields.io/pypi/pyversions/pixie-lab-dibs)](https://pypi.org/project/pixie-lab-dibs/)
35
+ [![License: BSD-3-Clause](https://img.shields.io/badge/license-BSD--3--Clause-blue)](LICENSE)
36
+ [![REUSE status](https://api.reuse.software/badge/github.com/safl/pixie-lab-dibs)](https://api.reuse.software/info/github.com/safl/pixie-lab-dibs)
37
+
38
+ Call dibs on shared lab machines.
39
+
40
+ A lab machine is shared with people, ai-agents and automation-runners.
41
+ dibs lets each of them see who is on a machine before touching it, by
42
+ claiming it. A claim is a note, not a lock: SSH and the console stay
43
+ open to everyone. Everywhere a claim shows up, it reads as one sentence:
44
+
45
+ ```
46
+ 'alice' has claimed 'node-01' for 'xnvme' expires in: 'never'
47
+ ```
48
+
49
+ dibs keeps a list of **things** to claim, mostly machines. It fills the
50
+ list from **sources**: a config file, and any number of
51
+ [pixie](https://github.com/safl/pixie) servers, whose machines show up
52
+ on their own, with their hardware inventory.
53
+
54
+ ## Install
55
+
56
+ ```
57
+ uv tool install pixie-lab-dibs
58
+ ```
59
+
60
+ That gives you the `dibs` command, for both the server and the client.
61
+ Signing in uses `ssh-keygen` from OpenSSH 8.1 or newer.
62
+
63
+ ## Run the server
64
+
65
+ On the host that will run it, list the things in `/etc/dibs/dibs.toml`:
66
+
67
+ ```toml
68
+ [[pixie]]
69
+ url = "http://pixie:8080" # every machine pixie knows becomes a thing
70
+
71
+ [[thing]]
72
+ name = "ryzen-01" # a machine pixie does not boot
73
+ mac = "aa:bb:cc:dd:ee:ff" # any other key is kept as an attribute
74
+ ```
75
+
76
+ Then make it a service and start it:
77
+
78
+ ```
79
+ sudo dibs server install
80
+ ```
81
+
82
+ The command writes a systemd unit for the `dibs` you ran, enables it
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,
86
+ `dibs serve` runs the server in the foreground.
87
+
88
+ The system service can only run a `dibs` installed outside your home
89
+ directory, for example with
90
+ `sudo UV_TOOL_DIR=/opt/uv UV_TOOL_BIN_DIR=/usr/local/bin uv tool install pixie-lab-dibs`.
91
+
92
+ ## Sign in
93
+
94
+ Claiming needs a user. Register a name with your SSH public key:
95
+
96
+ ```
97
+ export DIBS_URL=http://dibs:8090
98
+ dibs register alice
99
+ ```
100
+
101
+ The admin approves it on the server host:
102
+
103
+ ```
104
+ sudo dibs admin approve alice
105
+ ```
106
+
107
+ Then you log in once. `dibs` signs a one-time challenge with your key,
108
+ through your ssh-agent if the key lives there, and keeps the token it
109
+ gets back:
110
+
111
+ ```
112
+ dibs login
113
+ ```
114
+
115
+ The token does not expire; `dibs logout` drops it, and
116
+ `dibs admin revoke alice` ends every login alice has. `-i` picks a key
117
+ other than `~/.ssh/id_ed25519.pub`.
118
+
119
+ Over plain HTTP, a token can be read off the network. dibs gives claims
120
+ an identity among people who share a lab in good faith; it is not
121
+ protection against someone on that network. Put HTTPS in front of it if
122
+ the network is not trusted.
123
+
124
+ ## Claim
125
+
126
+ ```
127
+ dibs list [--mine]
128
+ dibs info <thing>
129
+ dibs claim <thing>... --for <purpose> [--expires 2h] [--force]
130
+ dibs release <thing>... | --mine
131
+ ```
132
+
133
+ A claim never expires unless given `--expires`, and an expired claim
134
+ counts as free. Claiming several things is all or nothing: if any of
135
+ them is claimed by someone else, you get none, and the refusal says who
136
+ holds it. `--force` takes or releases someone else's claim, and the
137
+ server's log records that it did.
138
+
139
+ `list` and `info` print for people. Add `--json`, `--yaml` or `--toml`
140
+ to get the answer as data instead; an agent or script should use one of
141
+ those rather than parse the text.
142
+
143
+ The exit status is 0 when done, 1 when refused because someone else
144
+ holds a thing, and 2 for anything else.
145
+
146
+ ### AI-agents
147
+
148
+ Point your agent at this section. An agent claims as its human, and
149
+ names itself the way an `Assisted-by:` trailer does:
150
+
151
+ ```
152
+ export DIBS_ASSISTED_BY="Claude Code:claude-opus-5-5"
153
+ ```
154
+
155
+ The claim then reads `'alice' assisted by 'Claude Code:claude-opus-5-5' has claimed ...`.
156
+
157
+ If you are an agent reading this: before you run anything on a lab
158
+ machine, run `dibs list` and claim the machine with
159
+ `dibs claim <machine> --for <purpose>`. Never use a machine someone else
160
+ has claimed, and never pass `--force`; ask your human instead. Release
161
+ what you claimed with `dibs release --mine` when the work is done, and
162
+ also when it fails or is abandoned. That releases only what you claimed,
163
+ not your human's own claims.
164
+
165
+ ### Automation-runners
166
+
167
+ Give the runner a user of its own: register it with a key made for it,
168
+ approve it, log in once, and store the token as the runner's
169
+ `DIBS_TOKEN` secret. A refused claim exits 1, which a runner can wait
170
+ out; anything else exits 2 and should fail the job. In GitHub Actions:
171
+
172
+ ```yaml
173
+ env:
174
+ DIBS_URL: http://dibs:8090
175
+ DIBS_TOKEN: ${{ secrets.DIBS_TOKEN }}
176
+ steps:
177
+ - name: Claim the machine
178
+ run: |
179
+ until dibs claim node-01 --for "${{ github.workflow }}" --expires 3h; do
180
+ [ $? -eq 1 ] || exit 1
181
+ sleep 60
182
+ done
183
+ - name: Test
184
+ run: ...
185
+ - name: Release the machine
186
+ if: always()
187
+ run: dibs release --mine
188
+ ```
189
+
190
+ The `--expires` is a safety net for a runner that dies before its
191
+ release step.
192
+
193
+ ## See the claim when you log in
194
+
195
+ Install the login hook on each machine. At every interactive login it
196
+ asks dibs for the machine's claim and prints it. It stays silent when
197
+ dibs is unreachable, and does nothing for non-interactive SSH such as
198
+ `scp`. It finds the machine by its MAC addresses, or else its hostname.
199
+
200
+ ```
201
+ curl -fsS http://dibs:8090/login.sh | sudo tee /etc/profile.d/dibs.sh
202
+ ```
203
+
204
+ ## Shell completion
205
+
206
+ ```
207
+ eval "$(dibs completion bash)"
208
+ ```
209
+
210
+ Add that line to `~/.bashrc`, or write the script to
211
+ `/etc/bash_completion.d/dibs`. It completes commands and options, and
212
+ thing names from the server.
213
+
214
+ ## HTTP
215
+
216
+ Anything that can send JSON can use dibs without the client. Reads need
217
+ no login; claim and release take `Authorization: Bearer <token>`.
218
+
219
+ | Route | |
220
+ | -------------------------- | -------------------------------------------------------------------------------------------- |
221
+ | `GET /things` | every thing, with its `sentence` and, while claimed, its `claim` |
222
+ | `GET /things/<name>` | one thing, plus `pixie` when a pixie server knows it |
223
+ | `GET /things/<name>/claim` | the sentence as one line of text |
224
+ | `POST /claims` | `{"things": [...], "purpose": "...", "assisted_by": "", "expires_in": null, "force": false}` |
225
+ | `POST /claims/release` | `{"things": [...], "force": false}`, or `{"mine": true, "assisted_by": ""}` |
226
+ | `POST /auth/register` | `{"user": "...", "pubkey": "ssh-ed25519 ..."}` |
227
+ | `POST /auth/challenge` | `{"user": "..."}` gives a nonce and the message to sign |
228
+ | `POST /auth/login` | `{"user", "nonce", "signature"}` gives a token |
229
+ | `GET /login.sh` | the login hook |
230
+
231
+ A claim or release blocked by someone else's claim answers 409 with
232
+ the blocking claims' sentences in `detail.claimed`.
233
+
234
+ ## Releasing
235
+
236
+ Set the version in `src/dibs/__init__.py`, move the changelog's
237
+ `Unreleased` entries under that version, commit, and push a tag named
238
+ after it:
239
+
240
+ ```
241
+ git tag v0.1.0 && git push origin v0.1.0
242
+ ```
243
+
244
+ The tag runs the publish workflow, which checks the tag matches the
245
+ version, runs the checks, and uploads `pixie-lab-dibs` to PyPI through trusted
246
+ publishing.
247
+
248
+ ## License
249
+
250
+ BSD-3-Clause. The repository follows [REUSE](https://reuse.software):
251
+ every file states its copyright and license, `LICENSES/` holds the
252
+ license text, and `REUSE.toml` covers the files that cannot carry a
253
+ header.