Skip to content
fatihaydostPublic

About

KDE Plasma 6 system tray widget: switch SSH port forwards (ssh -L tunnels) on and off, and see every port listening on your machine.

Topics

Resources

Stars

15 stars

Watchers

0 watching

Forks

Latest commit

 

History

13 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Porthole logo

Porthole

SSH port forwards from the KDE Plasma system tray. One switch per tunnel.

Release KDE Store Plasma 6 systemd user session License

Define a forward once, then turn it on and off from the tray instead of keeping an ssh -L terminal open. Below it, every port listening on your machine, named after its program and project.

The Porthole popup in Breeze Dark: three tunnels in different states, and the local ports Vite, Next.js and Python http.server

What it does

Tunnels

  • One switch per forward, with a live state: Active, Connecting…, Waiting for approval, Off, or the error ssh reported. Click a row to read the whole error.
  • Tunnels outlive Plasma. Each runs as a transient systemd user service (porthole-<id>), not as a child of plasmashell, and is found again after a restart.
  • Active means listening: a row turns green only once its ssh accepts connections on the local port.
  • Bounce a port between hosts: switching on a forward stops any other one on the same local address and port, so moving 3000 from staging to production is one click.
  • Or keep them side by side with a bind address: 127.0.1.1:3000 to staging and 127.0.1.2:3000 to production run at the same time. An address other machines can reach (0.0.0.0, *, a LAN address) is allowed, and the form says so.
  • When ssh needs you: the Tailscale SSH approval link opens from the row; an unknown host key can be trusted and retried; a changed host key is never trusted automatically.
  • Start with Plasma, per forward. The SSH host field suggests the hosts in ~/.ssh/config.
  • The tray icon counts active tunnels and marks a failure with a small red dot. It never bounces.

Local ports

  • Dev servers, containers and services listening on TCP: Vite · shop, Next.js · dashboard, Docker · postgres. The project is the process's git repository, or its folder.
  • Servers open to the network (0.0.0.0, ::, a LAN address) are marked Reachable from your network.
  • Open in Browser, Copy Address, and Stop for your own processes. Stop asks first, sends SIGTERM, and offers Force Stop if the process is still there after 3 seconds.
  • System services are folded away. Ports are only read while the popup is open.

It follows your colour scheme and accent colour:

Breeze Light A failed tunnel, opened
The same popup in Breeze Light A tunnel row opened to show its full error and actions
Adding a forward In the tray
The add-forward form, suggesting hosts from ~/.ssh/config The tray icon with its active-tunnel count

Requirements

  • KDE Plasma 6
  • a systemd user session (systemd-run --user). Distributions without systemd are not supported; the popup says so.
  • the OpenSSH client and iproute2 (ss), present on almost every desktop
  • SSH that logs in without a prompt: a key without a passphrase, or one loaded in an agent (see SSH agent)

Install

From the KDE Store. Right-click the desktop → Add Widgets… → Get New Widgets… → Download New Plasma Widgets, and search for Porthole. It is also on the KDE Store.

From a release. Download porthole.plasmoid from Releases, then:

kpackagetool6 --type Plasma/Applet --install porthole.plasmoid

Use --upgrade instead of --install to update.

From source.

git clone https://github.com/fatihaydost/porthole.git
cd porthole
./install.sh --reload

--reload restarts Plasma so that a running copy picks up the new code.

Porthole shows up in the system tray. If it is hidden, right-click the tray's arrow → Configure System Tray… → Entries and set Porthole to Always shown.

Uninstall: kpackagetool6 --type Plasma/Applet --remove io.github.fatihaydost.porthole

Using it

Click Add, give the forward a name, a local port, an SSH host and the remote port, and flip its switch.

  • Left click opens the list, middle click re-reads everything.
  • With the popup open: ↑/↓ select a tunnel, Space/Enter toggle, E edit, A add, R or F5 refresh. Delete asks first; Enter confirms, Esc cancels.

Configuration file

Forwards are kept in ~/.config/porthole/forwards.json. You can edit it by hand:

{
  "version": 1,
  "forwards": [
    {
      "label": "Staging web",
      "bindAddress": "",
      "localPort": 3000,
      "sshTarget": "staging",
      "remoteHost": "localhost",
      "remotePort": 3000,
      "autostart": false,
      "extraOptions": "-J bastion"
    }
  ]
}
  • localPort and sshTarget (a Host from ~/.ssh/config, or user@host) are required. remoteHost defaults to localhost, remotePort to the local port.
  • bindAddress is optional; empty means localhost (127.0.0.1 and ::1). Every address in 127.0.0.0/8 is loopback on Linux and needs no setup, so 127.0.1.1 and 127.0.1.2 can each have their own tunnel on port 3000. It takes an IPv4 or IPv6 address (brackets optional), localhost, or * for every interface; an entry with anything else, such as a host name, is kept as written but not used.
  • extraOptions is split on whitespace; anything that needs quoting belongs in ~/.ssh/config.
  • id may be left out; Porthole derives a stable one.

Porthole never loses what you wrote: loading never writes the file, entries it cannot read are kept as they are, unknown fields survive a save, and writes are atomic (symlinks and permissions are kept). If the file is not valid JSON, Porthole shows an error and will not save until you fix it.

How a tunnel runs

systemd-run --user --unit=porthole-<id> -- \
  ssh -N -T -o BatchMode=yes -o ExitOnForwardFailure=yes \
      -o ServerAliveInterval=30 -o ServerAliveCountMax=3 -o ConnectTimeout=10 \
      -L [<bindAddress>:]<localPort>:<remoteHost>:<remotePort> [extraOptions] -- <sshTarget>

systemd is the single source of truth: every 4 seconds Porthole reads each unit's state, who listens on its address and port, and its journal when something is wrong. There is no automatic reconnect; a dropped connection shows as an error, and the switch retries it. The units are ordinary ones:

systemctl --user list-units 'porthole-*'
journalctl --user -u porthole-<id>

SSH agent

Tunnels run with BatchMode=yes under the systemd user manager, which often has no SSH_AUTH_SOCK. Porthole looks for a running agent itself (the systemd environment, Plasma's environment, OpenSSH's ssh-agent.socket, GNOME Keyring, gpg-agent) and passes it to the tunnel. Without one, ssh reads your key files directly, which works for keys without a passphrase. For a key with a passphrase:

systemctl --user enable --now ssh-agent.socket
ssh-add

Docker containers are named in Local ports only if docker ps works for your user (the docker group). Otherwise their ports show as unnamed system services.

Development

node tests/logic.test.js   # 39 unit tests
tests/run-e2e.sh           # 75 end-to-end steps against a throwaway sshd on 127.0.0.1:2222
/usr/lib/qt6/bin/qmllint -I /usr/lib/qt6/qml package/contents/ui/*.qml
plasmoidviewer -a package

run-e2e.sh needs sshd, socat, curl and PyQt6. It drives the widget's own QML through real tunnels and local servers, and stops only what it started.

License

MIT, see LICENSE.

About

KDE Plasma 6 system tray widget: switch SSH port forwards (ssh -L tunnels) on and off, and see every port listening on your machine.

Topics

Resources

Stars

15 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages