Remote access for
the machines you ship.

Linux on board. Cameras or a screen. Behind someone else's network, far from the people who need to see it, drive it and fix it. If that's your product, Fjarr is how you reach it.

Live video, the desktop, a terminal and a direct network link — peer-to-peer, encrypted end to end, through firewalls and carrier NAT with no ports to open. Built into your machine, your backend and your dashboard.

Robots are where we started; vehicles, farm machinery and field gateways have the same shape. Open-core (AGPL-3.0) libraries you self-host, plus a managed cloud when you'd rather not. Fjärr is Swedish for "remote".

Read the docsGitHub

v0.1.2 released · Ubuntu 26.04 · amd64 and arm64 · apt or Docker

wget -qO- https://get.fjarr.io | sudo sh

Then sudo fjarr-agent --check. Install guide

What you probably have today

Rarely nothing. Usually a patchwork that kind of works:

Each piece works on a good day. Together they're slow to support, hard to secure, and a different login every time. Fjarr replaces the patchwork with one connection per machine that carries all of it.

We've built this stack three times before, inside robot companies — what we kept and what we didn't.

Three pieces, your stack

Fjarr embeds into what you already run. Your backend stays in charge of who may reach what; video and data go straight between the machine and the person looking at it.

Your backend: decide who may open what

// any JWT library, any language
const grant = jwt.sign({
  aud: "fjarr",
  tenant: "acme",
  robot_id: "robot-024",
  operator: { id: user.email, label: user.name },
  capabilities: [
    { name: "fjarr.camera" },
    { name: "fjarr.desktop" },
  ],
}, GRANT_KEY, { algorithm: "HS256", expiresIn: "5m" });

Your dashboard: mount it

import {
  createFjarrClient, FjarrProvider, DesktopView,
} from "@fjarr/react";

const client = createFjarrClient({
  serverUrl: "wss://fjarr.acme.example",
  grant: (id) => myApi.fjarrGrant(id), // your grant endpoint
});

<FjarrProvider client={client}>
  <DesktopView
    session={client.sessions.open("robot-024")} />
</FjarrProvider>

The contract between the pieces: interfaces · architecture

Capabilities — everything is a plugin, including yours

available

📷 Camera video

Many cameras, one encode fanned out to every viewer, adaptive bitrate. Cameras that already speak H.264 cost no encoder at all.

available

⌨️ Remote terminal

A shell in the browser, over the same session. Off until configured, audited when opened.

available

🔌 Network link

A routable address for one machine: ssh, scp, ROS 2, your debugger, your vendor tools.

available

🔬 Pipeline introspection

The live media pipeline as a graph, on the machine and in your dashboard.

in development

🖥️ Remote desktop

The machine's own desktop in your dashboard, unattended after a reboot, every monitor.

planned

🎙️ Audio

Hear the machine's microphones, talk through its speaker, push-to-talk.

planned

📦 Files

The machine's files as a drive, and resumable transfers that survive a flaky LTE link.

planned

📈 Telemetry & logs

Typed sensor streams with change-triggered updates, and logs that store and forward when offline.

planned

🩺 Fleet observability

Versions, host metrics and error rates across the fleet.

planned

🔄 Atomic OTA

A/B updates with rollback and streamed binary diffs.

available: in a release · in development: running on main · planned: on the roadmap. We don't market what doesn't run.

Measured, not promised

Every number below was measured on named hardware at a milestone gate, against a written budget. Click one for where and when.

56 mscamera glass-to-glass on a LANp50; p95 79 ms102 msthe same, p95, through a relayp50 69 ms52 msdesktop input-to-photonp50; p95 59 ms, real hardware11 msterminal round tripbudget 150 ms~250 Mbps1 GiB scp over the network link246–253 Mbps, hash-verified31–33 fpscamera beside a saturating transferno lost frames

The machine's desktop — and control that's safe for one that moves

The desktop your machine already has, in your dashboard — no tool for the end customer to install. in development

Who's in control is explicit

🛑 Taking control stops the robot first

Driving is one operator at a time, and never handed over on idle. Taking it over releases every input before anyone new can move it.

🖱️ One pointer, handed over cleanly

Desktop control frees five seconds after the holder's last input, or anyone can take it at once — and the holder is told.

💓 Gone means released

If an operator's connection dies, their held keys and buttons are released and their control lapses. A machine is never left stuck.

👁️ Watch without touching

