ipmg 2.4.0__tar.gz → 3.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.
- {ipmg-2.4.0 → ipmg-3.1.0}/PKG-INFO +142 -15
- ipmg-2.4.0/src/ipmg.egg-info/PKG-INFO → ipmg-3.1.0/README.md +132 -57
- {ipmg-2.4.0 → ipmg-3.1.0}/pyproject.toml +15 -7
- {ipmg-2.4.0 → ipmg-3.1.0}/src/ipmg/__init__.py +1 -1
- {ipmg-2.4.0 → ipmg-3.1.0}/src/ipmg/cli/commands.py +20 -2
- {ipmg-2.4.0 → ipmg-3.1.0}/src/ipmg/cli/config.py +5 -0
- {ipmg-2.4.0 → ipmg-3.1.0}/src/ipmg/cli/parser.py +30 -2
- {ipmg-2.4.0 → ipmg-3.1.0}/src/ipmg/core/diff.py +8 -4
- ipmg-3.1.0/src/ipmg/core/discovery.py +254 -0
- {ipmg-2.4.0 → ipmg-3.1.0}/src/ipmg/core/ping.py +37 -1
- {ipmg-2.4.0 → ipmg-3.1.0}/src/ipmg/core/portscan.py +6 -2
- {ipmg-2.4.0 → ipmg-3.1.0}/src/ipmg/infrastructure/database.py +17 -1
- {ipmg-2.4.0 → ipmg-3.1.0}/src/ipmg/infrastructure/file_io.py +180 -66
- {ipmg-2.4.0 → ipmg-3.1.0}/src/ipmg/infrastructure/incremental.py +57 -31
- ipmg-3.1.0/src/ipmg/reporting/frames.py +74 -0
- {ipmg-2.4.0 → ipmg-3.1.0}/src/ipmg/reporting/machine.py +5 -7
- ipmg-3.1.0/src/ipmg/reporting/metrics.py +125 -0
- {ipmg-2.4.0 → ipmg-3.1.0}/src/ipmg/reporting/summary.py +12 -9
- {ipmg-2.4.0 → ipmg-3.1.0}/src/ipmg/services/scan_service.py +9 -8
- {ipmg-2.4.0 → ipmg-3.1.0}/src/ipmg/web/app.py +36 -21
- {ipmg-2.4.0 → ipmg-3.1.0}/src/ipmg/web/server.py +7 -1
- ipmg-2.4.0/README.md → ipmg-3.1.0/src/ipmg.egg-info/PKG-INFO +184 -8
- {ipmg-2.4.0 → ipmg-3.1.0}/src/ipmg.egg-info/SOURCES.txt +3 -0
- {ipmg-2.4.0 → ipmg-3.1.0}/src/ipmg.egg-info/requires.txt +11 -6
- {ipmg-2.4.0 → ipmg-3.1.0}/tests/test_commands.py +39 -1
- {ipmg-2.4.0 → ipmg-3.1.0}/tests/test_config.py +16 -0
- {ipmg-2.4.0 → ipmg-3.1.0}/tests/test_file_io.py +61 -19
- {ipmg-2.4.0 → ipmg-3.1.0}/tests/test_incremental.py +7 -4
- ipmg-3.1.0/tests/test_ipv6.py +275 -0
- ipmg-3.1.0/tests/test_metrics.py +162 -0
- {ipmg-2.4.0 → ipmg-3.1.0}/tests/test_parser.py +20 -0
- {ipmg-2.4.0 → ipmg-3.1.0}/tests/test_scan_service.py +11 -8
- {ipmg-2.4.0 → ipmg-3.1.0}/tests/test_web_api.py +6 -5
- ipmg-2.4.0/src/ipmg/core/discovery.py +0 -75
- ipmg-2.4.0/src/ipmg/reporting/frames.py +0 -45
- {ipmg-2.4.0 → ipmg-3.1.0}/LICENSE +0 -0
- {ipmg-2.4.0 → ipmg-3.1.0}/setup.cfg +0 -0
- {ipmg-2.4.0 → ipmg-3.1.0}/src/ipmg/__main__.py +0 -0
- {ipmg-2.4.0 → ipmg-3.1.0}/src/ipmg/cli/__init__.py +0 -0
- {ipmg-2.4.0 → ipmg-3.1.0}/src/ipmg/core/__init__.py +0 -0
- {ipmg-2.4.0 → ipmg-3.1.0}/src/ipmg/core/engine.py +0 -0
- {ipmg-2.4.0 → ipmg-3.1.0}/src/ipmg/core/health.py +0 -0
- {ipmg-2.4.0 → ipmg-3.1.0}/src/ipmg/core/security.py +0 -0
- {ipmg-2.4.0 → ipmg-3.1.0}/src/ipmg/exceptions.py +0 -0
- {ipmg-2.4.0 → ipmg-3.1.0}/src/ipmg/infrastructure/__init__.py +0 -0
- {ipmg-2.4.0 → ipmg-3.1.0}/src/ipmg/infrastructure/notify.py +0 -0
- {ipmg-2.4.0 → ipmg-3.1.0}/src/ipmg/reporting/__init__.py +0 -0
- {ipmg-2.4.0 → ipmg-3.1.0}/src/ipmg/reporting/diff_report.py +0 -0
- {ipmg-2.4.0 → ipmg-3.1.0}/src/ipmg/reporting/live.py +0 -0
- {ipmg-2.4.0 → ipmg-3.1.0}/src/ipmg/reporting/ui.py +0 -0
- {ipmg-2.4.0 → ipmg-3.1.0}/src/ipmg/services/__init__.py +0 -0
- {ipmg-2.4.0 → ipmg-3.1.0}/src/ipmg/services/history_service.py +0 -0
- {ipmg-2.4.0 → ipmg-3.1.0}/src/ipmg/utils/__init__.py +0 -0
- {ipmg-2.4.0 → ipmg-3.1.0}/src/ipmg/utils/helpers.py +0 -0
- {ipmg-2.4.0 → ipmg-3.1.0}/src/ipmg/web/__init__.py +0 -0
- {ipmg-2.4.0 → ipmg-3.1.0}/src/ipmg/web/db.py +0 -0
- {ipmg-2.4.0 → ipmg-3.1.0}/src/ipmg/web/manager.py +0 -0
- {ipmg-2.4.0 → ipmg-3.1.0}/src/ipmg/web/schemas.py +0 -0
- {ipmg-2.4.0 → ipmg-3.1.0}/src/ipmg/web/static/css/app.css +0 -0
- {ipmg-2.4.0 → ipmg-3.1.0}/src/ipmg/web/static/index.html +0 -0
- {ipmg-2.4.0 → ipmg-3.1.0}/src/ipmg/web/static/js/api.js +0 -0
- {ipmg-2.4.0 → ipmg-3.1.0}/src/ipmg/web/static/js/app.js +0 -0
- {ipmg-2.4.0 → ipmg-3.1.0}/src/ipmg/web/static/js/charts.js +0 -0
- {ipmg-2.4.0 → ipmg-3.1.0}/src/ipmg/web/static/js/demo.js +0 -0
- {ipmg-2.4.0 → ipmg-3.1.0}/src/ipmg/web/static/js/views.js +0 -0
- {ipmg-2.4.0 → ipmg-3.1.0}/src/ipmg.egg-info/dependency_links.txt +0 -0
- {ipmg-2.4.0 → ipmg-3.1.0}/src/ipmg.egg-info/entry_points.txt +0 -0
- {ipmg-2.4.0 → ipmg-3.1.0}/src/ipmg.egg-info/top_level.txt +0 -0
- {ipmg-2.4.0 → ipmg-3.1.0}/tests/test_api_contract.py +0 -0
- {ipmg-2.4.0 → ipmg-3.1.0}/tests/test_database_history.py +0 -0
- {ipmg-2.4.0 → ipmg-3.1.0}/tests/test_diff.py +0 -0
- {ipmg-2.4.0 → ipmg-3.1.0}/tests/test_diff_report.py +0 -0
- {ipmg-2.4.0 → ipmg-3.1.0}/tests/test_discover.py +0 -0
- {ipmg-2.4.0 → ipmg-3.1.0}/tests/test_engine.py +0 -0
- {ipmg-2.4.0 → ipmg-3.1.0}/tests/test_health.py +0 -0
- {ipmg-2.4.0 → ipmg-3.1.0}/tests/test_history_service.py +0 -0
- {ipmg-2.4.0 → ipmg-3.1.0}/tests/test_live.py +0 -0
- {ipmg-2.4.0 → ipmg-3.1.0}/tests/test_machine_output.py +0 -0
- {ipmg-2.4.0 → ipmg-3.1.0}/tests/test_notify.py +0 -0
- {ipmg-2.4.0 → ipmg-3.1.0}/tests/test_ping.py +0 -0
- {ipmg-2.4.0 → ipmg-3.1.0}/tests/test_ping_command.py +0 -0
- {ipmg-2.4.0 → ipmg-3.1.0}/tests/test_portscan.py +0 -0
- {ipmg-2.4.0 → ipmg-3.1.0}/tests/test_ui.py +0 -0
- {ipmg-2.4.0 → ipmg-3.1.0}/tests/test_utils.py +0 -0
- {ipmg-2.4.0 → ipmg-3.1.0}/tests/test_web_db.py +0 -0
- {ipmg-2.4.0 → ipmg-3.1.0}/tests/test_web_manager.py +0 -0
- {ipmg-2.4.0 → ipmg-3.1.0}/tests/test_web_server.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: ipmg
|
|
3
|
-
Version:
|
|
3
|
+
Version: 3.1.0
|
|
4
4
|
Summary: IP Management & Ping Monitoring CLI Tool
|
|
5
5
|
Author: Sameer Alam
|
|
6
6
|
Maintainer-email: Sameer Alam <sameeralam3127@gmail.com>
|
|
@@ -29,16 +29,19 @@ Classifier: Environment :: Console
|
|
|
29
29
|
Requires-Python: >=3.9
|
|
30
30
|
Description-Content-Type: text/markdown
|
|
31
31
|
License-File: LICENSE
|
|
32
|
-
Requires-Dist: pandas>=2.2.2
|
|
33
32
|
Requires-Dist: openpyxl>=3.1
|
|
34
33
|
Requires-Dist: rich>=13.0
|
|
35
|
-
Requires-Dist: fastapi>=0.110
|
|
36
|
-
Requires-Dist: pydantic>=2.7
|
|
37
|
-
Requires-Dist: uvicorn>=0.27
|
|
38
|
-
Requires-Dist: websockets>=12
|
|
39
|
-
Requires-Dist: python-multipart>=0.0.9
|
|
40
34
|
Requires-Dist: tomli>=2.0.1; python_version < "3.11"
|
|
35
|
+
Provides-Extra: web
|
|
36
|
+
Requires-Dist: fastapi>=0.110; extra == "web"
|
|
37
|
+
Requires-Dist: pydantic>=2.7; extra == "web"
|
|
38
|
+
Requires-Dist: uvicorn>=0.27; extra == "web"
|
|
39
|
+
Requires-Dist: websockets>=12; extra == "web"
|
|
40
|
+
Requires-Dist: python-multipart>=0.0.9; extra == "web"
|
|
41
|
+
Provides-Extra: all
|
|
42
|
+
Requires-Dist: ipmg[web]; extra == "all"
|
|
41
43
|
Provides-Extra: dev
|
|
44
|
+
Requires-Dist: ipmg[web]; extra == "dev"
|
|
42
45
|
Requires-Dist: pytest>=7.4; extra == "dev"
|
|
43
46
|
Requires-Dist: pytest-cov>=5.0; extra == "dev"
|
|
44
47
|
Requires-Dist: ruff>=0.4; extra == "dev"
|
|
@@ -151,11 +154,24 @@ The formula lives in
|
|
|
151
154
|
and is updated with every release. Upgrade with `brew upgrade ipmg`, remove
|
|
152
155
|
with `brew uninstall ipmg`.
|
|
153
156
|
|
|
154
|
-
**Windows —
|
|
157
|
+
**Windows — one command in PowerShell:**
|
|
155
158
|
|
|
156
159
|
```powershell
|
|
157
|
-
|
|
158
|
-
|
|
160
|
+
irm https://raw.githubusercontent.com/sameeralam3127/ipmg/main/install.ps1 | iex
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
It works in Windows PowerShell 5.1 and PowerShell 7 and needs no
|
|
164
|
+
administrator rights: it installs uv if it is missing, installs IPMG, adds it
|
|
165
|
+
to your user `PATH`, and checks that `ipmg --version` runs. To pin a release:
|
|
166
|
+
|
|
167
|
+
```powershell
|
|
168
|
+
& ([scriptblock]::Create((irm https://raw.githubusercontent.com/sameeralam3127/ipmg/main/install.ps1))) -Version 2.3.0
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
**Docker (amd64 and arm64):**
|
|
172
|
+
|
|
173
|
+
```bash
|
|
174
|
+
docker run --rm ghcr.io/sameeralam3127/ipmg --input 10.0.0.0/24
|
|
159
175
|
```
|
|
160
176
|
|
|
161
177
|
Then check it works, on any platform:
|
|
@@ -164,6 +180,38 @@ Then check it works, on any platform:
|
|
|
164
180
|
ipmg --version
|
|
165
181
|
```
|
|
166
182
|
|
|
183
|
+
### Running in Docker
|
|
184
|
+
|
|
185
|
+
The image carries `ping`, runs as an unprivileged user (uid 10001), and keeps
|
|
186
|
+
everything a scan writes (reports and the history database) in `/data`. Mount
|
|
187
|
+
a volume there to keep it between runs:
|
|
188
|
+
|
|
189
|
+
```bash
|
|
190
|
+
docker run --rm -v ipmg-data:/data ghcr.io/sameeralam3127/ipmg --input 10.0.0.0/24 --compare
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
Ping needs no added capability: it uses the unprivileged ICMP sockets that
|
|
194
|
+
Docker and containerd enable by default, so it also works with
|
|
195
|
+
`--cap-drop ALL` and under Kubernetes' restricted pod security profile. To
|
|
196
|
+
scan your LAN rather than the container's network, add `--network host`
|
|
197
|
+
(Linux).
|
|
198
|
+
|
|
199
|
+
For IPMG Web, use the [`compose.yaml`](https://github.com/sameeralam3127/ipmg/blob/main/compose.yaml)
|
|
200
|
+
in this repository. Inside a container IPMG Web must listen on `0.0.0.0` or
|
|
201
|
+
nothing outside the container could reach it, so the compose file starts it
|
|
202
|
+
with `web --host 0.0.0.0` and publishes the port on the host's `127.0.0.1`
|
|
203
|
+
only. Set a fixed `IPMG_WEB_TOKEN` in `.env`, then:
|
|
204
|
+
|
|
205
|
+
```bash
|
|
206
|
+
docker compose up -d # http://127.0.0.1:8080/#token=<your token>
|
|
207
|
+
docker compose run --rm scan --input 10.0.0.0/24 # CLI scans land in the same history
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
Publishing the port more widely (`8080:8080`) exposes IPMG Web on your
|
|
211
|
+
network over plain HTTP; put a reverse proxy with TLS in front first. Images
|
|
212
|
+
are tagged with the release version (`2.4.0`), the minor line (`2.4`), and
|
|
213
|
+
`latest`.
|
|
214
|
+
|
|
167
215
|
### Installing with pip
|
|
168
216
|
|
|
169
217
|
`pip install ipmg` works inside a virtual environment, and inside one only.
|
|
@@ -193,6 +241,22 @@ python3 -m venv ~/.venvs/ipmg
|
|
|
193
241
|
|
|
194
242
|
Please do not reach for `--break-system-packages`. It does what it says.
|
|
195
243
|
|
|
244
|
+
### What gets installed
|
|
245
|
+
|
|
246
|
+
`pip install ipmg` is the lean core: scanning, every report format
|
|
247
|
+
(Excel included), history, change detection, notifications, and exit codes.
|
|
248
|
+
It is about 9 MB and 7 packages. IPMG Web, the browser UI with its REST API
|
|
249
|
+
and Prometheus `/metrics`, is the optional `web` extra, because its server
|
|
250
|
+
stack is most of the weight:
|
|
251
|
+
|
|
252
|
+
```bash
|
|
253
|
+
pip install "ipmg[web]" # or: uv tool install "ipmg[web]", pipx install "ipmg[web]"
|
|
254
|
+
```
|
|
255
|
+
|
|
256
|
+
The one-line installers, Homebrew, and the Docker image install it with the
|
|
257
|
+
`web` extra already. Run `ipmg web` without it and IPMG tells you the command
|
|
258
|
+
that adds it.
|
|
259
|
+
|
|
196
260
|
### The one thing IPMG needs from your system
|
|
197
261
|
|
|
198
262
|
IPMG probes hosts with your operating system's `ping` command, and minimal
|
|
@@ -494,6 +558,57 @@ your own scripts. The [API guide](https://github.com/sameeralam3127/ipmg/blob/ma
|
|
|
494
558
|
covers authentication, errors, and worked `curl` examples; the running server
|
|
495
559
|
also serves interactive docs at `http://127.0.0.1:8080/docs`.
|
|
496
560
|
|
|
561
|
+
### Prometheus metrics
|
|
562
|
+
|
|
563
|
+
`ipmg web --metrics` serves `/metrics` in the Prometheus text format, so the
|
|
564
|
+
results of every scan, whether run from cron with the CLI or from the browser,
|
|
565
|
+
land in the dashboards and alerts you already have. It reports the latest
|
|
566
|
+
completed scan of each source, and it is off unless you pass the flag:
|
|
567
|
+
|
|
568
|
+
```bash
|
|
569
|
+
IPMG_WEB_TOKEN=$(cat /etc/ipmg/token) ipmg web --metrics --no-browser
|
|
570
|
+
```
|
|
571
|
+
|
|
572
|
+
```yaml
|
|
573
|
+
# prometheus.yml: the token is the same one the web UI uses
|
|
574
|
+
scrape_configs:
|
|
575
|
+
- job_name: ipmg
|
|
576
|
+
authorization:
|
|
577
|
+
credentials_file: /etc/prometheus/ipmg-token
|
|
578
|
+
static_configs:
|
|
579
|
+
- targets: ["127.0.0.1:8080"]
|
|
580
|
+
|
|
581
|
+
# an alert rule: a host that stopped answering
|
|
582
|
+
groups:
|
|
583
|
+
- name: ipmg
|
|
584
|
+
rules:
|
|
585
|
+
- alert: HostDown
|
|
586
|
+
expr: ipmg_host_up == 0
|
|
587
|
+
for: 10m
|
|
588
|
+
annotations:
|
|
589
|
+
summary: "{{ $labels.ip }} ({{ $labels.source }}) is not answering ping"
|
|
590
|
+
```
|
|
591
|
+
|
|
592
|
+
| Metric | Labels | Meaning |
|
|
593
|
+
| --- | --- | --- |
|
|
594
|
+
| `ipmg_host_up` | `source`, `ip` | 1 if the host answered, else 0 |
|
|
595
|
+
| `ipmg_host_latency_seconds` | `source`, `ip` | Round-trip time, for hosts that answered |
|
|
596
|
+
| `ipmg_host_open_ports` | `source`, `ip` | Open TCP ports, for scans run with `--scan-ports` |
|
|
597
|
+
| `ipmg_host_info` | `source`, `ip`, `hostname` | Always 1; carries the reverse DNS name |
|
|
598
|
+
| `ipmg_scan_hosts` | `source`, `status` | Hosts in the latest scan, by status |
|
|
599
|
+
| `ipmg_scan_duration_seconds` | `source` | How long the latest scan took |
|
|
600
|
+
| `ipmg_scan_timestamp_seconds` | `source` | When the latest scan finished; alert on `time() - this` to catch a stalled cron job |
|
|
601
|
+
| `ipmg_scans_total` | `source` | Completed scans stored for the source |
|
|
602
|
+
| `ipmg_metrics_sources_omitted` | | Sources left out by `--metrics-sources` |
|
|
603
|
+
| `ipmg_build_info` | `version` | Always 1 |
|
|
604
|
+
|
|
605
|
+
**Cardinality is bounded.** Host series carry only `source` and `ip`, never
|
|
606
|
+
the hostname or status, which change, so a rename does not start a new series.
|
|
607
|
+
Only the `--metrics-sources` most recently scanned sources are exported
|
|
608
|
+
(default 20), and a scan holds at most 65,536 hosts, so the page stays at
|
|
609
|
+
about `sources × hosts × 4` series at most. Give recurring scans a stable
|
|
610
|
+
input (the same file) so each one updates its source rather than adding one.
|
|
611
|
+
|
|
497
612
|
### On a server with no browser
|
|
498
613
|
|
|
499
614
|
On a Linux server with no display (e.g. accessed over plain SSH), IPMG detects
|
|
@@ -517,9 +632,11 @@ of it (see [Security](#security)).
|
|
|
517
632
|
|
|
518
633
|
Anywhere IPMG takes `--input`, you can give it any of these:
|
|
519
634
|
|
|
520
|
-
- **A single IP** — `8.8.8.8`
|
|
521
|
-
|
|
522
|
-
- **A
|
|
635
|
+
- **A single IP** — `8.8.8.8`, or IPv6 such as `2001:db8::1` or a link-local
|
|
636
|
+
`fe80::1%eth0`
|
|
637
|
+
- **A CIDR block** — `10.0.0.0/24`, or an IPv6 prefix up to 65,536 hosts
|
|
638
|
+
(`2001:db8::/112`)
|
|
639
|
+
- **A range** — `10.0.0.1-10.0.0.200` or `2001:db8::10-2001:db8::20`
|
|
523
640
|
- **A text file** — one IP or CIDR per line. Blank lines and `#` comments are
|
|
524
641
|
ignored, so you can annotate it:
|
|
525
642
|
|
|
@@ -529,7 +646,7 @@ Anywhere IPMG takes `--input`, you can give it any of these:
|
|
|
529
646
|
192.168.1.0/30
|
|
530
647
|
```
|
|
531
648
|
|
|
532
|
-
- **An Excel or CSV file** (`.xlsx`, `.
|
|
649
|
+
- **An Excel or CSV file** (`.xlsx`, `.csv`) — must contain a column
|
|
533
650
|
named `IP Address`. Cells can hold single addresses or CIDR blocks:
|
|
534
651
|
|
|
535
652
|
| IP Address |
|
|
@@ -559,6 +676,16 @@ ipmg --input targets.txt 10.0.0.0/30 10.0.0.5
|
|
|
559
676
|
ipmg --input targets.txt --input 10.0.0.5 # the same thing
|
|
560
677
|
```
|
|
561
678
|
|
|
679
|
+
IPv4 and IPv6 targets mix freely in one scan, and reports, history, and change
|
|
680
|
+
detection handle both. An IPv6 `/64` holds 2^64 addresses, far too many to
|
|
681
|
+
sweep one by one, so it is rejected with a pointer to `--discover ipv6`, which
|
|
682
|
+
finds the hosts on your local links through neighbour discovery instead:
|
|
683
|
+
|
|
684
|
+
```bash
|
|
685
|
+
ipmg --discover ipv6 # IPv6 neighbours on the local links
|
|
686
|
+
ipmg --discover all # the IPv4 /24 and the IPv6 neighbours together
|
|
687
|
+
```
|
|
688
|
+
|
|
562
689
|
Duplicate targets are removed automatically — across sources too, so a host
|
|
563
690
|
that is both in the file and on the command line is scanned once — and one scan
|
|
564
691
|
expands to at most 65,536 hosts in total. Larger CIDR blocks, ranges, or
|
|
@@ -708,7 +835,7 @@ cannot combine flags that exclude each other, such as `json` and `jsonl`.
|
|
|
708
835
|
| Flag | Default | Description |
|
|
709
836
|
| --- | --- | --- |
|
|
710
837
|
| `--input` | `ip_list.xlsx` | What to scan: one or more files (`.xlsx`, `.xls`, `.csv`, `.json`, `.txt`, `.list`), IPs, CIDR blocks, or ranges (`10.0.0.1-10.0.0.50`), merged and de-duplicated |
|
|
711
|
-
| `--discover` | off |
|
|
838
|
+
| `--discover [FAMILY]` | off | Scan this machine's networks instead: `ipv4` (the default) sweeps the local /24, `ipv6` finds link neighbours, `all` does both |
|
|
712
839
|
| `--output` | `results` | Report file name prefix |
|
|
713
840
|
| `--formats` | `xlsx` | One or more of `xlsx`, `csv`, `json`, `jsonl`, `md` (no file at all with `--json`/`--jsonl`) |
|
|
714
841
|
| `--json` | off | Print the finished scan to stdout as a JSON array, human output on stderr |
|
|
@@ -1,52 +1,3 @@
|
|
|
1
|
-
Metadata-Version: 2.4
|
|
2
|
-
Name: ipmg
|
|
3
|
-
Version: 2.4.0
|
|
4
|
-
Summary: IP Management & Ping Monitoring CLI Tool
|
|
5
|
-
Author: Sameer Alam
|
|
6
|
-
Maintainer-email: Sameer Alam <sameeralam3127@gmail.com>
|
|
7
|
-
License-Expression: MIT
|
|
8
|
-
Project-URL: Homepage, https://sameeralam3127.github.io/ipmg/
|
|
9
|
-
Project-URL: Documentation, https://github.com/sameeralam3127/ipmg#readme
|
|
10
|
-
Project-URL: Repository, https://github.com/sameeralam3127/ipmg
|
|
11
|
-
Project-URL: Issues, https://github.com/sameeralam3127/ipmg/issues
|
|
12
|
-
Project-URL: Changelog, https://github.com/sameeralam3127/ipmg/releases
|
|
13
|
-
Keywords: ip,network,ping,monitoring,cli,port-scan,network-scanner,ping-sweep,subnet-scanner,homelab,sysadmin
|
|
14
|
-
Classifier: Programming Language :: Python :: 3
|
|
15
|
-
Classifier: Programming Language :: Python :: 3 :: Only
|
|
16
|
-
Classifier: Programming Language :: Python :: 3.9
|
|
17
|
-
Classifier: Programming Language :: Python :: 3.10
|
|
18
|
-
Classifier: Programming Language :: Python :: 3.11
|
|
19
|
-
Classifier: Programming Language :: Python :: 3.12
|
|
20
|
-
Classifier: Programming Language :: Python :: 3.13
|
|
21
|
-
Classifier: Programming Language :: Python :: 3.14
|
|
22
|
-
Classifier: Topic :: Internet
|
|
23
|
-
Classifier: Topic :: System :: Networking
|
|
24
|
-
Classifier: Topic :: System :: Monitoring
|
|
25
|
-
Classifier: Intended Audience :: System Administrators
|
|
26
|
-
Classifier: Intended Audience :: Information Technology
|
|
27
|
-
Classifier: Operating System :: OS Independent
|
|
28
|
-
Classifier: Environment :: Console
|
|
29
|
-
Requires-Python: >=3.9
|
|
30
|
-
Description-Content-Type: text/markdown
|
|
31
|
-
License-File: LICENSE
|
|
32
|
-
Requires-Dist: pandas>=2.2.2
|
|
33
|
-
Requires-Dist: openpyxl>=3.1
|
|
34
|
-
Requires-Dist: rich>=13.0
|
|
35
|
-
Requires-Dist: fastapi>=0.110
|
|
36
|
-
Requires-Dist: pydantic>=2.7
|
|
37
|
-
Requires-Dist: uvicorn>=0.27
|
|
38
|
-
Requires-Dist: websockets>=12
|
|
39
|
-
Requires-Dist: python-multipart>=0.0.9
|
|
40
|
-
Requires-Dist: tomli>=2.0.1; python_version < "3.11"
|
|
41
|
-
Provides-Extra: dev
|
|
42
|
-
Requires-Dist: pytest>=7.4; extra == "dev"
|
|
43
|
-
Requires-Dist: pytest-cov>=5.0; extra == "dev"
|
|
44
|
-
Requires-Dist: ruff>=0.4; extra == "dev"
|
|
45
|
-
Requires-Dist: pre-commit>=3.5.0; extra == "dev"
|
|
46
|
-
Requires-Dist: python-semantic-release>=10.5.3; extra == "dev"
|
|
47
|
-
Requires-Dist: httpx>=0.27; extra == "dev"
|
|
48
|
-
Dynamic: license-file
|
|
49
|
-
|
|
50
1
|
# IPMG — IP Management & Ping Monitoring Tool
|
|
51
2
|
|
|
52
3
|
[](https://pypi.org/project/ipmg/)
|
|
@@ -151,11 +102,24 @@ The formula lives in
|
|
|
151
102
|
and is updated with every release. Upgrade with `brew upgrade ipmg`, remove
|
|
152
103
|
with `brew uninstall ipmg`.
|
|
153
104
|
|
|
154
|
-
**Windows —
|
|
105
|
+
**Windows — one command in PowerShell:**
|
|
155
106
|
|
|
156
107
|
```powershell
|
|
157
|
-
|
|
158
|
-
|
|
108
|
+
irm https://raw.githubusercontent.com/sameeralam3127/ipmg/main/install.ps1 | iex
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
It works in Windows PowerShell 5.1 and PowerShell 7 and needs no
|
|
112
|
+
administrator rights: it installs uv if it is missing, installs IPMG, adds it
|
|
113
|
+
to your user `PATH`, and checks that `ipmg --version` runs. To pin a release:
|
|
114
|
+
|
|
115
|
+
```powershell
|
|
116
|
+
& ([scriptblock]::Create((irm https://raw.githubusercontent.com/sameeralam3127/ipmg/main/install.ps1))) -Version 2.3.0
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
**Docker (amd64 and arm64):**
|
|
120
|
+
|
|
121
|
+
```bash
|
|
122
|
+
docker run --rm ghcr.io/sameeralam3127/ipmg --input 10.0.0.0/24
|
|
159
123
|
```
|
|
160
124
|
|
|
161
125
|
Then check it works, on any platform:
|
|
@@ -164,6 +128,38 @@ Then check it works, on any platform:
|
|
|
164
128
|
ipmg --version
|
|
165
129
|
```
|
|
166
130
|
|
|
131
|
+
### Running in Docker
|
|
132
|
+
|
|
133
|
+
The image carries `ping`, runs as an unprivileged user (uid 10001), and keeps
|
|
134
|
+
everything a scan writes (reports and the history database) in `/data`. Mount
|
|
135
|
+
a volume there to keep it between runs:
|
|
136
|
+
|
|
137
|
+
```bash
|
|
138
|
+
docker run --rm -v ipmg-data:/data ghcr.io/sameeralam3127/ipmg --input 10.0.0.0/24 --compare
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
Ping needs no added capability: it uses the unprivileged ICMP sockets that
|
|
142
|
+
Docker and containerd enable by default, so it also works with
|
|
143
|
+
`--cap-drop ALL` and under Kubernetes' restricted pod security profile. To
|
|
144
|
+
scan your LAN rather than the container's network, add `--network host`
|
|
145
|
+
(Linux).
|
|
146
|
+
|
|
147
|
+
For IPMG Web, use the [`compose.yaml`](https://github.com/sameeralam3127/ipmg/blob/main/compose.yaml)
|
|
148
|
+
in this repository. Inside a container IPMG Web must listen on `0.0.0.0` or
|
|
149
|
+
nothing outside the container could reach it, so the compose file starts it
|
|
150
|
+
with `web --host 0.0.0.0` and publishes the port on the host's `127.0.0.1`
|
|
151
|
+
only. Set a fixed `IPMG_WEB_TOKEN` in `.env`, then:
|
|
152
|
+
|
|
153
|
+
```bash
|
|
154
|
+
docker compose up -d # http://127.0.0.1:8080/#token=<your token>
|
|
155
|
+
docker compose run --rm scan --input 10.0.0.0/24 # CLI scans land in the same history
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
Publishing the port more widely (`8080:8080`) exposes IPMG Web on your
|
|
159
|
+
network over plain HTTP; put a reverse proxy with TLS in front first. Images
|
|
160
|
+
are tagged with the release version (`2.4.0`), the minor line (`2.4`), and
|
|
161
|
+
`latest`.
|
|
162
|
+
|
|
167
163
|
### Installing with pip
|
|
168
164
|
|
|
169
165
|
`pip install ipmg` works inside a virtual environment, and inside one only.
|
|
@@ -193,6 +189,22 @@ python3 -m venv ~/.venvs/ipmg
|
|
|
193
189
|
|
|
194
190
|
Please do not reach for `--break-system-packages`. It does what it says.
|
|
195
191
|
|
|
192
|
+
### What gets installed
|
|
193
|
+
|
|
194
|
+
`pip install ipmg` is the lean core: scanning, every report format
|
|
195
|
+
(Excel included), history, change detection, notifications, and exit codes.
|
|
196
|
+
It is about 9 MB and 7 packages. IPMG Web, the browser UI with its REST API
|
|
197
|
+
and Prometheus `/metrics`, is the optional `web` extra, because its server
|
|
198
|
+
stack is most of the weight:
|
|
199
|
+
|
|
200
|
+
```bash
|
|
201
|
+
pip install "ipmg[web]" # or: uv tool install "ipmg[web]", pipx install "ipmg[web]"
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
The one-line installers, Homebrew, and the Docker image install it with the
|
|
205
|
+
`web` extra already. Run `ipmg web` without it and IPMG tells you the command
|
|
206
|
+
that adds it.
|
|
207
|
+
|
|
196
208
|
### The one thing IPMG needs from your system
|
|
197
209
|
|
|
198
210
|
IPMG probes hosts with your operating system's `ping` command, and minimal
|
|
@@ -494,6 +506,57 @@ your own scripts. The [API guide](https://github.com/sameeralam3127/ipmg/blob/ma
|
|
|
494
506
|
covers authentication, errors, and worked `curl` examples; the running server
|
|
495
507
|
also serves interactive docs at `http://127.0.0.1:8080/docs`.
|
|
496
508
|
|
|
509
|
+
### Prometheus metrics
|
|
510
|
+
|
|
511
|
+
`ipmg web --metrics` serves `/metrics` in the Prometheus text format, so the
|
|
512
|
+
results of every scan, whether run from cron with the CLI or from the browser,
|
|
513
|
+
land in the dashboards and alerts you already have. It reports the latest
|
|
514
|
+
completed scan of each source, and it is off unless you pass the flag:
|
|
515
|
+
|
|
516
|
+
```bash
|
|
517
|
+
IPMG_WEB_TOKEN=$(cat /etc/ipmg/token) ipmg web --metrics --no-browser
|
|
518
|
+
```
|
|
519
|
+
|
|
520
|
+
```yaml
|
|
521
|
+
# prometheus.yml: the token is the same one the web UI uses
|
|
522
|
+
scrape_configs:
|
|
523
|
+
- job_name: ipmg
|
|
524
|
+
authorization:
|
|
525
|
+
credentials_file: /etc/prometheus/ipmg-token
|
|
526
|
+
static_configs:
|
|
527
|
+
- targets: ["127.0.0.1:8080"]
|
|
528
|
+
|
|
529
|
+
# an alert rule: a host that stopped answering
|
|
530
|
+
groups:
|
|
531
|
+
- name: ipmg
|
|
532
|
+
rules:
|
|
533
|
+
- alert: HostDown
|
|
534
|
+
expr: ipmg_host_up == 0
|
|
535
|
+
for: 10m
|
|
536
|
+
annotations:
|
|
537
|
+
summary: "{{ $labels.ip }} ({{ $labels.source }}) is not answering ping"
|
|
538
|
+
```
|
|
539
|
+
|
|
540
|
+
| Metric | Labels | Meaning |
|
|
541
|
+
| --- | --- | --- |
|
|
542
|
+
| `ipmg_host_up` | `source`, `ip` | 1 if the host answered, else 0 |
|
|
543
|
+
| `ipmg_host_latency_seconds` | `source`, `ip` | Round-trip time, for hosts that answered |
|
|
544
|
+
| `ipmg_host_open_ports` | `source`, `ip` | Open TCP ports, for scans run with `--scan-ports` |
|
|
545
|
+
| `ipmg_host_info` | `source`, `ip`, `hostname` | Always 1; carries the reverse DNS name |
|
|
546
|
+
| `ipmg_scan_hosts` | `source`, `status` | Hosts in the latest scan, by status |
|
|
547
|
+
| `ipmg_scan_duration_seconds` | `source` | How long the latest scan took |
|
|
548
|
+
| `ipmg_scan_timestamp_seconds` | `source` | When the latest scan finished; alert on `time() - this` to catch a stalled cron job |
|
|
549
|
+
| `ipmg_scans_total` | `source` | Completed scans stored for the source |
|
|
550
|
+
| `ipmg_metrics_sources_omitted` | | Sources left out by `--metrics-sources` |
|
|
551
|
+
| `ipmg_build_info` | `version` | Always 1 |
|
|
552
|
+
|
|
553
|
+
**Cardinality is bounded.** Host series carry only `source` and `ip`, never
|
|
554
|
+
the hostname or status, which change, so a rename does not start a new series.
|
|
555
|
+
Only the `--metrics-sources` most recently scanned sources are exported
|
|
556
|
+
(default 20), and a scan holds at most 65,536 hosts, so the page stays at
|
|
557
|
+
about `sources × hosts × 4` series at most. Give recurring scans a stable
|
|
558
|
+
input (the same file) so each one updates its source rather than adding one.
|
|
559
|
+
|
|
497
560
|
### On a server with no browser
|
|
498
561
|
|
|
499
562
|
On a Linux server with no display (e.g. accessed over plain SSH), IPMG detects
|
|
@@ -517,9 +580,11 @@ of it (see [Security](#security)).
|
|
|
517
580
|
|
|
518
581
|
Anywhere IPMG takes `--input`, you can give it any of these:
|
|
519
582
|
|
|
520
|
-
- **A single IP** — `8.8.8.8`
|
|
521
|
-
|
|
522
|
-
- **A
|
|
583
|
+
- **A single IP** — `8.8.8.8`, or IPv6 such as `2001:db8::1` or a link-local
|
|
584
|
+
`fe80::1%eth0`
|
|
585
|
+
- **A CIDR block** — `10.0.0.0/24`, or an IPv6 prefix up to 65,536 hosts
|
|
586
|
+
(`2001:db8::/112`)
|
|
587
|
+
- **A range** — `10.0.0.1-10.0.0.200` or `2001:db8::10-2001:db8::20`
|
|
523
588
|
- **A text file** — one IP or CIDR per line. Blank lines and `#` comments are
|
|
524
589
|
ignored, so you can annotate it:
|
|
525
590
|
|
|
@@ -529,7 +594,7 @@ Anywhere IPMG takes `--input`, you can give it any of these:
|
|
|
529
594
|
192.168.1.0/30
|
|
530
595
|
```
|
|
531
596
|
|
|
532
|
-
- **An Excel or CSV file** (`.xlsx`, `.
|
|
597
|
+
- **An Excel or CSV file** (`.xlsx`, `.csv`) — must contain a column
|
|
533
598
|
named `IP Address`. Cells can hold single addresses or CIDR blocks:
|
|
534
599
|
|
|
535
600
|
| IP Address |
|
|
@@ -559,6 +624,16 @@ ipmg --input targets.txt 10.0.0.0/30 10.0.0.5
|
|
|
559
624
|
ipmg --input targets.txt --input 10.0.0.5 # the same thing
|
|
560
625
|
```
|
|
561
626
|
|
|
627
|
+
IPv4 and IPv6 targets mix freely in one scan, and reports, history, and change
|
|
628
|
+
detection handle both. An IPv6 `/64` holds 2^64 addresses, far too many to
|
|
629
|
+
sweep one by one, so it is rejected with a pointer to `--discover ipv6`, which
|
|
630
|
+
finds the hosts on your local links through neighbour discovery instead:
|
|
631
|
+
|
|
632
|
+
```bash
|
|
633
|
+
ipmg --discover ipv6 # IPv6 neighbours on the local links
|
|
634
|
+
ipmg --discover all # the IPv4 /24 and the IPv6 neighbours together
|
|
635
|
+
```
|
|
636
|
+
|
|
562
637
|
Duplicate targets are removed automatically — across sources too, so a host
|
|
563
638
|
that is both in the file and on the command line is scanned once — and one scan
|
|
564
639
|
expands to at most 65,536 hosts in total. Larger CIDR blocks, ranges, or
|
|
@@ -708,7 +783,7 @@ cannot combine flags that exclude each other, such as `json` and `jsonl`.
|
|
|
708
783
|
| Flag | Default | Description |
|
|
709
784
|
| --- | --- | --- |
|
|
710
785
|
| `--input` | `ip_list.xlsx` | What to scan: one or more files (`.xlsx`, `.xls`, `.csv`, `.json`, `.txt`, `.list`), IPs, CIDR blocks, or ranges (`10.0.0.1-10.0.0.50`), merged and de-duplicated |
|
|
711
|
-
| `--discover` | off |
|
|
786
|
+
| `--discover [FAMILY]` | off | Scan this machine's networks instead: `ipv4` (the default) sweeps the local /24, `ipv6` finds link neighbours, `all` does both |
|
|
712
787
|
| `--output` | `results` | Report file name prefix |
|
|
713
788
|
| `--formats` | `xlsx` | One or more of `xlsx`, `csv`, `json`, `jsonl`, `md` (no file at all with `--json`/`--jsonl`) |
|
|
714
789
|
| `--json` | off | Print the finished scan to stdout as a JSON array, human output on stderr |
|
|
@@ -13,7 +13,7 @@ build-backend = "setuptools.build_meta"
|
|
|
13
13
|
|
|
14
14
|
[project]
|
|
15
15
|
name = "ipmg"
|
|
16
|
-
version = "
|
|
16
|
+
version = "3.1.0" # Managed automatically by semantic-release
|
|
17
17
|
description = "IP Management & Ping Monitoring CLI Tool"
|
|
18
18
|
readme = "README.md"
|
|
19
19
|
requires-python = ">=3.9"
|
|
@@ -48,15 +48,11 @@ classifiers = [
|
|
|
48
48
|
"Environment :: Console",
|
|
49
49
|
]
|
|
50
50
|
|
|
51
|
+
# The core is what a scan needs: the terminal UI, Excel reports and input,
|
|
52
|
+
# and the config file parser. IPMG Web's server stack is the `web` extra.
|
|
51
53
|
dependencies = [
|
|
52
|
-
"pandas>=2.2.2",
|
|
53
54
|
"openpyxl>=3.1",
|
|
54
55
|
"rich>=13.0",
|
|
55
|
-
"fastapi>=0.110",
|
|
56
|
-
"pydantic>=2.7",
|
|
57
|
-
"uvicorn>=0.27",
|
|
58
|
-
"websockets>=12",
|
|
59
|
-
"python-multipart>=0.0.9",
|
|
60
56
|
# tomllib is in the standard library from 3.11; tomli 2.x is the same
|
|
61
57
|
# parser with the same API, so the floor must not drop below it.
|
|
62
58
|
"tomli>=2.0.1; python_version < '3.11'",
|
|
@@ -70,7 +66,18 @@ Issues = "https://github.com/sameeralam3127/ipmg/issues"
|
|
|
70
66
|
Changelog = "https://github.com/sameeralam3127/ipmg/releases"
|
|
71
67
|
|
|
72
68
|
[project.optional-dependencies]
|
|
69
|
+
# IPMG Web (`ipmg web`): the browser UI, its REST API, and /metrics.
|
|
70
|
+
web = [
|
|
71
|
+
"fastapi>=0.110",
|
|
72
|
+
"pydantic>=2.7",
|
|
73
|
+
"uvicorn>=0.27",
|
|
74
|
+
"websockets>=12",
|
|
75
|
+
"python-multipart>=0.0.9",
|
|
76
|
+
]
|
|
77
|
+
# Everything; what the one-line installers and the Docker image install.
|
|
78
|
+
all = ["ipmg[web]"]
|
|
73
79
|
dev = [
|
|
80
|
+
"ipmg[web]",
|
|
74
81
|
"pytest>=7.4",
|
|
75
82
|
"pytest-cov>=5.0",
|
|
76
83
|
"ruff>=0.4",
|
|
@@ -89,6 +96,7 @@ ipmg = "ipmg.cli.commands:run"
|
|
|
89
96
|
|
|
90
97
|
[dependency-groups]
|
|
91
98
|
dev = [
|
|
99
|
+
"ipmg[web]",
|
|
92
100
|
"pytest>=7.4",
|
|
93
101
|
"pytest-cov>=5.0",
|
|
94
102
|
"ruff>=0.4",
|
|
@@ -25,6 +25,17 @@ from ipmg.services.history_service import HistoryService
|
|
|
25
25
|
from ipmg.services.scan_service import machine_output, run_scan
|
|
26
26
|
from ipmg.utils.helpers import configure_logging
|
|
27
27
|
|
|
28
|
+
#: Top-level modules of the `web` extra, to tell a missing extra from a bug.
|
|
29
|
+
WEB_PACKAGES = frozenset({"fastapi", "pydantic", "starlette", "uvicorn", "websockets", "multipart"})
|
|
30
|
+
|
|
31
|
+
WEB_EXTRA_MISSING = (
|
|
32
|
+
"IPMG Web needs the optional 'web' packages, which this install does not have. "
|
|
33
|
+
"Add them with whichever tool installed IPMG:\n"
|
|
34
|
+
" uv tool install --force 'ipmg[web]'\n"
|
|
35
|
+
" pipx install --force 'ipmg[web]'\n"
|
|
36
|
+
" pip install 'ipmg[web]'"
|
|
37
|
+
)
|
|
38
|
+
|
|
28
39
|
EXIT_OK = 0
|
|
29
40
|
EXIT_ERROR = 1
|
|
30
41
|
EXIT_CHANGES_DETECTED = 2
|
|
@@ -57,14 +68,21 @@ def _web_command(argv: List[str]) -> int:
|
|
|
57
68
|
ui.header("web")
|
|
58
69
|
print_disclaimer_once()
|
|
59
70
|
|
|
60
|
-
# Imported lazily so plain CLI scans do not pay for the web stack
|
|
61
|
-
|
|
71
|
+
# Imported lazily so plain CLI scans do not pay for the web stack, which
|
|
72
|
+
# is the optional `web` extra.
|
|
73
|
+
try:
|
|
74
|
+
from ipmg.web.server import run_dashboard
|
|
75
|
+
except ModuleNotFoundError as exc:
|
|
76
|
+
if exc.name not in WEB_PACKAGES:
|
|
77
|
+
raise
|
|
78
|
+
raise IPMGError(WEB_EXTRA_MISSING) from exc
|
|
62
79
|
|
|
63
80
|
run_dashboard(
|
|
64
81
|
host=args.host,
|
|
65
82
|
port=args.port,
|
|
66
83
|
open_browser=not args.no_browser,
|
|
67
84
|
db_path=args.db,
|
|
85
|
+
metrics_sources=max(args.metrics_sources, 1) if args.metrics else None,
|
|
68
86
|
)
|
|
69
87
|
return EXIT_OK
|
|
70
88
|
|
|
@@ -264,6 +264,11 @@ def _coerce(action: argparse.Action, key: str, value: Any, path: Path) -> Any:
|
|
|
264
264
|
raise ConfigError(f"{where} must be true or false, not {_kind(value)}.")
|
|
265
265
|
return action.const if value else _KEEP
|
|
266
266
|
|
|
267
|
+
if action.nargs == "?" and isinstance(value, bool):
|
|
268
|
+
# A flag whose value is optional (--discover [FAMILY]): true is the bare
|
|
269
|
+
# flag, so "discover = true" keeps meaning what it did as a switch.
|
|
270
|
+
return action.const if value else _KEEP
|
|
271
|
+
|
|
267
272
|
if action.nargs in ("+", "*"):
|
|
268
273
|
values = value if isinstance(value, list) else [value]
|
|
269
274
|
if not values:
|
|
@@ -6,6 +6,7 @@ import argparse
|
|
|
6
6
|
|
|
7
7
|
from ipmg import __version__
|
|
8
8
|
from ipmg.cli.config import add_config_arguments, config_help
|
|
9
|
+
from ipmg.core.discovery import DISCOVERY_FAMILIES
|
|
9
10
|
from ipmg.core.portscan import DEFAULT_PORTS, parse_port_list
|
|
10
11
|
from ipmg.infrastructure.file_io import DEFAULT_INPUT_FILE
|
|
11
12
|
from ipmg.infrastructure.incremental import (
|
|
@@ -26,6 +27,7 @@ from ipmg.infrastructure.notify import (
|
|
|
26
27
|
)
|
|
27
28
|
from ipmg.reporting.diff_report import DIFF_FORMATS
|
|
28
29
|
from ipmg.reporting.live import DEFAULT_REFRESH_S, MAX_REFRESH_S, MIN_REFRESH_S
|
|
30
|
+
from ipmg.reporting.metrics import DEFAULT_MAX_SOURCES
|
|
29
31
|
|
|
30
32
|
PROG = "IPMG - IP Management & Ping Monitoring Tool"
|
|
31
33
|
|
|
@@ -259,8 +261,16 @@ def build_parser() -> argparse.ArgumentParser:
|
|
|
259
261
|
)
|
|
260
262
|
parser.add_argument(
|
|
261
263
|
"--discover",
|
|
262
|
-
|
|
263
|
-
|
|
264
|
+
nargs="?",
|
|
265
|
+
const="ipv4",
|
|
266
|
+
default=None,
|
|
267
|
+
choices=DISCOVERY_FAMILIES,
|
|
268
|
+
metavar="FAMILY",
|
|
269
|
+
help=(
|
|
270
|
+
"Scan this machine's networks instead of --input: ipv4 (the default) "
|
|
271
|
+
"sweeps the local /24, ipv6 finds hosts on the local links through "
|
|
272
|
+
"neighbour discovery, all does both."
|
|
273
|
+
),
|
|
264
274
|
)
|
|
265
275
|
parser.add_argument(
|
|
266
276
|
"--resolve",
|
|
@@ -449,6 +459,24 @@ def build_web_parser() -> argparse.ArgumentParser:
|
|
|
449
459
|
action="store_true",
|
|
450
460
|
help="Do not open IPMG Web in a browser automatically.",
|
|
451
461
|
)
|
|
462
|
+
parser.add_argument(
|
|
463
|
+
"--metrics",
|
|
464
|
+
action="store_true",
|
|
465
|
+
help=(
|
|
466
|
+
"Serve Prometheus metrics at /metrics for the latest completed scan of "
|
|
467
|
+
"each source (off by default; needs the access token)."
|
|
468
|
+
),
|
|
469
|
+
)
|
|
470
|
+
parser.add_argument(
|
|
471
|
+
"--metrics-sources",
|
|
472
|
+
type=int,
|
|
473
|
+
default=DEFAULT_MAX_SOURCES,
|
|
474
|
+
metavar="N",
|
|
475
|
+
help=(
|
|
476
|
+
"Export only the N most recently scanned sources, which bounds the "
|
|
477
|
+
f"number of series (default: {DEFAULT_MAX_SOURCES})."
|
|
478
|
+
),
|
|
479
|
+
)
|
|
452
480
|
_add_database_argument(parser)
|
|
453
481
|
parser.add_argument("--verbose", action="store_true")
|
|
454
482
|
return parser
|
|
@@ -256,13 +256,17 @@ def _index_by_hostname(snapshots: Mapping[str, HostSnapshot]) -> Dict[str, Set[s
|
|
|
256
256
|
return index
|
|
257
257
|
|
|
258
258
|
|
|
259
|
-
def ip_sort_key(value: str) -> Tuple[int, int, str]:
|
|
260
|
-
"""Sort key
|
|
259
|
+
def ip_sort_key(value: str) -> Tuple[int, int, int, str]:
|
|
260
|
+
"""Sort key: IPv4 in numeric order, then IPv6, then anything unparsable.
|
|
261
|
+
|
|
262
|
+
The family comes before the number, or ``::1`` (the integer 1) would sort
|
|
263
|
+
ahead of every IPv4 address. Shared with the DB layer.
|
|
264
|
+
"""
|
|
261
265
|
try:
|
|
262
266
|
address = ipaddress.ip_address(value)
|
|
263
267
|
except ValueError:
|
|
264
|
-
return (1, 0, value)
|
|
265
|
-
return (0, int(address),
|
|
268
|
+
return (1, 0, 0, value)
|
|
269
|
+
return (0, address.version, int(address), value)
|
|
266
270
|
|
|
267
271
|
|
|
268
272
|
def _sort_changes(changes: Sequence[HostChange]) -> Tuple[HostChange, ...]:
|