portop

module
v0.0.2 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Aug 15, 2026 License: MIT

README ΒΆ

portop πŸ”Œ

CI Release Latest release Downloads Go Report Card Go version

An htop-style terminal UI (TUI) that answers the question ss, netstat and lsof only half-answer: not just what's bound to a port, but which process, which systemd unit, which Docker container β€” and a one-key way to open, inspect or kill it, live.

padovanl.github.io/portop β†’

Filtering to a port, checking who owns it, and the confirm-before-kill flow β€” all in real time.

✨ Features

πŸ‘€ See what's using your ports
  • Live, color-coded table: LISTEN/ESTABLISHED/transient states, protocol and per-process CPU% (sampled like top) are all colored at a glance.
  • Well-known port names: :22 shows as ssh, :443 as https, parsed straight from /etc/services.
  • systemd & Docker aware: every row shows the owning .service unit and Docker container, resolved from cgroups β€” no D-Bus, no Docker SDK, no extra daemon.
  • Process detail view: cmdline, executable, cwd, user, RSS, thread count, start time.
  • Adapts to your terminal width: drops the least useful columns first so rows never wrap, down to 80 columns.
⚑ Act on it
  • Kill, with confirmation: k then y (SIGTERM) or f (SIGKILL) β€” never a stray keypress away from killing the wrong thing.
  • Open in the browser: o launches http(s)://localhost:PORT for the selected row.
  • Instant filter/search: f, then start typing β€” matches port, process name or PID as you type.
  • Copy to clipboard: c on the selected row.
πŸ›ŽοΈ Stay ahead of surprises
  • New-port alerts: a freshly opened listening port is highlighted the moment it appears, with an optional desktop notification (--watch-new).
  • Baseline drift detection: --save-baseline now, --diff later (e.g. from a cron job or systemd timer) β€” exits non-zero the instant a port that wasn't in your baseline starts listening. Nothing else in this space does this as a first-class feature.
  • Scriptable: --json prints a clean snapshot for piping into jq, dashboards, or your own tooling.
🎨 Make it yours
  • Live settings screen (,): cycle through 12 built-in themes (default, Dracula, Nord, Solarized, Gruvbox, Catppuccin, Tokyo Night, Monokai, Darcula, VS Code Dark+, Ubuntu, mono) with ←/β†’ β€” the whole UI re-skins as you move β€” and rebind any of 18 actions on the spot. Saved automatically; you never touch a file.
  • config.yml is there too if you'd rather hand-edit it β€” portop --init-config writes a fully-commented template.

πŸ†š Why not just ss -tlnp?

You can. But you'll be doing all of this by hand, every time:

ss / netstat lsof bandwhich portop
Live, refreshing view – – βœ“ βœ“
Kill from the UI – – – βœ“
Open port in browser – – – βœ“
systemd unit shown – – – βœ“
Docker container shown – – – βœ“
New-port alerts – – – βœ“
Baseline drift / audit mode – – – βœ“
Fuzzy filter/search – – – βœ“
Themeable / remappable – – – βœ“

πŸ“₯ Installation

Below, <version> means the release number without a leading v (a v0.1.0 tag produces portop_0.1.0_linux_amd64.tar.gz, not portop_v0.1.0_...) β€” check the exact asset names on the latest release rather than guessing.

One-line installer
curl -fsSL https://raw.githubusercontent.com/padovanl/portop/main/install.sh | sh

