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.
Files changed (85) hide show
  1. {ipmg-2.4.0 → ipmg-3.0.0}/PKG-INFO +91 -15
  2. ipmg-2.4.0/src/ipmg.egg-info/PKG-INFO → ipmg-3.0.0/README.md +81 -57
  3. {ipmg-2.4.0 → ipmg-3.0.0}/pyproject.toml +15 -7
  4. {ipmg-2.4.0 → ipmg-3.0.0}/src/ipmg/__init__.py +1 -1
  5. {ipmg-2.4.0 → ipmg-3.0.0}/src/ipmg/cli/commands.py +19 -2
  6. {ipmg-2.4.0 → ipmg-3.0.0}/src/ipmg/cli/config.py +5 -0
  7. {ipmg-2.4.0 → ipmg-3.0.0}/src/ipmg/cli/parser.py +11 -2
  8. {ipmg-2.4.0 → ipmg-3.0.0}/src/ipmg/core/diff.py +8 -4
  9. ipmg-3.0.0/src/ipmg/core/discovery.py +254 -0
  10. {ipmg-2.4.0 → ipmg-3.0.0}/src/ipmg/core/ping.py +37 -1
  11. {ipmg-2.4.0 → ipmg-3.0.0}/src/ipmg/core/portscan.py +6 -2
  12. {ipmg-2.4.0 → ipmg-3.0.0}/src/ipmg/infrastructure/file_io.py +180 -66
  13. {ipmg-2.4.0 → ipmg-3.0.0}/src/ipmg/infrastructure/incremental.py +57 -31
  14. ipmg-3.0.0/src/ipmg/reporting/frames.py +74 -0
  15. {ipmg-2.4.0 → ipmg-3.0.0}/src/ipmg/reporting/machine.py +5 -7
  16. {ipmg-2.4.0 → ipmg-3.0.0}/src/ipmg/reporting/summary.py +12 -9
  17. {ipmg-2.4.0 → ipmg-3.0.0}/src/ipmg/services/scan_service.py +9 -8
  18. {ipmg-2.4.0 → ipmg-3.0.0}/src/ipmg/web/app.py +9 -19
  19. ipmg-2.4.0/README.md → ipmg-3.0.0/src/ipmg.egg-info/PKG-INFO +133 -8
  20. {ipmg-2.4.0 → ipmg-3.0.0}/src/ipmg.egg-info/SOURCES.txt +1 -0
  21. {ipmg-2.4.0 → ipmg-3.0.0}/src/ipmg.egg-info/requires.txt +11 -6
  22. {ipmg-2.4.0 → ipmg-3.0.0}/tests/test_commands.py +30 -0
  23. {ipmg-2.4.0 → ipmg-3.0.0}/tests/test_config.py +16 -0
  24. {ipmg-2.4.0 → ipmg-3.0.0}/tests/test_file_io.py +61 -19
  25. {ipmg-2.4.0 → ipmg-3.0.0}/tests/test_incremental.py +7 -4
  26. ipmg-3.0.0/tests/test_ipv6.py +275 -0
  27. {ipmg-2.4.0 → ipmg-3.0.0}/tests/test_parser.py +20 -0
  28. {ipmg-2.4.0 → ipmg-3.0.0}/tests/test_scan_service.py +11 -8
  29. {ipmg-2.4.0 → ipmg-3.0.0}/tests/test_web_api.py +6 -5
  30. ipmg-2.4.0/src/ipmg/core/discovery.py +0 -75
  31. ipmg-2.4.0/src/ipmg/reporting/frames.py +0 -45
  32. {ipmg-2.4.0 → ipmg-3.0.0}/LICENSE +0 -0
  33. {ipmg-2.4.0 → ipmg-3.0.0}/setup.cfg +0 -0
  34. {ipmg-2.4.0 → ipmg-3.0.0}/src/ipmg/__main__.py +0 -0
  35. {ipmg-2.4.0 → ipmg-3.0.0}/src/ipmg/cli/__init__.py +0 -0
  36. {ipmg-2.4.0 → ipmg-3.0.0}/src/ipmg/core/__init__.py +0 -0
  37. {ipmg-2.4.0 → ipmg-3.0.0}/src/ipmg/core/engine.py +0 -0
  38. {ipmg-2.4.0 → ipmg-3.0.0}/src/ipmg/core/health.py +0 -0
  39. {ipmg-2.4.0 → ipmg-3.0.0}/src/ipmg/core/security.py +0 -0
  40. {ipmg-2.4.0 → ipmg-3.0.0}/src/ipmg/exceptions.py +0 -0
  41. {ipmg-2.4.0 → ipmg-3.0.0}/src/ipmg/infrastructure/__init__.py +0 -0
  42. {ipmg-2.4.0 → ipmg-3.0.0}/src/ipmg/infrastructure/database.py +0 -0
  43. {ipmg-2.4.0 → ipmg-3.0.0}/src/ipmg/infrastructure/notify.py +0 -0
  44. {ipmg-2.4.0 → ipmg-3.0.0}/src/ipmg/reporting/__init__.py +0 -0
  45. {ipmg-2.4.0 → ipmg-3.0.0}/src/ipmg/reporting/diff_report.py +0 -0
  46. {ipmg-2.4.0 → ipmg-3.0.0}/src/ipmg/reporting/live.py +0 -0
  47. {ipmg-2.4.0 → ipmg-3.0.0}/src/ipmg/reporting/ui.py +0 -0
  48. {ipmg-2.4.0 → ipmg-3.0.0}/src/ipmg/services/__init__.py +0 -0
  49. {ipmg-2.4.0 → ipmg-3.0.0}/src/ipmg/services/history_service.py +0 -0
  50. {ipmg-2.4.0 → ipmg-3.0.0}/src/ipmg/utils/__init__.py +0 -0
  51. {ipmg-2.4.0 → ipmg-3.0.0}/src/ipmg/utils/helpers.py +0 -0
  52. {ipmg-2.4.0 → ipmg-3.0.0}/src/ipmg/web/__init__.py +0 -0
  53. {ipmg-2.4.0 → ipmg-3.0.0}/src/ipmg/web/db.py +0 -0
  54. {ipmg-2.4.0 → ipmg-3.0.0}/src/ipmg/web/manager.py +0 -0
  55. {ipmg-2.4.0 → ipmg-3.0.0}/src/ipmg/web/schemas.py +0 -0
  56. {ipmg-2.4.0 → ipmg-3.0.0}/src/ipmg/web/server.py +0 -0
  57. {ipmg-2.4.0 → ipmg-3.0.0}/src/ipmg/web/static/css/app.css +0 -0
  58. {ipmg-2.4.0 → ipmg-3.0.0}/src/ipmg/web/static/index.html +0 -0
  59. {ipmg-2.4.0 → ipmg-3.0.0}/src/ipmg/web/static/js/api.js +0 -0
  60. {ipmg-2.4.0 → ipmg-3.0.0}/src/ipmg/web/static/js/app.js +0 -0
  61. {ipmg-2.4.0 → ipmg-3.0.0}/src/ipmg/web/static/js/charts.js +0 -0
  62. {ipmg-2.4.0 → ipmg-3.0.0}/src/ipmg/web/static/js/demo.js +0 -0
  63. {ipmg-2.4.0 → ipmg-3.0.0}/src/ipmg/web/static/js/views.js +0 -0
  64. {ipmg-2.4.0 → ipmg-3.0.0}/src/ipmg.egg-info/dependency_links.txt +0 -0
  65. {ipmg-2.4.0 → ipmg-3.0.0}/src/ipmg.egg-info/entry_points.txt +0 -0
  66. {ipmg-2.4.0 → ipmg-3.0.0}/src/ipmg.egg-info/top_level.txt +0 -0
  67. {ipmg-2.4.0 → ipmg-3.0.0}/tests/test_api_contract.py +0 -0
  68. {ipmg-2.4.0 → ipmg-3.0.0}/tests/test_database_history.py +0 -0
  69. {ipmg-2.4.0 → ipmg-3.0.0}/tests/test_diff.py +0 -0
  70. {ipmg-2.4.0 → ipmg-3.0.0}/tests/test_diff_report.py +0 -0
  71. {ipmg-2.4.0 → ipmg-3.0.0}/tests/test_discover.py +0 -0
  72. {ipmg-2.4.0 → ipmg-3.0.0}/tests/test_engine.py +0 -0
  73. {ipmg-2.4.0 → ipmg-3.0.0}/tests/test_health.py +0 -0
  74. {ipmg-2.4.0 → ipmg-3.0.0}/tests/test_history_service.py +0 -0
  75. {ipmg-2.4.0 → ipmg-3.0.0}/tests/test_live.py +0 -0
  76. {ipmg-2.4.0 → ipmg-3.0.0}/tests/test_machine_output.py +0 -0
  77. {ipmg-2.4.0 → ipmg-3.0.0}/tests/test_notify.py +0 -0
  78. {ipmg-2.4.0 → ipmg-3.0.0}/tests/test_ping.py +0 -0
  79. {ipmg-2.4.0 → ipmg-3.0.0}/tests/test_ping_command.py +0 -0
  80. {ipmg-2.4.0 → ipmg-3.0.0}/tests/test_portscan.py +0 -0
  81. {ipmg-2.4.0 → ipmg-3.0.0}/tests/test_ui.py +0 -0
  82. {ipmg-2.4.0 → ipmg-3.0.0}/tests/test_utils.py +0 -0
  83. {ipmg-2.4.0 → ipmg-3.0.0}/tests/test_web_db.py +0 -0
  84. {ipmg-2.4.0 → ipmg-3.0.0}/tests/test_web_manager.py +0 -0
  85. {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: 2.4.0
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 — two commands in PowerShell:**
157
+ **Windows — one command in PowerShell:**
155
158
 
156
159
  ```powershell
157
- powershell -ExecutionPolicy ByPass -c "irm https://astral.sh/uv/install.ps1 | iex"
158
- uv tool install ipmg
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
- - **A CIDR block** — `10.0.0.0/24`
522
- - **A range** — `10.0.0.1-10.0.0.200`
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`, `.xls`, `.csv`) — must contain a column
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 | Auto-detect and scan the local subnet instead |
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
  [![PyPI](https://img.shields.io/pypi/v/ipmg)](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 — two commands in PowerShell:**
105
+ **Windows — one command in PowerShell:**
155
106
 
156
107
  ```powershell
157
- powershell -ExecutionPolicy ByPass -c "irm https://astral.sh/uv/install.ps1 | iex"
158
- uv tool install ipmg
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
- - **A CIDR block** — `10.0.0.0/24`
522
- - **A range** — `10.0.0.1-10.0.0.200`
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`, `.xls`, `.csv`) — must contain a column
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 | Auto-detect and scan the local subnet instead |
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 = "2.4.0" # Managed automatically by semantic-release
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",
@@ -2,4 +2,4 @@
2
2
  ipmg - IP Management & Ping Monitoring Tool
3
3
  """
4
4
 
5
- __version__ = "2.4.0"
5
+ __version__ = "3.0.0"
@@ -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
- from ipmg.web.server import run_dashboard
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
- action="store_true",
263
- help="Auto-detect this machine's subnet and scan it instead of --input.",
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 placing valid IPs first in numeric order; shared with the DB layer."""
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, ...]: