udevmon 0.8.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.
- udevmon-0.8.1/CHANGELOG.md +430 -0
- udevmon-0.8.1/CLAUDE.md +32 -0
- udevmon-0.8.1/DEVELOPMENT.md +156 -0
- udevmon-0.8.1/LICENSE +674 -0
- udevmon-0.8.1/MANIFEST.in +9 -0
- udevmon-0.8.1/PKG-INFO +1575 -0
- udevmon-0.8.1/README.md +1538 -0
- udevmon-0.8.1/SPECIFICATIONS.md +1176 -0
- udevmon-0.8.1/contrib/rclone-config/LICENSE +674 -0
- udevmon-0.8.1/contrib/rclone-config/README.md +147 -0
- udevmon-0.8.1/contrib/rclone-config/pyproject.toml +25 -0
- udevmon-0.8.1/contrib/rclone-config/rclone_config.py +402 -0
- udevmon-0.8.1/contrib/rclone-config/udevmon.toml.example +24 -0
- udevmon-0.8.1/docs/_ext/cli_help.py +45 -0
- udevmon-0.8.1/docs/_ext/module_reference.py +49 -0
- udevmon-0.8.1/docs/_static/layout.css +59 -0
- udevmon-0.8.1/docs/api.rst +29 -0
- udevmon-0.8.1/docs/cli.rst +8 -0
- udevmon-0.8.1/docs/conf.py +31 -0
- udevmon-0.8.1/docs/contributing.rst +51 -0
- udevmon-0.8.1/docs/getting-started.rst +34 -0
- udevmon-0.8.1/docs/index.rst +23 -0
- udevmon-0.8.1/docs/integration.rst +85 -0
- udevmon-0.8.1/docs/internals.rst +60 -0
- udevmon-0.8.1/docs/modules.rst +8 -0
- udevmon-0.8.1/docs/requirements.txt +3 -0
- udevmon-0.8.1/man/udevmon.1 +958 -0
- udevmon-0.8.1/man/udevmon.toml.5 +1217 -0
- udevmon-0.8.1/pyproject.toml +48 -0
- udevmon-0.8.1/setup.cfg +4 -0
- udevmon-0.8.1/setup.py +34 -0
- udevmon-0.8.1/src/udevmon/__init__.py +18 -0
- udevmon-0.8.1/src/udevmon/__main__.py +7 -0
- udevmon-0.8.1/src/udevmon/_scm.py +108 -0
- udevmon-0.8.1/src/udevmon/actions.py +306 -0
- udevmon-0.8.1/src/udevmon/cli.py +1044 -0
- udevmon-0.8.1/src/udevmon/completion.py +327 -0
- udevmon-0.8.1/src/udevmon/config.py +787 -0
- udevmon-0.8.1/src/udevmon/configedit.py +893 -0
- udevmon-0.8.1/src/udevmon/control.py +179 -0
- udevmon-0.8.1/src/udevmon/daemon.py +260 -0
- udevmon-0.8.1/src/udevmon/engine.py +238 -0
- udevmon-0.8.1/src/udevmon/events.py +113 -0
- udevmon-0.8.1/src/udevmon/explain.py +112 -0
- udevmon-0.8.1/src/udevmon/matching.py +201 -0
- udevmon-0.8.1/src/udevmon/notify.py +66 -0
- udevmon-0.8.1/src/udevmon/pause.py +88 -0
- udevmon-0.8.1/src/udevmon/runtime.py +30 -0
- udevmon-0.8.1/src/udevmon/scan.py +373 -0
- udevmon-0.8.1/src/udevmon/service.py +91 -0
- udevmon-0.8.1/src/udevmon/session.py +205 -0
- udevmon-0.8.1/src/udevmon/sources/__init__.py +3 -0
- udevmon-0.8.1/src/udevmon/sources/udev.py +179 -0
- udevmon-0.8.1/src/udevmon/sources/udisks.py +302 -0
- udevmon-0.8.1/src/udevmon/state.py +51 -0
- udevmon-0.8.1/src/udevmon.egg-info/PKG-INFO +1575 -0
- udevmon-0.8.1/src/udevmon.egg-info/SOURCES.txt +107 -0
- udevmon-0.8.1/src/udevmon.egg-info/dependency_links.txt +1 -0
- udevmon-0.8.1/src/udevmon.egg-info/entry_points.txt +2 -0
- udevmon-0.8.1/src/udevmon.egg-info/requires.txt +9 -0
- udevmon-0.8.1/src/udevmon.egg-info/top_level.txt +1 -0
- udevmon-0.8.1/tests/__init__.py +38 -0
- udevmon-0.8.1/tests/test_all_props.py +86 -0
- udevmon-0.8.1/tests/test_cli.py +58 -0
- udevmon-0.8.1/tests/test_completion.py +444 -0
- udevmon-0.8.1/tests/test_config.py +154 -0
- udevmon-0.8.1/tests/test_configedit_layout.py +192 -0
- udevmon-0.8.1/tests/test_configedit_tomlkit.py +241 -0
- udevmon-0.8.1/tests/test_contrib_rclone_config.py +417 -0
- udevmon-0.8.1/tests/test_docs.py +37 -0
- udevmon-0.8.1/tests/test_documentation.py +159 -0
- udevmon-0.8.1/tests/test_dropins.py +316 -0
- udevmon-0.8.1/tests/test_edit_more.py +266 -0
- udevmon-0.8.1/tests/test_engine_actions.py +139 -0
- udevmon-0.8.1/tests/test_exclude.py +269 -0
- udevmon-0.8.1/tests/test_explain.py +213 -0
- udevmon-0.8.1/tests/test_from_mountpoint.py +131 -0
- udevmon-0.8.1/tests/test_instance.py +369 -0
- udevmon-0.8.1/tests/test_isolation.py +25 -0
- udevmon-0.8.1/tests/test_license.py +55 -0
- udevmon-0.8.1/tests/test_lock.py +340 -0
- udevmon-0.8.1/tests/test_manpages.py +98 -0
- udevmon-0.8.1/tests/test_matching.py +46 -0
- udevmon-0.8.1/tests/test_no_serial.py +197 -0
- udevmon-0.8.1/tests/test_notify.py +123 -0
- udevmon-0.8.1/tests/test_order.py +159 -0
- udevmon-0.8.1/tests/test_pause.py +254 -0
- udevmon-0.8.1/tests/test_present.py +67 -0
- udevmon-0.8.1/tests/test_raw_placeholders.py +76 -0
- udevmon-0.8.1/tests/test_reload.py +221 -0
- udevmon-0.8.1/tests/test_removed.py +137 -0
- udevmon-0.8.1/tests/test_rename.py +413 -0
- udevmon-0.8.1/tests/test_retarget.py +221 -0
- udevmon-0.8.1/tests/test_retries.py +290 -0
- udevmon-0.8.1/tests/test_runner.py +275 -0
- udevmon-0.8.1/tests/test_scan_wizard.py +337 -0
- udevmon-0.8.1/tests/test_scm.py +161 -0
- udevmon-0.8.1/tests/test_script_dir.py +189 -0
- udevmon-0.8.1/tests/test_service.py +126 -0
- udevmon-0.8.1/tests/test_session.py +191 -0
- udevmon-0.8.1/tests/test_sources.py +105 -0
- udevmon-0.8.1/tests/test_status.py +412 -0
- udevmon-0.8.1/tests/test_tag_all.py +155 -0
- udevmon-0.8.1/tests/test_tags.py +463 -0
- udevmon-0.8.1/tests/test_trigger.py +256 -0
- udevmon-0.8.1/tests/test_vars.py +337 -0
- udevmon-0.8.1/tests/test_wildcards.py +144 -0
- udevmon-0.8.1/tools/docs.py +52 -0
- udevmon-0.8.1/tools/usb-test.py +452 -0
|
@@ -0,0 +1,430 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to udevmon are recorded here. The format follows
|
|
4
|
+
[Keep a Changelog](https://keepachangelog.com/), and the version numbers follow
|
|
5
|
+
[Semantic Versioning](https://semver.org/) (while below 1.0, a new minor
|
|
6
|
+
version may change behaviour).
|
|
7
|
+
|
|
8
|
+
How to keep it: add a line under **Unreleased** in the same commit as any
|
|
9
|
+
change a user would notice (new or changed commands, options, config keys,
|
|
10
|
+
dependencies, behaviour, fixes). When a release is tagged, rename that heading
|
|
11
|
+
to the version and date, and start a new empty **Unreleased** above it.
|
|
12
|
+
|
|
13
|
+
## [Unreleased]
|
|
14
|
+
|
|
15
|
+
## [0.8.1] - 2026-11-11
|
|
16
|
+
|
|
17
|
+
The first public release, on GitHub and PyPI. udevmon behaves as in 0.8.0:
|
|
18
|
+
this release adds the license, the packaging and the HTML manual.
|
|
19
|
+
|
|
20
|
+
### Added
|
|
21
|
+
|
|
22
|
+
- **An HTML reference manual** (Sphinx, optional `docs` extra):
|
|
23
|
+
`python tools/docs.py build` or `serve`. It has the integration contracts,
|
|
24
|
+
which interfaces are provisional and which internal, the command reference
|
|
25
|
+
generated from the parser, and the module reference from the docstrings.
|
|
26
|
+
The core modules gained docstrings for it. The sdist now includes the docs,
|
|
27
|
+
man pages, tests and tools.
|
|
28
|
+
- **Licensed under GPL-3.0-or-later**: `LICENSE`, an SPDX header in every
|
|
29
|
+
Python file (a test checks them), and the license in the package metadata.
|
|
30
|
+
The same for `contrib/rclone-config`.
|
|
31
|
+
- **Ready for PyPI, GitHub and Read the Docs**: project metadata (readme,
|
|
32
|
+
authors, URLs, classifiers), install instructions with `pipx`, CI and a
|
|
33
|
+
release workflow that publishes a `vX.Y.Z` tag to PyPI, and a Read the
|
|
34
|
+
Docs configuration. The README says which interfaces other programs can
|
|
35
|
+
use (the CLI, the config format, what actions receive, `present --json`)
|
|
36
|
+
and that the Python modules are internal.
|
|
37
|
+
|
|
38
|
+
### Fixed
|
|
39
|
+
|
|
40
|
+
- An editable install (`pip install -e .`) failed while writing
|
|
41
|
+
`_version.py`; it now skips that file and asks git, like a source checkout.
|
|
42
|
+
- `tests/test_manpages.py` failed to import on Python 3.10 (`tomllib`).
|
|
43
|
+
|
|
44
|
+
### Changed
|
|
45
|
+
|
|
46
|
+
- The examples and tests use neutral drive labels, serials and host names.
|
|
47
|
+
|
|
48
|
+
## [0.8.0] - 2026-11-10
|
|
49
|
+
|
|
50
|
+
### Changed
|
|
51
|
+
|
|
52
|
+
- **`scan` now confirms before saving, and lets you go back.** Before anything is
|
|
53
|
+
written it shows what would be saved (name, match or rule with its strength, and
|
|
54
|
+
tags for a device) and asks you to confirm; `r`, `m` and `t` redo the name, the
|
|
55
|
+
match/rule or the tags without losing the rest, and `c` (also `n`, `q`) cancels,
|
|
56
|
+
leaving the thing in the list to pick again. A weak or medium match now needs an
|
|
57
|
+
explicit `y` at that screen instead of a separate "Save anyway?" prompt. A name
|
|
58
|
+
already in use is reported there, so you can rename and try again instead of
|
|
59
|
+
losing what you typed.
|
|
60
|
+
- **`scan` can now declare a category instead of a device.** For each thing you
|
|
61
|
+
pick to name, it first asks whether to register it as a **device** (one
|
|
62
|
+
particular, trusted thing) or a **category** (a declared tag with a rule,
|
|
63
|
+
matching every thing like it); a thing with no serial number, such as a YubiKey,
|
|
64
|
+
defaults to category, since no device match can single out one such unit anyway.
|
|
65
|
+
A category has no tags of its own and no strength gate, and the "add an action"
|
|
66
|
+
hint printed afterwards includes `--allow-unnamed`, since a category is unnamed
|
|
67
|
+
by default until it also matches a device you named. `iN` (ignore) is unchanged:
|
|
68
|
+
it is device-only and skips the kind choice.
|
|
69
|
+
|
|
70
|
+
## [0.7.0] - 2026-11-05
|
|
71
|
+
|
|
72
|
+
### Added
|
|
73
|
+
|
|
74
|
+
- **Drop-in files: `config.d/*.toml`.** Read after `config.toml`, in file-name
|
|
75
|
+
order, with the same tables except `[daemon]`. Their devices, tags and
|
|
76
|
+
actions join the main file's, so a program can install its own in a file it
|
|
77
|
+
owns and replaces or deletes whole. With the same `order` (below), actions
|
|
78
|
+
run in the order the files are read, main file first. A name
|
|
79
|
+
defined in two files is an error naming both. `check` lists the files read;
|
|
80
|
+
`list`, `show` and `explain` mark entries from a drop-in. The `add` commands
|
|
81
|
+
take `--file NAME` to write to `config.d/NAME.toml`; the other editing
|
|
82
|
+
commands change an entry where it is defined, and a rename fixes references
|
|
83
|
+
in every file. With only `config.toml` nothing changes.
|
|
84
|
+
- **`udevmon trigger DEVICE [EVENT]`** asks the running daemon to run a present
|
|
85
|
+
device's actions now, as for a real `startup` (default), `shutdown`,
|
|
86
|
+
`connected`, `mounted` or `unmounted`: for a drive named while plugged in, or
|
|
87
|
+
to test an action without unplugging. It follows pause, `disabled` and
|
|
88
|
+
`ignored`, and is logged like a real event. `--wait` waits for the actions
|
|
89
|
+
and shows their exit status. The daemon now listens on
|
|
90
|
+
`$XDG_RUNTIME_DIR/udevmon.sock` for this (only you can connect).
|
|
91
|
+
- **`udevmon devices add NAME --from-mountpoint PATH`** names the plugged-in
|
|
92
|
+
drive mounted at `PATH` without typing its identity: the match is its
|
|
93
|
+
filesystem's `fs_uuid` and its drive's `drive_serial`. It refuses (and writes
|
|
94
|
+
nothing) when nothing is mounted there, the drive has no usable serial number,
|
|
95
|
+
or a device already matches that filesystem.
|
|
96
|
+
- **`notify_on_failure = true` on an action** (`actions add|set
|
|
97
|
+
--notify-on-failure`): a desktop notification when it exits non-zero, times
|
|
98
|
+
out or cannot start, after its last attempt with `retries`. Sent on the
|
|
99
|
+
desktop's D-Bus; without a session bus nothing is shown. Off by default.
|
|
100
|
+
- **`order` on an action** (a whole number, default 50): the actions of one
|
|
101
|
+
event run by `order`, lower first, then in config order. This nests one
|
|
102
|
+
tool's actions around another's across files, which one file order cannot
|
|
103
|
+
(credentials up first on plugging, down last on unplugging).
|
|
104
|
+
`actions add|set --order N` sets it; `actions list` shows the actions in the
|
|
105
|
+
order they run, and `explain` shows a non-default order. Without `order`
|
|
106
|
+
nothing changes.
|
|
107
|
+
- **`udevmon present --json`**: what is plugged in, for scripts, with every
|
|
108
|
+
property, the devices and tags each thing is, and the suggested match.
|
|
109
|
+
|
|
110
|
+
### Changed
|
|
111
|
+
|
|
112
|
+
- **A missing optional `script_dir` folder is a note, not a warning.** When
|
|
113
|
+
another folder of the list exists, one that does not (an example's
|
|
114
|
+
`~/.local/udev/scripts` on a machine without it) is a note in `udevmon check`
|
|
115
|
+
and an ordinary line in the log at start and reload. A path that exists but is
|
|
116
|
+
not a folder, or a list where no folder exists, is still a warning.
|
|
117
|
+
- **`contrib/rclone-config`: the example uses `order`** (10 for `up`, 90 for
|
|
118
|
+
`down`) and goes in `config.d/rclone-config.toml`, so it wraps the actions
|
|
119
|
+
that use the credentials whatever file they are in.
|
|
120
|
+
|
|
121
|
+
## [0.6.0] - 2026-11-03
|
|
122
|
+
|
|
123
|
+
### Added
|
|
124
|
+
|
|
125
|
+
- **`contrib/rclone-config`: rclone credentials carried on a drive.** A
|
|
126
|
+
separate helper package (not part of udevmon) whose `rclone-config`
|
|
127
|
+
command udevmon runs as an action: while a drive tagged `rclone` is present, its
|
|
128
|
+
`config/rclone/rclone.conf` is published to
|
|
129
|
+
`$XDG_RUNTIME_DIR/rclone/rclone.conf` for any rclone user; it is switched to
|
|
130
|
+
another tagged drive's config, or deleted, as soon as that drive is gone or
|
|
131
|
+
udevmon stops. See its README for installing and the two actions.
|
|
132
|
+
|
|
133
|
+
### Fixed
|
|
134
|
+
|
|
135
|
+
- **Graphical programs started by actions work under the systemd service.**
|
|
136
|
+
The service got its environment once, when it started; if that was before the
|
|
137
|
+
desktop gave systemd its `DISPLAY` (or on a desktop that never does), every
|
|
138
|
+
action ran without a display. Now the session's `DISPLAY`, `WAYLAND_DISPLAY`,
|
|
139
|
+
`XAUTHORITY`, `DBUS_SESSION_BUS_ADDRESS` and `XDG_*` desktop variables are
|
|
140
|
+
looked up for each action: from systemd's environment for your user as it is
|
|
141
|
+
now, or else from one of your own running programs that has a display
|
|
142
|
+
(preferring those of your desktop login over, say, an `ssh -X` shell). At
|
|
143
|
+
the very beginning of a login, before the desktop has a display, an action
|
|
144
|
+
waits for it, up to 30 s. The log says which display is used, and warns when
|
|
145
|
+
there is none. Run from a terminal with a display, nothing changes.
|
|
146
|
+
- **Debounce no longer drops events of a different thing, or a real second
|
|
147
|
+
event.** It was keyed on the device name, so two things matching one device
|
|
148
|
+
(two YubiKeys without a serial in the match) within a second counted as one:
|
|
149
|
+
at startup only one got its `startup` action. And a `mounted`, `unmounted`,
|
|
150
|
+
`mounted` within a second lost the second `mounted`. Now a repeat is the same
|
|
151
|
+
kind of event for the same thing with nothing else in between, and a dropped
|
|
152
|
+
repeat is logged at the normal level.
|
|
153
|
+
- **Works with tomlkit 0.15.** Removing the last action left `actions = []` at
|
|
154
|
+
the top of the file (tomlkit 0.15 writes an emptied `[[actions]]` list that
|
|
155
|
+
way), which also broke the file's first line. The key is now removed with its
|
|
156
|
+
last entry.
|
|
157
|
+
|
|
158
|
+
### Dependencies
|
|
159
|
+
|
|
160
|
+
- tomlkit **0.11.7** or newer (was 0.11.4), **except 0.15.0**: 0.11.4 to 0.11.6
|
|
161
|
+
fail two of the tests on variables, and 0.15.0 writes invalid TOML when a
|
|
162
|
+
variable in an inline table is changed (the edit is refused and rolled back;
|
|
163
|
+
fixed in tomlkit 0.15.1). Tested with 0.11.7, 0.12, 0.13, 0.14, 0.15.1 and
|
|
164
|
+
0.15.2.
|
|
165
|
+
|
|
166
|
+
### Documentation
|
|
167
|
+
|
|
168
|
+
- README: installing into a virtual environment without the system packages
|
|
169
|
+
(PyGObject's build needs, and `PyGObject<3.51` on Ubuntu 22.04), and the
|
|
170
|
+
`UDISKS_AUTO=0` udev rule as the clean way to keep a partition from being
|
|
171
|
+
automounted, rather than an action that unmounts it.
|
|
172
|
+
|
|
173
|
+
## [0.5.0] - 2026-09-21
|
|
174
|
+
|
|
175
|
+
### Added
|
|
176
|
+
|
|
177
|
+
- **`instance` and a state folder, to match an unplug with its plug.** Every plug of a
|
|
178
|
+
USB device and every mount of a filesystem has one `instance` value (`{instance}`,
|
|
179
|
+
`$UDEVMON_INSTANCE`), identical in all its events and new for the next plug or mount,
|
|
180
|
+
and a private folder (`{state_dir}`, `$UDEVMON_STATE_DIR`,
|
|
181
|
+
`$XDG_RUNTIME_DIR/udevmon/state/<instance>`) that exists from the first action to the
|
|
182
|
+
end of its last event, so a connect script can leave what it found out (the serial that
|
|
183
|
+
`ykman` reported) for the disconnect script. Wiped when the daemon starts and stops.
|
|
184
|
+
`instance` shows in `--all-props` and cannot be used in tag rules. See "Matching an
|
|
185
|
+
unplug with its plug" in the README.
|
|
186
|
+
- **`retries` and `retry_delay`** on an action (`actions add --retries N --retry-delay S`,
|
|
187
|
+
`actions set` the same): a command that exits with a non-zero status is run again after
|
|
188
|
+
the delay, up to `retries` more times, for things like `udisksctl unmount` right after a
|
|
189
|
+
desktop mounted the drive. Never after a timeout, when the program cannot start, or when
|
|
190
|
+
the action was skipped; each attempt has its own timeout; retrying stops when automation
|
|
191
|
+
is paused or the daemon is stopping; a `lock` is held across the attempts. Not with
|
|
192
|
+
`detach`. `actions list` and `explain` show it.
|
|
193
|
+
- **`lock = "name"`** on an action (`actions add --lock NAME`, `actions set --lock NAME`
|
|
194
|
+
/ `--no-lock`): actions that share a lock name never run at the same time, and run in
|
|
195
|
+
the order their events arrived, so the actions of a plug are never overtaken by those of
|
|
196
|
+
the unplug that follows. Not affected: actions without a lock, other lock names, and the
|
|
197
|
+
file order within one event. Released when the command exits (or, for `detach`, has
|
|
198
|
+
started), also after a crash. `actions list` and `explain` show it, and the log says
|
|
199
|
+
when an action had to wait. See "Running actions one at a time" in the README.
|
|
200
|
+
- **`detach = true`** on an action (`actions add --detach`, `actions set --detach` /
|
|
201
|
+
`--no-detach`): the command is started in its own session and not waited for, for a
|
|
202
|
+
command that is itself a long-running program such as `keepassxc`. No timeout (setting
|
|
203
|
+
one is an error), the actions after it and `shutdown` are not held up, its output is
|
|
204
|
+
logged and its exit status is logged when it ends. `actions list` and `explain` show
|
|
205
|
+
it. See "Programs that keep running" in the README.
|
|
206
|
+
- **`[daemon] script_dir`**, a folder or a list of folders for your own scripts. A
|
|
207
|
+
command given by bare name is looked for in them, in order, and then only in the
|
|
208
|
+
standard system paths (`/usr/local/sbin`, `/usr/local/bin`, `/usr/sbin`, `/usr/bin`,
|
|
209
|
+
`/sbin`, `/bin`), never in the daemon's own `PATH`, so it is found the same way
|
|
210
|
+
under systemd and in a terminal. Resolved at every event (a script added later
|
|
211
|
+
needs no reload), shown in full by `explain`, and checked by `check`. Without it
|
|
212
|
+
nothing changes.
|
|
213
|
+
- **`scan --all-props` and `present --all-props`** print every property of each thing,
|
|
214
|
+
one per line, including the raw `udev_*` ones that the normal output hides, and mark
|
|
215
|
+
those that change on every plug (they cannot be used in tag rules). It shows the
|
|
216
|
+
names to use in a rule or as `{udev_NAME}` in a command.
|
|
217
|
+
|
|
218
|
+
### Fixed
|
|
219
|
+
|
|
220
|
+
- **A script that leaves a program running in the background** (`keepassxc &`, then
|
|
221
|
+
exit) made the action look stuck until its `timeout`, log "timed out" and be
|
|
222
|
+
reported as failed: the program kept the output pipe open and udevmon waited for the
|
|
223
|
+
pipe, not for the script. udevmon now waits for the command to exit; the program
|
|
224
|
+
keeps running, and whatever it writes is still logged. Output is also logged line by
|
|
225
|
+
line as it arrives instead of at the end.
|
|
226
|
+
- **`{udev_NAME}` placeholders** (for example `{udev_id_model}`) in a command made the
|
|
227
|
+
configuration fail to load, although the documentation said raw udev properties can
|
|
228
|
+
be used. They are accepted for every event; a thing that lacks the property makes the
|
|
229
|
+
action be skipped, with an error in the log, as for any unavailable placeholder.
|
|
230
|
+
|
|
231
|
+
## [0.4.1] - 2026-09-21
|
|
232
|
+
|
|
233
|
+
### Changed
|
|
234
|
+
|
|
235
|
+
- **Devices without a serial number are no longer given a suggested match.** `scan`
|
|
236
|
+
used to pre-select `vendor_id`, `product_id` and `port_path`, which ties the rule to
|
|
237
|
+
a USB port (it silently stops matching in another port or behind a hub, and matches
|
|
238
|
+
any unit of the model in that port). It now says nothing identifies the unit and asks
|
|
239
|
+
you to choose between "this model, in this port only" and "this model, in any port",
|
|
240
|
+
or type a list.
|
|
241
|
+
- `present` and the scan list mark such a device `[medium: no serial, matches the
|
|
242
|
+
port]`, and `check` notes any device or tag rule that uses `port_path`.
|
|
243
|
+
|
|
244
|
+
## [0.4.0] - 2026-09-21
|
|
245
|
+
|
|
246
|
+
### Added
|
|
247
|
+
|
|
248
|
+
- **Wildcards in tag rules.** `*` (any run of characters) and `?` (one) in a rule
|
|
249
|
+
value, for example `fs_label = "BACKUP*"`. The whole value must match, ignoring
|
|
250
|
+
capitals; nothing else is special. A rule with a pattern is rated weak. In a
|
|
251
|
+
device's own `match` they stay ordinary characters, and `check` warns.
|
|
252
|
+
- **"All of" for tags.** An action with several tags can require every one of them:
|
|
253
|
+
`tag_mode = "all"` (default `"any"`), `actions add --all-tags`, and
|
|
254
|
+
`actions set --all-tags` / `--any-tags`. `actions list` and `explain` show it.
|
|
255
|
+
- **Opting a device out of a rule tag.** `exclude_tags` on a device, and
|
|
256
|
+
`devices exclude` / `devices include` (also `devices add --exclude-tag`). The
|
|
257
|
+
device stays named and keeps its own actions; explicit tags are never affected.
|
|
258
|
+
`explain`, `scan` and `present` show `excluded from: TAG`, and renaming a tag
|
|
259
|
+
updates `exclude_tags`.
|
|
260
|
+
- **Per-device variables.** `vars = { dest = "..." }` on a device, used as
|
|
261
|
+
`{var_dest}` in commands and as `UDEVMON_VAR_DEST` in the environment, so one
|
|
262
|
+
action can serve several devices differently. `devices add --var NAME=VALUE`,
|
|
263
|
+
`devices set --var NAME=VALUE --unset-var NAME`, listed by `devices show`. A
|
|
264
|
+
variable no device defines is an error; `check` warns about devices that lack
|
|
265
|
+
one, and about `allow_unnamed` actions that use one (unnamed things have none).
|
|
266
|
+
|
|
267
|
+
- **`actions set NAME -- COMMAND...`** replaces an action's command, as `actions add`
|
|
268
|
+
takes it. A placeholder the action's events don't provide is refused, and a
|
|
269
|
+
comment after the old `run` line is kept.
|
|
270
|
+
- **`tags set NAME`** changes a declared tag: `--match` replaces its rule,
|
|
271
|
+
`--no-rule` removes it, `--description` changes the text. Comments and layout
|
|
272
|
+
are kept, and a rule with a property that changes from plug to plug is refused.
|
|
273
|
+
|
|
274
|
+
## [0.3.0] - 2026-09-21
|
|
275
|
+
|
|
276
|
+
### Added
|
|
277
|
+
|
|
278
|
+
- **Device status: `disabled` and `ignored`.** A `status` key on a device (absent
|
|
279
|
+
or empty means active). A disabled device's own actions don't run, but actions
|
|
280
|
+
reached through its tags still do; an ignored device runs nothing at all, and
|
|
281
|
+
ignore wins if a thing matches several devices. Both stay "named", so they never
|
|
282
|
+
fall into the unnamed category. New commands: `devices disable`, `devices ignore`
|
|
283
|
+
and `devices enable`, which take several names and change nothing unless all exist.
|
|
284
|
+
- **Renaming.** `devices rename`, `tags rename` and `actions rename` change a name
|
|
285
|
+
everywhere it is used (headers and sub-tables, every action's `device` or `tag`,
|
|
286
|
+
the `tags` of devices), keep comments, layout and order, and refuse (writing
|
|
287
|
+
nothing) unless the result is exactly the old config with that one name changed.
|
|
288
|
+
They report what was updated, made-up action names that changed, and the comments,
|
|
289
|
+
descriptions and commands that still mention the old name (left alone).
|
|
290
|
+
- **`devices set NAME`** replaces a device's match (`--match`) and/or its
|
|
291
|
+
`--description`, keeping a comment after the old value.
|
|
292
|
+
- **Global pause.** `udevmon pause` stops every action without touching the config,
|
|
293
|
+
`udevmon pause --for 30m` (`90s`, `2h`, `1d`, `1h30m`) for a while, and
|
|
294
|
+
`udevmon resume` lets them run again. Nothing runs while paused, not even
|
|
295
|
+
`startup` and `shutdown` actions, and events during a pause are not replayed. The
|
|
296
|
+
state is a file in the runtime directory, so it survives a daemon restart but
|
|
297
|
+
not a logout or reboot. `explain` and the editing commands say when automation
|
|
298
|
+
is paused, and the daemon's start-up line shows `[PAUSED]`.
|
|
299
|
+
- **Surprise removal.** A mounted filesystem that vanishes without being unmounted
|
|
300
|
+
(a pulled stick) now fires a new **`removed`** event after `unmounted`, so an
|
|
301
|
+
action can warn about it (`on = ["removed"]`). The new `surprise` value (`{surprise}`,
|
|
302
|
+
`$UDEVMON_SURPRISE`) is `1` for a pulled stick and `0` after a clean unmount, on
|
|
303
|
+
`unmounted` and `removed`. Existing `on = ["unmounted"]` actions still fire.
|
|
304
|
+
- **`enabled = false` on actions**, with `actions disable` and `actions enable`.
|
|
305
|
+
- **`actions set NAME`** changes `allow_unnamed` (`--allow-unnamed`, `--named-only`,
|
|
306
|
+
`--default-unnamed` to clear it), the events (`--on`) or the `--timeout` of an
|
|
307
|
+
action without removing and re-adding it.
|
|
308
|
+
- **Retargeting actions:** `actions set NAME` also takes `--add-device`,
|
|
309
|
+
`--remove-device`, `--add-tag` and `--remove-tag` (repeatable) to change which
|
|
310
|
+
devices or tags an action applies to. Comments and layout are kept; removing a
|
|
311
|
+
target the action lacks, or its last target, is refused.
|
|
312
|
+
- **`scan` can ignore things:** answer `i3` (or `i1,3`) instead of a number to save
|
|
313
|
+
them as ignored devices, without asking for tags.
|
|
314
|
+
- `devices list`, `devices show`, `actions list`, `present`, `scan` and `explain`
|
|
315
|
+
mark what is switched off; `explain` says why an action was skipped (disabled
|
|
316
|
+
action, disabled device) and `check` notes it.
|
|
317
|
+
- **Manual pages:** `udevmon(1)` for every command and option, and
|
|
318
|
+
`udevmon.toml(5)` for the configuration file, the events, the properties and
|
|
319
|
+
how commands are run. They are installed by `pip` (under `share/man`) and can
|
|
320
|
+
be read from a checkout with `man -l man/udevmon.1`. A test fails when a
|
|
321
|
+
command, option, config key, event or property is missing from them.
|
|
322
|
+
|
|
323
|
+
### Fixed
|
|
324
|
+
|
|
325
|
+
- **Names containing a dot** (`usb.1`) were accepted but written as a nested header, so
|
|
326
|
+
the file was rejected. They are now written quoted (`[devices."usb.1"]`).
|
|
327
|
+
- **Clearer reasons** when an edit cannot proceed: the message names the entry it could
|
|
328
|
+
not find, and says how to give an unnamed action a name so it can be changed.
|
|
329
|
+
- **Documentation:** the README described property matching as exact; values are
|
|
330
|
+
compared without regard to capitals (`fs_label = "backup"` matches `BACKUP`).
|
|
331
|
+
|
|
332
|
+
### Internal
|
|
333
|
+
|
|
334
|
+
- **`tools/usb-test.py`**, a guided real-hardware test: it asks you to plug a
|
|
335
|
+
stick in, pull it out and plug it in again, and checks events, status, pause,
|
|
336
|
+
the unnamed gate and surprise removal (16 checks) on a throwaway config. The
|
|
337
|
+
`removed` event and `surprise` value were confirmed on a real stick with it.
|
|
338
|
+
- The test suite no longer leaves temporary folders behind: it works in one
|
|
339
|
+
per-run folder that is removed on exit, and a test guards this.
|
|
340
|
+
|
|
341
|
+
## [0.2.0] - 2026-09-20
|
|
342
|
+
|
|
343
|
+
### Added
|
|
344
|
+
|
|
345
|
+
- **Bash tab completion.** `udevmon completion install` (or `completion bash`
|
|
346
|
+
to print the script) completes commands, sub-commands and options, and reads
|
|
347
|
+
your config for live names: devices, tags, actions, event lists and
|
|
348
|
+
`--match` keys. `udevmon completion uninstall` removes it.
|
|
349
|
+
- **`udevmon explain`** shows what would run for everything plugged in now, or
|
|
350
|
+
for a simulated thing (`--device NAME`, `--prop KEY=VALUE`). It applies the
|
|
351
|
+
same rules as the daemon (named or unnamed, tags, `allow_unnamed`), shows
|
|
352
|
+
each command with its values filled in, and says why an action is skipped.
|
|
353
|
+
- **`udevmon service uninstall`** stops and disables the service, removes the
|
|
354
|
+
unit file and reloads systemd. Files that are not udevmon's own are left
|
|
355
|
+
alone.
|
|
356
|
+
- **Automatic reload after edits.** The commands that add, remove, tag or
|
|
357
|
+
untag devices, tags and actions, and `scan` when it saves a device, reload a
|
|
358
|
+
running daemon that uses the same config file. `--no-reload` skips it.
|
|
359
|
+
- **Warning for programs that cannot run.** `check` warns when an action's
|
|
360
|
+
program is not found in `PATH` or is not executable. The daemon logs the same
|
|
361
|
+
warning at start and on every reload, using its own `PATH`.
|
|
362
|
+
|
|
363
|
+
### Changed
|
|
364
|
+
|
|
365
|
+
- **Editing existing entries uses `tomlkit`.** `devices remove|tag|untag`,
|
|
366
|
+
`tags remove` and `actions remove` keep comments and layout however the file
|
|
367
|
+
was written by hand, and keep CRLF line endings (the old editor turned them
|
|
368
|
+
into LF). Adding new entries still appends text as before.
|
|
369
|
+
- **New dependency: `tomlkit` 0.11.4 or newer**, installed automatically by
|
|
370
|
+
`pip`. Only the commands above need it; adding entries, `check`, `explain`
|
|
371
|
+
and the daemon do not, and a missing `tomlkit` produces a message saying how
|
|
372
|
+
to install it. The system package is recent enough on Debian 13 and Ubuntu
|
|
373
|
+
24.04; Ubuntu 22.04's is too old, so `pip` fetches a current one there.
|
|
374
|
+
- **The daemon's lock file** (`$XDG_RUNTIME_DIR/udevmon.lock`) now holds the PID
|
|
375
|
+
on the first line and the resolved config path on the second.
|
|
376
|
+
|
|
377
|
+
### Fixed
|
|
378
|
+
|
|
379
|
+
- **Commented table headers.** A comment after a header (`[devices.x] # note`,
|
|
380
|
+
`[[actions]] # note`) or after an action's `name = "x"` made `remove` and
|
|
381
|
+
`tag` fail with "could not locate block" or "has no explicit name".
|
|
382
|
+
- **Hand-formatted files.** Editing failed on a device written as a sub-table
|
|
383
|
+
(`[devices.x]` plus `[devices.x.match]`), on a block whose multi-line string
|
|
384
|
+
has a line starting with `[`, and on a multi-line `tags` array.
|
|
385
|
+
- **Documentation.** A doubled code fence in the README and SPECIFICATIONS
|
|
386
|
+
rendered the command list as an empty box followed by plain text.
|
|
387
|
+
|
|
388
|
+
### Internal
|
|
389
|
+
|
|
390
|
+
- The test suite grew from 94 to 194 tests. It gets its own runtime directory,
|
|
391
|
+
so it can never signal a real daemon. New tests keep the docs renderable
|
|
392
|
+
(balanced code fences, code lines that fit an 80-column terminal) and fail
|
|
393
|
+
when the CLI gains a command, positional argument or value option that shell
|
|
394
|
+
completion does not cover.
|
|
395
|
+
|
|
396
|
+
### Upgrading
|
|
397
|
+
|
|
398
|
+
- If you run the service, restart it after upgrading
|
|
399
|
+
(`systemctl --user restart udevmon`). An old `udevmon reload` cannot read the
|
|
400
|
+
new two-line lock file and would report that no daemon is running. A new CLI
|
|
401
|
+
works with an old daemon.
|
|
402
|
+
- Editing commands now reload the daemon by themselves; add `--no-reload` to
|
|
403
|
+
get the previous behaviour.
|
|
404
|
+
|
|
405
|
+
## [0.1.0] - 2026-09-20
|
|
406
|
+
|
|
407
|
+
First version.
|
|
408
|
+
|
|
409
|
+
### Added
|
|
410
|
+
|
|
411
|
+
- **Automation without root.** A daemon watches USB devices (udev) and
|
|
412
|
+
filesystem mounts (udisks2 over D-Bus) and runs your commands on `connected`,
|
|
413
|
+
`disconnected`, `mounted`, `unmounted`, and, for things already present,
|
|
414
|
+
`startup` and `shutdown`.
|
|
415
|
+
- **Named devices** recognised by serial number, filesystem UUID or partition
|
|
416
|
+
UUID, with a strength rating (strong, medium, weak) and warnings for weak
|
|
417
|
+
matches. **Tags**, set on devices or selected by rules on properties, and
|
|
418
|
+
actions that target one device, several devices or a tag.
|
|
419
|
+
- **Safe autorun.** Things you have not named run only actions that set
|
|
420
|
+
`allow_unnamed = true`; `check` explains the choice.
|
|
421
|
+
- **Configuration** in a TOML file (`~/.config/udevmon/config.toml`), with
|
|
422
|
+
placeholders (`{mountpoint}`, `{device}`, `{tag}`, ...) and `UDEVMON_*`
|
|
423
|
+
environment variables. Commands are never run through a shell, each action has
|
|
424
|
+
a timeout, and a config that is not yours or is writable by others is refused.
|
|
425
|
+
- **Commands:** `scan` (watch events and name devices, also for things already
|
|
426
|
+
present with `--present`), `present`, `devices`, `tags`, `actions`, `check`,
|
|
427
|
+
`run` (with `--dry-run`), `reload` and `service install`.
|
|
428
|
+
- **Version taken from git**, in the format setuptools-scm uses.
|
|
429
|
+
- **Python 3.10 or newer** (with `tomli` as the reader on 3.10), a README and a
|
|
430
|
+
detailed SPECIFICATIONS, and 94 tests.
|
udevmon-0.8.1/CLAUDE.md
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# udevmon: notes for Claude sessions
|
|
2
|
+
|
|
3
|
+
Read `DEVELOPMENT.md` first: tests, the checks that keep CLI, completion, man
|
|
4
|
+
pages and docs in step, live testing, branches and releasing. `SPECIFICATIONS.md`
|
|
5
|
+
is the design (§12 lists every open idea); `README.md` is the user guide;
|
|
6
|
+
`CHANGELOG.md` the history.
|
|
7
|
+
|
|
8
|
+
## Quick reference
|
|
9
|
+
|
|
10
|
+
```
|
|
11
|
+
PYTHONPATH=src python3 -m unittest discover -s tests -t .
|
|
12
|
+
udevmon check # the user's real config
|
|
13
|
+
VENV/bin/python tools/docs.py build # HTML manual; needs the docs extra
|
|
14
|
+
git log --oneline -5; git branch -a
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
## Rules
|
|
18
|
+
|
|
19
|
+
- **Branches:** work on a branch off `main`; commits there need no asking.
|
|
20
|
+
Merging into `main`, tagging and pushing only when the user says so.
|
|
21
|
+
- **No backwards compatibility** while udevmon is preliminary: change formats
|
|
22
|
+
and names in place, no aliases or migration code; fix the user's own config
|
|
23
|
+
(`~/.config/udevmon/config.toml`, `config.d/`) by hand if it is affected.
|
|
24
|
+
- **A new option is not done** until `completion.py`, the man pages, README,
|
|
25
|
+
SPECIFICATIONS and the changelog have it; the tests enforce most of this.
|
|
26
|
+
- **Live tests** use a private daemon (own config and `XDG_RUNTIME_DIR`), never
|
|
27
|
+
the user's running service, and leave nothing behind (DEVELOPMENT.md, "Live
|
|
28
|
+
testing"). Ask before restarting the real service.
|
|
29
|
+
- **Untracked notes and run books** go in files named `*.local.md` (ignored
|
|
30
|
+
by Git); delete them once their work is done.
|
|
31
|
+
- **The owner's environment** (remotes, other machines, projects that use
|
|
32
|
+
udevmon) is in `CLAUDE.local.md`, which Git ignores, when it exists.
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
# Working on udevmon
|
|
2
|
+
|
|
3
|
+
How the project is worked on: tests, the checks that keep the docs in step with
|
|
4
|
+
the code, live testing, and releasing. `SPECIFICATIONS.md` describes the design;
|
|
5
|
+
`README.md` is for users.
|
|
6
|
+
|
|
7
|
+
## Tests
|
|
8
|
+
|
|
9
|
+
```
|
|
10
|
+
PYTHONPATH=src python3 -m unittest discover -s tests -t .
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
Running from the source tree needs the dependencies installed (`pyudev`,
|
|
14
|
+
`PyGObject`, `tomlkit`). Run one module with `python3 -m unittest tests.test_x`,
|
|
15
|
+
not `python3 tests/test_x.py`: importing the `tests` package is what isolates
|
|
16
|
+
the suite (`tests/__init__.py`):
|
|
17
|
+
|
|
18
|
+
- every temporary file goes in one per-run folder, deleted at the end, so
|
|
19
|
+
nothing is left in `/tmp`;
|
|
20
|
+
- the tests get their own `XDG_RUNTIME_DIR`, so they never reach a real daemon,
|
|
21
|
+
its pause flag or its control socket;
|
|
22
|
+
- a fake `DISPLAY`, and no `DBUS_SESSION_BUS_ADDRESS`, so nothing reaches the
|
|
23
|
+
real desktop or sends a notification.
|
|
24
|
+
|
|
25
|
+
A new test that needs a daemon, a session bus or the desktop must fake it the
|
|
26
|
+
same way; `tests/test_notify.py` and `tests/test_trigger.py` show how.
|
|
27
|
+
|
|
28
|
+
After writing a test for a rule, break the rule in the code on purpose and check
|
|
29
|
+
that the test fails (a quick mutation check). It catches a test that passes for
|
|
30
|
+
the wrong reason.
|
|
31
|
+
|
|
32
|
+
## Checks that keep things in step
|
|
33
|
+
|
|
34
|
+
These tests fail when a change is only half done. Fix them by finishing the
|
|
35
|
+
change, not by loosening the test.
|
|
36
|
+
|
|
37
|
+
| Test | Fails when |
|
|
38
|
+
|---|---|
|
|
39
|
+
| `test_completion` | a command, positional argument or value option of the CLI is missing from the completion tables in `completion.py` |
|
|
40
|
+
| `test_manpages` | a command, option, config key, event or property is not described in `man/`, the example config there is invalid, or `groff` warns |
|
|
41
|
+
| `test_documentation` | the HTML manual has a warning (broken reference, bad docstring markup), or building it reads the config, starts a source, opens a socket or runs a command other than `git`; skipped without Sphinx |
|
|
42
|
+
| `test_docs` | a code block in `README.md`, `SPECIFICATIONS.md`, `CHANGELOG.md` or this file is unbalanced, empty, or has a line wider than 76 characters (it must fit an 80-column `glow`) |
|
|
43
|
+
| `test_license` | a Python file lacks the two-line SPDX header (after any `#!` line), or `LICENSE` or the license metadata of udevmon or `contrib/rclone-config` is missing |
|
|
44
|
+
|
|
45
|
+
So a new option normally touches: the parser in `cli.py`, `completion.py`, both
|
|
46
|
+
man pages where relevant, README, SPECIFICATIONS, and the changelog.
|
|
47
|
+
|
|
48
|
+
## Docs
|
|
49
|
+
|
|
50
|
+
- `CHANGELOG.md`: add a line under **Unreleased** for any change a user would
|
|
51
|
+
notice, in the section it belongs to (Added, Changed, Fixed, ...).
|
|
52
|
+
- Man pages: when a page's content changes, set the date in its `.TH` line to
|
|
53
|
+
the day of the change (`man/udevmon.1`, `man/udevmon.toml.5`).
|
|
54
|
+
- Prose is plain and short; examples are real commands that work.
|
|
55
|
+
- Every Python file starts with the license header (after a `#!` line):
|
|
56
|
+
|
|
57
|
+
```
|
|
58
|
+
# SPDX-License-Identifier: GPL-3.0-or-later
|
|
59
|
+
# Copyright (C) 2026 Juan Domingo Martín
|
|
60
|
+
```
|
|
61
|
+
- No personal data in tracked files: examples use neutral drive labels
|
|
62
|
+
(`KEYDRIVE`), serials (`1111222233334444`), UUIDs (`AB12-CD34`) and host
|
|
63
|
+
names (`alpha`, `beta`).
|
|
64
|
+
- No backwards compatibility while udevmon is preliminary: a format change is
|
|
65
|
+
made in place, and existing configs are fixed by hand, not by migration code.
|
|
66
|
+
|
|
67
|
+
### The HTML manual
|
|
68
|
+
|
|
69
|
+
`docs/` is a Sphinx manual for integrators and maintainers: integration
|
|
70
|
+
contracts, what is provisional and what internal, the command reference
|
|
71
|
+
(generated from the parser) and the module reference (from docstrings).
|
|
72
|
+
Sphinx is optional (the `docs` extra); install it in a private virtual
|
|
73
|
+
environment, never in `~/.venvs/tools`:
|
|
74
|
+
|
|
75
|
+
```
|
|
76
|
+
python3 -m venv --system-site-packages /path/to/venv
|
|
77
|
+
/path/to/venv/bin/python -m pip install -e '.[docs]'
|
|
78
|
+
/path/to/venv/bin/python tools/docs.py build
|
|
79
|
+
/path/to/venv/bin/python tools/docs.py serve --port 8000
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
The build is fresh, nitpicky and fails on any warning; the output, in
|
|
83
|
+
`docs/_build/html` (ignored by git) or `--output PATH`, is never committed.
|
|
84
|
+
The preview listens on 127.0.0.1 only, does not rebuild after edits, and
|
|
85
|
+
stops with Ctrl-C. Run the test suite with that venv's Python too, so that
|
|
86
|
+
`test_documentation` builds the manual instead of being skipped.
|
|
87
|
+
|
|
88
|
+
Docstrings are PEP 257 and reStructuredText (double backquotes for code);
|
|
89
|
+
Napoleon reads Google-style `Args:`, `Returns:` and `Raises:` sections. Give
|
|
90
|
+
the contract a caller needs (blocking work, files written, subprocesses,
|
|
91
|
+
failure), not boilerplate for trivial helpers. A new module shows up in the
|
|
92
|
+
manual by itself, under "Other modules"; list a core one in
|
|
93
|
+
`docs/internals.rst`. `docs/contributing.rst` has the details.
|
|
94
|
+
|
|
95
|
+
## Live testing
|
|
96
|
+
|
|
97
|
+
The unit tests use synthetic events. For the real thing:
|
|
98
|
+
|
|
99
|
+
- `tools/usb-test.py` walks through plugging and pulling a USB stick (see
|
|
100
|
+
README, "Real-hardware test").
|
|
101
|
+
- A private daemon, so the real service is not disturbed: give it its own
|
|
102
|
+
config and runtime folder, and stop it at the end.
|
|
103
|
+
|
|
104
|
+
```
|
|
105
|
+
rt=$(mktemp -d /tmp/udm-XXXX) # short: a socket path is limited
|
|
106
|
+
trap 'rm -rf "$rt"' EXIT
|
|
107
|
+
XDG_RUNTIME_DIR=$rt udevmon --config test.toml run --dry-run
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
Commands that talk to it (`trigger`, `pause`, `reload`) need the same
|
|
111
|
+
`XDG_RUNTIME_DIR`.
|
|
112
|
+
|
|
113
|
+
While testing: leave nothing behind in `/tmp` (a `trap` as above), keep the
|
|
114
|
+
desktop from opening a file-manager window for each mounted stick
|
|
115
|
+
(`usb-test.py` turns that setting off and back on), and avoid anything that asks
|
|
116
|
+
for a password, such as `udisksctl` without `--no-user-interaction`.
|
|
117
|
+
|
|
118
|
+
## Branches
|
|
119
|
+
|
|
120
|
+
Work goes on a branch off `main` (`feature/...`, `fix/...`, `docs/...`), one
|
|
121
|
+
commit per step that was tested. It is merged into `main` as one clean commit,
|
|
122
|
+
squashed if the branch has a long trial-and-error history.
|
|
123
|
+
|
|
124
|
+
## Releasing
|
|
125
|
+
|
|
126
|
+
The version comes from git tags (`src/udevmon/_scm.py`, SPECIFICATIONS §13.1);
|
|
127
|
+
nothing in the source holds it.
|
|
128
|
+
|
|
129
|
+
1. Choose the number. Before 1.0: a new feature or a change in behaviour bumps
|
|
130
|
+
the minor version (0.7.0 to 0.8.0); fixes only bump the patch (0.4.0 to
|
|
131
|
+
0.4.1).
|
|
132
|
+
2. In `CHANGELOG.md`, add `## [X.Y.Z] - YYYY-MM-DD` under `## [Unreleased]`,
|
|
133
|
+
which stays, empty, above it.
|
|
134
|
+
3. Check the man page dates (see Docs) and run the tests.
|
|
135
|
+
4. Commit as `Release X.Y.Z`, then tag: `git tag -a vX.Y.Z -m "udevmon X.Y.Z"`.
|
|
136
|
+
5. Push `main` and the tag. On GitHub the tag starts the release workflow
|
|
137
|
+
(`.github/workflows/release.yml`): it checks the tag against the version
|
|
138
|
+
from git, builds, and publishes to PyPI. Never re-upload a version.
|
|
139
|
+
6. Reinstall where it is used, and restart the service if the daemon changed.
|
|
140
|
+
|
|
141
|
+
## Installing a development build
|
|
142
|
+
|
|
143
|
+
udevmon is installed in a virtual environment made with
|
|
144
|
+
`include-system-site-packages = true`, so it uses the system's PyGObject (`gi`),
|
|
145
|
+
`pyudev` and `tomlkit`, with the commands linked into `~/.local/bin`. With `uv`:
|
|
146
|
+
|
|
147
|
+
```
|
|
148
|
+
uv pip install --python ~/.venvs/tools/bin/python --no-deps .
|
|
149
|
+
udevmon completion install
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
`--no-deps` is needed because `uv` does not count the system packages as
|
|
153
|
+
installed and would fetch and build PyGObject from PyPI. The man
|
|
154
|
+
pages are installed into the environment's `share/man`; `man` finds them only
|
|
155
|
+
if they are linked from `~/.local/share/man/man1` and `man5`. The links
|
|
156
|
+
survive reinstalls.
|