Gemina — a macOS reliability VPN that sends every packet over two uplinks at once and keeps the first copy through, so calls and SSH survive a dropped link. Open-core: AGPL gateway, Apache client.
  • Go 59.6%
  • Swift 12.1%
  • C 11.5%
  • HTML 7.6%
  • Shell 5.1%
  • Other 4%
Find a file
Repository files (latest commit first)
Filename Latest commit message Latest commit date
Joan Marc Riera Duocastella 4d6f8eeab2 style(macos): satisfy swiftlint --strict (CI lint gate)
swiftlint is not installed in the local dev env, so these strict-mode violations
only surfaced in CI. Fix all 7:
- large_tuple: HandshakeResult.assignedIPv4 (UInt8 x4) -> [UInt8]; the readOutbound
  snapshot 3-tuple -> a private RelaySnapshot struct.
- identifier_name (min 2): q -> queue, s -> state.
- force_try: makeEchoServer uses guard-let try? + fatalError instead of try!.
- implicit_optional_initialization: drop the redundant `= nil`.

Verified locally with swiftlint 0.64.1: 0 violations; swift build + 3 headless
checks + headless xcodebuild all green.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-27 11:44:53 +01:00
.claude refactor: rename product Continuity -> Gemina 2026-06-26 23:06:49 +01:00
.github chore(funding): add Seshat donations Lemon Squeezy checkout 2026-06-26 23:26:37 +01:00
api refactor: rename product Continuity -> Gemina 2026-06-26 23:06:49 +01:00
apps/macos style(macos): satisfy swiftlint --strict (CI lint gate) 2026-06-27 11:44:53 +01:00
bridge fix(bridge): cancel in-flight handshakes so an aborted connect cannot leak 2026-06-27 11:44:53 +01:00
cmd refactor: rename product Continuity -> Gemina 2026-06-26 23:06:49 +01:00
db Bootstrap Stage 0 continuity VPN repo 2026-06-19 09:44:42 +01:00
deploy refactor: rename product Continuity -> Gemina 2026-06-26 23:06:49 +01:00
docs docs(plan): stage the passwordReference token hardening for WS-F 2026-06-27 11:44:53 +01:00
internal feat(gateway): assign a tunnel IP during the handshake 2026-06-27 11:44:53 +01:00
LICENSES chore(licence): dual-licence open-core — AGPL-3.0 gateway, Apache-2.0 client/core 2026-06-25 14:56:56 +01:00
observability refactor: rename product Continuity -> Gemina 2026-06-26 23:06:49 +01:00
pkg/clientcore feat(clientcore): carry the assigned tunnel IPv4 in ServerHello 2026-06-27 11:44:53 +01:00
research refactor: rename product Continuity -> Gemina 2026-06-26 23:06:49 +01:00
scripts refactor: rename product Continuity -> Gemina 2026-06-26 23:06:49 +01:00
tests refactor: rename product Continuity -> Gemina 2026-06-26 23:06:49 +01:00
website refactor: rename product Continuity -> Gemina 2026-06-26 23:06:49 +01:00
.editorconfig Bootstrap Stage 0 continuity VPN repo 2026-06-19 09:44:42 +01:00
.gitignore refactor: rename product Continuity -> Gemina 2026-06-26 23:06:49 +01:00
.golangci.yml ci: harden Go CI, add golangci-lint, audit gate and changelog config 2026-06-26 22:41:51 +01:00
.mcp.json docs(macos): Apple-dev tooling after Developer Program activation 2026-06-25 14:56:56 +01:00
.swiftlint.yml Bootstrap Stage 0 continuity VPN repo 2026-06-19 09:44:42 +01:00
AGENTS.md chore(release): repo strategy, public-readiness audit, redaction 2026-06-25 14:56:56 +01:00
cliff.toml ci: harden Go CI, add golangci-lint, audit gate and changelog config 2026-06-26 22:41:51 +01:00
CODEOWNERS Add Darwin interface state collector 2026-06-19 17:20:21 +01:00
COMPATIBILITY.md refactor: rename product Continuity -> Gemina 2026-06-26 23:06:49 +01:00
CONTRIBUTING.md docs(oss): open-core README + self-hosting guide + hosted-tier landing page 2026-06-25 14:56:56 +01:00
DECISIONS.md refactor: rename product Continuity -> Gemina 2026-06-26 23:06:49 +01:00
go.mod refactor: rename product Continuity -> Gemina 2026-06-26 23:06:49 +01:00
go.work Bootstrap Stage 0 continuity VPN repo 2026-06-19 09:44:42 +01:00
hero.png Add hero image for landing 2026-06-26 22:30:24 +01:00
LICENSE refactor: rename product Continuity -> Gemina 2026-06-26 23:06:49 +01:00
Makefile ci: harden Go CI, add golangci-lint, audit gate and changelog config 2026-06-26 22:41:51 +01:00
NOTICE chore(licence): dual-licence open-core — AGPL-3.0 gateway, Apache-2.0 client/core 2026-06-25 14:56:56 +01:00
PROJECT_STATE.md refactor: rename product Continuity -> Gemina 2026-06-26 23:06:49 +01:00
README.md refactor: rename product Continuity -> Gemina 2026-06-26 23:06:49 +01:00
SECURITY.md Add Darwin interface state collector 2026-06-19 17:20:21 +01:00
SESSION-CONTEXT.md Add Darwin path observation boundary 2026-06-19 16:59:52 +01:00
TASKS.md refactor: rename product Continuity -> Gemina 2026-06-26 23:06:49 +01:00

