Skip to content
 
 

Repository files navigation

goports – Local macOS Port Management Utility

goports Screenshot

Table of Contents

  1. Features
  2. Requirements
  3. Getting Started
  4. Contribute

goports runs as a macOS menu‑bar app and CLI utility that shows every listening TCP socket (and now UDP listeners too). IPv4 and IPv6 addresses are handled, and the output identifies the protocol so you can distinguish tcp/80 from udp/53 easily. It's a single Go binary with no external runtime requirements.

Whether you’re developing servers, debugging networking issues or simply curious, goports lets you inspect, open or kill port owners without leaving the keyboard.

Quick start

  1. Install – From this repo’s GitHub Releases (tags v*), grab the binary for your OS, or build: make build./bin/goports.
  2. Inspect./bin/goports lists listeners once; ./bin/goports --watch refreshes every 5s.
  3. Script./bin/goports --json or --csv for machine-readable output; ./bin/goports --help for grouped flags and exit codes; ./bin/goports --version for the build label. Use -q / --quiet for plain tables (and non-TTY-friendly --watch without cursor tricks).
  4. Optional API./bin/goports --http-port 8765 then open http://127.0.0.1:8765/ for the activity UI (see HTTP API).

On macOS, ./bin/goports --gui starts the menu-bar app (if built with GUI support).


Features

goports exposes the same discovery engine to both a menu-bar GUI and a command-line interface. Highlights:

  • Real-time port monitoring – all listening TCP sockets (and UDP listeners) are listed and updated every few seconds.
  • Host name resolution — reverse DNS is performed on each address, so 127.0.0.1 may appear as localhost.
  • Application identification — see the executable name and, when possible, its CFBundleIdentifier. The GUI looks up a matching bundle and icon via Spotlight; for plain binaries we fall back to built-in PNGs for a handful of common tools (ruby, prometheus, netcat, nxrunner, onedrive, rapportd, etc.). Icons require the sips tool; the code uses a temporary file to avoid earlier "exit status 13" problems. Launch the GUI from a terminal and check stderr for iconForBundle: messages if something still goes wrong.
  • Process control — terminate listeners directly from the menu or with --kill.
  • Activity events — the internal API now publishes open/close events for ports; consumers can SubscribeActivity in-process or connect over HTTP. The binary can start a small local web server (--http-port) exposing /events (SSE) and a basic web UI that shows a live log of opens/closes. External applications may also consume the SSE stream for their own graphs or dashboards.
  • Browser integration--open or the GUI menu item launches http://localhost:<port> in the default browser.
  • Lightweight Go binary — single executable produced with go build; legacy Python/py2app support is only retained for historical reference.
  • Configurable preferences — a Settings submenu controls start‑at‑login (launch automatically when you sign in), notifications, and refresh interval; preferences persist in ~/.config/goports/settings.json.

Requirements

  • macOS (apps use lsof and Cocoa menu bar APIs)
  • lsof must be present on your PATH (installed by default on macOS)
  • sips required if you want application icons in the GUI
  • optional: Spotlight indexing enabled for icon resolution

Getting Started

Note: the CLI engine is now abstracted so ports discovery can be implemented on non‑macOS platforms. The application separates GUI code behind build tags; non‑darwin builds produce a CLI-only binary that will compile anywhere. Currently only macOS provides a working backend, but the scaffolding is in place for Linux/Windows support.

Quick start

See the Quick start at the top of this file for the shortest path from binary to JSON/API.

Download

You can download a pre‑built bundle from the dist directory on the repository, for example:

``` https://raw.githubusercontent.com/alextheberge/goports/master/dist/goports.zip ```

Alternatively clone the repo and build locally (see Building below).
Legacy Python source has been moved to legacy/python for historical reference; the modern code is all Go.

Building

The project is now a pure Go application; there is no Python dependency. Use the standard make targets to compile and package.

```bash

compile the CLI binary (output: bin/goports)

make build

create a macOS app bundle

make build-app # writes goports.app

build then immediately run the bundle (GUI mode)

the app has LSUIElement=true so no Dock icon is shown –

look for its icon in the menu bar instead.

make run-app

package the bundle for release

make dist # writes dist/goports.zip ```

Drop goports.app in your /Applications folder after building, or unzip the archive produced by make dist.

⚠️ make python-build and setup.py are maintained only for historic reference; they no longer produce a usable application.

Versioning (MVS)

