python-dmon 0.3.1__tar.gz → 0.4.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.
- python_dmon-0.4.0/PKG-INFO +481 -0
- python_dmon-0.4.0/README.md +462 -0
- {python_dmon-0.3.1 → python_dmon-0.4.0}/pyproject.toml +2 -1
- python_dmon-0.4.0/src/dmon/__init__.py +23 -0
- python_dmon-0.4.0/src/dmon/api.py +264 -0
- python_dmon-0.4.0/src/dmon/cli.py +794 -0
- {python_dmon-0.3.1 → python_dmon-0.4.0}/src/dmon/config.py +206 -0
- {python_dmon-0.3.1 → python_dmon-0.4.0}/src/dmon/constants.py +3 -0
- {python_dmon-0.3.1 → python_dmon-0.4.0}/src/dmon/control.py +180 -57
- python_dmon-0.4.0/src/dmon/inspection.py +36 -0
- python_dmon-0.4.0/src/dmon/logs.py +240 -0
- python_dmon-0.4.0/src/dmon/readiness.py +148 -0
- python_dmon-0.4.0/src/dmon/results.py +111 -0
- python_dmon-0.4.0/src/dmon/serialization.py +28 -0
- python_dmon-0.4.0/src/dmon/stack_runner.py +18 -0
- python_dmon-0.4.0/src/dmon/supervisor.py +951 -0
- python_dmon-0.4.0/src/dmon/types.py +189 -0
- python_dmon-0.3.1/PKG-INFO +0 -266
- python_dmon-0.3.1/README.md +0 -248
- python_dmon-0.3.1/src/dmon/__init__.py +0 -0
- python_dmon-0.3.1/src/dmon/cli.py +0 -342
- python_dmon-0.3.1/src/dmon/types.py +0 -100
- {python_dmon-0.3.1 → python_dmon-0.4.0}/src/dmon/__main__.py +0 -0
- {python_dmon-0.3.1 → python_dmon-0.4.0}/src/dmon/py.typed +0 -0
- {python_dmon-0.3.1 → python_dmon-0.4.0}/src/dmon/runner.py +0 -0
- {python_dmon-0.3.1 → python_dmon-0.4.0}/src/dmon/utils.py +0 -0
|
@@ -0,0 +1,481 @@
|
|
|
1
|
+
Metadata-Version: 2.3
|
|
2
|
+
Name: python-dmon
|
|
3
|
+
Version: 0.4.0
|
|
4
|
+
Summary: A lightweight, cross-platform daemon manager that runs any command as a background process.
|
|
5
|
+
Keywords: python-dmon,dmon,daemon,background,detach,process management
|
|
6
|
+
Author: Atomie CHEN
|
|
7
|
+
Author-email: Atomie CHEN <atomic_cwh@163.com>
|
|
8
|
+
Requires-Dist: colorama>=0.4.6
|
|
9
|
+
Requires-Dist: psutil>=7.1.0
|
|
10
|
+
Requires-Dist: python-dotenv>=1.0.1,<1.1
|
|
11
|
+
Requires-Dist: pyyaml>=6.0.3
|
|
12
|
+
Requires-Dist: termcolor>=2.4.0
|
|
13
|
+
Requires-Dist: tomli>=2.2.1 ; python_full_version < '3.11'
|
|
14
|
+
Requires-Python: >=3.8
|
|
15
|
+
Project-URL: Bug Tracker, https://github.com/atomiechen/python-dmon/issues
|
|
16
|
+
Project-URL: Changelog, https://github.com/atomiechen/python-dmon/blob/main/CHANGELOG.md
|
|
17
|
+
Project-URL: Homepage, https://github.com/atomiechen/python-dmon
|
|
18
|
+
Description-Content-Type: text/markdown
|
|
19
|
+
|
|
20
|
+
# python-dmon
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
[](https://github.com/atomiechen/python-dmon)
|
|
24
|
+
[](https://pypi.org/project/python-dmon/)
|
|
25
|
+
[](https://deepwiki.com/atomiechen/python-dmon)
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
A lightweight, cross-platform daemon manager that runs any command — called a *task* — as a background process.
|
|
29
|
+
It also supports logging and log rotation out of the box.
|
|
30
|
+
**No external runtime required**.
|
|
31
|
+
|
|
32
|
+
Shipped as the CLI tool `dmon`.
|
|
33
|
+
It is a Python-based and more powerful successor to the [handy-backend shell scripts](https://github.com/atomiechen/handy-backend).
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
## Features
|
|
37
|
+
|
|
38
|
+
- 🖥️ **Cross-platform:** Works on Linux, macOS, and Windows.
|
|
39
|
+
- ⚡ **Lightweight:** No daemon service or container runtime required.
|
|
40
|
+
- 🧩 **Flexible tasks:** Tasks can be configured in `pyproject.toml` or `dmon.yaml`; or run ad-hoc commands directly.
|
|
41
|
+
- 🔗 **Supervised stacks:** Start dependent tasks in order, wait for HTTP, TCP,
|
|
42
|
+
or command readiness, and report runtime degradation.
|
|
43
|
+
- 🌙 **Foreground or detached:** Keep a stack attached for development, or run
|
|
44
|
+
it under a recoverable background supervisor with `dmon stack up -d` and
|
|
45
|
+
`dmon stack down`.
|
|
46
|
+
- ⏱️ **Reusable readiness:** Wait for configured tasks or direct HTTP, TCP, and
|
|
47
|
+
command probes from scripts and deployment workflows.
|
|
48
|
+
- 🪵 **Logging & log rotation:** Keep active log files manageable, with optional archive retention limits.
|
|
49
|
+
|
|
50
|
+

|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
## Installation
|
|
54
|
+
|
|
55
|
+
`python-dmon` is available on [PyPI](https://pypi.org/project/python-dmon/):
|
|
56
|
+
|
|
57
|
+
```sh
|
|
58
|
+
pip install python-dmon
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
We recommend installing into an isolated environment, e.g., with `uv` / `pipx`:
|
|
62
|
+
|
|
63
|
+
```sh
|
|
64
|
+
# Install globally with uv tool
|
|
65
|
+
uv tool install python-dmon
|
|
66
|
+
|
|
67
|
+
# Or with pipx
|
|
68
|
+
pipx install python-dmon
|
|
69
|
+
|
|
70
|
+
# Add as a dev dependency in your project
|
|
71
|
+
uv add --dev python-dmon
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
You can also invoke without installing:
|
|
75
|
+
|
|
76
|
+
```sh
|
|
77
|
+
# With uvx (uv tool run)
|
|
78
|
+
uvx python-dmon
|
|
79
|
+
|
|
80
|
+
# Or with pipx
|
|
81
|
+
pipx run python-dmon
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
To get the latest features, install from source:
|
|
85
|
+
|
|
86
|
+
```sh
|
|
87
|
+
pip install git+https://github.com/atomiechen/python-dmon.git
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
## Getting Started
|
|
91
|
+
|
|
92
|
+
### Prepare Configuration
|
|
93
|
+
|
|
94
|
+
Create a `dmon.yaml` file:
|
|
95
|
+
|
|
96
|
+
```yaml
|
|
97
|
+
tasks:
|
|
98
|
+
app: ["python", "-u", "server.py"] # option 1: exec form
|
|
99
|
+
# app: "python -u server.py" # option 2: shell string
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
Or add to your `pyproject.toml`:
|
|
103
|
+
|
|
104
|
+
```toml
|
|
105
|
+
[tool.dmon.tasks]
|
|
106
|
+
app = ["python", "-u", "server.py"] # option 1: exec form
|
|
107
|
+
# app = "python -u server.py" # option 2: shell string
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
Commands can be a single string (run in shell), or list of strings (exec form).
|
|
111
|
+
See [Example Configuration](#example-configuration) for more configuration options.
|
|
112
|
+
Without `--config`, dmon searches the current directory and its parents for
|
|
113
|
+
`dmon.yaml`, `dmon.yml`, or `pyproject.toml`.
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
### Run tasks
|
|
117
|
+
|
|
118
|
+
Run a configured task by its name:
|
|
119
|
+
|
|
120
|
+
```sh
|
|
121
|
+
# Start a task
|
|
122
|
+
dmon start app
|
|
123
|
+
|
|
124
|
+
# Stop a running task
|
|
125
|
+
dmon stop app
|
|
126
|
+
|
|
127
|
+
# Restart a task
|
|
128
|
+
dmon restart app
|
|
129
|
+
|
|
130
|
+
# Check task status
|
|
131
|
+
dmon status app
|
|
132
|
+
|
|
133
|
+
# Execute a task in the foreground (useful for debugging)
|
|
134
|
+
dmon exec app
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
`dmon exec` runs one configured command directly in the current terminal for
|
|
138
|
+
debugging; it is not registered as a managed background task. Use `dmon start`
|
|
139
|
+
when the task should remain discoverable through `dmon status` and `dmon list`.
|
|
140
|
+
|
|
141
|
+
You can specify multiple tasks at once, e.g.: `dmon start app1 app2 app3`, except for `dmon exec` which only accepts one task.
|
|
142
|
+
|
|
143
|
+
Multi-task `start` is best-effort: dmon attempts every requested task and leaves
|
|
144
|
+
successful tasks running if another task cannot start. The command returns a
|
|
145
|
+
non-zero status and prints a summary naming the failed tasks. This is useful for
|
|
146
|
+
independent background services and does not provide atomic stack semantics.
|
|
147
|
+
|
|
148
|
+
For related services that should start and stop as one unit, define a stack and
|
|
149
|
+
run it in the foreground. Unlike a direct `dmon exec`, a foreground stack is a
|
|
150
|
+
managed multi-task lifecycle and remains discoverable from another terminal:
|
|
151
|
+
|
|
152
|
+
```yaml
|
|
153
|
+
tasks:
|
|
154
|
+
database:
|
|
155
|
+
cmd: [python, database.py]
|
|
156
|
+
ready:
|
|
157
|
+
tcp: {host: 127.0.0.1, port: 5432}
|
|
158
|
+
timeout: 20
|
|
159
|
+
api:
|
|
160
|
+
cmd: [python, api.py]
|
|
161
|
+
depends_on: [database]
|
|
162
|
+
ready:
|
|
163
|
+
http: http://127.0.0.1:8000/health
|
|
164
|
+
worker:
|
|
165
|
+
cmd: [python, worker.py]
|
|
166
|
+
depends_on: [api]
|
|
167
|
+
|
|
168
|
+
stacks:
|
|
169
|
+
dev: [api, worker]
|
|
170
|
+
default_stack: dev
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
```sh
|
|
174
|
+
dmon stack up dev
|
|
175
|
+
# Or omit the name when default_stack (or only one stack) is configured
|
|
176
|
+
dmon stack up
|
|
177
|
+
|
|
178
|
+
# Keep the supervised stack running in the background
|
|
179
|
+
dmon stack up -d dev
|
|
180
|
+
dmon stack status dev
|
|
181
|
+
dmon stack restart dev
|
|
182
|
+
dmon stack logs --tail 100 dev
|
|
183
|
+
dmon stack logs -f dev
|
|
184
|
+
dmon stack list
|
|
185
|
+
dmon stack down dev
|
|
186
|
+
|
|
187
|
+
# Optional fail-fast policy for foreground or detached stacks
|
|
188
|
+
dmon stack up --abort-on-exit dev
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
`dmon stack up` starts dependencies in order and waits for each task's optional
|
|
192
|
+
readiness probe. A startup failure or readiness timeout rolls back every task
|
|
193
|
+
started by that invocation. After startup, an exited task marks the stack as
|
|
194
|
+
degraded while unrelated tasks continue running, matching Docker Compose's
|
|
195
|
+
default behavior. Use `--abort-on-exit` when the whole stack should stop after
|
|
196
|
+
any runtime exit. Ctrl-C or SIGTERM cleans up a foreground stack in reverse
|
|
197
|
+
order. `dmon stack down` requests the same cleanup for an active foreground or
|
|
198
|
+
detached stack from another terminal.
|
|
199
|
+
|
|
200
|
+
Foreground `dmon stack up` displays new task output with task-name prefixes,
|
|
201
|
+
while retaining it in each task's configured `log_path`. Detached mode does not
|
|
202
|
+
attach output. `dmon stack logs` reads the latest 100 lines per task by default;
|
|
203
|
+
`--tail` changes that count and `-f` follows new output. Log viewing never
|
|
204
|
+
modifies the underlying files and never controls running processes. Docker
|
|
205
|
+
Compose is still appropriate when container behavior itself must be tested.
|
|
206
|
+
|
|
207
|
+
Terminal attachment and process management are separate choices: `dmon exec`
|
|
208
|
+
is foreground but intentionally unmanaged, while foreground `dmon stack up` is
|
|
209
|
+
supervised, recorded, and discoverable from another terminal. Detached stacks
|
|
210
|
+
use the same ownership model without attaching their output.
|
|
211
|
+
|
|
212
|
+
Detached mode waits for the same startup and readiness checks before returning.
|
|
213
|
+
A lightweight background supervisor keeps monitoring the stack; `dmon stack down`
|
|
214
|
+
requests the same graceful reverse-order cleanup on every platform. If that
|
|
215
|
+
supervisor is killed, its persisted ownership metadata lets `down` recover and
|
|
216
|
+
clean the tasks it started. Supervisor diagnostics are written to
|
|
217
|
+
`logs/<stack>.stack.log`. `dmon stack status` includes every owned task and its
|
|
218
|
+
process tree; `dmon stack list` summarizes all recorded foreground and detached
|
|
219
|
+
stacks. `dmon stack restart` applies to detached stacks: it performs a clean
|
|
220
|
+
`down` followed by a detached `up` and preserves the stack's exit policy.
|
|
221
|
+
|
|
222
|
+
### Wait for readiness
|
|
223
|
+
|
|
224
|
+
`dmon wait` checks readiness without starting or stopping anything. For a
|
|
225
|
+
configured task, the task must already be managed by `dmon start` or a stack;
|
|
226
|
+
the command uses that task's configured probe, working directory, environment,
|
|
227
|
+
and process identity. The task name can be omitted when `default_task` is set or
|
|
228
|
+
the configuration contains only one task:
|
|
229
|
+
|
|
230
|
+
```sh
|
|
231
|
+
dmon wait api
|
|
232
|
+
dmon wait api worker --timeout 60 --interval 0.5
|
|
233
|
+
```
|
|
234
|
+
|
|
235
|
+
Direct probes need no dmon configuration:
|
|
236
|
+
|
|
237
|
+
```sh
|
|
238
|
+
dmon wait --http http://127.0.0.1:8000/health
|
|
239
|
+
dmon wait --tcp 127.0.0.1:5432
|
|
240
|
+
dmon wait --timeout 30 --command -- python healthcheck.py --verbose
|
|
241
|
+
```
|
|
242
|
+
|
|
243
|
+
For command probes, dmon options must precede `--command`; the optional second
|
|
244
|
+
`--` marks the child-command boundary. The command returns zero only when every
|
|
245
|
+
target is ready, one for a completed unsuccessful wait, and 130 when interrupted.
|
|
246
|
+
Configured timeouts and intervals can be overridden with positive values.
|
|
247
|
+
|
|
248
|
+
Or use `--all` to operate on all tasks:
|
|
249
|
+
|
|
250
|
+
```sh
|
|
251
|
+
# All configured tasks
|
|
252
|
+
dmon start --all
|
|
253
|
+
dmon restart --all
|
|
254
|
+
|
|
255
|
+
# All recorded task metadata in the project
|
|
256
|
+
dmon status --all
|
|
257
|
+
dmon stop --all
|
|
258
|
+
```
|
|
259
|
+
|
|
260
|
+
If you have defined `default_task`, or only one task is defined in the config file, you can omit the task name:
|
|
261
|
+
|
|
262
|
+
```sh
|
|
263
|
+
dmon start
|
|
264
|
+
dmon stop
|
|
265
|
+
dmon restart
|
|
266
|
+
dmon status
|
|
267
|
+
dmon exec
|
|
268
|
+
```
|
|
269
|
+
|
|
270
|
+
You can use `-c` / `--config` to specify a custom config file or the directory containing it:
|
|
271
|
+
|
|
272
|
+
```sh
|
|
273
|
+
dmon start --config /path/to/dmon.yaml app # YAML
|
|
274
|
+
dmon start --config /path/to/pyproject.toml app # or TOML
|
|
275
|
+
dmon start -c /path/to/dir app # shorter, dir with `dmon.y(a)ml` or `pyproject.toml`
|
|
276
|
+
```
|
|
277
|
+
|
|
278
|
+
The same config discovery and selection rules apply to stacks. A stack name can
|
|
279
|
+
be omitted when `default_stack` is set or only one stack is configured. Options
|
|
280
|
+
belonging to a stack operation go after that operation and may appear before or
|
|
281
|
+
after the stack name; for example, `dmon stack up -d dev` and `dmon stack up dev
|
|
282
|
+
-d` are equivalent.
|
|
283
|
+
|
|
284
|
+
And yes, you can use `dmon` to run in a nested manner:
|
|
285
|
+
|
|
286
|
+
```yaml
|
|
287
|
+
tasks:
|
|
288
|
+
app: ["python", "-u", "server.py"]
|
|
289
|
+
nested: pwd && dmon exec app # nest `dmon exec`
|
|
290
|
+
subdir_task1:
|
|
291
|
+
cwd: /path/to/dir
|
|
292
|
+
cmd: ["dmon", "exec", "app"] # run task defined in another folder
|
|
293
|
+
subdir_task2: dmon exec app --config /path/to/dir/dmon.yaml # like above
|
|
294
|
+
```
|
|
295
|
+
|
|
296
|
+
|
|
297
|
+
### Run an ad-hoc command
|
|
298
|
+
|
|
299
|
+
```sh
|
|
300
|
+
# Run a command with arguments in the background
|
|
301
|
+
dmon run --name myserver python -u server.py
|
|
302
|
+
|
|
303
|
+
# Optionally use -- to make the child-command boundary explicit
|
|
304
|
+
dmon run --name timer -- python -c 'import time; time.sleep(30)'
|
|
305
|
+
|
|
306
|
+
# Run a shell command in the background
|
|
307
|
+
dmon run --shell echo "Hello World"
|
|
308
|
+
|
|
309
|
+
# Run a shell script in the background
|
|
310
|
+
dmon run --cwd /path/to/script bash myscript.sh
|
|
311
|
+
```
|
|
312
|
+
|
|
313
|
+
> [!NOTE]
|
|
314
|
+
> If no name is provided, `dmon` automatically assigns a fixed task name `default_run` to prevent duplicate runs.
|
|
315
|
+
|
|
316
|
+
|
|
317
|
+
### List recorded tasks and their status
|
|
318
|
+
|
|
319
|
+
```sh
|
|
320
|
+
dmon list
|
|
321
|
+
```
|
|
322
|
+
|
|
323
|
+
Finite inspection and readiness commands also support machine-readable output:
|
|
324
|
+
|
|
325
|
+
```sh
|
|
326
|
+
dmon status app --format json
|
|
327
|
+
dmon list --format json
|
|
328
|
+
dmon stack status dev --format json
|
|
329
|
+
dmon stack list --format json
|
|
330
|
+
dmon wait api --format json
|
|
331
|
+
```
|
|
332
|
+
|
|
333
|
+
JSON is written only to stdout; actionable diagnostics remain on stderr. The
|
|
334
|
+
payload has a top-level `ok` field and a `tasks`, `stacks`, or `waits` array.
|
|
335
|
+
Inspection results contain `name`, `ok`, `error`, and an optional `snapshot`;
|
|
336
|
+
wait results contain the target, outcome, reason, elapsed time, and attempt
|
|
337
|
+
count. Existing exit-code semantics are unchanged. Interactive and streaming
|
|
338
|
+
commands do not offer JSON output.
|
|
339
|
+
|
|
340
|
+
### Python API
|
|
341
|
+
|
|
342
|
+
The same task lifecycle and inspection logic is available without parsing CLI
|
|
343
|
+
output:
|
|
344
|
+
|
|
345
|
+
```python
|
|
346
|
+
from dmon import Dmon
|
|
347
|
+
|
|
348
|
+
client = Dmon(config="dmon.yaml")
|
|
349
|
+
started = client.start("app")
|
|
350
|
+
ready = client.wait("app", timeout=30)
|
|
351
|
+
task = client.status("app")
|
|
352
|
+
stacks = client.list_stacks()
|
|
353
|
+
client.stop("app")
|
|
354
|
+
```
|
|
355
|
+
|
|
356
|
+
API calls are synchronous and silent. They return immutable `ActionResult`,
|
|
357
|
+
`TaskResult`, `StackResult`, and `WaitResult` data; expected runtime states such
|
|
358
|
+
as missing or exited metadata are results, while invalid configuration raises
|
|
359
|
+
`DmonConfigError`. The initial API intentionally does not start a supervised
|
|
360
|
+
stack or create implicit background threads.
|
|
361
|
+
|
|
362
|
+
|
|
363
|
+
## Example Configuration
|
|
364
|
+
|
|
365
|
+
A task can be a **string**, **list**, or **dictionary**.
|
|
366
|
+
|
|
367
|
+
When rotation is enabled, dmon keeps timestamped archives such as
|
|
368
|
+
`app.log.20260807-142106`. Both task and runner logs use this cross-platform
|
|
369
|
+
format; a same-second collision adds `.1`, `.2`, and so on. Archives are never
|
|
370
|
+
deleted by default. Set a backup count explicitly to enable retention cleanup.
|
|
371
|
+
`log_path` contains task output; `rotate_log_path` contains diagnostics from the
|
|
372
|
+
dmon process that captures and rotates that output. They are independent log
|
|
373
|
+
streams and use independent retention settings.
|
|
374
|
+
The size limit is checked at line boundaries, so a single long line may exceed
|
|
375
|
+
the configured limit.
|
|
376
|
+
|
|
377
|
+
Here is a more complete example with default values:
|
|
378
|
+
|
|
379
|
+
```yaml
|
|
380
|
+
tasks:
|
|
381
|
+
your_task_name:
|
|
382
|
+
# Command to run; can be a string (run in shell) or list of strings (exec form)
|
|
383
|
+
cmd: ["python", "server.py"] # required
|
|
384
|
+
cwd: "/path/to/working/dir" # (default: current dir)
|
|
385
|
+
env_file: [".env", ".env.local"] # optional; later files override earlier files
|
|
386
|
+
env: # (default: inherit from parent process)
|
|
387
|
+
PYTHONUNBUFFERED: "1"
|
|
388
|
+
override_env: false # true omits parent env; use env_file and env only
|
|
389
|
+
log_path: "logs/<task>.log" # path to log file
|
|
390
|
+
log_rotate: false # enable log rotation
|
|
391
|
+
log_max_size: 5 # max log file size before rotation in MB
|
|
392
|
+
# log_backup_count: 10 # optional; omit to retain all task log archives
|
|
393
|
+
rotate_log_path: "logs/<task>.rotate.log" # path to rotation log
|
|
394
|
+
rotate_log_max_size: 5 # max rotation log file size in MB
|
|
395
|
+
# rotate_log_backup_count: 10 # optional; omit to retain all runner log archives
|
|
396
|
+
meta_path: ".dmon/<task>.meta.json" # path to meta file
|
|
397
|
+
depends_on: [another_task] # dependency order used by `dmon stack up`
|
|
398
|
+
ready: # optional; exactly one of http, tcp, or command
|
|
399
|
+
command: [python, healthcheck.py]
|
|
400
|
+
timeout: 30 # total seconds to wait (default: 30)
|
|
401
|
+
interval: 0.2 # seconds between attempts (default: 0.2)
|
|
402
|
+
default_task: your_task_name # the default task name
|
|
403
|
+
stacks:
|
|
404
|
+
dev: [your_task_name]
|
|
405
|
+
default_stack: dev
|
|
406
|
+
```
|
|
407
|
+
|
|
408
|
+
`env_file` accepts one path or an ordered list of dotenv files. Paths are
|
|
409
|
+
relative to the dmon configuration file. Later files override earlier files,
|
|
410
|
+
the existing process environment overrides file values, and the explicit `env`
|
|
411
|
+
table has highest priority. Set `override_env: true` to omit the existing
|
|
412
|
+
process environment. Standard dotenv `${NAME}` expansion can refer to earlier
|
|
413
|
+
values in the same file or any earlier file in the list. Missing files and keys
|
|
414
|
+
without assigned values fail startup without creating task metadata.
|
|
415
|
+
Environment values and environment-file paths are never written to metadata or
|
|
416
|
+
JSON inspection output.
|
|
417
|
+
|
|
418
|
+
In TOML, write like this:
|
|
419
|
+
|
|
420
|
+
```toml
|
|
421
|
+
[tool.dmon.tasks]
|
|
422
|
+
your_task_name = { cmd = [
|
|
423
|
+
"python", "-u", "server.py"
|
|
424
|
+
], ... }
|
|
425
|
+
another_task = "cd subdir && ls && bash start.sh"
|
|
426
|
+
|
|
427
|
+
[tool.dmon]
|
|
428
|
+
default_task = "your_task_name"
|
|
429
|
+
```
|
|
430
|
+
|
|
431
|
+
All paths, including `env_file`, can be absolute or relative to the **config
|
|
432
|
+
file location**.
|
|
433
|
+
|
|
434
|
+
YAML anchors and merge keys can reuse task fragments within one configuration
|
|
435
|
+
file. Task dependencies are resolved transitively and cycles are rejected.
|
|
436
|
+
dmon intentionally does not recursively include or merge other configuration
|
|
437
|
+
files; keeping one path owner makes command, environment, log, and metadata
|
|
438
|
+
paths unambiguous.
|
|
439
|
+
|
|
440
|
+
|
|
441
|
+
## Under the Hood
|
|
442
|
+
|
|
443
|
+
Each task has `.dmon/<task>.meta.json`, which records its command, PID, process
|
|
444
|
+
creation time, and log paths. An active foreground or detached stack also has
|
|
445
|
+
`.dmon/<stack>.stack.json`, which records its mode, supervisor, and the exact
|
|
446
|
+
task processes it owns. Metadata paths are reserved exclusively and subsequent
|
|
447
|
+
updates replace the JSON atomically; a per-run ID isolates stop requests, while
|
|
448
|
+
PID plus creation time prevents a recycled PID from being mistaken for the
|
|
449
|
+
original process. Environment values are used only to launch and probe the task;
|
|
450
|
+
they are never written to metadata. Normal foreground cleanup removes its stack
|
|
451
|
+
metadata.
|
|
452
|
+
|
|
453
|
+
`dmon stack down` normally asks the foreground or detached supervisor to stop
|
|
454
|
+
tasks in reverse order.
|
|
455
|
+
If the supervisor has crashed, it uses the persisted process identities to
|
|
456
|
+
recover the orphaned stack without inferring ownership from current
|
|
457
|
+
configuration. Existing or unreadable stack metadata is preserved rather than
|
|
458
|
+
overwritten; use `dmon stack down` for stale, readable state. **Do not** edit or
|
|
459
|
+
delete `.dmon` files manually.
|
|
460
|
+
|
|
461
|
+
The log viewer is deliberately separate from process control. It opens task log
|
|
462
|
+
files only while reading, closes them before waiting for more output, and uses
|
|
463
|
+
file identity to reopen a replacement after rotation. Stopping `dmon stack logs
|
|
464
|
+
-f` cannot stop or restart a task or stack. The same read-only component powers
|
|
465
|
+
foreground log attachment; a display failure does not change stack lifecycle.
|
|
466
|
+
|
|
467
|
+
`dmon status` returns a non-zero status if a recorded task has exited. Starting
|
|
468
|
+
that task again removes its stale metadata automatically. `dmon stop` terminates
|
|
469
|
+
the complete process tree and also cleans stale metadata left by an exited task.
|
|
470
|
+
|
|
471
|
+
|
|
472
|
+
## Development
|
|
473
|
+
|
|
474
|
+
See [CONTRIBUTING.md](CONTRIBUTING.md) for architecture, behavioral contracts,
|
|
475
|
+
validation, and the release workflow. Process, signal, log-rotation, and stack
|
|
476
|
+
changes must also pass the reproducible [manual test lab](tests/manual/README.md).
|
|
477
|
+
|
|
478
|
+
|
|
479
|
+
## License
|
|
480
|
+
|
|
481
|
+
[python-dmon](https://github.com/atomiechen/python-dmon) © 2025 by [Atomie CHEN](https://github.com/atomiechen) is licensed under the [MIT License](https://github.com/atomiechen/python-dmon/blob/main/LICENSE).
|