April 29, 20245 minutes
Building a dynamic reverse proxy with secure remote access for Klipper printers and camera services.
The software stack for a typical Klipper setup involves a few services. Klipper - the 3d-pinter firmware, Moonraker - the web API layer, a client (e.g. Guppy Screen, Mainsail, Fluidd), and some camera service for monitoring your prints.
Klipper runs local and exposes its interface using a Unix Domain Socket. In order to expose this local interface over the network, Moonraker connects to it and relies the communication through a defined HTTP(S)/Websocket protocol. Now any HTTP server can be used to host a WebUI for controlling the printer, e.g. Nginx, lighttpd, apache, python -m http.server. Since camera services typically run their own servers, these streams are already available over the network. With these couple services, users gain a modern and power way to control their printers locally.
Imagine you want the same experience for more than one printer. Most of the above steps would be repeated. For seasoned IT professionals, you might centralize the WebUI hosting portion, but then realize Fluidd/Mainsail do not support non-root prefix. Currently you can’t simply host Fluidd/Mainsail for a printer at /MyPrinter1
and another on /MyOtherPrinter
, and expect it to route to the correct printer. How about figuring out all those stream paths for all these camera services? Do you need remote access (secure, bandwidth limited) for your printer? What about remote access for those camera streams?
Take a look a demo with GuppyFLO proxying a printer and a webcam stream to Mobileraker
over tailscale
.
Most of the above problems are solved
, from self hosted VPNs to open source/commercial tunneling solutions. GuppyFLO is my attempt at doing the same thing, offering an open source solutions that simplifies Klipper printer management. When planning for GuppyFLO
, the following are its hard requirements:
If you call out reverse proxy, and you’re correct. GuppyFLO
is a reverse proxy that dynamically configures routes for Klipper/Moonraker and camera services like ustreamer/mjpegstreamer. It slaps a CRUD API and reactjs
UI on top for printers/cameras. It hosts forked versions of Fluidd and Mainsail which add support for prefix based routing. It auto discovers camera stream paths for ustreamer/mpegstreamer/go2rtc using their APIs. It then integrates with tailscale
(public/private key)/ngrok (OAuth) to secure remote access. All of that in a simple golang
service and a reactjs
app (skipping the detail of the reactjs
app, there are many contents and tutorial covering this reactjs
).
Building a reverse proxy in go
is surprisingly simple. GuppyFLO
mainly uses the following function, reverseProxyHandler
, to setup reverse proxy routes:
The proxying is complete after calling this function on each of Moonraker and camera services paths:
Since each camera service is unique, their paths are also unique. To discover these paths, GuppyFLO
queries their known API endpoints at commonly configures ports.
After discoverying the endpoints, the same reverseProxyHandler
function is called.
GuppyFLO
also hosts two forked version of Fluidd and Mainsail. go
also makes this as simple as two go routines serving a http.FileServer
handlder pointing to UI assets.
Both of these services provide native golang libraries to integrate with their service. The libraries also implemtns golang net.Listener
which makes integration as simple as creating the respective server and serving them with http.Server
. In term of security, tailscale
uses public/private key for their wireguard network and hence connection is end-to-end encrypted. For ngrok
, it’s a tunneling solution where traffic traverses ngrok
servers. Even though ngrok
endpoint is HTTPS, printer/camera traffic going over ngrok
service is unencrypted and ngrok
sees them. GuppyFLO enforces OAuth when using ngrok
. This feature offsets the first handshake to ngrok
and your OAuth provider (don’t roll your own authentication, use battle tested authentication flow). GuppyFLO
will only start answer remote requests once the OAuth is successfull.
This is the piece that enables WebUI/HTTP client to get, add, remove, and modify managed printers and cameras in GuppyFLO
. It’s a simple API that accepts JSON requests and returns JSON response. Detail implements for printers and cameras endpoints starts here.
In combination of a reverse proxy, tailscale/ngrok, camera service discovery, and prefix base Mainsail/Fluidd, GuppyFLO is my take on multi-printer management. For completeness, the whole server component of GuppyFLO
is a single main.go
(pinned to tag 0.0.10).