Files
continuum-proxy/continuum.example.toml
T
octoturgeandClaude Opus 5 1967aae42c Report discovered printers to the cloud, off by default
The gateway could already find printers; only a human reading a terminal
could see the result. So adding a printer in the web console still began with
walking to a machine to read its IP address off the screen.

Now a background task scans on a timer and sends a `discovered_printers`
frame. It cannot finish the job - the credential is on that screen and no
probe can read it - but it removes the address, the serial and the guesswork
about which vendor the thing is.

OFF by default, which is the load-bearing part of this commit. A /24 sweep is
254 TLS handshakes and up to three HTTP requests per host, and nothing
watching can tell that from a port scan; on a corporate network, being
indistinguishable from a port scan is itself the problem. A gateway that did
this by default would scan networks its operator never meant to point it at,
and the first they would hear of it is from their own security team. So:

- `discovery.enabled` is false unless set, per gateway, in that gateway's own
  config. The cloud cannot switch it on. A setting whose whole purpose is to
  bound what this process does to the local network has to be decided where
  that network is understood, which is not in a data centre.
- `subnets` is explicit, never derived from this host's interfaces, so a
  second NIC, a VPN or a container bridge cannot widen the sweep. Anything
  wider than a /24 is still refused, now at config-import time rather than
  when the timer first fires.
- A flag that is neither `true` nor `false` is an error, not a shrug. The two
  readings of a typo are not equally safe.
- Naming a subnet without setting `enabled` scans nothing - the safe reading
  of an incomplete instruction - and `enabled` with nowhere to look is
  refused as the mistake it is.
- The env var can switch it *off* over a file that enables it, which is the
  direction that has to work: a Proxy Box's config is signed in the cloud, so
  an operator who finds their gateway probing cannot just edit it.

What a scheduled scan will not do, unlike the example CLI: send `M115`.
Opening a serial port toggles DTR, which reboots an Arduino-based board and
ruins a print in progress. The example can do it behind
`--yes-reset-the-printer` because a human is standing there; a timer has
nobody to ask, so it does not get the option and reads sysfs instead.

Two things that took more care than expected:

`Scan::looked()`. An empty report means "this gateway looked and its network
has no unconfigured printers", so sending one after every probe failed would
state something untrue about the farm. A partial failure is still evidence,
though - withholding a good sweep because a container can never bind SSDP
would suppress it forever - so the test is "did anything succeed", not "did
everything".

`fleet::configured()` reads the fleet file at parse level rather than through
`load`, because the entry it most needs to recognise is the one `load`
refuses: a printer discovery suggested, pasted in, with the access code not
yet typed in. Re-offering that printer as a fresh find is noise at the one
moment the operator is mid-way through adding it.

Verified against the real LAN: a sweep found both printers by TLS certificate
with correct serials and no credentials, and the frame it produced validates
against the backend's EdgeClientMessageSchema. The `wire` subcommand on the
example prints exactly that frame, and the capture is pinned as a backend
fixture. SSDP remains untestable here - this container is a router hop from
the printers and multicast does not cross one.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-10-19 08:21:30 +00:00

78 lines
3.3 KiB
TOML

# The proxy's OWN settings - not the printers (that is printers.toml, which
# holds per-site secrets and is never shipped on a pendrive). See
# src/config.rs for how this file layers with CONTINUUM_* environment
# variables: this file is the base, an env var overrides the same key.
#
# PC / server: copy to continuum.toml next to the proxy and edit it. Nothing
# else is needed; removable media is never read.
# Proxy Box: this file is NOT written by hand. Continuum Cloud's
# configurator produces continuum-config.zip: config.toml,
# config.toml.sha256 and config.toml.sig (an Ed25519 signature
# naming this box and a config_version), every entry
# AES-256-encrypted under the box's own key. A Box refuses
# plaintext, unsigned, other-box and older-version bundles.
# See the module docs of src/config_import.rs for the exact
# format the cloud must produce.
#
# Unknown keys are rejected on purpose, so a typo cannot silently fall back to
# a default. On a Proxy Box the file must be complete by itself.
gateway_id = "REPLACE_ME"
farm_id = "REPLACE_ME"
gateway_token = "REPLACE_ME"
# uplink_url = "wss://api.continuum.local/ws/edge/v1"
# go2rtc_bin = "go2rtc"
# go2rtc_config = "./go2rtc.yaml"
# go2rtc_api_listen = ":1984"
# stream_base_url = "https://proxy.example/"
# printers_path = "/var/lib/continuum/printers.toml" # absolute on a box
# Proxy Box only. PARSED AND VALIDATED, NOT APPLIED - this build never
# touches the OS network configuration.
# [network]
# mode = "static" # or "dhcp"
# ip = "192.168.1.60"
# prefix_len = 24
# gateway = "192.168.1.1"
# dns = ["1.1.1.1"]
# [network.wifi]
# ssid = "farm"
# psk = "REPLACE_ME"
# Printer discovery: look for printers nobody has configured and report them
# to the cloud, so adding one in the web console does not start with walking
# to a machine to read its IP address off the screen.
#
# OFF by default, and the whole block is commented out here for that reason.
# A sweep is unsolicited traffic on someone else's network - 254 TLS
# handshakes and up to three HTTP requests per host, which nothing watching
# can distinguish from a port scan. Switch it on only for a network you are
# responsible for.
#
# Discovery never supplies a credential, because nothing can: a Bambu access
# code is displayed on the printer and a PrusaLink API key is in its web UI.
# It supplies the address, the serial and the vendor - the parts you would
# otherwise have to look up.
#
# `subnets` is explicit and never derived from this host's interfaces, so a
# second NIC, a VPN or a container bridge cannot widen what gets swept.
# Anything wider than a /24 is refused.
#
# `serial` reads USB descriptors from sysfs and opens nothing. It will never
# send an `M115` handshake: opening a serial port toggles DTR, which reboots
# an Arduino-based board, and on a printer mid-job that ruins the print. Use
# `cargo run --example discover_printers -- serial-probe <port>` when you want
# the handshake and are standing in front of the machine.
#
# `ssdp` listens passively for Bambu announcements. It sends nothing, but
# multicast does not cross a router, so on a real farm network a sweep is
# what actually works.
#
# [discovery]
# enabled = true
# subnets = ["192.168.0.0/24"]
# ssdp = false
# serial = false
# interval_minutes = 30