ipmg 2.4.0__tar.gz → 3.0.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.0.0}/PKG-INFO +91 -15
- ipmg-2.4.0/src/ipmg.egg-info/PKG-INFO → ipmg-3.0.0/README.md +81 -57
- {ipmg-2.4.0 → ipmg-3.0.0}/pyproject.toml +15 -7
- {ipmg-2.4.0 → ipmg-3.0.0}/src/ipmg/__init__.py +1 -1
- {ipmg-2.4.0 → ipmg-3.0.0}/src/ipmg/cli/commands.py +19 -2
- {ipmg-2.4.0 → ipmg-3.0.0}/src/ipmg/cli/config.py +5 -0
- {ipmg-2.4.0 → ipmg-3.0.0}/src/ipmg/cli/parser.py +11 -2
- {ipmg-2.4.0 → ipmg-3.0.0}/src/ipmg/core/diff.py +8 -4
- ipmg-3.0.0/src/ipmg/core/discovery.py +254 -0
- {ipmg-2.4.0 → ipmg-3.0.0}/src/ipmg/core/ping.py +37 -1
- {ipmg-2.4.0 → ipmg-3.0.0}/src/ipmg/core/portscan.py +6 -2
- {ipmg-2.4.0 → ipmg-3.0.0}/src/ipmg/infrastructure/file_io.py +180 -66
- {ipmg-2.4.0 → ipmg-3.0.0}/src/ipmg/infrastructure/incremental.py +57 -31
- ipmg-3.0.0/src/ipmg/reporting/frames.py +74 -0
- {ipmg-2.4.0 → ipmg-3.0.0}/src/ipmg/reporting/machine.py +5 -7
- {ipmg-2.4.0 → ipmg-3.0.0}/src/ipmg/reporting/summary.py +12 -9
- {ipmg-2.4.0 → ipmg-3.0.0}/src/ipmg/services/scan_service.py +9 -8
- {ipmg-2.4.0 → ipmg-3.0.0}/src/ipmg/web/app.py +9 -19
- ipmg-2.4.0/README.md → ipmg-3.0.0/src/ipmg.egg-info/PKG-INFO +133 -8
- {ipmg-2.4.0 → ipmg-3.0.0}/src/ipmg.egg-info/SOURCES.txt +1 -0
- {ipmg-2.4.0 → ipmg-3.0.0}/src/ipmg.egg-info/requires.txt +11 -6
- {ipmg-2.4.0 → ipmg-3.0.0}/tests/test_commands.py +30 -0
- {ipmg-2.4.0 → ipmg-3.0.0}/tests/test_config.py +16 -0
- {ipmg-2.4.0 → ipmg-3.0.0}/tests/test_file_io.py +61 -19
- {ipmg-2.4.0 → ipmg-3.0.0}/tests/test_incremental.py +7 -4
- ipmg-3.0.0/tests/test_ipv6.py +275 -0
- {ipmg-2.4.0 → ipmg-3.0.0}/tests/test_parser.py +20 -0
- {ipmg-2.4.0 → ipmg-3.0.0}/tests/test_scan_service.py +11 -8
- {ipmg-2.4.0 → ipmg-3.0.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.0.0}/LICENSE +0 -0
- {ipmg-2.4.0 → ipmg-3.0.0}/setup.cfg +0 -0
- {ipmg-2.4.0 → ipmg-3.0.0}/src/ipmg/__main__.py +0 -0
- {ipmg-2.4.0 → ipmg-3.0.0}/src/ipmg/cli/__init__.py +0 -0
- {ipmg-2.4.0 → ipmg-3.0.0}/src/ipmg/core/__init__.py +0 -0
- {ipmg-2.4.0 → ipmg-3.0.0}/src/ipmg/core/engine.py +0 -0
- {ipmg-2.4.0 → ipmg-3.0.0}/src/ipmg/core/health.py +0 -0
- {ipmg-2.4.0 → ipmg-3.0.0}/src/ipmg/core/security.py +0 -0
- {ipmg-2.4.0 → ipmg-3.0.0}/src/ipmg/exceptions.py +0 -0
- {ipmg-2.4.0 → ipmg-3.0.0}/src/ipmg/infrastructure/__init__.py +0 -0
- {ipmg-2.4.0 → ipmg-3.0.0}/src/ipmg/infrastructure/database.py +0 -0
- {ipmg-2.4.0 → ipmg-3.0.0}/src/ipmg/infrastructure/notify.py +0 -0
- {ipmg-2.4.0 → ipmg-3.0.0}/src/ipmg/reporting/__init__.py +0 -0
- {ipmg-2.4.0 → ipmg-3.0.0}/src/ipmg/reporting/diff_report.py +0 -0
- {ipmg-2.4.0 → ipmg-3.0.0}/src/ipmg/reporting/live.py +0 -0
- {ipmg-2.4.0 → ipmg-3.0.0}/src/ipmg/reporting/ui.py +0 -0
- {ipmg-2.4.0 → ipmg-3.0.0}/src/ipmg/services/__init__.py +0 -0
- {ipmg-2.4.0 → ipmg-3.0.0}/src/ipmg/services/history_service.py +0 -0
- {ipmg-2.4.0 → ipmg-3.0.0}/src/ipmg/utils/__init__.py +0 -0
- {ipmg-2.4.0 → ipmg-3.0.0}/src/ipmg/utils/helpers.py +0 -0
- {ipmg-2.4.0 → ipmg-3.0.0}/src/ipmg/web/__init__.py +0 -0
- {ipmg-2.4.0 → ipmg-3.0.0}/src/ipmg/web/db.py +0 -0
- {ipmg-2.4.0 → ipmg-3.0.0}/src/ipmg/web/manager.py +0 -0
- {ipmg-2.4.0 → ipmg-3.0.0}/src/ipmg/web/schemas.py +0 -0
- {ipmg-2.4.0 → ipmg-3.0.0}/src/ipmg/web/server.py +0 -0
- {ipmg-2.4.0 → ipmg-3.0.0}/src/ipmg/web/static/css/app.css +0 -0
- {ipmg-2.4.0 → ipmg-3.0.0}/src/ipmg/web/static/index.html +0 -0
- {ipmg-2.4.0 → ipmg-3.0.0}/src/ipmg/web/static/js/api.js +0 -0
- {ipmg-2.4.0 → ipmg-3.0.0}/src/ipmg/web/static/js/app.js +0 -0
- {ipmg-2.4.0 → ipmg-3.0.0}/src/ipmg/web/static/js/charts.js +0 -0
- {ipmg-2.4.0 → ipmg-3.0.0}/src/ipmg/web/static/js/demo.js +0 -0
- {ipmg-2.4.0 → ipmg-3.0.0}/src/ipmg/web/static/js/views.js +0 -0
- {ipmg-2.4.0 → ipmg-3.0.0}/src/ipmg.egg-info/dependency_links.txt +0 -0
- {ipmg-2.4.0 → ipmg-3.0.0}/src/ipmg.egg-info/entry_points.txt +0 -0
- {ipmg-2.4.0 → ipmg-3.0.0}/src/ipmg.egg-info/top_level.txt +0 -0
- {ipmg-2.4.0 → ipmg-3.0.0}/tests/test_api_contract.py +0 -0
- {ipmg-2.4.0 → ipmg-3.0.0}/tests/test_database_history.py +0 -0
- {ipmg-2.4.0 → ipmg-3.0.0}/tests/test_diff.py +0 -0
- {ipmg-2.4.0 → ipmg-3.0.0}/tests/test_diff_report.py +0 -0
- {ipmg-2.4.0 → ipmg-3.0.0}/tests/test_discover.py +0 -0
- {ipmg-2.4.0 → ipmg-3.0.0}/tests/test_engine.py +0 -0
- {ipmg-2.4.0 → ipmg-3.0.0}/tests/test_health.py +0 -0
- {ipmg-2.4.0 → ipmg-3.0.0}/tests/test_history_service.py +0 -0
- {ipmg-2.4.0 → ipmg-3.0.0}/tests/test_live.py +0 -0
- {ipmg-2.4.0 → ipmg-3.0.0}/tests/test_machine_output.py +0 -0
- {ipmg-2.4.0 → ipmg-3.0.0}/tests/test_notify.py +0 -0
- {ipmg-2.4.0 → ipmg-3.0.0}/tests/test_ping.py +0 -0
- {ipmg-2.4.0 → ipmg-3.0.0}/tests/test_ping_command.py +0 -0
- {ipmg-2.4.0 → ipmg-3.0.0}/tests/test_portscan.py +0 -0
- {ipmg-2.4.0 → ipmg-3.0.0}/tests/test_ui.py +0 -0
- {ipmg-2.4.0 → ipmg-3.0.0}/tests/test_utils.py +0 -0
- {ipmg-2.4.0 → ipmg-3.0.0}/tests/test_web_db.py +0 -0
- {ipmg-2.4.0 → ipmg-3.0.0}/tests/test_web_manager.py +0 -0
- {ipmg-2.4.0 → ipmg-3.0.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.0.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
|
|
@@ -517,9 +581,11 @@ of it (see [Security](#security)).
|
|
|
517
581
|
|
|
518
582
|
Anywhere IPMG takes `--input`, you can give it any of these:
|
|
519
583
|
|
|
520
|
-
- **A single IP** — `8.8.8.8`
|
|
521
|
-
|
|
522
|
-
- **A
|
|
584
|
+
- **A single IP** — `8.8.8.8`, or IPv6 such as `2001:db8::1` or a link-local
|
|
585
|
+
`fe80::1%eth0`
|
|
586
|
+
- **A CIDR block** — `10.0.0.0/24`, or an IPv6 prefix up to 65,536 hosts
|
|
587
|
+
(`2001:db8::/112`)
|
|
588
|
+
- **A range** — `10.0.0.1-10.0.0.200` or `2001:db8::10-2001:db8::20`
|
|
523
589
|
- **A text file** — one IP or CIDR per line. Blank lines and `#` comments are
|
|
524
590
|
ignored, so you can annotate it:
|
|
525
591
|
|
|
@@ -529,7 +595,7 @@ Anywhere IPMG takes `--input`, you can give it any of these:
|
|
|
529
595
|
192.168.1.0/30
|
|
530
596
|
```
|
|
531
597
|
|
|
532
|
-
- **An Excel or CSV file** (`.xlsx`, `.
|
|
598
|
+
- **An Excel or CSV file** (`.xlsx`, `.csv`) — must contain a column
|
|
533
599
|
named `IP Address`. Cells can hold single addresses or CIDR blocks:
|
|
534
600
|
|
|
535
601
|
| IP Address |
|
|
@@ -559,6 +625,16 @@ ipmg --input targets.txt 10.0.0.0/30 10.0.0.5
|
|
|
559
625
|
ipmg --input targets.txt --input 10.0.0.5 # the same thing
|
|
560
626
|
```
|
|
561
627
|
|
|
628
|
+
IPv4 and IPv6 targets mix freely in one scan, and reports, history, and change
|
|
629
|
+
detection handle both. An IPv6 `/64` holds 2^64 addresses, far too many to
|
|
630
|
+
sweep one by one, so it is rejected with a pointer to `--discover ipv6`, which
|
|
631
|
+
finds the hosts on your local links through neighbour discovery instead:
|
|
632
|
+
|
|
633
|
+
```bash
|
|
634
|
+
ipmg --discover ipv6 # IPv6 neighbours on the local links
|
|
635
|
+
ipmg --discover all # the IPv4 /24 and the IPv6 neighbours together
|
|
636
|
+
```
|
|
637
|
+
|
|
562
638
|
Duplicate targets are removed automatically — across sources too, so a host
|
|
563
639
|
that is both in the file and on the command line is scanned once — and one scan
|
|
564
640
|
expands to at most 65,536 hosts in total. Larger CIDR blocks, ranges, or
|
|
@@ -708,7 +784,7 @@ cannot combine flags that exclude each other, such as `json` and `jsonl`.
|
|
|
708
784
|
| Flag | Default | Description |
|
|
709
785
|
| --- | --- | --- |
|
|
710
786
|
| `--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 |
|
|
787
|
+
| `--discover [FAMILY]` | off | Scan this machine's networks instead: `ipv4` (the default) sweeps the local /24, `ipv6` finds link neighbours, `all` does both |
|
|
712
788
|
| `--output` | `results` | Report file name prefix |
|
|
713
789
|
| `--formats` | `xlsx` | One or more of `xlsx`, `csv`, `json`, `jsonl`, `md` (no file at all with `--json`/`--jsonl`) |
|
|
714
790
|
| `--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
|
|
@@ -517,9 +529,11 @@ of it (see [Security](#security)).
|
|
|
517
529
|
|
|
518
530
|
Anywhere IPMG takes `--input`, you can give it any of these:
|
|
519
531
|
|
|
520
|
-
- **A single IP** — `8.8.8.8`
|
|
521
|
-
|
|
522
|
-
- **A
|
|
532
|
+
- **A single IP** — `8.8.8.8`, or IPv6 such as `2001:db8::1` or a link-local
|
|
533
|
+
`fe80::1%eth0`
|
|
534
|
+
- **A CIDR block** — `10.0.0.0/24`, or an IPv6 prefix up to 65,536 hosts
|
|
535
|
+
(`2001:db8::/112`)
|
|
536
|
+
- **A range** — `10.0.0.1-10.0.0.200` or `2001:db8::10-2001:db8::20`
|
|
523
537
|
- **A text file** — one IP or CIDR per line. Blank lines and `#` comments are
|
|
524
538
|
ignored, so you can annotate it:
|
|
525
539
|
|
|
@@ -529,7 +543,7 @@ Anywhere IPMG takes `--input`, you can give it any of these:
|
|
|
529
543
|
192.168.1.0/30
|
|
530
544
|
```
|
|
531
545
|
|
|
532
|
-
- **An Excel or CSV file** (`.xlsx`, `.
|
|
546
|
+
- **An Excel or CSV file** (`.xlsx`, `.csv`) — must contain a column
|
|
533
547
|
named `IP Address`. Cells can hold single addresses or CIDR blocks:
|
|
534
548
|
|
|
535
549
|
| IP Address |
|
|
@@ -559,6 +573,16 @@ ipmg --input targets.txt 10.0.0.0/30 10.0.0.5
|
|
|
559
573
|
ipmg --input targets.txt --input 10.0.0.5 # the same thing
|
|
560
574
|
```
|
|
561
575
|
|
|
576
|
+
IPv4 and IPv6 targets mix freely in one scan, and reports, history, and change
|
|
577
|
+
detection handle both. An IPv6 `/64` holds 2^64 addresses, far too many to
|
|
578
|
+
sweep one by one, so it is rejected with a pointer to `--discover ipv6`, which
|
|
579
|
+
finds the hosts on your local links through neighbour discovery instead:
|
|
580
|
+
|
|
581
|
+
```bash
|
|
582
|
+
ipmg --discover ipv6 # IPv6 neighbours on the local links
|
|
583
|
+
ipmg --discover all # the IPv4 /24 and the IPv6 neighbours together
|
|
584
|
+
```
|
|
585
|
+
|
|
562
586
|
Duplicate targets are removed automatically — across sources too, so a host
|
|
563
587
|
that is both in the file and on the command line is scanned once — and one scan
|
|
564
588
|
expands to at most 65,536 hosts in total. Larger CIDR blocks, ranges, or
|
|
@@ -708,7 +732,7 @@ cannot combine flags that exclude each other, such as `json` and `jsonl`.
|
|
|
708
732
|
| Flag | Default | Description |
|
|
709
733
|
| --- | --- | --- |
|
|
710
734
|
| `--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 |
|
|
735
|
+
| `--discover [FAMILY]` | off | Scan this machine's networks instead: `ipv4` (the default) sweeps the local /24, `ipv6` finds link neighbours, `all` does both |
|
|
712
736
|
| `--output` | `results` | Report file name prefix |
|
|
713
737
|
| `--formats` | `xlsx` | One or more of `xlsx`, `csv`, `json`, `jsonl`, `md` (no file at all with `--json`/`--jsonl`) |
|
|
714
738
|
| `--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.0.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,8 +68,14 @@ 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,
|
|
@@ -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 (
|
|
@@ -259,8 +260,16 @@ def build_parser() -> argparse.ArgumentParser:
|
|
|
259
260
|
)
|
|
260
261
|
parser.add_argument(
|
|
261
262
|
"--discover",
|
|
262
|
-
|
|
263
|
-
|
|
263
|
+
nargs="?",
|
|
264
|
+
const="ipv4",
|
|
265
|
+
default=None,
|
|
266
|
+
choices=DISCOVERY_FAMILIES,
|
|
267
|
+
metavar="FAMILY",
|
|
268
|
+
help=(
|
|
269
|
+
"Scan this machine's networks instead of --input: ipv4 (the default) "
|
|
270
|
+
"sweeps the local /24, ipv6 finds hosts on the local links through "
|
|
271
|
+
"neighbour discovery, all does both."
|
|
272
|
+
),
|
|
264
273
|
)
|
|
265
274
|
parser.add_argument(
|
|
266
275
|
"--resolve",
|
|
@@ -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, ...]:
|