isitup-cli 0.1.3__tar.gz → 0.2.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.
@@ -0,0 +1,310 @@
1
+ Metadata-Version: 2.4
2
+ Name: isitup-cli
3
+ Version: 0.2.0
4
+ Summary: A small CLI that periodically checks whether servers respond to ping, HTTP, and TCP ports
5
+ Author: stefan.insam
6
+ Author-email: stefan.insam <stefan.insam@netgo.de>
7
+ License-Expression: GPL-3.0-only
8
+ License-File: LICENSE
9
+ Classifier: Development Status :: 4 - Beta
10
+ Classifier: Environment :: Console
11
+ Classifier: Intended Audience :: System Administrators
12
+ Classifier: Topic :: System :: Networking :: Monitoring
13
+ Classifier: Topic :: Utilities
14
+ Requires-Dist: pyyaml>=6.0.3
15
+ Requires-Dist: requests>=2.34.2
16
+ Requires-Dist: rich>=15.0.0
17
+ Requires-Python: >=3.14
18
+ Project-URL: Repository, https://github.com/ramsesoriginal/isitup
19
+ Project-URL: Issues, https://github.com/ramsesoriginal/isitup/issues
20
+ Description-Content-Type: text/markdown
21
+
22
+ <div align="center">
23
+ <img src="https://raw.githubusercontent.com/ramsesoriginal/isitup/main/logo/main_hero.svg" alt="isitup" width="720">
24
+
25
+ <p><strong>Is it up? Ping, HTTP, and TCP checks in one small, fast CLI.</strong></p>
26
+
27
+ [![PyPI](https://img.shields.io/pypi/v/isitup-cli?color=blue)](https://pypi.org/project/isitup-cli/)
28
+ [![CI](https://github.com/ramsesoriginal/isitup/actions/workflows/ci.yml/badge.svg)](https://github.com/ramsesoriginal/isitup/actions/workflows/ci.yml)
29
+ [![License: GPL v3](https://img.shields.io/badge/License-GPLv3-blue.svg)](LICENSE)
30
+ [![Python 3.14+](https://img.shields.io/badge/python-3.14%2B-3776AB?logo=python&logoColor=white)](pyproject.toml)
31
+ [![uv](https://img.shields.io/badge/uv-managed-DE5FE9?logo=uv&logoColor=white)](https://github.com/astral-sh/uv)
32
+ [![Ruff](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/ruff/main/assets/badge/v2.json)](https://github.com/astral-sh/ruff)
33
+ [![Checked with mypy](https://www.mypy-lang.org/static/mypy_badge.svg)](https://mypy-lang.org/)
34
+ [![GitHub stars](https://img.shields.io/github/stars/ramsesoriginal/isitup?style=social)](https://github.com/ramsesoriginal/isitup)
35
+ </div>
36
+
37
+ ---
38
+
39
+ **isitup** is a small CLI that periodically checks whether servers respond to ping and,
40
+ depending on how the target is written, HTTP, a TCP port, or SSH:
41
+
42
+ | Target | Checks |
43
+ | --- | --- |
44
+ | `example.com` | ping only |
45
+ | `example.com:1337` | ping + TCP connect to port 1337 |
46
+ | `http://example.com` / `https://example.com` | ping + HTTP (catching a 500, a DNS failure, an expired TLS certificate, a redirect loop, etc.) |
47
+ | `https://example.com:1337` | ping + HTTP, against that port |
48
+ | `ssh://example.com` / `ssh://example.com:1337` | ping + SSH (connects and verifies the server's SSH identification banner; defaults to port 22) |
49
+
50
+ Ping is checked independently from the HTTP/TCP/SSH probe: many hosts (behind
51
+ load balancers, CDNs, or firewalls) drop ICMP but serve their actual service
52
+ just fine, so a blocked ping alone doesn't mark a target down: the "Status"
53
+ column is based on the probe, with ping shown alongside as extra diagnostic
54
+ info. A target only shows as `OFFLINE` when both ping and the probe fail; a
55
+ ping-only target's status is based on ping alone, since there's nothing else
56
+ to check.
57
+
58
+ If the same host appears more than once (e.g. `example.com` and
59
+ `https://example.com/health`), it's only pinged once per round. The ping
60
+ result is shared across all targets pointing at that host.
61
+
62
+ While checks are running you get a live spinner and a progress bar per
63
+ target counting down its timeout; results are then shown in a table that
64
+ updates in place (via [`rich`](https://github.com/Textualize/rich)) rather
65
+ than reprinting on every round.
66
+
67
+ Each target also keeps a rolling window of its last 20 response times for
68
+ the session (HTTP response time, TCP connect time, or SSH connect+banner
69
+ time, whichever applies), shown as Min/Max latency columns and a tiny
70
+ sparkline ("Trend"), along with
71
+ the last time it was seen online and the last time it was seen offline.
72
+ This history is in-memory only and resets each time you start the tool.
73
+
74
+ ## Installation
75
+
76
+ The package is published on PyPI as `isitup-cli`, but it installs a plain
77
+ `isitup` command:
78
+
79
+ ```bash
80
+ uv tool install isitup-cli
81
+ # or: pipx install isitup-cli
82
+ # or: pip install isitup-cli
83
+ ```
84
+
85
+ To try unreleased changes straight from `main` instead (no PyPI release
86
+ needed):
87
+
88
+ ```bash
89
+ uv tool install git+https://github.com/ramsesoriginal/isitup
90
+ ```
91
+
92
+ Either way, remove it later with `uv tool uninstall isitup`.
93
+
94
+ ## Usage
95
+
96
+ ```bash
97
+ isitup example.com https://example.org db.internal:5432
98
+ ```
99
+
100
+ `--url`/`-u` work the same way and can be freely mixed with positional
101
+ targets: `isitup example.com -u https://example.org` is the same as
102
+ `isitup example.com https://example.org`:
103
+
104
+ ```bash
105
+ isitup --url example.com --url https://example.org --url db.internal:5432
106
+ ```
107
+
108
+ Or with a config file:
109
+
110
+ ```bash
111
+ cp config.example.yaml config.yaml
112
+ # edit config.yaml
113
+ isitup --config config.yaml
114
+ ```
115
+
116
+ Config files and CLI-supplied targets (positional or `--url`) can be
117
+ combined; the tool watches the union of both. The config file is watched for
118
+ changes and reloaded automatically: add, remove, or edit targets without
119
+ restarting the tool (CLI-supplied targets can't change at runtime, only
120
+ what's in the file).
121
+
122
+ ### Options
123
+
124
+ | Flag | Description |
125
+ | --- | --- |
126
+ | `-c`, `--config PATH` | YAML file listing targets; reloaded automatically on change |
127
+ | `TARGET` (positional) | a target to monitor: same as `-u`/`--url`, just without the flag (repeatable) |
128
+ | `-u`, `--url TARGET` | a target to monitor (repeatable): hostname, `hostname:port`, or a URL |
129
+ | `-i`, `--interval SECONDS` | seconds between check rounds (default: 30) |
130
+ | `--ping-timeout SECONDS` | ping reply timeout (default: 2) |
131
+ | `--http-timeout SECONDS` | timeout for the HTTP request or TCP connect (default: 5) |
132
+ | `--no-ping` | skip ICMP entirely, HTTP/TCP-only checks |
133
+ | `--columns LIST` | comma-separated list of table columns to show, in order (interactive UI only; `--plain`/`--json` always include every field) — see below |
134
+ | `--basic-auth USER:PASS` | HTTP Basic Auth for CLI-supplied HTTP targets that don't set their own `basic_auth:` in the config file — see below |
135
+ | `--basic-auth-env USER_VAR:PASS_VAR` | same as `--basic-auth`, but reads the username/password from the named environment variables |
136
+ | `--tag TAG` | tag applied to CLI-supplied targets (repeatable) — see below |
137
+ | `--filter-tag TAG` | only monitor targets carrying at least one of the given tags (repeatable) — see below |
138
+ | `--once` | run a single round and exit; exit code is `1` if any target is down, `0` otherwise. Useful as a cron/CI health gate |
139
+ | `--plain` | print one plain-text line per target per round instead of the live UI (for logging/piping) |
140
+ | `--json` | print one JSON object per target per round instead of the live UI (for machine consumption) |
141
+
142
+ `--plain` and `--json` are mutually exclusive with each other, and both skip
143
+ the interactive spinner/progress UI entirely.
144
+
145
+ ### Note on ping
146
+
147
+ Some servers/networks (common on cloud providers, behind load balancers or
148
+ CDNs) block ICMP echo requests entirely even though the service itself is
149
+ perfectly reachable. The "Status" column already accounts for this and
150
+ won't flag such a target as down (unless it's ping-only, in which case ping
151
+ *is* the only signal there is). If you'd rather not run ping checks against
152
+ a target at all (e.g. to skip the ~2s ping timeout), either set `ping: false`
153
+ for it in the config file, or pass `--no-ping` to disable ICMP checks
154
+ globally. Note that a ping-only target (bare hostname, no port) can't have
155
+ `ping: false`, there would be nothing left to check.
156
+
157
+ ### SSH availability check
158
+
159
+ `ssh://example.com` (or `ssh://example.com:1234` for a non-standard port,
160
+ default 22) connects over TCP and reads the server's SSH identification
161
+ banner (the string like `SSH-2.0-OpenSSH_9.6` that a compliant server sends
162
+ before any client input, per RFC 4253) rather than just checking whether
163
+ the port is open. If the port is open but the banner doesn't start with
164
+ `SSH-`, the target is reported down with `error_kind: protocol` — this
165
+ catches something else listening on the port. No SSH handshake, key
166
+ exchange, or authentication is attempted, so no credentials are needed or
167
+ used.
168
+
169
+ ### Customizing table columns
170
+
171
+ The interactive table shows all of `target`, `status`, `ping`, `service`,
172
+ `latency`, `min`, `max`, `trend`, `last_online`, `last_offline`, `detail`,
173
+ `checked` by default, in that order. A few more are available but not
174
+ shown by default — `tags` (see above) and the HTTP header columns
175
+ `content_length`, `last_modified`, `etag`, `content_type`, `server`,
176
+ `header_change` (see below). To show a different subset (or reorder
177
+ them), pass `--columns` with a comma-separated list:
178
+
179
+ ```bash
180
+ isitup --columns target,status,detail --url https://example.com
181
+ ```
182
+
183
+ Or set it once in the config file:
184
+
185
+ ```yaml
186
+ columns: [target, status, service, latency, detail]
187
+ ```
188
+
189
+ `--columns` takes precedence over a config file's `columns:` list, which
190
+ takes precedence over the default. This only affects the interactive UI —
191
+ `--plain` and `--json` always include every field, since they're meant for
192
+ machine consumption.
193
+
194
+ ### HTTP Basic Auth
195
+
196
+ Set `basic_auth` on a config file target to send an `Authorization` header
197
+ with its HTTP requests. Each of `username`/`password` can be given either
198
+ literally or via a `*_env` reference to an environment variable (but not
199
+ both):
200
+
201
+ ```yaml
202
+ targets:
203
+ - name: Protected API
204
+ url: https://api.internal.example.com
205
+ basic_auth:
206
+ username: admin
207
+ password_env: API_PASSWORD # read from $API_PASSWORD at startup
208
+ ```
209
+
210
+ `basic_auth` only applies to `http://`/`https://` targets — it's rejected
211
+ on a bare hostname or `hostname:port` target.
212
+
213
+ For targets passed directly on the command line (positional or `--url`),
214
+ use `--basic-auth USER:PASS` or `--basic-auth-env USER_VAR:PASS_VAR`
215
+ instead. Either flag applies to every CLI-supplied HTTP target in that
216
+ invocation, but never to config-file targets — those always use their own
217
+ `basic_auth:` (or none), so the two never mix unexpectedly:
218
+
219
+ ```bash
220
+ isitup --basic-auth-env API_USER:API_PASSWORD --url https://api.internal.example.com
221
+ ```
222
+
223
+ ### HTTP header/metadata tracking
224
+
225
+ For HTTP(S) targets, a handful of response headers are captured every
226
+ round — `Content-Length` (response size), `Last-Modified`, `ETag`,
227
+ `Content-Type`, and `Server` — without ever touching the response body
228
+ itself. `--plain`/`--json` always include them (nested under `http` in
229
+ JSON, as `content_length=`/`last_modified=`/etc. fields in `--plain`),
230
+ alongside a `last_header_change`/`header_change` timestamp: the last time
231
+ any of those five values actually differed from the previous round (not
232
+ set on the very first check, since there's nothing yet to compare against).
233
+ This is handy for noticing "something on this page changed" without
234
+ diffing the body yourself.
235
+
236
+ In the interactive table these show up as the `content_length`,
237
+ `last_modified`, `etag`, `content_type`, `server`, and `header_change`
238
+ columns — available via `--columns`/config `columns:`, but not part of the
239
+ default set (see above), so the table's default look is unaffected.
240
+
241
+ ### Tagging and grouping
242
+
243
+ Any target (ping-only, TCP, or HTTP) can carry `tags` in the config file:
244
+
245
+ ```yaml
246
+ targets:
247
+ - name: Prod Website
248
+ url: https://example.com
249
+ tags: [prod, web]
250
+ - name: Staging Website
251
+ url: https://staging.example.com
252
+ tags: [staging, web]
253
+ ```
254
+
255
+ Tags show up as their own `Tags` table column — add it with `--columns
256
+ target,tags,status,...` (or a config `columns:` list), since it isn't in
257
+ the default set — and always as a `tags` field in `--plain`/`--json`
258
+ output.
259
+
260
+ To only monitor a subset, pass `--filter-tag` (repeatable — a target
261
+ matches if it has *any* of the given tags):
262
+
263
+ ```bash
264
+ isitup --config config.yaml --filter-tag prod
265
+ ```
266
+
267
+ With no `--filter-tag` given, every target is monitored, same as today.
268
+ For targets passed directly on the command line, `--tag TAG` (repeatable)
269
+ tags every CLI-supplied target in that invocation — like `--basic-auth`,
270
+ it never touches config-file targets, which set their own `tags:` instead.
271
+
272
+ ### Failure classification
273
+
274
+ When the HTTP, TCP, or SSH probe fails, the specific reason is captured as
275
+ `error_kind` (visible in `--json` output, and shown as a short tag like
276
+ `DOWN (DNS)` in the Service column otherwise):
277
+
278
+ | `error_kind` | Meaning |
279
+ | --- | --- |
280
+ | `dns` | the hostname failed to resolve |
281
+ | `tls` | a TLS/certificate error (HTTP only, e.g. expired or self-signed cert) |
282
+ | `redirect` | too many redirects (HTTP only, possible redirect loop) |
283
+ | `timeout` | didn't complete within `--http-timeout` |
284
+ | `connection` | connection refused or otherwise unreachable |
285
+ | `protocol` | connected, but the server's response wasn't a valid SSH banner (SSH only) |
286
+ | `client_error` / `server_error` | got a 4xx / 5xx HTTP response |
287
+ | `request_error` | anything else `requests` raised |
288
+
289
+ ## Development
290
+
291
+ ```bash
292
+ uv sync
293
+ uv run isitup --once --url https://example.com
294
+
295
+ uv run pytest # tests
296
+ uv run ruff check . # lint
297
+ uv run ruff format . # format
298
+ uv run mypy src tests # type check
299
+ ```
300
+
301
+ Every push and pull request runs this same lint/type-check/test suite via
302
+ [GitHub Actions](.github/workflows/ci.yml), across Linux and Windows.
303
+
304
+ ## Changelog
305
+
306
+ See [CHANGELOG.md](CHANGELOG.md).
307
+
308
+ ## License
309
+
310
+ [GPL-3.0](LICENSE): see [LICENSE](LICENSE) for the full text.
@@ -0,0 +1,289 @@
1
+ <div align="center">
2
+ <img src="https://raw.githubusercontent.com/ramsesoriginal/isitup/main/logo/main_hero.svg" alt="isitup" width="720">
3
+
4
+ <p><strong>Is it up? Ping, HTTP, and TCP checks in one small, fast CLI.</strong></p>
5
+
6
+ [![PyPI](https://img.shields.io/pypi/v/isitup-cli?color=blue)](https://pypi.org/project/isitup-cli/)
7
+ [![CI](https://github.com/ramsesoriginal/isitup/actions/workflows/ci.yml/badge.svg)](https://github.com/ramsesoriginal/isitup/actions/workflows/ci.yml)
8
+ [![License: GPL v3](https://img.shields.io/badge/License-GPLv3-blue.svg)](LICENSE)
9
+ [![Python 3.14+](https://img.shields.io/badge/python-3.14%2B-3776AB?logo=python&logoColor=white)](pyproject.toml)
10
+ [![uv](https://img.shields.io/badge/uv-managed-DE5FE9?logo=uv&logoColor=white)](https://github.com/astral-sh/uv)
11
+ [![Ruff](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/ruff/main/assets/badge/v2.json)](https://github.com/astral-sh/ruff)
12
+ [![Checked with mypy](https://www.mypy-lang.org/static/mypy_badge.svg)](https://mypy-lang.org/)
13
+ [![GitHub stars](https://img.shields.io/github/stars/ramsesoriginal/isitup?style=social)](https://github.com/ramsesoriginal/isitup)
14
+ </div>
15
+
16
+ ---
17
+
18
+ **isitup** is a small CLI that periodically checks whether servers respond to ping and,
19
+ depending on how the target is written, HTTP, a TCP port, or SSH:
20
+
21
+ | Target | Checks |
22
+ | --- | --- |
23
+ | `example.com` | ping only |
24
+ | `example.com:1337` | ping + TCP connect to port 1337 |
25
+ | `http://example.com` / `https://example.com` | ping + HTTP (catching a 500, a DNS failure, an expired TLS certificate, a redirect loop, etc.) |
26
+ | `https://example.com:1337` | ping + HTTP, against that port |
27
+ | `ssh://example.com` / `ssh://example.com:1337` | ping + SSH (connects and verifies the server's SSH identification banner; defaults to port 22) |
28
+
29
+ Ping is checked independently from the HTTP/TCP/SSH probe: many hosts (behind
30
+ load balancers, CDNs, or firewalls) drop ICMP but serve their actual service
31
+ just fine, so a blocked ping alone doesn't mark a target down: the "Status"
32
+ column is based on the probe, with ping shown alongside as extra diagnostic
33
+ info. A target only shows as `OFFLINE` when both ping and the probe fail; a
34
+ ping-only target's status is based on ping alone, since there's nothing else
35
+ to check.
36
+
37
+ If the same host appears more than once (e.g. `example.com` and
38
+ `https://example.com/health`), it's only pinged once per round. The ping
39
+ result is shared across all targets pointing at that host.
40
+
41
+ While checks are running you get a live spinner and a progress bar per
42
+ target counting down its timeout; results are then shown in a table that
43
+ updates in place (via [`rich`](https://github.com/Textualize/rich)) rather
44
+ than reprinting on every round.
45
+
46
+ Each target also keeps a rolling window of its last 20 response times for
47
+ the session (HTTP response time, TCP connect time, or SSH connect+banner
48
+ time, whichever applies), shown as Min/Max latency columns and a tiny
49
+ sparkline ("Trend"), along with
50
+ the last time it was seen online and the last time it was seen offline.
51
+ This history is in-memory only and resets each time you start the tool.
52
+
53
+ ## Installation
54
+
55
+ The package is published on PyPI as `isitup-cli`, but it installs a plain
56
+ `isitup` command:
57
+
58
+ ```bash
59
+ uv tool install isitup-cli
60
+ # or: pipx install isitup-cli
61
+ # or: pip install isitup-cli
62
+ ```
63
+
64
+ To try unreleased changes straight from `main` instead (no PyPI release
65
+ needed):
66
+
67
+ ```bash
68
+ uv tool install git+https://github.com/ramsesoriginal/isitup
69
+ ```
70
+
71
+ Either way, remove it later with `uv tool uninstall isitup`.
72
+
73
+ ## Usage
74
+
75
+ ```bash
76
+ isitup example.com https://example.org db.internal:5432
77
+ ```
78
+
79
+ `--url`/`-u` work the same way and can be freely mixed with positional
80
+ targets: `isitup example.com -u https://example.org` is the same as
81
+ `isitup example.com https://example.org`:
82
+
83
+ ```bash
84
+ isitup --url example.com --url https://example.org --url db.internal:5432
85
+ ```
86
+
87
+ Or with a config file:
88
+
89
+ ```bash
90
+ cp config.example.yaml config.yaml
91
+ # edit config.yaml
92
+ isitup --config config.yaml
93
+ ```
94
+
95
+ Config files and CLI-supplied targets (positional or `--url`) can be
96
+ combined; the tool watches the union of both. The config file is watched for
97
+ changes and reloaded automatically: add, remove, or edit targets without
98
+ restarting the tool (CLI-supplied targets can't change at runtime, only
99
+ what's in the file).
100
+
101
+ ### Options
102
+
103
+ | Flag | Description |
104
+ | --- | --- |
105
+ | `-c`, `--config PATH` | YAML file listing targets; reloaded automatically on change |
106
+ | `TARGET` (positional) | a target to monitor: same as `-u`/`--url`, just without the flag (repeatable) |
107
+ | `-u`, `--url TARGET` | a target to monitor (repeatable): hostname, `hostname:port`, or a URL |
108
+ | `-i`, `--interval SECONDS` | seconds between check rounds (default: 30) |
109
+ | `--ping-timeout SECONDS` | ping reply timeout (default: 2) |
110
+ | `--http-timeout SECONDS` | timeout for the HTTP request or TCP connect (default: 5) |
111
+ | `--no-ping` | skip ICMP entirely, HTTP/TCP-only checks |
112
+ | `--columns LIST` | comma-separated list of table columns to show, in order (interactive UI only; `--plain`/`--json` always include every field) — see below |
113
+ | `--basic-auth USER:PASS` | HTTP Basic Auth for CLI-supplied HTTP targets that don't set their own `basic_auth:` in the config file — see below |
114
+ | `--basic-auth-env USER_VAR:PASS_VAR` | same as `--basic-auth`, but reads the username/password from the named environment variables |
115
+ | `--tag TAG` | tag applied to CLI-supplied targets (repeatable) — see below |
116
+ | `--filter-tag TAG` | only monitor targets carrying at least one of the given tags (repeatable) — see below |
117
+ | `--once` | run a single round and exit; exit code is `1` if any target is down, `0` otherwise. Useful as a cron/CI health gate |
118
+ | `--plain` | print one plain-text line per target per round instead of the live UI (for logging/piping) |
119
+ | `--json` | print one JSON object per target per round instead of the live UI (for machine consumption) |
120
+
121
+ `--plain` and `--json` are mutually exclusive with each other, and both skip
122
+ the interactive spinner/progress UI entirely.
123
+
124
+ ### Note on ping
125
+
126
+ Some servers/networks (common on cloud providers, behind load balancers or
127
+ CDNs) block ICMP echo requests entirely even though the service itself is
128
+ perfectly reachable. The "Status" column already accounts for this and
129
+ won't flag such a target as down (unless it's ping-only, in which case ping
130
+ *is* the only signal there is). If you'd rather not run ping checks against
131
+ a target at all (e.g. to skip the ~2s ping timeout), either set `ping: false`
132
+ for it in the config file, or pass `--no-ping` to disable ICMP checks
133
+ globally. Note that a ping-only target (bare hostname, no port) can't have
134
+ `ping: false`, there would be nothing left to check.
135
+
136
+ ### SSH availability check
137
+
138
+ `ssh://example.com` (or `ssh://example.com:1234` for a non-standard port,
139
+ default 22) connects over TCP and reads the server's SSH identification
140
+ banner (the string like `SSH-2.0-OpenSSH_9.6` that a compliant server sends
141
+ before any client input, per RFC 4253) rather than just checking whether
142
+ the port is open. If the port is open but the banner doesn't start with
143
+ `SSH-`, the target is reported down with `error_kind: protocol` — this
144
+ catches something else listening on the port. No SSH handshake, key
145
+ exchange, or authentication is attempted, so no credentials are needed or
146
+ used.
147
+
148
+ ### Customizing table columns
149
+
150
+ The interactive table shows all of `target`, `status`, `ping`, `service`,
151
+ `latency`, `min`, `max`, `trend`, `last_online`, `last_offline`, `detail`,
152
+ `checked` by default, in that order. A few more are available but not
153
+ shown by default — `tags` (see above) and the HTTP header columns
154
+ `content_length`, `last_modified`, `etag`, `content_type`, `server`,
155
+ `header_change` (see below). To show a different subset (or reorder
156
+ them), pass `--columns` with a comma-separated list:
157
+
158
+ ```bash
159
+ isitup --columns target,status,detail --url https://example.com
160
+ ```
161
+
162
+ Or set it once in the config file:
163
+
164
+ ```yaml
165
+ columns: [target, status, service, latency, detail]
166
+ ```
167
+
168
+ `--columns` takes precedence over a config file's `columns:` list, which
169
+ takes precedence over the default. This only affects the interactive UI —
170
+ `--plain` and `--json` always include every field, since they're meant for
171
+ machine consumption.
172
+
173
+ ### HTTP Basic Auth
174
+
175
+ Set `basic_auth` on a config file target to send an `Authorization` header
176
+ with its HTTP requests. Each of `username`/`password` can be given either
177
+ literally or via a `*_env` reference to an environment variable (but not
178
+ both):
179
+
180
+ ```yaml
181
+ targets:
182
+ - name: Protected API
183
+ url: https://api.internal.example.com
184
+ basic_auth:
185
+ username: admin
186
+ password_env: API_PASSWORD # read from $API_PASSWORD at startup
187
+ ```
188
+
189
+ `basic_auth` only applies to `http://`/`https://` targets — it's rejected
190
+ on a bare hostname or `hostname:port` target.
191
+
192
+ For targets passed directly on the command line (positional or `--url`),
193
+ use `--basic-auth USER:PASS` or `--basic-auth-env USER_VAR:PASS_VAR`
194
+ instead. Either flag applies to every CLI-supplied HTTP target in that
195
+ invocation, but never to config-file targets — those always use their own
196
+ `basic_auth:` (or none), so the two never mix unexpectedly:
197
+
198
+ ```bash
199
+ isitup --basic-auth-env API_USER:API_PASSWORD --url https://api.internal.example.com
200
+ ```
201
+
202
+ ### HTTP header/metadata tracking
203
+
204
+ For HTTP(S) targets, a handful of response headers are captured every
205
+ round — `Content-Length` (response size), `Last-Modified`, `ETag`,
206
+ `Content-Type`, and `Server` — without ever touching the response body
207
+ itself. `--plain`/`--json` always include them (nested under `http` in
208
+ JSON, as `content_length=`/`last_modified=`/etc. fields in `--plain`),
209
+ alongside a `last_header_change`/`header_change` timestamp: the last time
210
+ any of those five values actually differed from the previous round (not
211
+ set on the very first check, since there's nothing yet to compare against).
212
+ This is handy for noticing "something on this page changed" without
213
+ diffing the body yourself.
214
+
215
+ In the interactive table these show up as the `content_length`,
216
+ `last_modified`, `etag`, `content_type`, `server`, and `header_change`
217
+ columns — available via `--columns`/config `columns:`, but not part of the
218
+ default set (see above), so the table's default look is unaffected.
219
+
220
+ ### Tagging and grouping
221
+
222
+ Any target (ping-only, TCP, or HTTP) can carry `tags` in the config file:
223
+
224
+ ```yaml
225
+ targets:
226
+ - name: Prod Website
227
+ url: https://example.com
228
+ tags: [prod, web]
229
+ - name: Staging Website
230
+ url: https://staging.example.com
231
+ tags: [staging, web]
232
+ ```
233
+
234
+ Tags show up as their own `Tags` table column — add it with `--columns
235
+ target,tags,status,...` (or a config `columns:` list), since it isn't in
236
+ the default set — and always as a `tags` field in `--plain`/`--json`
237
+ output.
238
+
239
+ To only monitor a subset, pass `--filter-tag` (repeatable — a target
240
+ matches if it has *any* of the given tags):
241
+
242
+ ```bash
243
+ isitup --config config.yaml --filter-tag prod
244
+ ```
245
+
246
+ With no `--filter-tag` given, every target is monitored, same as today.
247
+ For targets passed directly on the command line, `--tag TAG` (repeatable)
248
+ tags every CLI-supplied target in that invocation — like `--basic-auth`,
249
+ it never touches config-file targets, which set their own `tags:` instead.
250
+
251
+ ### Failure classification
252
+
253
+ When the HTTP, TCP, or SSH probe fails, the specific reason is captured as
254
+ `error_kind` (visible in `--json` output, and shown as a short tag like
255
+ `DOWN (DNS)` in the Service column otherwise):
256
+
257
+ | `error_kind` | Meaning |
258
+ | --- | --- |
259
+ | `dns` | the hostname failed to resolve |
260
+ | `tls` | a TLS/certificate error (HTTP only, e.g. expired or self-signed cert) |
261
+ | `redirect` | too many redirects (HTTP only, possible redirect loop) |
262
+ | `timeout` | didn't complete within `--http-timeout` |
263
+ | `connection` | connection refused or otherwise unreachable |
264
+ | `protocol` | connected, but the server's response wasn't a valid SSH banner (SSH only) |
265
+ | `client_error` / `server_error` | got a 4xx / 5xx HTTP response |
266
+ | `request_error` | anything else `requests` raised |
267
+
268
+ ## Development
269
+
270
+ ```bash
271
+ uv sync
272
+ uv run isitup --once --url https://example.com
273
+
274
+ uv run pytest # tests
275
+ uv run ruff check . # lint
276
+ uv run ruff format . # format
277
+ uv run mypy src tests # type check
278
+ ```
279
+
280
+ Every push and pull request runs this same lint/type-check/test suite via
281
+ [GitHub Actions](.github/workflows/ci.yml), across Linux and Windows.
282
+
283
+ ## Changelog
284
+
285
+ See [CHANGELOG.md](CHANGELOG.md).
286
+
287
+ ## License
288
+
289
+ [GPL-3.0](LICENSE): see [LICENSE](LICENSE) for the full text.
@@ -1,12 +1,13 @@
1
1
  [project]
2
2
  name = "isitup-cli"
3
- version = "0.1.3"
3
+ version = "0.2.0"
4
4
  description = "A small CLI that periodically checks whether servers respond to ping, HTTP, and TCP ports"
5
5
  readme = "README.md"
6
6
  license = "GPL-3.0-only"
7
7
  license-files = ["LICENSE"]
8
8
  requires-python = ">=3.14"
9
9
  classifiers = [
10
+ "Development Status :: 4 - Beta",
10
11
  "Environment :: Console",
11
12
  "Intended Audience :: System Administrators",
12
13
  "Topic :: System :: Networking :: Monitoring",
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "isitup-cli"
3
- version = "0.1.3"
3
+ version = "0.2.0"
4
4
  description = "A small CLI that periodically checks whether servers respond to ping, HTTP, and TCP ports"
5
5
  readme = "README.md"
6
6
  license = "GPL-3.0-only"
@@ -10,6 +10,7 @@ authors = [
10
10
  ]
11
11
  requires-python = ">=3.14"
12
12
  classifiers = [
13
+ "Development Status :: 4 - Beta",
13
14
  "Environment :: Console",
14
15
  "Intended Audience :: System Administrators",
15
16
  "Topic :: System :: Networking :: Monitoring",
@@ -0,0 +1,3 @@
1
+ from .cli import __version__, main
2
+
3
+ __all__ = ["main", "__version__"]