Skip to content

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

52 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Portlet

What is it?

VPN Gate client for MacOS / Linux

Why?

There is not official GUI client for connecting with MacOS or Linux

Roadmap features

  • VPN connection lifecycle (connect, disconnect, retry)
  • Saved server profiles
  • Status bar management
  • Improve UI
  • Release and troubleshooting documentation

Development Setup

  • Clone repo
  • Navigate to repo
  • Install packages npm i
  • Run program npm run start

Connection lifecycle

The connection panel is authoritative: use its state and failure message rather than assuming a click succeeded. Server-row Connect buttons are enabled only while disconnected or failed. The available lifecycle actions are:

State Visible label Meaning Available action
disconnected Disconnected No connection attempt is active. Connect from a server row.
authorizing Authorizing The operating system authorization flow is active. Disconnect to cancel.
connecting Connecting OpenVPN started and is negotiating the tunnel. Disconnect.
connected Connected The authenticated management channel reported the tunnel up. Disconnect.
disconnecting Disconnecting Portlet is stopping the current attempt. None; wait for Disconnected.
reconnecting Reconnecting An unexpected loss has a retry pending. Disconnect to cancel recovery.
failed Failed The displayed failure stopped the attempt or exhausted retries. Reconnect the same server, or Connect to another server.

Retry and reconnect behavior

The automatic retry setting defaults to 1; the supported range is 0 through 5, and the saved setting persists across launches. Failures before a managed OpenVPN attempt starts—including authorization, invalid configuration, and executable discovery or version validation—become failed and do not consume automatic retries. A launcher failure can occur before launch completes or before the first connected signal; it also becomes failed without an automatic retry. A management-channel loss after a managed attempt starts is retryable, whether the UI has reached connecting or connected. To avoid promising recovery during startup, this guide treats an unexpected OpenVPN process exit as retryable only after the first connected signal. Authorization cancellation and denial are not retried. A missing or unsupported OpenVPN executable is not retried.

Disconnect during authorizing, connecting, connected, or reconnecting cancels the active attempt and any pending automatic retry. Reconnect retries the same server after a failure and resets the automatic-retry count. The displayed attempt number counts connection/authorization attempts. It increments before authorization and launch, so it does not prove that an OpenVPN process started; the first automatic retry normally shows attempt 2. Events are tied to their attempt: stale attempt events are ignored and a late initial status snapshot cannot replace a newer status event.

Portlet does not publish disconnected, start a retry, or start a replacement attempt until the previous privileged OpenVPN process has confirmed exit. If termination cannot be confirmed within the bounded helper deadline, the state becomes failed and the application remains open during an attempted quit so it cannot silently leave a privileged tunnel behind. At startup, Portlet removes only current-user stale session directories whose private layout, ownership, file types, and modes validate exactly; an unvalidated matching path fails startup without being deleted.

Troubleshooting

Portlet displays the safe failure message in the connection panel. Fix the cause before selecting Reconnect; repeated Connect requests while a connection is active do not start another process.

Failure code or symptom Actual behavior Operator/user response
Startup/early failure: authorization-cancelled, authorization-denied, invalid-config, openvpn-not-found, or openvpn-unsupported Failure occurs before a managed OpenVPN attempt starts; state becomes failed and the failure does not consume automatic retries. Resolve the displayed cause, then use Reconnect or select another server. For missing system OpenVPN, install it at a supported path; for authorization failure, accept the next operating-system prompt.
launch-failed Launcher failure can occur before launch completes or before the first connected signal; state becomes failed with no automatic retry. Verify the signed macOS bridge/helper or Linux pkexec, helper, policy, and graphical authentication agent, then use Reconnect.
management-failed after a managed attempt starts State becomes reconnecting while retries remain. Wait for recovery or Disconnect to cancel it.
Unexpected OpenVPN process exit after the first connected signal State becomes reconnecting while retries remain. Wait for recovery or Disconnect to cancel it. For a deterministic release test, wait for connected before forcing each process loss.
retry-exhausted State becomes failed. Correct the underlying tunnel problem, then use Reconnect.

The operating system owns every privileged prompt. There is no Portlet password field: never type a system password into Portlet. If no system prompt appears, do not work around authorization with sudo; verify the installed privileged infrastructure described below.

Release smoke-test matrix

Run this matrix on every installer and OpenVPN variant intended for release. Record the observed states rather than marking a scenario passed from its final screen alone.

Scenario Test operation Expected visible state sequence Expected result and user action
Successful connect Select a reachable server and approve the system prompt. authorizingconnectingconnected Tunnel traffic is usable; Disconnect remains available.
User cancels authorization Select Connect, then cancel the operating-system prompt. authorizingfailed Cancellation is displayed and is not retried; use Reconnect to try again.
System OpenVPN absent On a disposable test image with no OpenVPN installed at any supported path, select Connect. authorizingfailed openvpn-not-found is displayed; Install OpenVPN, then use Reconnect.
Intentional disconnect Select Disconnect from an active connection. connecteddisconnectingdisconnected No automatic retry starts; use Connect for a new session.
Forced unexpected OpenVPN exit Terminate the active OpenVPN process outside Portlet. connectedreconnecting A configured retry is scheduled; Disconnect to cancel recovery.
One successful automatic reconnect Restore the test network before the first retry completes. reconnectingauthorizingconnectingconnected The attempt number advances and the tunnel works; Disconnect remains available.
Retry exhaustion Set automatic retries to 1. After each attempt reaches connected, terminate its OpenVPN process: once to start the retry and again after the retry connects. connectedreconnectingauthorizingconnectingconnectedfailed The second post-connected loss displays retry-exhausted; repair the cause and use Reconnect.
Restart after an unclean exit Force-quit Portlet while connected, then reopen it. loading → disconnected Inspect for an orphaned OpenVPN process and stale private session directories before using Connect; any leftover blocks release.
Bundled/system build startup Launch each installed release variant, then test Connect. loading → disconnected Startup is idle because executable discovery is lazy; Connect must exercise the correct bundled or system path.
Stale event isolation after reconnect After a successful retry, induce or replay a late exit from the old attempt. connected The newer status remains connected; Disconnect remains available.