This repo tracks a multidimensional version in mvs.json ([ARCH].[FEAT].[PROT]-[CONT]). Source comments such as @mvs-feature(...) and @mvs-protocol(...) tag capability and integration surfaces; the public CLI entrypoint cli.Run is the Go API boundary for lint. After changing flags, HTTP contract, or those tags, refresh the manifest and commit it:

# install mvs-manager once (see upstream README for Windows)
curl -fsSL https://raw.githubusercontent.com/alextheberge/MVSengine/master/scripts/install.sh | bash

make mvs-generate   # reconcile mvs.json with the tree
make lint-mvs       # CI check: manifest matches code

Use make install-hooks to point Git at .githooks so commits run make lint-mvs. GitHub Actions (.github/workflows/ci.yml) runs go vet, go test, go build, a Windows cross-compile, and mvs-manager lint on pushes and pull requests.

Pushing a tag matching v* (for example v0.3.0) triggers .github/workflows/release.yml, which uploads Linux, Windows, and macOS binaries plus a checksums.txt file to the GitHub release.

Usage

goports enumerates listening sockets via a platform-specific engine. On macOS the implementation now reads listener information via the sysctl interface (net.inet.{tcp,udp}.pcblist) and subsequently scans process file descriptors via libproc, so PIDs, names and command-line paths are obtained without ever spawning netstat/lsof. A short in‑memory cache (≈1 s) avoids repeated kernel calls during rapid polling. Netstat is only consulted as a fallback if the syscall fails. On Linux we likewise parse /proc/net/* and then inspect /proc/<pid>/fd entries to map sockets back to processes, populating PID, command line and executable name; the native map is also cached briefly. On Windows the backend uses the IP Helper API (GetExtendedTcpTable/GetExtendedUdpTable) plus QueryFullProcessImageName to provide equivalent native data. In all cases, fallback to the appropriate system tool (lsof/netstat) remains possible if the native call fails. The code is structured so that fully native backends (e.g. Win32 APIs) may replace the external command dependencies later without touching rendering logic. The CLI output includes a PROTO column so the protocol is obvious. When running the GUI you will initially need macOS; non-darwin builds are CLI- only. Both GUI and CLI share the same engine; items appear in the menu bar and the table automatically.

Behaviors you get “for free”:

  • reverse DNS on local addresses (127.0.0.1localhost, bracketed IPv6 addresses are also handled)
  • process bundle ID and icon lookups when available
  • GUI menu‑bar titles now include the PID(s) and a short command‑line snippet for each listening process, making it easy to identify and drill into the responsible application without opening a submenu

CLI flags

Run goports --help for the full grouped list. Summary (note: PROTO and FAMILY columns appear when no filters are applied):

-h, --help            # usage (exit 0)
--version             # build version (set at link time for releases)

# Exit codes: 0 ok, 1 error, 2 bad flags (Go flag package), 3 kill had failures
--watch, -w           # refresh every 5 seconds
-q, --quiet           # plain table columns; with --watch, plain refresh if stdout is not a TTY
--json                # structured JSON
--csv                 # CSV rows
--tui                 # ASCII graph in the terminal
--export              # dump activity history as JSON and exit
--spec                # print OpenAPI JSON and exit

--kill PORT           # kill listeners on PORT (any protocol)
--kill-name SUBSTR    # kill processes whose command contains SUBSTR
--kill-bundle SUBSTR  # kill processes whose bundle id contains SUBSTR
--signal NAME         # TERM, KILL, etc. (default KILL)

--proto tcp|udp       # filter by protocol
--name SUBSTR         # filter by process name
--bundle SUBSTR       # filter by bundle id substring
--family IPv4|IPv6    # filter by address family
--native              # native discovery only (no lsof fallback)

--http-port N         # serve HTTP API (see below) on port N
--open PORT           # open http://localhost:PORT in the default browser

# Parsed on all platforms for a consistent argv; effective on macOS GUI only:
--gui                 # menu-bar app (handled in main before CLI)
--webview-width N
--webview-height N
--webview-x N
--webview-y N
--webview-debug
--webview-title TEXT
--webview-child [URL] # internal webview helper process

HTTP API

When the CLI is started with --http-port (or via the GUI "Open in Browser" action), a tiny HTTP server listens on localhost. It supports:

  • GET /events – a continuous stream of port open/close events. By default it uses Server-Sent Events (SSE); clients may upgrade to a WebSocket by supplying the Upgrade: websocket header. The stream optionally accepts since (RFC3339 timestamp) and token query parameters for historical replay and authentication.
  • GET /status – return a simple JSON object containing the current number of open ports (e.g. { "open": 5 }). This is useful for initializing dashboards.
  • GET /history – a JSON array of recent events whose history is kept in memory (size configurable at compile time). Supports filtering by protocol, port, since and limit. Authentication is enforced via the GOPORTS_API_TOKEN environment variable; supply the token either as a token query parameter or as a Bearer Authorization header.
  • GET /openapi.json – a machine-readable OpenAPI 3.0 schema describing the above endpoints, including parameter types and response models. Third-party tools (Swagger UI, curl, etc.) can consume this spec.

The server honors GOPORTS_TLS_CERT/GOPORTS_TLS_KEY if present and will fall back to generating a short-lived self-signed certificate if --http-tls is requested but no files are provided. It also supports listen addresses prefixed with unix: to bind a UNIX domain socket.

The embedded web UI served at / displays a simple Chart.js graph of port activity and can be used as an example client. The page is lightly styled with Tailwind CSS for a cleaner look. On startup it first queries /status for the current open‑port count, then replays any recent history; this ensures the chart always has a sensible baseline even if no ports have opened or closed yet. A second panel shows a live snapshot of all listening ports and associated metadata (PID, command line, bundle identifier) drawn from the /ports endpoint. The snapshot is refreshed whenever an open/close event arrives. The page updates in real time, and controls let you reset the stored history or download the entire buffer as JSON for offline analysis.

Example clients

Fetch the spec with curl:

curl http://localhost:<port>/openapi.json | jq

Stream events using SSE from bash:

curl -N "http://localhost:<port>/events?since=$(date -u +%FT%TZ)"

A minimal JavaScript consumer:

<script>
let url = "/events";
let evt = new EventSource(url);
evt.onmessage = e => console.log("activity", JSON.parse(e.data));
</script>

You can also view the API interactively by opening /swagger in your browser; it renders the spec with Swagger UI.

Security and error responses

Treat the HTTP server as local-only unless you know what you are doing. Bind to loopback (default :port is all interfaces — prefer 127.0.0.1:PORT or unix:/path for a socket). Set GOPORTS_API_TOKEN when anything other than trusted clients can reach the port; pass the token as query token= or Authorization: Bearer …. For TLS, supply GOPORTS_TLS_CERT and GOPORTS_TLS_KEY, or use a reverse proxy.

Clients that send Accept: application/json receive structured errors on failure, for example {"error":{"code":"unauthorized","message":"unauthorized"}}. Other clients get plain text/plain bodies from net/http.

Diagnostics

Set GOPORTS_LOG=debug for verbose slog output to stderr (and the same lines append to settings.json.log next to your config). See docs/RELEASING.md for tagging releases and docs/MACOS_SIGNING.md for optional signing/notarization.

GUI Settings

When running in GUI mode a Settings submenu is available. Highlights:

  • Show TCP / Show UDP – toggle visibility of each protocol from Settings.

  • Filter… – show only menu items matching a case-insensitive substring; the current filter is shown in the menu and persisted across launches.

  • Per-port notifications – each port submenu can mute or enable alerts; choices persist between sessions.

  • Use native discovery only – when checked the GUI avoids invoking lsof and similar helpers and uses built-in platform APIs (sysctl/libproc on macOS, /proc on Linux, IP Helper on Windows). Results are cached.

    Why enable this mode?

    • Self-contained binary with no external command dependencies – containers, sandboxes, or hosts without lsof.
    • Less CPU/battery than repeated lsof during polling.
    • On Linux/Windows, native discovery is the primary path; the flag matches the CLI’s --native.

    Downsides

    The OS may hide process metadata (macOS SIP/sandbox, Linux /proc permissions). You may see Pid == 0 and empty command lines; turn native mode off to allow lsof fallback when you need rich PID/cmdline data.

  • The menu bar icon switches between light and dark variants depending on system appearance.

  • Reset all preferences… – restores defaults (notifications, TCP/UDP, native mode, filter text, refresh interval, webview fields, blocked notification map). Confirms via a dialog before writing settings.json.

  • Embedded webview dimensions & title – if you prefer a different size for the activity graph window, you can set webview_width and webview_height in the config file (~/.config/goports/settings.json) or launch the GUI with --webview-width / --webview-height. You can also specify a custom window title using the webview_title field or the --webview-title flag. A --webview-debug flag enables the underlying webview library's debug mode (useful when the window fails to appear). The Settings menu now offers Webview width…, height…, and title… items, plus Reset webview settings to restore defaults.

When the embedded view is requested the app no longer tries to host it in its own process; instead it spawns a helper copy of itself (--webview-child) on the main thread so macOS’s WebKit requirements are satisfied. The parent menu-bar process therefore cannot crash if webview initialization fails. The helper is launched directly (not via open) which lets us capture its stdout/stderr for logging; this is also why you may see file descriptors in use after the call. The child process explicitly locks its OS thread before creating and running the webview; failing to do so previously caused nothing to appear while the helper stayed alive with no errors logged. If you still encounter a blank window check the diagnostic log – you should see messages like

webview child starting with URL=…
webview.Run about to execute
webview.Run returned, exiting child

On non‑macOS builds the internal/gui package is a no-op stub – there is no menu bar or embedded webview, but the HTTP API still runs and you can open the activity graph manually in your browser (--browse flag). A proper tray‑icon GUI for Linux/Windows is intended for a future release.

The helper now also attempts to place the window at sensible coordinates – by default it sits about 100 points from the left edge and 50 points below the top of the main display (i.e. just under the menu bar). It will then slide into position with a short animation and activate the app so the window floats above other applications such as VSCode. You can customise the position via the --webview-x/--webview-y CLI flags or in the Preferences menu; y is interpreted as a distance from the top of the screen. The settings are persisted along with the width/height/title. If the helper still refuses to appear the log will contain panic information. If the child cannot be started a notification is shown, the menu item is disabled and relabelled "Activity Graph (unavailable)", and the page opens in your default browser. Diagnostic messages are emitted to stderr and also appended to a log file alongside the configuration (e.g. /Users/…/Library/Application Support/goports/settings.json.log). Inspect that file if you don’t run from a terminal – it will contain the child process output and error reasons.

  • Descriptive tooltips on menu items improve accessibility for assistive technologies such as VoiceOver.

The following preferences are persisted across launches:

  • Start at Login – toggles whether goports is added to your macOS login items. Enabling will attempt to create/delete a System Events login item via AppleScript; failures are non‑fatal.
  • Enable Notifications – when on, the menu will send native notifications for ports opening and closing. Turning it off silences those alerts.
  • Refresh interval – click repeatedly to cycle the polling interval between 5, 10 and 15 seconds; the current value is shown in the menu label.

These settings are stored in ~/.config/goports/settings.json.

  • --gui — launch the menu‑bar GUI (default when no flags are provided). Ignored on non‑macOS builds where only the CLI/API is available.
  • --native — avoid calling external tools (like lsof); use built-in platform APIs only. See the discussion above about native discovery limitations: metadata may still be missing if the kernel refuses to expose it. The environment variables GOPORTS_DEBUG=1 and GOPORTS_FAKE_SYSCTL=1 can be used to diagnose or simulate failures when running from a terminal.
  • --watch, -w — refresh the CLI output every 5 seconds.
  • --kill <port> — terminate all processes listening on <port> (any protocol). On Unix-like systems this sends the chosen signal, while on Windows taskkill is invoked.
  • --kill-name <substr> — terminate processes whose command name contains <substr>.
  • --kill-bundle <substr> — terminate processes whose bundle identifier contains <substr>.
  • --signal <name> — specify signal to use for kills (TERM, INT, KILL, etc.).
  • --open <port> — open http://localhost:<port> in the default browser.
  • --http-port <port> — if nonzero, start a local HTTP server on the given port. In addition to streaming activity events at /events, the server serves a minimal web UI on / that displays a scrolling log of changes and a live count graph. The page loads recent history on startup and provides buttons to reset the stored history or download it as JSON for offline analysis. A browser will automatically update as ports open and close; this makes it easy to view activity graphically without any external tooling. When running in GUI mode the menu bar shows a “View Activity Graph” item that opens the page at the appropriate address.

Examples:

# show a one‑shot table of current ports (proto column added)
./bin/goports

```bash

show a one‑shot table of current ports

./bin/goports

continuously update the table until interrupted

./bin/goports --watch

kill whatever is bound to 8080

./bin/goports --kill 8080

open a local web server on 3000

./bin/goports --open 3000

start the GUI explicitly (normally invoked by double-clicking goports.app)

./bin/goports --gui ```

The GUI mode is also the default when you launch goports.app from Finder.

Contribute

Feel free to open issues or PRs. The implementation lives under internal/ and is intentionally small — adding new features such as cross-platform support, stats collection, or UI polish is straightforward.

About

Ports Mac OS X App in Go

Resources

Stars

Watchers

Forks

Releases

Packages

Contributors

Languages