A view-only grant can never take control or read the clipboard — for the customer, the auditor, the second pair of eyes.

Each of these ships with a regression test (safety behaviors). How control works: security · remote desktop.

A link to one machine — not a VPN

Every team keeps a list of things it occasionally needs to do on a machine that nobody will ever write a protocol for. The network link is the escape hatch: an authorized operator gets a routable address for one machine, inside the session that already carries its video — and the tools they already use just work.

$ fjarr-connect login                 # your company's sign-in, in the browser
$ fjarr-connect robot-024
robot-024  100.70.118.224  mtu 1184  up in 0.8 s

The machine's CAN bus on your laptop

Bridge its bus to a virtual interface; SavvyCAN or the vendor's tool sees it live.

cannelloni -I vcan0 -R <machine> -r 20000 -l 20000
candump vcan0

How-to →

Your debugger, on its process

VS Code Remote-SSH for the editor, gdbserver for a process that's already running.

(gdb) target remote <machine>:2345

How-to →

ROS 2 from your desk

DDS discovery crosses the link, so the CLI and rviz2 just see the robot's graph.

ros2 topic list
rviz2

How-to →

Its containers, from your CLI

Your docker client, the machine's engine — logs, exec, restarts.

DOCKER_HOST=ssh://user@<machine> \
  docker logs -f my-node

How-to →

And ssh, scp, RDP, your file manager, Wireshark, a serial port, Modbus — more ideas.

🔒 Isolation you can't misconfigure

Connect to two machines at once and they still can't see each other. Every link ends at you and nothing is forwarded between them — there's no path to leave open by mistake.

🎫 One set of permissions

You already decide who may reach machine 42. The link is that same decision, in the same grant, in the same audit log. No second identity system to run beside yours.

⏱️ Access that expires

The link exists while a session does, then it's gone. Nothing becomes permanently reachable, and there's no extra daemon on the machine to install, update or license.

It deliberately isn't a mesh: no shared network, no machine-to-machine routing, no always-on reachability. It's developer and support access — the design says exactly where that line sits.

Safe to put on a customer's network

🔐 Encrypted end to end

Media and data are encrypted between the machine and the viewer. When a relay is needed, it only ever forwards ciphertext.

🎫 Your backend decides

Access is a short-lived grant your backend signs, scoped per capability. The machine re-checks it against its own config, so a grant can't switch on what you didn't.

⏳ No static secrets

Relay credentials are minted per session and expire with the grant. Nothing long-lived is compiled into a frontend.

🧱 Nothing to open

The machine dials out. It never asks the customer's router to open a port, and their IT department has no inbound rule to approve.

🪪 Least privilege on the machine

The agent runs as its own unprivileged user. Desktop access is narrowed to the screen — not the microphone.

✍️ Signed from repo to robot

A signed apt repository, and container images signed with an SBOM.

Light on the machine

🎞️ One encode, any number of viewers

A second viewer costs fan-out, not another encoder.

🧮 The integrated GPU first

Video encodes on the iGPU, so the discrete GPU stays with perception — the job the machine exists to do.

🚦 Bulk never starves video

A transfer saturating the link leaves the camera beside it at full frame rate.

The whole threat model is public: security. Encoders and hardware: supported platforms.

Built for the people who debug machines

🔬 See the media plane

The live pipeline graph, with rates and drops, on the machine and in your dashboard — readable by a support engineer or an AI coding agent. Introspection

✅ One command says if it's healthy

sudo fjarr-agent --check lists every configured source and backend, and gives the one-line fix for anything missing. Install & drivers

🧩 Only the drivers it needs

fjarr-agent drivers detect matches the hardware actually attached against a catalog and names the package each needs. Install those, and nothing else lands on the machine.

Open core, honest split

Free & openAll three libraries, the signaling sidecar, the network link and its client, the protocol, the docs, the demos. Genuinely usable alone, self-hosted forever.
Fjarr CloudManaged signaling and relays, fleet observability, staged OTA campaigns, SSO — metered by machines, session-minutes and relay bandwidth. Same contract as self-hosting: switch either way, no rework.
LicenceAGPL-3.0, or a commercial licence when AGPL doesn't fit your product. Nothing we ship depends on GPL code, and the build environment refuses it.

The whole business plan is a public document: product strategy.

See if it fits

The specs are the product's source of truth, and they're all public.

Read the docsGitHubRoadmap