Skip to content

Releases: steeb-k/nullgate

v0.7.1

Choose a tag to compare

@github-actions github-actions released this 14 Sep 05:35

Fixed

  • The daemon's runaway memory (and the restart loop it caused) — properly this time. Since
    0.2.3 the daemon has restarted itself whenever resident memory passed 1 GB, blamed on iroh's
    never-evicted mapped-address cache (iroh#4293).
    On some machines that tripped every few minutes, and each trip dropped every connection at once.
    That diagnosis was wrong: the cache is keyed per remote node and cannot grow past the roster on a
    private mesh. The real cause is iroh's pending_open_paths retry queue
    (iroh#4390, also
    iroh#4509; unfixed upstream through 1.2.0): a
    candidate address whose QUIC path fails to open because the connection already holds its 8 paths
    is re-queued once per connection to that peer and retried every 333 ms, so with Nullgate's
    several connections per peer (mesh, gossip, docs, blobs) the queue multiplied every tick — 1–4 GB
    within 90 s of a start on Linux, a single ~80 GB allocation on Windows. Peers advertising
    unroutable addresses (IPv6 on a v4-only host, the members' own 10.99.0.x TUN addresses) keep the
    path budget pinned and so keep it firing. iroh is now consumed from a patched fork
    ([patch.crates-io]steeb-k/iroh, branch nullgate-1.0.0, identical to 1.0.0 plus the fix)
    that deduplicates the queue and caps it at 64 addresses, with regression tests. The memory
    watchdog stays as a backstop and its messages no longer claim the old cause; the docs and source
    comments that carried the wrong diagnosis are corrected. See docs/architecture.md.
  • Adding a relay access token now actually takes effect. relay add with a token reported
    "applied to the running daemon — no restart needed", but iroh only reads a relay's token when it
    first spawns that relay's connection actor, and an actor that is busy or is the home relay never
    exits. So a device that had already been dialing your relay token-less (because a peer advertised
    it) kept being refused indefinitely after the token was saved. A new or changed token now rebinds
    the endpoint in place (same node identity, a few seconds' blip) so the relay is connected with the
    token; token-less relays, removals, and policy changes still apply live as before.

Added

  • Releases are built and published by CI from a tag. .github/workflows/release.yml builds
    the Windows x86_64 + ARM64 MSIs (signed with Azure Trusted Signing over OIDC), the Linux tarball,
    the macOS universal tarball (Developer-ID signed, notarized and stapled — a first), and the
    release-keystore-signed Android APK, then publishes one GitHub release with all five assets in a
    single call after a version gate (tag == workspace version == Android versionName/versionCode).
    v<ver>-test<N> tags publish a prerelease the updaters ignore, for rehearsals. Modeled on
    steeb-k/commune's pipeline. See docs/ci-release.md and docs/releasing.md.

Changed

  • Both desktop updaters now verify what they download. Windows refuses an MSI whose Authenticode
    status is not Valid; macOS nullgatectl refuses a bundle that fails codesign --verify or whose
    Team ID differs from the installed app's (an older ad-hoc install accepts either, so it migrates).
  • scripts/package-macos.sh signs with CODESIGN_IDENTITY when set (hardened runtime + timestamp)
    and notarizes with NULLGATE_NOTARIZE=1; ad-hoc remains the default on a dev box.

v0.7.0

Choose a tag to compare

@steeb-k steeb-k released this 31 Aug 21:24

Fixed

Android battery: the mesh no longer tunnels its own traffic through itself.

A phone reported 78% of a 13% battery drop with Nullgate merely running in the background. The cause turned out to be a routing loop rather than anything to do with pacing or wakelocks.

iroh advertises every non-loopback local interface address as a direct-address candidate — including our own tunnel's 10.99.0.x, which the interface enumeration has no reason to treat specially — and every member routes the whole 10.99.0.0/24. So a peer's virtual IP looked like a perfectly reachable UDP address, iroh scored it as a direct path (ranking it above the relay), and the mesh's own QUIC packets were routed into the mesh's own tunnel and re-sent over the connection they belonged to.

The loop feeds itself — every keepalive it carries is re-tunnelled — so it ratchets. On an idle desktop mesh it measured ~113 packets/second and ~1.5 MB/min (~2 GB/day), almost all of it relayed to the phone, keeping its radio out of idle around the clock. Two stray ICMP packets were enough to start it.

Three guards now, because the address leaks through QUIC NAT-traversal candidates that no iroh API filters:

  • the path selector never chooses a path into the virtual /24;
  • the tunnel pump drops packets sent from our own endpoint's bound ports, so the bogus path also starves and gets retired;
  • on Android the VpnService excludes Nullgate itself from Nullgate's own tunnel (the same thing Tailscale does).

This is strictly a connectivity improvement — it removes a fake path that iroh was preferring over the working one. The guards are per-device, so a given pair of peers stays affected until both ends are on 0.7.0.

Windows: opening the app can no longer leave a duplicate tray icon (or window).

Tray-agent and GUI uniqueness relied on GApplication, whose deduplication runs over GLib's autolaunched D-Bus session bus — and when that bus's published address goes stale, every launch silently becomes its own primary, so each "Open Nullgate" spawned another agent and another tray icon until the next logoff. Both roles now guard themselves with named kernel mutexes, and a second GUI launch signals a named event so the existing window comes forward.


Install: Windows — download the MSI matching your architecture. Linux/macOS — curl -fsSL https://raw.githubusercontent.com/steeb-k/nullgate/main/install.sh | sh. Android — the .apk asset (or via Obtainium).

v0.6.0

Choose a tag to compare

@steeb-k steeb-k released this 10 Aug 19:24

Android: it heals itself now

This release is almost entirely about making the Android app trustworthy in the background.

It recovers on its own. The failure where the phone silently dropped off the network after
sitting idle — and stayed gone until you toggled Always-on VPN by hand — is fixed. The app now
runs a doze-safe health check that detects the dead state and escalates through progressively
stronger recoveries, up to and including restarting itself. In soak testing that reproduces the
exact failure (connectivity killed silently under doze, 12 cycles overnight), the old build
recovered 0 of 12 times; this build recovered 12 of 12, typically in about 10 minutes.
Sending traffic to an unreachable device (an SSH attempt, a KDE Connect push) also triggers an
immediate reconnect attempt instead of waiting out a retry timer.

It uses drastically less data. The old build could burn mobile data even while broken —
especially while broken. Measured idle usage dropped from ~17 MB/hour to ~0.25 MB/hour
(roughly 6 MB/day) with the app connected and the screen off. Network-change handling no longer
storms the network on flaky links, and the system's per-app data accounting no longer risks
billing Wi-Fi traffic to your mobile-data counter.

Sharper eyes for the next bug. Both the daemon (desktop) and the app (Android logcat) now log
a compact one-line network summary every minute, so any future "where is my data going" question
is answerable from evidence.

Desktop platforms are unaffected by the behavior changes in this release aside from the new
net-stats log line; this update ships for all platforms to keep versions aligned.

Update paths: desktop installs update themselves as usual. Android updates in place
(Obtainium or adb install -r) — no uninstall needed.

v0.5.0

Choose a tag to compare

@steeb-k steeb-k released this 22 Jul 15:10

Nullgate v0.5.0

Focused on the Android app: much lower background battery use, and automatic reconnection after switching VPNs. Desktop behaviour is unchanged.

Android battery

  • The engine now eases off its housekeeping when the app isn't on screen — the maintenance loop and presence heartbeat drop from a 3-second to a 60-second cadence with the screen off or the app backgrounded, and pick back up the moment you open it.
  • The roster is now rebuilt only when it actually changes (driven by iroh-docs live-sync events) instead of every few seconds on every platform.
  • Persistently unreachable peers are retried on an exponential backoff (capped at 5 minutes) instead of a flat ~20 seconds forever.
  • The Wi-Fi multicast lock is held only while the app is in the foreground, and the ongoing notification is re-posted only when its text actually changes.

Your peers' online status is unaffected by any of this (it comes from the live connection, not the heartbeat).

Android reconnect

  • Switching to a different VPN and back used to leave the member list empty until Always-on VPN was toggled off and on. Nullgate now watches the device's connectivity and reconnects on its own after a Wi-Fi↔mobile switch or when another VPN is turned off.
  • While another VPN is active, Nullgate steps aside (it can't route while another VPN owns the connection) and comes back automatically once that VPN is switched off — no manual toggling.

Platforms

Windows (x86_64 + ARM64 MSIs), Linux (x86_64 tarball), Android (universal APK). macOS to follow.

v0.4.1 — Windows ARM64 (pre-release)

Pre-release

Choose a tag to compare

@steeb-k steeb-k released this 14 Jul 05:23

Windows on ARM — pre-release

This pre-release contains the ARM64 Windows installer only. It is here to be tested on real
Windows-on-ARM hardware. x86_64 users should stay on v0.4.0
— nothing has changed for you, and the auto-updater ignores pre-releases, so no existing install
will pick this up.

Why this exists

Windows on ARM would already install and run the x86_64 build under emulation — but only in the
sense that the app opened. wintun is backed by a kernel driver; an ARM64 kernel will not load
an x64 driver, and an emulated x64 process cannot load the ARM64 one instead. So the app ran and the
routing silently did not work — which is the entire point of Nullgate. This build is native
ARM64 end to end, driver included.

Download

nullgate-0.4.1-windows-arm64.msi — code-signed. Only for ARM64 machines (Snapdragon and similar;
Settings → System → About → System type). On an x86_64 PC it will refuse to install.

If you already have the x86_64 build on an ARM machine, this MSI replaces it in place — the two
share an upgrade code, so it's a normal upgrade, not a second copy.

Please check

  • The app opens, and the tray icon appears and survives closing the window.
  • The NullgateDaemon service is running (sc query NullgateDaemon).
  • Routing actually works — the thing emulation couldn't do. Join a network and ping/RDP/SSH a
    peer on 10.99.0.0/24.
  • schtasks /Query /TN NullgateUpdate exists.

Known caveat: this binary has been verified structurally (every shipped binary is ARM64, every
imported DLL resolves) but has not yet been run on ARM64 hardware — that's what this
pre-release is for.

v0.4.0

Choose a tag to compare

@steeb-k steeb-k released this 14 Jul 03:37

Per-device action buttons, and relays you can test before you trust them

Added

  • Per-device action buttons. A device can be given a labelled, coloured button — "RDP", "SSH", "Web" — that runs a command of your choosing. It appears on that device's row in the member list and in a new section at the top of the tray menu, read as "device (label)", so a machine can be reached without opening the window. Configure it from Action button in the member panel: a label, one of eight colours, a command, and an Open in a terminal window checkbox. {ip}, {name}, {hostname} and {node_id} are substituted at click time.

    Open in a terminal window is what makes ssh (or any console program) usable: without it the command is detached with no streams, which is right for a graphical program like mstsc and useless for one that wants a console.

    Actions are stored per machine, in your own config directory — they are the one piece of per-member state the daemon knows nothing about. The command that reaches a device is a property of the machine you're sitting at (mstsc on Windows, xfreerdp on Linux), so the roster would only push a Windows command line onto a Mac; and unlike a note or a nickname, an action is an executable command line, which has no business in a root-owned store. It is spawned as a program with arguments — no shell — so a config file can't smuggle in a pipeline.

  • A relay is checked against the relay server before it is saved. Both the CLI and the GUI now connect to the relay with the credentials you gave, from a throwaway endpoint. relay add asks for the token again if the relay won't accept it; the GUI keeps its Add a relay server dialog open, reports the failure in place, and offers Try again or Add anyway (a relay that is merely down looks exactly like a wrong token from out here). Saving a relay this device can't use is not a small mistake: the device homes on that relay, and peers lose both the relay path to it and — hole-punching being relay-coordinated — usually the direct one.

  • Test a relay you've already added, from the ⇄ button beside it in the relay list. A relay can start refusing a token it used to accept (rotated, revoked, redeployed) and nothing else would tell you — the symptom is peers quietly losing sight of this device.

Fixed

  • A GUI/daemon version mismatch reported itself as "The Nullgate service isn't running". The service was running perfectly well; it just spoke a different IPC protocol version — so the one message that could have told you to update the other half was replaced, a frame after it appeared, by one that sent you to restart a service that was already up. The GUI now reports why the event stream ended, so a mismatch stays on screen.
  • The "Relay servers" warning banners now span the panel, instead of sitting as narrow cards inside it.
  • The window title is vertically centred again.

Changed

  • nullgate-cli relay add no longer takes --token; it asks for the token. Passing a secret as an argument put it in the shell's history file and in every other local user's view of ps — a leak to exactly the readers that root ownership of the data dir is meant to exclude. It is now read with echo off (from the terminal, or from stdin when piped, so scripting still works), and a blank answer means the relay has no token.
  • The app version no longer sits under the title in the header bar. It crowded the bar, and the version is already on the About row and in the About dialog.

Windows: install the MSI. Linux/macOS: curl -fsSL https://raw.githubusercontent.com/steeb-k/nullgate/main/install.sh | sh (or nullgatectl --update). Android: install the APK (it updates in place). Artifacts are uploaded as each platform's build finishes, so a download that isn't here yet will appear shortly.

v0.3.3

Choose a tag to compare

@steeb-k steeb-k released this 13 Jul 20:32

Custom relay rework

Behaviour change. preferred — the default relay policy — now keeps the public iroh relays in the relay map alongside your custom relay instead of replacing them. Devices that were on a custom relay become reachable again to peers that lack the relay or its token. That is the fix. If you want the old "never touch third-party infrastructure" behaviour, use only.

Fixed

  • A self-hosted relay on some devices but not others silently cut the network in half. Under preferred, configuring a custom relay removed the public relays from that device's relay map. The configured device then advertised the custom relay as its only home relay and had no transport that could reach a peer homed on a public one; a peer without the token dialled the custom relay, got 401, and — hole-punching being relay-coordinated — had no direct path either. The two groups went mutually invisible while the relay was up and authenticating normally. preferred was, in effect, identical to only. It now keeps both sets in the map, with the path selector biasing traffic onto your relay, so partial deployment is no longer fatal.
  • nullgate-cli relay add hung indefinitely, and a new relay only took effect after a daemon restart. insert_relay/remove_relay await iroh's bounded socket-actor channel, which a peer stuck sending to an unreachable relay can block indefinitely — so the call blocked for 20+ minutes on a live mesh, after writing the config but before swapping the in-memory settings. Disk, path selector, endpoint map and reported settings all disagreed, and relay show truthfully reported the stale value. Settings are now saved, applied and reported atomically and return immediately (~20 ms); the endpoint map is updated by a background task with per-call timeouts.
  • The relay watchdog could never have helped, and is gone. It fired on "can I reach my relay" — always yes — and could not observe "my peers can't reach my relay", which was the actual failure.
  • The daemon kept working on requests from clients that had gone away, stranding the task. It now cancels an in-flight request when the client disconnects.
  • A non-root daemon crashed instead of falling back to a writable log directory (it accepted an existing but root-owned /var/log/nullgate, then panicked with PermissionDenied). It now probes writability first.

Added

  • Relay settings on Android (⋮ → Relay servers): add a relay with an optional access token, remove one, and choose whether the public relays stay as a backup. The phone had no relay surface at all before — the only reason it stayed reachable through the outage above. Reachable before joining a network, since a token-gated relay may be exactly what a device needs in order to join.
  • Relay changes now report whether they were actually applied, rather than unconditionally claiming "no restart needed". Note that iroh keeps a home relay that has left the map until another relay takes over, so switching to only while your custom relay is unreachable genuinely does need a restart — and now says so.
  • Both UIs warn that relay settings are per-device and must be set on every member with the same URL and token.

Upgrading

If you run your own relay: put it on every device, with the same address and token, or on none. Half-and-half is the one configuration that cannot work. After updating, devices that were stranded should find each other again without any change on your part.


Windows: install the MSI. Linux: curl -fsSL https://raw.githubusercontent.com/steeb-k/nullgate/main/install.sh | sh (or nullgatectl --update). Android: install the APK (updates in place). macOS is unchanged from 0.3.2 apart from the shared relay fixes; a macOS artifact will follow.

v0.3.2

Choose a tag to compare

@steeb-k steeb-k released this 10 Jul 01:58

Custom relay servers

You can now run Nullgate through your own iroh relay server instead of the free public ones:

  • Add relays (with an optional access token, sent as Authorization: Bearer) from the new Relay servers page in the app — on the main screen, or from the "No network yet" page before you join. Headless: nullgate-cli relay add/remove/mode/show/clear.
  • Two policies: preferred (default) — your relays carry the traffic whenever one is reachable, and Nullgate quietly falls back to the public relays while they're all down, returning automatically when one recovers; or only — never touch the public relays.
  • Direct (hole-punched) connections still always win; among relayed paths, your own relay is preferred over any other.
  • Changes apply immediately — no service restart. Set the same relays (and tokens) on every device: a token-protected relay turns away devices that don't have the token.
  • Diagnostics (and nullgate-cli status) show when the public-relay fallback is active.

The app stopped eating clicks

The GUI used to rebuild its entire interface several times a second while peers were online, so clicks often landed on a button that was being destroyed and simply did nothing — and keyboard focus kept jumping around. This release fixes the whole chain:

  • the background service only announces changes when something you can see actually changed (and batches bursts into a single update),
  • the app now updates the existing widgets in place instead of rebuilding the page — member rows, the Diagnostics panel, and the Administration panel (Approve/Deny buttons no longer vanish under your cursor).

The app should feel dramatically more responsive with peers connected.

Notes

  • The app↔service IPC protocol is now v4: update the app and the service together (the installers do this for you).
  • macOS build will be attached shortly after the other platforms.

v0.3.1

Choose a tag to compare

@steeb-k steeb-k released this 07 Jul 05:24

Nullgate v0.3.1

A stability release focused on connection reliability.

Fixed

  • Intermittent connection drops to remote peers. Two root causes in the engine:
    • The maintenance tick re-dialed every member — including unreachable ones — on every
      roster-sync and presence tick, minting permanent entries in iroh's unbounded mapped-address
      cache (iroh#4293). Daemon memory grew until
      the watchdog restarted it, dropping every connection at once. Re-seeding is now throttled to
      reachable members, while a membership change still re-seeds everyone immediately so
      additions/removals keep propagating within seconds.
    • A duplicate-connection race: when both peers dialed each other at once, the orphaned
      connection's close-watcher could evict the live connection — a spurious per-peer drop. Both
      ends now apply a deterministic tie-break and the close-watcher only evicts the entry that
      actually closed.
  • A freshly created (or rotated) network briefly rejected joiners with "this invite code is no
    longer valid," because the creator hadn't folded its own genesis entry into its in-memory roster
    yet. The roster is now refreshed immediately after creation/rotation.

Added

  • Mesh connection lifecycle logging in the daemon log — connections log when they're
    established, replaced by a duplicate, and closed (with the QUIC close reason), so intermittent
    drops are diagnosable from the default log.

Changed

  • Refreshed the app + tray icon artwork — a new circular "gate" mark across every platform.

Install: Windows MSI, Linux tarball (curl … | sh), macOS .app tarball, and an Android APK
(auto-updates via Obtainium). Desktop apps auto-update to this release.

v0.3.0

Choose a tag to compare

@steeb-k steeb-k released this 05 Jul 21:39

Nullgate 0.3.0 — a headless tray agent, full-width status banners, and Android admin-UI role gating.

Highlights

Desktop

  • The system tray + notifications now live in a lightweight background agent. Previously the
    whole GUI ran hidden to host the tray, so closing (or crashing) it made the tray icon vanish while
    the network kept running. Now a small headless agent (nullgate --agent) autostarts at login,
    owns the tray icon and all notifications, and launches the GUI on demand. Closing the window
    just closes it
    — the daemon and tray keep running.
  • "Restart Nullgate daemon" in the tray menu, and a "Start / Restart service" button on the
    status banner — bounce the privileged daemon (raising your OS's own admin prompt) without opening
    a terminal. On Windows this elevates the code-signed daemon directly (UAC shows the Nullgate
    publisher).
  • Clicking a notification opens Nullgate.
  • Status and join-request alerts are now full-width banners. The old flashing red "Join Request"
    chip is replaced by a banner with a Review button that opens the emoji-SAS approval screen.
  • Keyboard navigation no longer jumps back to "Administration" every few seconds (most visible
    on Windows) — a durable fix that saves and restores the focused row across any page rebuild.
  • Windows: the tray GUI now restarts after an auto-update (it could previously be left gone or
    stale across the SYSTEM/user-session boundary).

macOS

  • Nullgate now shows up in Spotlight (and "Open With") after install — the installer registers
    the bundle with Launch Services and runs mdimport on every install and update.

Android

  • The app hides admin actions that Peers and Controllers can't use, matching the desktop apps:
    Peers see Activity log, their own details, and Leave; Controllers additionally get the Peer ticket,
    Settings, Rename, and Peer removal; the originator keeps the full set. Engine-side authorization is
    unchanged — this only aligns the UI's affordances with the roster rules.
  • Added "Restore originator access" for members who hold the master recovery key but aren't the
    originator on this device, closing the last Android/desktop role-UI gap.

Install / update

Existing installs auto-update. New installs:

  • Windows: download the .msi.
  • Linux/macOS: curl -fsSL https://raw.githubusercontent.com/steeb-k/nullgate/main/install.sh | sh
  • Android: install the .apk (or track releases with Obtainium via https://github.com/steeb-k/nullgate).

See the full CHANGELOG.