Detects your arch, verifies the release checksum, and installs to /usr/local/bin (or ~/.local/bin if that's not writable).

.deb package (Debian/Ubuntu and derivatives)
curl -fLO https://github.com/padovanl/portop/releases/latest/download/portop_<version>_linux_amd64.deb
sudo dpkg -i portop_<version>_linux_amd64.deb
Binary tarball (any Linux distro)
curl -fLO https://github.com/padovanl/portop/releases/latest/download/portop_<version>_linux_amd64.tar.gz
tar -xzf portop_<version>_linux_amd64.tar.gz
sudo install -m 755 portop /usr/local/bin/portop
From source
go install github.com/padovanl/portop/cmd/portop@latest

⌨️ Usage

portop                 # launch the TUI (LISTEN + ESTABLISHED)
portop 8080             # launch the TUI pre-filtered on port 8080
portop redis            # launch the TUI pre-filtered on a process name
portop --listen          # show only listening (LISTEN) sockets
portop --json            # print one JSON snapshot and exit
portop --watch-new       # desktop-notify when a new port starts listening

portop --save-baseline   # remember which ports are currently listening
portop --diff            # compare live ports against the saved baseline
                          # (exit code 3 if something changed β€” great for cron)

Run portop --help for the full flag list.

Rows with no process/PID are owned by another user (root's docker-proxy, systemd-resolved, ...) β€” the same limitation lsof/ss -p have without sudo. portop tells you when that's happening instead of leaving you to guess.

Keybindings

These are the defaults β€” every one can be remapped, see Configuration below. The in-app ? help always reflects whatever's actually bound, including your overrides.

Key Action
↑ ↓ move the cursor
g G jump to top / bottom
enter process details
k kill process (then y=SIGTERM, f=SIGKILL)
o open http(s)://localhost:PORT in the browser
f / filter/search by port, process or PID
v toggle IPv4 / IPv6 / both
e show/hide ESTABLISHED connections
s cycle sort column
c copy selected row to clipboard
n clear the new-port highlight
r refresh now
, settings β€” live theme picker, remap any key
? help
q quit

βš™οΈ Configuration

Everything below is optional β€” portop works with no config file at all.

Press , inside portop:

  • Theme: ←/β†’ cycles through it live β€” the whole UI re-skins as you move, no restart, no confirmation needed.
  • Keybindings: pick any of the 18 actions, hit enter, then press whatever you want it bound to. esc cancels instead of capturing.
  • Reset keybindings to defaults at the bottom of the list, one keypress.

Every change is written to config.yml immediately β€” you never have to open the file yourself.

Or hand-edit config.yml
portop --init-config   # writes a fully-commented template and exits

That writes to ~/.config/portop/config.yml (pass --config /path to use a different one). Example:

theme: tokyo-night        # default | dracula | nord | solarized | gruvbox
                          # | catppuccin | tokyo-night | monokai | darcula
                          # | vscode | ubuntu | mono
show_established: false   # same as always passing --listen
refresh_interval: 1s

keybindings:
  kill: ["x"]
  quit: ["q", "ctrl+c"]

Command-line flags always win over config.yml when both set the same thing. An unknown theme name or keybinding action fails fast with a message telling you what's valid, instead of silently doing nothing. Fields the settings screen doesn't touch (like refresh_interval above) are left exactly as you wrote them.

πŸ”§ How it works

portop reads /proc/net/{tcp,tcp6,udp,udp6} for the socket table and walks /proc/<pid>/fd to match socket inodes to owning processes β€” the same technique lsof/ss use, no root required beyond what's needed to see other users' processes. systemd unit and Docker container association are derived from each process's cgroup path, so no D-Bus or Docker SDK dependency is needed; Docker container names are resolved via a couple of read-only calls to the Docker Engine API over its unix socket when available. Well-known port names come from parsing /etc/services, and baseline diffing just snapshots the LISTEN set to a small JSON file under your OS's config directory.

βœ… Requirements

  • Linux only. The scanner reads /proc/net directly β€” there's no /proc on macOS or Windows, so portop doesn't ship builds for either.
  • sudo/root only if you want to see sockets owned by other users (e.g. root's docker-proxy) β€” portop runs fine without it, it just can't resolve those specific rows.
  • Go 1.24+ only if building from source.
  • A terminal that reports 256-color or truecolor support. A bare TERM=xterm (no -256color suffix) gets detected as a 16-color terminal, which downsamples every theme's colors and can make some hard to tell apart β€” export TERM=xterm-256color (or COLORTERM=truecolor) fixes it. This is common in minimal Docker/SSH sessions.

🀝 Contributing

Pull requests are welcome β€” see CONTRIBUTING.md for the branch policy and PR requirements. Short version: open PRs against develop, not main, and run gofmt -l . && go vet ./... && go test ./... before pushing.

πŸ“„ License

MIT β€” see LICENSE.

Directories ΒΆ

Path Synopsis
cmd
portop command
Command portop is a terminal UI showing which processes are really using your TCP/UDP ports β€” like htop, but for ports.
Command portop is a terminal UI showing which processes are really using your TCP/UDP ports β€” like htop, but for ports.
internal
app
Package app wires together the scanner, docker, systemd, dns and cpu packages into the enriched rows the UI (and --json mode) display, and holds the CLI-level configuration for a run.
Package app wires together the scanner, docker, systemd, dns and cpu packages into the enriched rows the UI (and --json mode) display, and holds the CLI-level configuration for a run.
baseline
Package baseline implements portop's drift-detection feature: save a snapshot of which ports are currently listening, then later diff the live system against it to see what changed.
Package baseline implements portop's drift-detection feature: save a snapshot of which ports are currently listening, then later diff the live system against it to see what changed.
cli
Package cli parses portop's command-line interface and dispatches to either the interactive TUI or the non-interactive --json snapshot mode.
Package cli parses portop's command-line interface and dispatches to either the interactive TUI or the non-interactive --json snapshot mode.
config
Package config loads portop's optional config.yml: default flag values, the color theme, and keybinding overrides.
Package config loads portop's optional config.yml: default flag values, the color theme, and keybinding overrides.
dnscache
Package dnscache provides a caching, non-blocking reverse DNS resolver suitable for calling from a UI render loop: a lookup that has not completed yet returns immediately with ok=false and completes in the background, so the UI never stalls on a slow or dead resolver.
Package dnscache provides a caching, non-blocking reverse DNS resolver suitable for calling from a UI render loop: a lookup that has not completed yet returns immediately with ok=false and completes in the background, so the UI never stalls on a slow or dead resolver.
docker
Package docker associates a process with the Docker container that owns it (via the process's cgroup) and looks up that container's name through the Docker Engine API over its unix socket.
Package docker associates a process with the Docker container that owns it (via the process's cgroup) and looks up that container's name through the Docker Engine API over its unix socket.
netutil
Package netutil decodes the hex-encoded address/port fields used by Linux's /proc/net/{tcp,tcp6,udp,udp6} pseudo-files.
Package netutil decodes the hex-encoded address/port fields used by Linux's /proc/net/{tcp,tcp6,udp,udp6} pseudo-files.
notify
Package notify sends best-effort desktop notifications (used by portop's "alert on new listening port" feature).
Package notify sends best-effort desktop notifications (used by portop's "alert on new listening port" feature).
openurl
Package openurl opens a URL in the user's default browser, on whichever OS portop happens to be running on.
Package openurl opens a URL in the user's default browser, on whichever OS portop happens to be running on.
procctl
Package procctl sends termination signals to processes by PID, for portop's "k" (kill) action.
Package procctl sends termination signals to processes by PID, for portop's "k" (kill) action.
procinfo
Package procinfo reads detailed, on-demand information about a single process for the TUI's detail view (Enter on a row): command line, executable path, working directory, owning user, memory usage and start time.
Package procinfo reads detailed, on-demand information about a single process for the TUI's detail view (Enter on a row): command line, executable path, working directory, owning user, memory usage and start time.
scanner
Package scanner enumerates TCP/UDP sockets on Linux by reading the /proc/net/{tcp,tcp6,udp,udp6} pseudo-files and resolves each socket to the owning process by walking /proc/<pid>/fd.
Package scanner enumerates TCP/UDP sockets on Linux by reading the /proc/net/{tcp,tcp6,udp,udp6} pseudo-files and resolves each socket to the owning process by walking /proc/<pid>/fd.
services
Package services resolves well-known port numbers to service names (22 -> ssh, 443 -> https) by parsing the system's /etc/services file, so portop can hint at what a port is for even when the owning process can't be resolved (e.g.
Package services resolves well-known port numbers to service names (22 -> ssh, 443 -> https) by parsing the system's /etc/services file, so portop can hint at what a port is for even when the owning process can't be resolved (e.g.
systemdinfo
Package systemdinfo associates a process with the systemd unit that owns it, by inspecting the process's cgroup membership.
Package systemdinfo associates a process with the systemd unit that owns it, by inspecting the process's cgroup membership.
ui
Package ui implements portop's Bubble Tea terminal UI: a live, sortable, filterable table of TCP/UDP sockets enriched with process, systemd and Docker context.
Package ui implements portop's Bubble Tea terminal UI: a live, sortable, filterable table of TCP/UDP sockets enriched with process, systemd and Docker context.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL