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>
76 lines
1.5 KiB
JSON
76 lines
1.5 KiB
JSON
{
|
|
"version": "7",
|
|
"dialect": "sqlite",
|
|
"entries": [
|
|
{
|
|
"idx": 0,
|
|
"version": "6",
|
|
"when": 1789419241921,
|
|
"tag": "0000_colossal_violations",
|
|
"breakpoints": true
|
|
},
|
|
{
|
|
"idx": 1,
|
|
"version": "6",
|
|
"when": 1789468464784,
|
|
"tag": "0001_yummy_luke_cage",
|
|
"breakpoints": true
|
|
},
|
|
{
|
|
"idx": 2,
|
|
"version": "6",
|
|
"when": 1789506601790,
|
|
"tag": "0002_motionless_lord_hawal",
|
|
"breakpoints": true
|
|
},
|
|
{
|
|
"idx": 3,
|
|
"version": "6",
|
|
"when": 1789673523898,
|
|
"tag": "0003_fantastic_randall",
|
|
"breakpoints": true
|
|
},
|
|
{
|
|
"idx": 4,
|
|
"version": "6",
|
|
"when": 1789677988329,
|
|
"tag": "0004_brown_gambit",
|
|
"breakpoints": true
|
|
},
|
|
{
|
|
"idx": 5,
|
|
"version": "6",
|
|
"when": 1789759852933,
|
|
"tag": "0005_bent_violations",
|
|
"breakpoints": true
|
|
},
|
|
{
|
|
"idx": 6,
|
|
"version": "6",
|
|
"when": 1790103102037,
|
|
"tag": "0006_aberrant_darkhawk",
|
|
"breakpoints": true
|
|
},
|
|
{
|
|
"idx": 7,
|
|
"version": "6",
|
|
"when": 1790372043652,
|
|
"tag": "0007_secret_madripoor",
|
|
"breakpoints": true
|
|
},
|
|
{
|
|
"idx": 8,
|
|
"version": "6",
|
|
"when": 1790381917814,
|
|
"tag": "0008_thin_boom_boom",
|
|
"breakpoints": true
|
|
},
|
|
{
|
|
"idx": 9,
|
|
"version": "6",
|
|
"when": 1790382571470,
|
|
"tag": "0009_sharp_magus",
|
|
"breakpoints": true
|
|
}
|
|
]
|
|
} |