Add a Ports card to server pages: scan for open ports, find free ones, and keep notes
Each server's detail page now has a Ports card. "Scan…" runs a TCP connect scan of a chosen range from the app and shows what's open, along with the ranges that were actually confirmed free; clicking a free range starts a reservation. Any port can carry a service name and a comment, so the page also answers "what is this port for". A port with a note counts as taken even when nothing is listening, which is what makes a reservation work. Operators can scan and edit; everyone can read. Scans and note changes are audit-logged. Details that matter for correctness: - "Free" means the host actively refused the connection AND nobody has claimed the port. A port that never answers (firewall drop, host down) is reported as not answering, not as free. - A scan from elsewhere can't see services bound to localhost only, so the agent now also reports what is bound on the host (ss -tulnp) and those ports are treated as taken. They show as "local only". Existing agents keep working; re-run the install one-liner to add this. The field is validated leniently so one odd line can never cost an agent its whole report, tasks included. - If nothing answers at all during a scan, existing results are left alone instead of being marked all-closed. - Scan targets are limited to private addresses (RFC1918, Tailscale 100.64/10, link-local, IPv6 ULA/link-local); loopback and public addresses are refused. Ranges are capped at 20,000 ports, and only one scan runs per server at a time. - Rows exist only while they carry information: an open port, or one with a note. A closed port with no note disappears on the next scan; one with a note stays as "reserved". New table server_ports plus two columns on servers (migration 0009). Verified with 76 backend checks (scanner open/refused/filtered, address rules, agent report leniency, note/reserve/clear semantics, free-range calculation including the localhost-only case, roles, concurrency lock, no-response guard, audit entries, cascade delete) and by driving the real component against the real router in a browser. Real dev database mtime untouched. Not verified: the agent's ss/awk/jq pipeline on a real host — the awk step was checked against sample ss output and the script passes bash -n, but jq isn't available here to run the whole thing. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
aae4f0d74f
commit
4c11158e98
14 files changed
+2521
-1
No files matched your search
@@ -226,8 +226,26 @@ collect_system_info() {
|
||||
')
|
||||
fi
|
||||
|
||||
# Everything bound to a port on this host, including services listening on localhost only (which a network
|
||||
# scan from elsewhere can't see). `ss -p` needs root to name the process; without it the process is blank.
|
||||
# One row per socket — the server groups them per port.
|
||||
local ports_json="[]"
|
||||
if command -v ss >/dev/null 2>&1; then
|
||||
ports_json=$(ss -H -tulnp 2>/dev/null \
|
||||
| awk '{
|
||||
local = $5; port = local; sub(/.*:/, "", port); addr = local; sub(/:[0-9]+$/, "", addr);
|
||||
proc = ""; if (match($0, /users:\(\("[^"]+"/)) { proc = substr($0, RSTART + 9, RLENGTH - 10) }
|
||||
if (port ~ /^[0-9]+$/) print $1 "\t" port "\t" addr "\t" proc
|
||||
}' \
|
||||
| jq -R -s -c '
|
||||
split("\n") | map(select(length > 0) | split("\t")) |
|
||||
map({protocol: .[0], port: (.[1]|tonumber), address: .[2], process: (.[3] // "")})
|
||||
')
|
||||
fi
|
||||
|
||||
SYSTEM_JSON=$(jq -n \
|
||||
--argjson ip_addresses "$ip_json" \
|
||||
--argjson listening_ports "$ports_json" \
|
||||
--arg cpu_model "$cpu_model" \
|
||||
--argjson cpu_cores "${cpu_cores:-0}" \
|
||||
--argjson cpu_load_percent "$cpu_load_percent" \
|
||||
@@ -238,7 +256,8 @@ collect_system_info() {
|
||||
ip_addresses: $ip_addresses,
|
||||
cpu: { model: $cpu_model, cores: $cpu_cores, load_percent: $cpu_load_percent },
|
||||
memory: { total_bytes: $mem_total_bytes, used_bytes: $mem_used_bytes },
|
||||
disks: $disks
|
||||
disks: $disks,
|
||||
listening_ports: $listening_ports
|
||||
}')
|
||||
}
|
||||
|
||||
|
||||
Reference in new issue
Block a user