For each fresh-install run, confirm that the first Connect produces the system authorization prompt, Portlet contains no password field, Disconnect cancels a pending retry, and stale events never replace the newer connected attempt. Run forced-exit and unclean-restart cases only on a disposable test machine. After the unclean restart scenario, inspect both the process list and Portlet's vpn-sessions directory; the current automated suite does not substitute for this machine-level observation. Capture evidence using the release verification record, without capturing credentials or generated OpenVPN configuration.

Packaging

npm run package creates an unsigned, developer-only application directory. It deliberately omits the signed macOS bridge and helper or the installed Linux helper and Polkit policy, so its output is not VPN-capable. Use it for local UI and lifecycle development, never as a release artifact.

VPN-capable release packaging supports a signed macOS ZIP with system OpenVPN, plus Debian (.deb) and RPM (.rpm) packages with system or bundled OpenVPN. The named release commands set PORTLET_RELEASE_BUILD=1; Forge rejects any other make attempt before it creates a misleading developer artifact.

Privileged launchers are built as native x64 and arm64 binaries. On both macOS and Linux, the requested release architecture must match the build host; cross-architecture builds are rejected before packaging. macOS universal release packages are not supported because the current helper build does not produce or verify a universal native launcher. Build each supported architecture on a matching CI runner instead.

System OpenVPN

Run npm run make:system to build release artifacts that discover OpenVPN on the host. OpenVPN must already be installed, executable, root-owned, not group- or world-writable, and report a supported version. On macOS it must also be a regular file rather than a Homebrew-style symlink; this is checked before the authorization prompt. The system build searches these locations in order:

  • macOS: /opt/homebrew/sbin/openvpn, /usr/local/sbin/openvpn, /usr/sbin/openvpn, and /usr/bin/openvpn
  • Linux: /usr/sbin/openvpn, /usr/bin/openvpn, and /usr/local/sbin/openvpn

Bundled OpenVPN

Run npm run make:bundled on Linux only after release automation supplies a vetted, supported executable at this exact path:

  • resources/openvpn/linux/openvpn

The file must be executable. Release-supplied binaries are ignored by Git and copied into the packaged application only for bundled builds. The Linux package installer establishes the privileged launcher ownership described below.

macOS bundled OpenVPN release packaging is intentionally rejected. A ZIP cannot make resources/openvpn/darwin/openvpn (or the corresponding path below /Applications/portlet.app) root-owned, so it cannot satisfy the privileged helper's trust boundary. Use npm run make:system with a root-owned regular OpenVPN executable until an installer-managed bundled path is implemented.

Authorization and installer requirements

The operating system owns the authorization prompt; Portlet never asks for, receives, or stores the user's system password.

On macOS, release packaging requires a valid Developer ID application identity in PORTLET_APP_SIGNING_IDENTITY and its ten-character team identifier in PORTLET_APP_TEAM_ID. Forge signs the application, embeds the SMPrivilegedExecutables requirement, then stages the signed helper at Contents/Library/LaunchServices/com.benbousquet.portlet.vpn-helper and the signed authorization bridge at Contents/Resources/native/portlet-vpn-bridge. The bridge asks SMJobBless to install the helper when the VPN starts. Release smoke tests install the signed application at /Applications/portlet.app. The bridge and helper accept only fixed system OpenVPN paths, and the helper independently requires a root-owned regular executable that is not group- or world-writable. An unsigned development ZIP cannot satisfy these requirements.

On Linux, release packaging compiles the narrow native helper before packaging. The Debian and RPM lifecycle scripts require pkexec, install /usr/libexec/portlet-vpn-helper as root:root and mode 0755, and install the policy at /usr/share/polkit-1/actions/com.benbousquet.portlet.vpn.policy as root:root and mode 0644. Installation fails if either packaged asset is missing or its final metadata cannot be verified. A graphical Polkit authentication agent is also required so the desktop can show the system authorization prompt.

Do not set PORTLET_RELEASE_BUILD=1 by hand to bypass prerequisites. Use npm run make:system or npm run make:bundled; missing compilers, signing credentials, native assets, or package tools are release failures.

Flatpak packages are not VPN-capable builds because their sandbox cannot install or execute Portlet's required host-privileged OpenVPN launch path.

About

VPNGate GUI Client for MacOS and Linux

Topics

Resources

Stars

3 stars

Watchers

1 watching

Forks

Releases

Packages

Contributors

Languages