Gemina VPN

Keep calls and SSH sessions alive when one network blips — by sending your traffic over two uplinks at once.

Gemina VPN is a macOS reliability tool. It duplicates your protected traffic over two independent connections at once — your Wi-Fi and a second connection of your choice (your phone's cellular over USB — iPhone or Android — a second broadband line, or an LTE/USB-Wi-Fi dongle) — sends both copies to a single gateway, and the gateway delivers the first valid copy while discarding the duplicate. If one link drops mid-call, the other is already carrying the same packets, so the session does not notice.

The two connections just need to be independent (different paths to the internet). On macOS, an iPhone or a Pixel/AOSP phone appears as a network interface natively; other Android phones are driven by the app's bundled userspace driver — no root on the phone, no kernel extension on the Mac.

This is a reliability product, not a bandwidth-aggregation one. It does not combine the speed of both links; it keeps your connection up. It is open source and self-hostable, with an optional paid hosted gateway for people who would rather not run their own server.

Status: pre-release. The dual-path transport is proven end-to-end, but this is not yet a shipping VPN. See Status below for exactly what is and is not done.

The name

Gemina is Latin for twinned. When a Roman legion was too depleted to be sure of holding the line, two were merged into one — a Legio Gemina, the twin legion (Caesar's Tenth and the Thirteenth both bore the name) — so the force always arrived at full strength. Gemina does the same with your connection: it sends every packet down two paths at once and keeps whichever copy arrives first, so you stay online even when one path drops. The same story is in the app's About Gemina dialog, and recorded in ADR-0008.

How it works

The client sends every packet twice — once over each available uplink — tagging both copies with the same identity. The gateway keeps a short window of recently seen identities, forwards the first copy of each, and drops any later copy. When a link fails, its copies simply stop arriving; the surviving link's copies keep the session going with no reconnection.

flowchart LR
    Mac[macOS client]
    Mac -->|copy A| WiFi[Wi-Fi uplink]
    Mac -->|copy B| Phone[Second connection<br/>phone cellular / 2nd line]
    WiFi --> GW[Gateway<br/>UDP 51820]
    Phone --> GW
    GW -->|first copy delivered<br/>duplicate discarded| Net[Internet]

Because the two copies travel over genuinely independent networks, a blip on either one — a tunnel, a lift, a flaky access point — does not interrupt the flow. The cost is roughly double the data on the protected traffic; the benefit is continuity.

Status

This repository is pre-release. The dual-path transport has been proven end-to-end: the same logical packet sent over Wi-Fi and over an Android phone's cellular link both reach the deployed gateway, are deduplicated to a single delivery, and either path can drop without ending the session. See the Definition of Dual-Path Success — ACHIEVED section of PROJECT_STATE.md for the evidence.

What is proven:

  • Dual-path duplicate transmission over two independent WANs (Wi-Fi + cellular).
  • Server-side deduplication to a single delivery per packet identity.
  • Survival of either path dropping mid-session.
  • A userspace Android USB tether data plane that needs no root on the phone, no kernel extension and no SIP changes on the Mac.

What is not yet done (and is therefore not claimed):

  • Encryption and real VPN traffic — today's proof carries probe packets, not your IP traffic.
  • The shipping macOS app data path (the NEPacketTunnelProvider integration).
  • Accounts, payments and the hosted entitlement flow.

Current state, decisions and the outstanding task list live in:

Self-host the gateway

The gateway is the server half of the system: a single container that listens on one UDP port and deduplicates the copies arriving over each client path. There is nothing to configure beyond the port — no database, no accounts.

docker run --rm \
  --read-only \
  -p 51820:51820/udp \
  -e GEMINA_GATEWAY_ADDR=:51820 \
  ghcr.io/example/gemina-gateway:latest

Replace ghcr.io/example/gemina-gateway:latest with your own image, and open UDP 51820 to the host. Build the image from deploy/docker/gateway.Dockerfile.

Then point the client at your gateway by hostname — for example gateway.example.com:51820. The gateway address is always configurable; it is never hard-coded, so self-hosting and the hosted option use the same client.

Full instructions, the available environment variables, and the self-host vs hosted trade-off are in docs/self-hosting.md.

Use our hosted gateway

If you would rather not run a server, an optional paid hosted gateway will be offered: you point the client at our endpoint and skip the operations entirely. The client and the gateway remain open source either way — the hosted option only saves you from running the container yourself.

Pricing: TBD. The hosted tier is not yet available; this is the planned commercial model behind the open-source core.

Compatibility

  • macOS client. Apple Silicon is the primary target.
  • Any second independent connection as the second path: your phone's cellular over USB (iPhone Personal Hotspot and Pixel/AOSP phones appear as a network interface natively; other Android phones are driven by the app's bundled userspace driver — no root, no kernel extension, no SIP changes), a second broadband line, or an LTE/USB-Wi-Fi dongle.
  • No special hardware and no second SIM beyond your normal phone plan.

See the community COMPATIBILITY.md catalogue, and run geminactl preflight -share to check your own setup and contribute a report.

Run geminactl preflight to confirm a given Mac + Android combination is supported before you commit: it returns a plain verdict (for example supported, connect your Android, connect to Wi-Fi) and the one thing to change. Add -json for the machine-readable report the app and website consume. The geminactl darwin-evidence diagnostic reports the underlying path evidence.

Build & test

The repository bootstraps and tests with make:

make bootstrap      # prepare the workspace
make test           # Go + macOS + docs checks
make lint           # formatting and static analysis
make licence-check  # licence/provenance gate

make fetch-research clones pinned upstream sources into a Git-ignored .research-src/ for due diligence only; it must never copy upstream implementation files into product directories. See the Makefile for the full set of targets.

Source rules

  • Do not copy GPL implementation code into product directories.
  • Engarde and OpenMPTCProuter are inspiration-only.
  • WireGuard Apple and wireguard-go are permitted foundations, subject to retained notices and provenance records.
  • Do not invent cryptography.
  • Do not log secrets or store raw access keys.

Licence

This project is dual-licensed (open-core):

  • The gateway/server is AGPL-3.0-onlycmd/gateway/, internal/gateway/, and gateway-only server assets under deploy/.
  • The client and shared core are Apache-2.0apps/macos/, the pkg/ packages, cmd/geminactl/, and the shared internal/ packages.

The AGPL keeps the hosted gateway open; the client stays Apache-2.0 so it can ship on the Mac App Store, which cannot carry AGPL software. Apache-2.0 is one-way compatible into AGPL-3.0, so the gateway may include the core but the client must never include gateway code.

Full texts: LICENSES/AGPL-3.0.txt and LICENSES/Apache-2.0.txt. The directory map and rationale are in docs/legal/licensing.md; see also LICENSE and NOTICE. Third-party material, if ever imported, retains its original licence and attribution as recorded under docs/legal/.