Files
continuum-proxy/printers.example.toml
octoturge da28597eeb Report the OctoPrint API as what it is, and let the operator name it
Reporting a detected printer as `prusa_link` asserted a product nothing on
the wire mentions. What a probe observes is that something speaks the
OctoPrint REST API; PrusaLink is one implementation of it, OctoPrint itself
is another, and without a credential the two are indistinguishable - every
PrusaLink identity endpoint requires digest auth.

So discovery now reports `vendor = "octoprint"`, the protocol it actually
saw, and carries `ambiguous_with` so the rendered entry prints a comment
offering `prusa_link` directly above the `vendor` line. Changing it is a
one-word edit in the file the operator is already pasting into, which is the
right place for a question only they can answer. The exception is a body
that names itself: `/api/version`'s `text` field reads "PrusaLink 0.7.0" or
"OctoPrint 1.9.3", so on the rare unauthenticated 200 the answer is known
and no choice is offered.

For that to mean anything, `octoprint` had to become a vendor `fleet.rs`
accepts - otherwise discovery printed a block that would not load. It maps
to the same client as `prusa_link`, because they speak the same API, with an
`OctoPrintFlavour` recording which. The flavour is stored now, while the
client is still a stub, precisely because no probe can settle it: it is the
operator's answer, and without somewhere to keep it the real client would
have to ask them a second time. `PrusaLinkPrinter` is accordingly now
`OctoPrintPrinter` - one client for the API family - since a struct called
PrusaLink holding a flavour that says "Generic" is a contradiction someone
would later have to unpick.

**Found by the new test, and the reason it exists:** `fleet::load` did *not*
reject an entry whose credential was blank. `require_field` took an
`Option`, so `access_code = ""` arrived as `Some("")` and loaded happily -
which falsified the claim this feature was built on, that printing a block
with an empty credential is safe because pasting it in unfinished fails
loudly at startup. It does now. An empty credential is not a usable one; it
only moves the failure somewhere worse, where a Bambu printer is refused at
MQTT and a PrusaLink one 401s on every poll with nothing saying the field is
blank. This is the same trap as `??`-instead-of-`||` on an environment
variable that ships empty, which the backend's `auth/edge_secret.ts` and
`s3/index.ts` both document; it bites identically on a TOML key.

tests/discovery_to_fleet.rs is new and closes the loop: discovery and fleet
are coupled by nothing but a string, unit tests on either side cannot catch
a drift between them, and the symptom of a drift is an operator pasting in
exactly what they were told to and the gateway refusing to start. It renders
a candidate for every vendor, fills in the credential the way a human would,
loads it for real, and checks the handle matches the vendor asked for - not
merely that something loaded, since `octoprint` and `prusa_link` share a
client and a mapping that collapsed them would still load and still be
wrong.

Verification: the Moonraker probe now runs end to end over real HTTP against
a stand-in serving Moonraker's documented `/printer/info`, and correctly
reports a klipper entry named from the returned hostname. Port 80 cannot be
bound without root here, so the PrusaLink path is covered by a test that
takes a real socket answering a real digest challenge - the one link unit
tests on the classifier cannot reach, and the one PrusaLink is identified by.
2026-10-19 07:40:28 +00:00

75 lines
3.0 KiB
TOML

# Copy this file to printers.toml (gitignored) and fill in your real
# printers. See src/fleet.rs for the loader, examples/test_bambu_certs.rs
# for a way to test a Bambu printer's certificate against this config.
#
# Rather than reading these fields off each printer's screen:
#
# cargo run --example discover_printers -- all 192.168.0.0/24
#
# finds the printers on your network and on your USB ports and prints blocks
# like the ones below, with the addresses and serials already filled in. It
# covers Bambu, Klipper/Moonraker, PrusaLink and Marlin-over-serial. Also
# `listen` (Bambu on this subnet only), `sweep <subnet>` (works through a
# router), `identify <host>`, and `serial`.
#
# It cannot find a credential - a Bambu access code is on the printer's
# screen and a PrusaLink API key is in its web UI, which is the point of both
# - so every block it prints still needs that one field before this file will
# load. Note that only `bambu` has a working client today; the others are
# found and reported, but their drivers in src/printer/ are still stubs.
#
# One thing discovery cannot answer for you: `octoprint` vs `prusa_link`.
# PrusaLink implements OctoPrint's API, so from the network the two are
# identical - discovery suggests `octoprint` (the protocol it actually saw)
# and leaves a comment offering the other. Change it if you know it is a
# Prusa serving its own API.
[[printer]]
id = "p1p"
name = "P1P"
# Forced instead of "bambu" here since this one's a known-old printer -
# see fleet.rs's PrinterEntry doc comment for the full vendor list.
vendor = "bambu_v1"
host = "192.168.1.50"
access_code = "REPLACE_WITH_ACCESS_CODE"
sn = "REPLACE_WITH_SERIAL_NUMBER" # printer screen > Settings > Device (used for MQTT topics later)
# No ca_cert_path needed even though P1P doesn't chain to the bundled CA -
# `connect()` auto-pins a certificate via trust-on-first-connect the first
# time it can't verify against the bundled one, and saves it under
# certs/pinned/<sn>.pem for every run after that (see BambuPrinter::
# ensure_trusted's doc comment). Only set ca_cert_path explicitly if you
# want to point at a certificate you've captured/vetted some other way.
[[printer]]
id = "h2c"
name = "H2C"
# "bambu" auto-detects MQTT v5-vs-v3.1.1 (BambuMQTT v2-vs-v1) on connect -
# the recommended default, since a firmware update can change which one a
# given printer speaks without you knowing ahead of time.
vendor = "bambu"
host = "192.168.1.51"
access_code = "REPLACE_WITH_ACCESS_CODE"
sn = "REPLACE_WITH_SERIAL_NUMBER"
# No ca_cert_path - current-generation printers verify against the bundled
# CA (certs/bambu_ca2.pem) fine.
# [[printer]]
# id = "voron"
# name = "Voron 2.4"
# vendor = "klipper"
# host = "192.168.1.52"
# [[printer]]
# id = "mk4"
# name = "MK4"
# vendor = "prusa_link"
# host = "192.168.1.53"
# api_key = "REPLACE_WITH_API_KEY"
# [[printer]]
# id = "mk3s"
# name = "MK3S+"
# vendor = "prusa_serial"
# port = "/dev/ttyACM0"
# baud_rate = 115200 # optional, defaults to 115200