Update docs and prune examples superseded by real functionality
README/certs/README/.env.example/printers.example.toml were all still describing this as a learning-stage stub with no real printer protocol clients - rewritten to reflect the real Bambu MQTT client, cert auto-pinning, and marlin serial connection that now exist. - Remove examples/fetch_bambu_cert.rs: its manual fetch-and-save workflow is now what BambuPrinter::connect()'s ensure_trusted does automatically on every connect, so it has no remaining use case. - Fix examples/printer_polymorphism.rs, which no longer compiled - dispatch_gcode was replaced by real methods earlier this session; swapped in print_status() as the vendor-only-extension example. - Remove Config::bambu_ca_cert_overrides/bambu_require_valid_cert and the .env vars documenting them - confirmed dead (nothing read them), fully superseded by printers.toml's per-printer ca_cert_path/ require_valid_cert plus automatic trust-on-first-connect pinning. - Fix stale doc comments in printer/mod.rs and printer/bambu.rs that described certificate trust and MQTT as not built yet, or referencing the now-removed example. No behavior change in the daemon itself - verified with cargo build --examples and a live run of printer_polymorphism and auto_connect_bambu against the real P1P/H2C printers. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
+5
-6
@@ -10,11 +10,10 @@ CONTINUUM_UPLINK_URL=wss://api.continuum.local/ws/edge/v1
|
|||||||
CONTINUUM_GO2RTC_BIN=/usr/local/bin/go2rtc
|
CONTINUUM_GO2RTC_BIN=/usr/local/bin/go2rtc
|
||||||
CONTINUUM_GO2RTC_CONFIG=./go2rtc.yaml
|
CONTINUUM_GO2RTC_CONFIG=./go2rtc.yaml
|
||||||
|
|
||||||
# Bambu MQTTS certificate trust. Most printers verify fine against the
|
# Fleet config - see printers.example.toml. Bambu MQTTS certificate trust
|
||||||
# bundled shared CA (certs/bambu_ca2.pem) and need nothing here. For a
|
# (per-printer ca_cert_path/require_valid_cert, and automatic
|
||||||
# printer that doesn't chain to it (P1P, at least), add "printer_id=path"
|
# trust-on-first-connect pinning otherwise) is configured per printer in
|
||||||
# entries, comma-separated: CONTINUUM_BAMBU_CA_CERT_OVERRIDES=p1=./certs/p1p.pem
|
# that file, not here.
|
||||||
CONTINUUM_BAMBU_CA_CERT_OVERRIDES=
|
CONTINUUM_PRINTERS_PATH=printers.toml
|
||||||
CONTINUUM_BAMBU_REQUIRE_VALID_CERT=true
|
|
||||||
|
|
||||||
RUST_LOG=info,continuum_proxy=debug
|
RUST_LOG=info,continuum_proxy=debug
|
||||||
|
|||||||
@@ -21,10 +21,6 @@ path = "examples/printer_polymorphism.rs"
|
|||||||
name = "test_bambu_certs"
|
name = "test_bambu_certs"
|
||||||
path = "examples/test_bambu_certs.rs"
|
path = "examples/test_bambu_certs.rs"
|
||||||
|
|
||||||
[[example]]
|
|
||||||
name = "fetch_bambu_cert"
|
|
||||||
path = "examples/fetch_bambu_cert.rs"
|
|
||||||
|
|
||||||
[[example]]
|
[[example]]
|
||||||
name = "auto_connect_bambu"
|
name = "auto_connect_bambu"
|
||||||
path = "examples/auto_connect_bambu.rs"
|
path = "examples/auto_connect_bambu.rs"
|
||||||
|
|||||||
@@ -4,62 +4,90 @@ Edge gateway daemon for the Continuum print farm platform. Runs on a Linux
|
|||||||
SBC on-site, talks to printers over the LAN, and keeps a connection to the
|
SBC on-site, talks to printers over the LAN, and keeps a connection to the
|
||||||
cloud control plane (`continuum-backend`).
|
cloud control plane (`continuum-backend`).
|
||||||
|
|
||||||
## This is a learning-stage boilerplate
|
## What's real right now
|
||||||
|
|
||||||
This repo is deliberately minimal right now - real printer protocol clients
|
- **Bambu MQTT** - a real client for both protocol generations (MQTT
|
||||||
(Bambu MQTT+FTPS, PrusaLink REST, Klipper/Moonraker WebSocket) are **not**
|
v3.1.1+BambuMQTT v1 on older firmware, MQTT v5+BambuMQTT v2 on current
|
||||||
implemented yet. Each vendor is a stub that just prints what it would do
|
firmware), auto-detected per printer on connect (a firmware update can
|
||||||
(`src/printer/bambu.rs`, `prusa.rs`, `klipper.rs`). The idea is to learn
|
move the same physical printer between them, so this isn't something
|
||||||
Rust's polymorphism pattern (trait + enum, since Rust has no class
|
config can hard-code once and forget - see `printer/bambu.rs`'s
|
||||||
inheritance) on something simple before adding real networking on top.
|
`BambuProtocolPreference::Auto`). Request/reply correlation via
|
||||||
|
`sequence_id`, an in-memory `print.push_status` cache that merges v1's
|
||||||
|
partial updates and replaces on v2's full ones, and TLS trust that
|
||||||
|
auto-pins a printer's certificate via trust-on-first-connect the first
|
||||||
|
time the bundled CA doesn't verify (`printer/bambu.rs`'s
|
||||||
|
`ensure_trusted`) - see `printer/bambu_mqtt.rs`'s doc comment for the
|
||||||
|
full protocol write-up.
|
||||||
|
- **Marlin/RepRap serial** - a real held-open serial connection that paces
|
||||||
|
gcode lines by Marlin's own "ok" acknowledgment (`printer/marlin_serial.rs`).
|
||||||
|
- **Wire shapes from schemas, not hand duplication** - Bambu's raw MQTT
|
||||||
|
JSON is built straight from `continuum-schemas`' JSON Schema files via
|
||||||
|
`continuum-schema-instance`, instead of a second hand-written copy of
|
||||||
|
every request shape drifting out of sync with the schema.
|
||||||
|
- **Cloud uplink** - a WebSocket client to `continuum-backend` that
|
||||||
|
connects, sends a heartbeat on a timer, and reconnects on drop
|
||||||
|
(`src/uplink/`).
|
||||||
|
- **go2rtc watchdog** - restarts the camera-restreaming subprocess if it
|
||||||
|
dies (`src/go2rtc.rs`).
|
||||||
|
|
||||||
|
## What's still a stub
|
||||||
|
|
||||||
|
PrusaLink (REST) and Klipper/Moonraker (WebSocket) - `printer/prusa.rs`
|
||||||
|
and `printer/klipper.rs` just `println!` what they'd send. Also not built
|
||||||
|
yet: dispatching a cloud-issued command to a connected printer (the uplink
|
||||||
|
and the printer fleet run side by side in `main.rs`, but nothing routes
|
||||||
|
between them yet), local buffering for telemetry across connectivity gaps,
|
||||||
|
LAN printer discovery (SSDP/mDNS), and the mechanical plate-changer
|
||||||
|
interface.
|
||||||
|
|
||||||
## Getting started
|
## Getting started
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
cp .env.example .env
|
cp .env.example .env
|
||||||
cargo run # the daemon: cloud uplink + go2rtc watchdog
|
cargo run # the daemon: connects every configured printer, then runs the cloud uplink + go2rtc watchdog
|
||||||
cargo run --example printer_polymorphism # standalone demo, no network/env needed
|
|
||||||
|
|
||||||
cp printers.example.toml printers.toml # fill in your real printers (gitignored)
|
cp printers.example.toml printers.toml # fill in your real printers (gitignored)
|
||||||
cargo run --example test_bambu_certs # real TLS handshake test against each Bambu printer
|
cargo run --example auto_connect_bambu # checks/auto-pins each Bambu printer's certificate, no MQTT needed
|
||||||
|
cargo run --example test_bambu_certs # narrower: just the raw TLS handshake, no auto-pinning
|
||||||
|
cargo run --example printer_polymorphism # standalone trait+enum demo, fake data, no network/env needed
|
||||||
|
cargo run --example bambu_commands_demo # round-trips hand-written structs against real captured Bambu JSON
|
||||||
```
|
```
|
||||||
|
|
||||||
## Structure
|
## Structure
|
||||||
|
|
||||||
```
|
```
|
||||||
src/
|
src/
|
||||||
main.rs Runs the uplink and the go2rtc watchdog side by side
|
main.rs Loads+connects the printer fleet, then runs the uplink and go2rtc watchdog side by side
|
||||||
config.rs Loads settings from environment variables
|
config.rs Loads settings from environment variables
|
||||||
fleet.rs Loads printers.toml into ready-to-use PrinterHandles
|
fleet.rs Loads printers.toml into ready-to-use PrinterHandles
|
||||||
uplink/ WebSocket client to continuum-backend: connect, heartbeat, reconnect on drop
|
uplink/ WebSocket client to continuum-backend: connect, heartbeat, reconnect on drop
|
||||||
printer/ GenericPrinter trait + PrinterBase + PrinterHandle enum + one stub per vendor
|
go2rtc.rs Restarts the go2rtc camera-restreaming process if it dies
|
||||||
go2rtc.rs Restarts the go2rtc camera-restreaming process if it dies
|
printer/
|
||||||
|
mod.rs GenericPrinter trait + PrinterBase + PrinterHandle enum - the trait+enum
|
||||||
|
"inheritance" pattern this project uses instead of class inheritance
|
||||||
|
bambu.rs The real Bambu printer type: cert trust, protocol version auto-detect, commands
|
||||||
|
bambu_mqtt.rs The real MQTT wiring underneath bambu.rs - see its doc comment for the protocol details
|
||||||
|
bambu_commands/ Hand-written serde structs mirroring continuum-schemas' bambulab/ shapes
|
||||||
|
marlin_serial.rs A real held-open Marlin/RepRap serial connection
|
||||||
|
prusa.rs, klipper.rs Still stubs - see "What's still a stub" above
|
||||||
examples/
|
examples/
|
||||||
printer_polymorphism.rs Runs all five printer stubs through one `connect()` call site
|
auto_connect_bambu.rs Checks/auto-pins every Bambu printer's certificate (no MQTT)
|
||||||
test_bambu_certs.rs Loads printers.toml, does a real TLS handshake to each Bambu printer
|
test_bambu_certs.rs Narrower: just a raw TLS handshake per Bambu printer
|
||||||
|
printer_polymorphism.rs Standalone trait+enum demo, five fake printers, no network needed
|
||||||
|
bambu_commands_demo.rs Round-trips bambu_commands' structs against real captured Bambu JSON
|
||||||
```
|
```
|
||||||
|
|
||||||
`src/printer/` is where the "inheritance" question lives - see that
|
|
||||||
module's doc comment for the trait+enum pattern this project uses instead
|
|
||||||
of class inheritance, and run `printer_polymorphism` to see it work.
|
|
||||||
|
|
||||||
`printers.toml` (gitignored - copy from `printers.example.toml`) holds real
|
`printers.toml` (gitignored - copy from `printers.example.toml`) holds real
|
||||||
per-printer connection details: host, access code, and - since not every
|
per-printer connection details: host, access code/API key/serial port
|
||||||
Bambu printer trusts the same certificate (see `certs/README.md`) - an
|
depending on vendor, and - for Bambu - an optional per-printer CA
|
||||||
optional per-printer CA override. `src/fleet.rs` loads it; nothing in
|
certificate override (see `certs/README.md`; most printers don't need one,
|
||||||
`main.rs` uses it yet, but `test_bambu_certs` does, as a real (if narrow -
|
since `connect()` auto-pins automatically).
|
||||||
just the TLS handshake, no MQTT) way to check a printer's certificate
|
|
||||||
without needing the full MQTT client built yet.
|
|
||||||
|
|
||||||
## What's not here yet (on purpose)
|
## Learning Rust through this repo
|
||||||
|
|
||||||
- Real MQTT/FTPS/HTTP/WebSocket printer clients - `src/printer/*.rs` has a
|
`src/printer/mod.rs`'s doc comment is where the "how do I do inheritance
|
||||||
`println!` where each of these will go.
|
without classes" question lives - trait (shared behavior) + struct
|
||||||
- Local SQLite buffering for telemetry across connectivity gaps.
|
(shared fields) + enum (the closed set of concrete types), instead of
|
||||||
- LAN printer discovery (SSDP/mDNS).
|
`GenericPrinter -> BambuPrinter/PrusaLinkPrinter/...` class inheritance.
|
||||||
- The mechanical plate-changer interface (serial/GPIO).
|
Run `printer_polymorphism` to see it work end to end without needing any
|
||||||
|
real printer on the network.
|
||||||
Add these back in one at a time as you get comfortable with the Rust
|
|
||||||
underneath them - each is its own small lesson (async I/O, a new crate's
|
|
||||||
API, error handling for a real protocol) rather than something to absorb
|
|
||||||
all at once.
|
|
||||||
|
|||||||
@@ -28,3 +28,16 @@ certificates at the same time.
|
|||||||
If a printer's connection ever fails certificate verification against this
|
If a printer's connection ever fails certificate verification against this
|
||||||
file, re-fetch from the source URL above (BambuStudio ships whatever the
|
file, re-fetch from the source URL above (BambuStudio ships whatever the
|
||||||
current fleet needs) and update the fingerprint here.
|
current fleet needs) and update the fingerprint here.
|
||||||
|
|
||||||
|
## `pinned/`
|
||||||
|
|
||||||
|
Not checked in (gitignored) - where `BambuPrinter::ensure_trusted` saves a
|
||||||
|
certificate it auto-pinned via trust-on-first-connect, one file per
|
||||||
|
printer, named `<serial number>.pem`. This only happens for a printer
|
||||||
|
whose certificate doesn't verify against `bambu_ca2.pem` above (P1P, at
|
||||||
|
least) and that doesn't already have an explicit `ca_cert_path` set in
|
||||||
|
`printers.toml` - see that method's doc comment in `src/printer/bambu.rs`
|
||||||
|
for the full trust-on-first-connect flow, and the SSH-host-key analogy it's
|
||||||
|
built on. Delete a printer's file here to force it to re-pin (a real
|
||||||
|
error, not silent, if the certificate it captures the second time still
|
||||||
|
doesn't verify) the next time it connects.
|
||||||
|
|||||||
@@ -1,12 +1,13 @@
|
|||||||
//! Run with: cargo run --example auto_connect_bambu
|
//! Run with: cargo run --example auto_connect_bambu
|
||||||
//!
|
//!
|
||||||
//! The "smooth, no manual steps" version of test_bambu_certs +
|
//! For each Bambu printer in printers.toml, makes sure it has a working
|
||||||
//! fetch_bambu_cert combined: for each Bambu printer in printers.toml,
|
//! certificate, auto-pinning one via trust-on-first-connect if the
|
||||||
//! makes sure it has a working certificate, auto-pinning one via
|
//! bundled CA doesn't verify - the exact same `ensure_trusted` step
|
||||||
//! trust-on-first-connect if the bundled CA doesn't verify. Safe to run
|
//! `BambuPrinter::connect()` runs before opening MQTT, isolated here so
|
||||||
//! repeatedly - a printer that's already pinned (certs/pinned/<serial
|
//! you can check/establish trust without needing a full MQTT handshake to
|
||||||
//! number>.pem) just gets re-verified against its pin, no network trust
|
//! succeed too. Safe to run repeatedly - a printer that's already pinned
|
||||||
//! decision is made again.
|
//! (certs/pinned/<serial number>.pem) just gets re-verified against its
|
||||||
|
//! pin, no network trust decision is made again.
|
||||||
|
|
||||||
use continuum_proxy::fleet;
|
use continuum_proxy::fleet;
|
||||||
use continuum_proxy::printer::PrinterHandle;
|
use continuum_proxy::printer::PrinterHandle;
|
||||||
|
|||||||
@@ -1,36 +0,0 @@
|
|||||||
//! Run with: cargo run --example fetch_bambu_cert -- <printer_id> <output_path>
|
|
||||||
//!
|
|
||||||
//! "Trust on first connect": connects to one printer from printers.toml
|
|
||||||
//! with certificate verification disabled, captures whatever certificate
|
|
||||||
//! it presents, and saves it as PEM. Meant for a printer like P1P that
|
|
||||||
//! doesn't chain to the bundled CA - after running this, point that
|
|
||||||
//! printer's `ca_cert_path` in printers.toml at the saved file and
|
|
||||||
//! `test_bambu_certs` should report it as verified from then on.
|
|
||||||
//!
|
|
||||||
//! This trusts whoever answers on the network *right now* - only run it on
|
|
||||||
//! a network you trust, ideally right after unboxing the printer.
|
|
||||||
|
|
||||||
use continuum_proxy::fleet;
|
|
||||||
use continuum_proxy::printer::{GenericPrinter, PrinterHandle};
|
|
||||||
|
|
||||||
fn main() -> anyhow::Result<()> {
|
|
||||||
let mut args = std::env::args().skip(1);
|
|
||||||
let printer_id = args.next().ok_or_else(|| anyhow::anyhow!("usage: fetch_bambu_cert <printer_id> <output_path>"))?;
|
|
||||||
let output_path = args.next().ok_or_else(|| anyhow::anyhow!("usage: fetch_bambu_cert <printer_id> <output_path>"))?;
|
|
||||||
|
|
||||||
let printers = fleet::load(std::path::Path::new("printers.toml"))?;
|
|
||||||
|
|
||||||
let pem = printers
|
|
||||||
.iter()
|
|
||||||
.find_map(|printer| match printer {
|
|
||||||
PrinterHandle::Bambu(bambu) if bambu.base().id == printer_id => Some(bambu.fetch_certificate()),
|
|
||||||
_ => None,
|
|
||||||
})
|
|
||||||
.ok_or_else(|| anyhow::anyhow!("no Bambu printer with id '{printer_id}' in printers.toml"))??;
|
|
||||||
|
|
||||||
std::fs::write(&output_path, &pem)?;
|
|
||||||
println!("saved {printer_id}'s certificate to {output_path}");
|
|
||||||
println!("now set ca_cert_path = \"{output_path}\" for {printer_id} in printers.toml");
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
@@ -56,9 +56,13 @@ async fn main() {
|
|||||||
// Vendor-only extension: reachable only after matching the concrete
|
// Vendor-only extension: reachable only after matching the concrete
|
||||||
// variant back out of the enum - one arm now that Bambu is a single
|
// variant back out of the enum - one arm now that Bambu is a single
|
||||||
// type regardless of which protocol version `connect()` settled on.
|
// type regardless of which protocol version `connect()` settled on.
|
||||||
|
// `print_status()` is real Bambu-only behavior with no equivalent on
|
||||||
|
// `GenericPrinter` (no other vendor here has an in-memory report
|
||||||
|
// cache) - none of these connected above (fake IPs), so it's always
|
||||||
|
// `None`, but the point is the downcast, not a live reading.
|
||||||
for printer in &fleet {
|
for printer in &fleet {
|
||||||
match printer {
|
match printer {
|
||||||
PrinterHandle::Bambu(bambu) => bambu.dispatch_gcode("part.gcode.3mf"),
|
PrinterHandle::Bambu(bambu) => println!(" {}: cached print_status = {:?}", bambu.sn, bambu.print_status().await),
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,11 +11,12 @@ vendor = "bambu_v1"
|
|||||||
host = "192.168.1.50"
|
host = "192.168.1.50"
|
||||||
access_code = "REPLACE_WITH_ACCESS_CODE"
|
access_code = "REPLACE_WITH_ACCESS_CODE"
|
||||||
sn = "REPLACE_WITH_SERIAL_NUMBER" # printer screen > Settings > Device (used for MQTT topics later)
|
sn = "REPLACE_WITH_SERIAL_NUMBER" # printer screen > Settings > Device (used for MQTT topics later)
|
||||||
# P1P doesn't chain to the bundled CA - point this at a cert you've
|
# No ca_cert_path needed even though P1P doesn't chain to the bundled CA -
|
||||||
# downloaded from the printer itself. Leave unset to use the bundled CA.
|
# `connect()` auto-pins a certificate via trust-on-first-connect the first
|
||||||
# `cargo run --example fetch_bambu_cert -- p1p ./certs/p1p.pem` can fetch it
|
# time it can't verify against the bundled one, and saves it under
|
||||||
# for you (trust-on-first-connect - see that example's doc comment).
|
# certs/pinned/<sn>.pem for every run after that (see BambuPrinter::
|
||||||
ca_cert_path = "./certs/p1p.pem"
|
# 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]]
|
[[printer]]
|
||||||
id = "h2c"
|
id = "h2c"
|
||||||
|
|||||||
+5
-28
@@ -1,6 +1,4 @@
|
|||||||
use std::collections::HashMap;
|
|
||||||
use std::env;
|
use std::env;
|
||||||
use std::path::PathBuf;
|
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct Config {
|
pub struct Config {
|
||||||
@@ -11,21 +9,12 @@ pub struct Config {
|
|||||||
pub go2rtc_bin: String,
|
pub go2rtc_bin: String,
|
||||||
pub go2rtc_config: String,
|
pub go2rtc_config: String,
|
||||||
/// Path to the fleet config file - see `fleet.rs` and
|
/// Path to the fleet config file - see `fleet.rs` and
|
||||||
/// `printers.example.toml` for the shape.
|
/// `printers.example.toml` for the shape. Bambu certificate trust
|
||||||
|
/// (per-printer `ca_cert_path`/`require_valid_cert`, and automatic
|
||||||
|
/// trust-on-first-connect pinning otherwise - see
|
||||||
|
/// `BambuPrinter::ensure_trusted`) lives entirely in that file now,
|
||||||
|
/// not as a separate fleet-wide env-configured policy here.
|
||||||
pub printers_path: String,
|
pub printers_path: String,
|
||||||
|
|
||||||
/// Per-printer CA certificate overrides, keyed by printer ID. Most
|
|
||||||
/// Bambu printers chain to the bundled shared CA (`certs/bambu_ca2.pem`)
|
|
||||||
/// and don't need an entry here - but some models (P1P, at least) ship
|
|
||||||
/// their own certificate instead of one signed by that shared CA, with
|
|
||||||
/// no single file covering all of them. Look up a printer's own entry
|
|
||||||
/// (if any) when constructing it; there's no fleet-wide "the" override.
|
|
||||||
pub bambu_ca_cert_overrides: HashMap<String, PathBuf>,
|
|
||||||
/// When `false`, Bambu MQTTS connections skip certificate verification
|
|
||||||
/// entirely instead of checking against a CA. `true` (the default) is
|
|
||||||
/// the secure choice; only flip this fleet-wide policy if you have
|
|
||||||
/// printers you can't get to verify any other way.
|
|
||||||
pub bambu_require_valid_cert: bool,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Config {
|
impl Config {
|
||||||
@@ -38,22 +27,10 @@ impl Config {
|
|||||||
go2rtc_bin: env_or("CONTINUUM_GO2RTC_BIN", "go2rtc"),
|
go2rtc_bin: env_or("CONTINUUM_GO2RTC_BIN", "go2rtc"),
|
||||||
go2rtc_config: env_or("CONTINUUM_GO2RTC_CONFIG", "./go2rtc.yaml"),
|
go2rtc_config: env_or("CONTINUUM_GO2RTC_CONFIG", "./go2rtc.yaml"),
|
||||||
printers_path: env_or("CONTINUUM_PRINTERS_PATH", "printers.toml"),
|
printers_path: env_or("CONTINUUM_PRINTERS_PATH", "printers.toml"),
|
||||||
|
|
||||||
bambu_ca_cert_overrides: parse_cert_overrides(&env_or("CONTINUUM_BAMBU_CA_CERT_OVERRIDES", "")),
|
|
||||||
bambu_require_valid_cert: env_or("CONTINUUM_BAMBU_REQUIRE_VALID_CERT", "true") == "true",
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Parses `"printer_id=path,printer_id2=path2"` into a lookup map. Empty
|
|
||||||
/// input (the common case - no overrides needed) yields an empty map.
|
|
||||||
fn parse_cert_overrides(raw: &str) -> HashMap<String, PathBuf> {
|
|
||||||
raw.split(',')
|
|
||||||
.filter_map(|entry| entry.split_once('='))
|
|
||||||
.map(|(id, path)| (id.trim().to_string(), PathBuf::from(path.trim())))
|
|
||||||
.collect()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn require(key: &str) -> anyhow::Result<String> {
|
fn require(key: &str) -> anyhow::Result<String> {
|
||||||
env::var(key).map_err(|_| anyhow::anyhow!("missing required env var {key}"))
|
env::var(key).map_err(|_| anyhow::anyhow!("missing required env var {key}"))
|
||||||
}
|
}
|
||||||
|
|||||||
+18
-14
@@ -51,11 +51,13 @@ pub enum BambuTls {
|
|||||||
|
|
||||||
impl BambuTls {
|
impl BambuTls {
|
||||||
/// Resolves the trust mode for *one* printer. Call this per printer,
|
/// Resolves the trust mode for *one* printer. Call this per printer,
|
||||||
/// passing `config.bambu_ca_cert_overrides.get(printer_id)` - there's
|
/// passing that printer's own `ca_cert_path`/`require_valid_cert` from
|
||||||
/// no single override for the whole fleet, because not every Bambu
|
/// `printers.toml` (see `fleet.rs::build_bambu`) - there's no single
|
||||||
/// model chains to the same CA. `require_valid_cert = false` always
|
/// override for the whole fleet, because not every Bambu model chains
|
||||||
/// means `Insecure`, regardless of whether that printer also has an
|
/// to the same CA, and most printers don't need this at all (see
|
||||||
/// override entry.
|
/// `BambuPrinter::ensure_trusted` for the automatic path).
|
||||||
|
/// `require_valid_cert = false` always means `Insecure`, regardless of
|
||||||
|
/// whether that printer also has a `cert_path`.
|
||||||
pub fn resolve(cert_path: Option<&Path>, require_valid_cert: bool) -> anyhow::Result<Self> {
|
pub fn resolve(cert_path: Option<&Path>, require_valid_cert: bool) -> anyhow::Result<Self> {
|
||||||
if !require_valid_cert {
|
if !require_valid_cert {
|
||||||
return Ok(BambuTls::Insecure);
|
return Ok(BambuTls::Insecure);
|
||||||
@@ -287,8 +289,8 @@ impl BambuPrinter {
|
|||||||
if presented != pem_to_der(pinned_pem)? {
|
if presented != pem_to_der(pinned_pem)? {
|
||||||
anyhow::bail!(
|
anyhow::bail!(
|
||||||
"{} presented a certificate different from the one pinned - could be a legitimate \
|
"{} presented a certificate different from the one pinned - could be a legitimate \
|
||||||
certificate rotation, could be something worse; re-run fetch_bambu_cert deliberately \
|
certificate rotation, could be something worse; delete its file under certs/pinned/ \
|
||||||
if you're sure it's the former",
|
and reconnect to re-pin deliberately if you're sure it's the former",
|
||||||
self.base.name
|
self.base.name
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -306,11 +308,14 @@ impl BambuPrinter {
|
|||||||
/// presents, and returns it as PEM bytes.
|
/// presents, and returns it as PEM bytes.
|
||||||
///
|
///
|
||||||
/// This is meaningfully weaker than verifying against a CA you already
|
/// This is meaningfully weaker than verifying against a CA you already
|
||||||
/// trust - anyone on the network at the exact moment you run this gets
|
/// trust - anyone on the network at the exact moment this runs gets
|
||||||
/// trusted forever after. That's why it's a method you call once, by
|
/// trusted forever after. `ensure_trusted` below is the only caller -
|
||||||
/// hand (see `examples/fetch_bambu_cert.rs`), rather than something
|
/// it only reaches this path when the bundled CA didn't verify, saves
|
||||||
/// `connect()` falls back to silently. Save the result to a file and
|
/// the result under `certs/pinned/`, and every connection after this
|
||||||
/// point that printer's `ca_cert_path` in `printers.toml` at it. */
|
/// one re-verifies against that pinned file instead of trusting the
|
||||||
|
/// network again. Not exposed as a manual step for the caller to run
|
||||||
|
/// ahead of time - there's no longer a reason to, now that `connect()`
|
||||||
|
/// does this automatically the first time a printer needs it. */
|
||||||
pub fn fetch_certificate(&self) -> anyhow::Result<Vec<u8>> {
|
pub fn fetch_certificate(&self) -> anyhow::Result<Vec<u8>> {
|
||||||
let addr = resolve_addr(&self.base.host)?;
|
let addr = resolve_addr(&self.base.host)?;
|
||||||
let stream = TcpStream::connect_timeout(&addr, Duration::from_secs(5))?;
|
let stream = TcpStream::connect_timeout(&addr, Duration::from_secs(5))?;
|
||||||
@@ -327,8 +332,7 @@ impl BambuPrinter {
|
|||||||
|
|
||||||
/* Makes sure this printer has a *working* trust configuration,
|
/* Makes sure this printer has a *working* trust configuration,
|
||||||
/// auto-pinning a certificate via trust-on-first-connect if needed -
|
/// auto-pinning a certificate via trust-on-first-connect if needed -
|
||||||
/// the "smooth, no manual steps" version of the fetch_bambu_cert
|
/// no manual step required, built the way SSH handles host keys:
|
||||||
/// workflow, built the way SSH handles host keys:
|
|
||||||
///
|
///
|
||||||
/// - A pinned certificate already on disk (`cert_dir/<sn>.pem`) is used
|
/// - A pinned certificate already on disk (`cert_dir/<sn>.pem`) is used
|
||||||
/// directly. No new trust decision gets made on every run - that
|
/// directly. No new trust decision gets made on every run - that
|
||||||
|
|||||||
+4
-4
@@ -21,10 +21,10 @@
|
|||||||
//!
|
//!
|
||||||
//! Run `cargo run --example printer_polymorphism` to see it in action.
|
//! Run `cargo run --example printer_polymorphism` to see it in action.
|
||||||
//!
|
//!
|
||||||
//! Deliberately left out of this module for now, to keep it focused on one
|
//! Real MQTT is built now - `bambu.rs`/`bambu_mqtt.rs` for Bambu's
|
||||||
//! idea at a time: real MQTT/HTTP/FTPS calls (that's `src/adapters/`) and
|
//! protocol, `marlin_serial.rs` for a real Marlin serial connection - see
|
||||||
//! `Send`-bound futures (matters once this is wired into a multi-threaded
|
//! each module's own doc comment. PrusaLink/Klipper's HTTP clients are
|
||||||
//! `tokio::spawn`, not yet).
|
//! still stubs; that's the next piece to build past this module.
|
||||||
|
|
||||||
mod bambu;
|
mod bambu;
|
||||||
mod bambu_commands;
|
mod bambu_commands;
|
||||||
|
|||||||
Reference in New Issue
Block a user