Kepos / User guide
Use Kepos to share and connect
A publisher owns a service and decides which trusted devices may open it. A subscriber receives those selected services at local URLs or ports. Kepos has no account system; choose the role that matches the device in front of you.
01 / Install
Platforms and installation
On narrow screens, scroll horizontally to see all columns.
| Platform | Role | Artifact | Release boundary |
|---|---|---|---|
| Android | Subscriber only | APK download | Android 12+ · arm64-v8a |
| macOS | Publisher + subscriber | Apple Silicon ZIP | Apple Silicon · ad-hoc signed |
| Windows | Publisher + subscriber | x64 ZIP | Windows 10 build 19045+ or Windows 11 |
All three downloads stay available.
Android subscriber
Sideload the release APK, approve the device's request to install from that source if Android asks, and start the foreground service from the app. Android does not publish services, install a VPN, or provide a TUN interface.
Apple Silicon macOS
Unzip the release and open Kepos.app. macOS Gatekeeper may warn on first launch because this
local preview is ad-hoc signed and not notarized; it is not a Developer ID or Mac App Store package.
Windows desktop
Extract the ZIP as a complete Kepos\ tree. Launch App\Kepos.exe for a portable
run, or use Install.cmd for the supported per-user installation. The ZIP includes Windows
App Runtime files, but WebView2 and the Microsoft Visual C++ Redistributable remain system prerequisites.
SmartScreen and Defender Firewall prompts are expected boundaries for this unsigned-by-Authenticode
preview.
02 / Connecting device
Connect as a subscriber
A subscriber receives selected services from a publisher. Its device identity is a key pair; it shares only the public key for approval. The publisher's allowlist names subscriber keys it accepts, and the subscriber pins the publisher public key before connecting.
-
Install and start
Install the artifact for the device you will carry. Packaged desktop startup fills missing config and enabled role state; Android creates its subscriber identity in app-private storage. See Trust and identities for what that state contains.
-
Pair one publisher
For Android, open Add device on a running macOS or Windows desktop publisher and scan its two-minute QR invitation. The publisher sees the candidate's label, platform, and subscriber-key fingerprint before you choose Allow or Deny. This is the supported interactive QR path.
For a desktop subscriber, click Copy this device subscriber public key. On the desktop publisher, add that key to the
[publisher].allowlist and restart after the manual TOML edit. Copy the publisher public key from the publisher app, enter it in Connect this subscriber, and choose Connect / Save. Desktop QR or deep-link receipt is not supported in this release. -
Confirm success
When the subscriber shows Connected and its service list appears, open or copy the first service address below. If it stays Connecting, use the checks in troubleshooting.
Open the first service
Once the registry is current, an HTTP service normally appears at
http://<service-id>.localhost:17480/, for example
http://navidrome.localhost:17480/. Kepos can open a web action or copy the local URL for a
media client. SSH and other raw TCP services use the local listener configured for that subscriber; use
the address and port shown by the app rather than assuming a universal port.
ssh -p <configured-local-port> 127.0.0.1
03 / Service owner
Publisher (Desktop)
Use the packaged macOS or Windows app when the service owner wants a graphical relationship view. The desktop app reads its publisher policy from TOML.
Configure the packaged app
The packaged app uses the normal Kepos configuration file: ~/.config/kepos/config.toml on
macOS unless XDG_CONFIG_HOME is set, and %APPDATA%\Kepos\config.toml on
Windows. Edit the file before launch when you need to enable publishing or change the services, then
restart after a manual policy edit. There is no graphical service editor in this preview.
[publisher]
enabled = true
display_name = "Home"
allow = []
[[publisher.services]]
id = "navidrome"
name = "Navidrome"
target_port = 4533
List loopback TCP targets that already run on the publisher device. An empty allow list
starts in deny-all mode; approving a device later adds its public key to the running policy.
Let startup create the identity
When enabled = true, desktop startup creates missing publisher state from this policy before
starting the role. On later launches it validates the existing state and reuses the same seed and public
key; it never rotates or overwrites that state. TOML allowlist and service changes are runtime policy, so
they can change without rewriting the identity.
Launch and approve a device
-
Keep the app running
Open Kepos and leave the process running while it publishes.
-
Choose Add device for Android
Generate the invitation and let the Android subscriber scan it. The invitation expires after two minutes.
-
Inspect, then Allow
Check the label, platform hint, and public-key fingerprint. Allow persists the subscriber public key, updates the live allowlist, and promotes the same connection without restarting.
04 / Headless service owner
Publisher (CLI)
Use this path for repository checkouts, Nix, containers, and other headless hosts with explicit state directories and manual public-key exchange.
Check out and initialize
On the subscriber device, create an identity and copy the public key printed by the command:
npm run kepos -- setup subscriber \
--state <subscriber-state-directory>
On the publisher device, put that public key in the publisher TOML policy:
[publisher]
display_name = "Home"
allow = ["<subscriber-public-key>"]
[[publisher.services]]
id = "navidrome"
name = "Navidrome"
target_port = 4533
npm run kepos -- setup publisher \
--state <publisher-state-directory> \
--config <publisher-config.toml>
This creates the publisher with the subscriber already allowed. The setup command writes the manifest and
identity into the explicit state directory. Repeat it only with the same policy; CLI setup remains strict
and never rotates an identity. Without a TOML publisher table, the same command accepts
--display-name, --allow, and repeated --service options instead.
Update the allowlist and pin the publisher
If publisher state already exists in deny-all mode and no [publisher] TOML table owns its
policy, add the subscriber public key with publisher set-allow. For a TOML-owned publisher,
edit its allow array instead; do not combine TOML policy with CLI policy flags.
npm run kepos -- publisher set-allow \
--state <publisher-state-directory> \
--allow <subscriber-public-key>
npm run kepos -- publisher key \
--state <publisher-state-directory>
npm run kepos -- subscriber set-publisher \
--state <subscriber-state-directory> \
--label Home \
--publisher-key <publisher-public-key>
Copy the Publisher key: output and pin it with subscriber set-publisher.
Run and update a headless publisher
npm run kepos -- publisher run \
--state <publisher-state-directory> \
--config <publisher-config.toml>
npm run kepos -- subscriber run \
--state <subscriber-state-directory>
Start the publisher before the subscriber. A headless process reads its policy at startup, so restart
publisher run after a TOML allowlist or service edit, then restart the subscriber if its
pinned contact changed. Use the repository's Nix, container, Windows, and NUC deployment guides for
process supervision and platform-specific lifecycle details.
05 / Identity and boundary
Public keys move; secrets stay home
Kepos uses device-owned identities rather than an account or shared bearer token. Publisher authorization remains local to the publisher that owns the service.
- Separate identities
- Publishers and subscribers each keep their own key pair. A displayed long hexadecimal key is public.
- Local approval
- The publisher's allowlist and optional per-service allowlist decide what an approved subscriber may open.
- No secret copies
- Keep publisher seeds and subscriber secret keys in the private storage that generated them. Do not put identity files in logs.
Configuration and role state
TOML is mutable role policy: it names services and allowed subscriber keys. Role state is private identity storage: publisher state holds its seed and subscriber state holds its key pair and publisher contact. Packaged startup behavior is described in Publisher (Desktop); Android keeps its subscriber identity in app-private storage across an in-place update.
06 / Why this shape
Keep the app; change the route
Kepos changes how a selected service is distributed, not what the service is. That keeps mature software's capabilities and client experience intact.
-
Keep mature applications
Navidrome, Jellyfin, Forgejo or Gitea, SSH, and other self-hosted services continue to run as ordinary local applications.
-
Change service distribution
Kepos carries selected TCP service byte streams through an authenticated direct peer connection with named channels.
-
Preserve clients
A browser opens a local URL, a media client receives a local address, a Git client uses its normal endpoint, and SSH uses a loopback port.
07 / A respectful comparison
Kepos and Holesail
Both projects offer direct encrypted peer-to-peer access without requiring a public service port. The
difference is the unit of sharing: Kepos starts with a persistent authenticated device relationship;
Holesail documents a port tunnel and connection key.
Compared with Holesail 2.4.1 and its current official documentation, checked 2026-08-22.
The architectural difference
Pair once. Open many named services.
Kepos turns a trusted device relationship into durable transport: adding a service opens a named channel, not another tunnel/key workflow.
Kepos
One relationship, many channels
One authenticated outer connection carries the registry and independent service streams.
Holesail
One port tunnel at a time
Its connection key resolves to one host and port; another service begins another tunnel/key workflow.
Documented capabilityNot in the current documented model
On narrow screens, scroll horizontally to see all columns.
| Dimension | Kepos | Holesail |
|---|---|---|
| Direct encrypted P2P | Yes. Authenticated HyperDHT connection. | Yes. Direct encrypted tunnel. |
| Persistent device relationship | Yes. Separate publisher and subscriber identities. | No. Official docs define private or public connection keys per tunnel. |
| Pair once; add named services | Yes. One durable outer connection carries named service channels. | No. Its documented model uses one port tunnel and connection key per exposed service. |
| Per-service allowlists | Yes. Services may narrow the publisher allowlist. | No. Private mode checks the connection key; public mode has no key authentication. |
| Public connection mode | No. Service access requires an allowed subscriber identity. | Yes. --public exposes a public connection key without key authentication. |
| Graphical desktop onboarding | Yes. macOS and Windows show relationships, services, and approval. | No. The official desktop guide documents terminal executables. |
| Mobile QR onboarding | Yes. Android scans a two-minute publisher invitation. | Yes. Holesail Go scans connection-key QR codes on iOS and Android. |
| TCP service path | Yes. Local TCP ends at each Kepos peer. | Yes. TCP tunnels are supported. |
| UDP service path | No. Kepos services are currently TCP-only. | Yes. Official docs include UDP tunnels. |
| One-command port sharing | No. Kepos starts from a device and service policy. | Yes. Start with holesail --live <PORT>. |
Choose Kepos for long-lived devices and named service policy; choose Holesail for a quick port tunnel or UDP. The indicators describe current documented capabilities, not a quality score. Sources:
- Holesail official repository — one-command port sharing and TCP/UDP flags.
- Holesail official documentation — direct encrypted P2P transport claims.
- Holesail official overview — server/client and key or QR workflow.
- Holesail server guide — port-oriented startup and connection options.
- Holesail connection-key model — private authentication and public mode.
- Holesail desktop executables — terminal onboarding on Windows, macOS, and Linux.
- Holesail Go — iOS/Android availability and QR onboarding.
08 / When it does not connect
Find the boundary
Kepos's current direct path needs usable outbound UDP and a NAT pair that HyperDHT can punch. Check the observable boundary in order.
Read the connection states
- Connecting
- The subscriber is attempting the pinned publisher connection.
- Connected
- The publisher registry has loaded; open or copy a listed service.
- Reconnecting
- Local listeners stay in place while Kepos replaces the outer connection; active client streams need their own retry.
Configured but still Connecting
- Confirm the subscriber's pinned publisher public key is the key shown by the intended publisher.
- Confirm the publisher is running and its subscriber public key is in the publisher allowlist. An empty allowlist denies everyone.
- Keep the desktop process in the tray or menu bar. Closing the window hides it; Quit Kepos stops publishing.
- Wait for a direct attempt to finish, then check local diagnostics rather than copying state files or secrets into a report.
Firewall, VPN, or TUN interference
On Windows, allow Kepos through Windows Defender Firewall on the networks where the session needs DHT connectivity. Kepos does not open a public TCP service port and does not automatically bypass a firewall. VPN or TUN software can intercept or block the UDP path; test its documented bypass or pause behavior only if you control that device and understand the consequence. Kepos does not promise a relay fallback.
Bootstrap and configuration
Packaged desktop releases include bootstrap endpoints for a fresh default config. A configured
[network].bootstrap list takes precedence over that packaged default; verify each host and
UDP port is reachable from both peers. In the CLI, an empty bootstrap list selects HyperDHT defaults. A
bootstrap node helps a peer enter the DHT; it does not authorize a subscriber or carry the service stream.
Allowlist and policy edits
A desktop Add device approval updates the live publisher. A headless TOML edit does not:
restart the headless publisher run process after changing the publisher allowlist or service
policy. A per-service allowlist can be narrower than the publisher-wide list, so an approved device may
still be unable to open one service.
Copy sanitized diagnostics
On desktop, open Settings and choose Copy diagnostics. The summary is bounded and sanitized for support: it can describe roles, states, observations, and counters, but omits secret identity material, pairing tokens, and candidate IP addresses. Do not paste config or state files into a bug report.