Take the link from wg-app-link instead of keeping a second copy
The five modules underneath this backend that were never about AI sessions -- the pinned CA and leaf, QR enrollment and the bearer token, wg0 binding and the certificate's SANs, owner-only files, and the RON house rules -- were written twice, once here and once in dev-updater, and had drifted. They now come from the submodule, as a path dependency so both projects stay locked to one commit. What stayed is what makes this project itself: the routes, the drivers, the config schema, and the auth middleware, which is generic over this server's state. Sharing a transport is worth doing; sharing an API would mean inventing a vocabulary neither project wants. Four dependencies go with the code -- rcgen, qrcode, subtle and if-addrs are no longer named here at all -- and the three that remain are now described by what still uses them rather than by what used to. Verified by running it, not only by building: a fresh server generates its CA, prints an `aiapp://enroll` QR with the scheme now passed as a parameter, covers 127.0.0.1, 10.0.2.2 and wg0's 10.66.0.1 in the leaf, answers an enrolled token and returns 401 without one, and writes config.ron in the house rules with every file owner-only. 36 tests pass, clippy is silent, rustfmt is clean.
This commit is contained in:
1 parent
a83dbcff6a
commit
aa05ff9336
13 files changed
+76
-526
No files matched your search
+4
-60
@@ -7,9 +7,10 @@
|
||||
//! funnels through `SessionManager` (the registry pattern), so in-memory
|
||||
//! and on-disk state can't come apart.
|
||||
//!
|
||||
//! The file is RON, in the shape the [`mod@format`] module describes -- the
|
||||
//! The file is RON, in the shape [`wg_app_link::format`] describes -- the
|
||||
//! same format, and the same two house rules, as the sibling dev-updater
|
||||
//! project's config, because both are written and read by hand.
|
||||
//! project's config, because both are written and read by hand, and both
|
||||
//! now read and write them through the one module.
|
||||
//!
|
||||
//! Transcripts do NOT live here -- each session's events are an append-only
|
||||
//! JSONL file in its own directory (see `session::transcript`); this file
|
||||
@@ -21,64 +22,7 @@ use std::path::{Path, PathBuf};
|
||||
use anyhow::{Context, Result};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::private;
|
||||
|
||||
/// Reading and writing the RON this file is in.
|
||||
///
|
||||
/// Two things are house rules rather than plain RON, and they are here
|
||||
/// together because they are inverses of each other -- change one and the
|
||||
/// other stops round-tripping.
|
||||
///
|
||||
/// **No outer parentheses.** A file *is* the body of the struct, so nothing
|
||||
/// in it is indented for the sake of a wrapper. RON has no implicit
|
||||
/// top-level struct (`de/mod.rs` requires the `(`), so [`format::parse`]
|
||||
/// adds it and [`format::render`] takes it back off. The opening paren is
|
||||
/// not followed by a newline, so a parse error's line number still points
|
||||
/// at the real line.
|
||||
///
|
||||
/// **`Some` is implicit.** Enabled on the deserializer rather than by a
|
||||
/// `#![enable(implicit_some)]` header the file would have to carry, and
|
||||
/// matched on the writing side by `skip_serializing_if` on every optional
|
||||
/// field so nothing writes back a `Some(...)` a person didn't type.
|
||||
pub(crate) mod format {
|
||||
use serde::{Serialize, de::DeserializeOwned};
|
||||
|
||||
fn options() -> ron::Options {
|
||||
ron::Options::default().with_default_extension(ron::extensions::Extensions::IMPLICIT_SOME)
|
||||
}
|
||||
|
||||
pub fn parse<T: DeserializeOwned>(text: &str) -> Result<T, ron::error::SpannedError> {
|
||||
options().from_str(&format!("({text})"))
|
||||
}
|
||||
|
||||
pub fn render<T: Serialize>(value: &T) -> Result<String, ron::Error> {
|
||||
let pretty = ron::ser::PrettyConfig::new();
|
||||
let text = options().to_string_pretty(value, pretty)?;
|
||||
Ok(unwrap_outer(&text))
|
||||
}
|
||||
|
||||
/// Strips the outer `(`/`)` the writer always emits and removes the
|
||||
/// indent level they cost. Deliberately narrow: it accepts only the
|
||||
/// exact shape `PrettyConfig` produces, and leaves anything else alone
|
||||
/// rather than guessing -- a file with stray parentheses is better than
|
||||
/// one silently mangled. `parse` round-trips either way, since a
|
||||
/// wrapped body parses the same as an unwrapped one re-wrapped.
|
||||
fn unwrap_outer(text: &str) -> String {
|
||||
let Some(body) = text
|
||||
.strip_prefix("(\n")
|
||||
.and_then(|rest| rest.strip_suffix("\n)"))
|
||||
else {
|
||||
return text.to_string();
|
||||
};
|
||||
let mut out: String = body
|
||||
.lines()
|
||||
.map(|line| line.strip_prefix(" ").unwrap_or(line))
|
||||
.collect::<Vec<_>>()
|
||||
.join("\n");
|
||||
out.push('\n');
|
||||
out
|
||||
}
|
||||
}
|
||||
use wg_app_link::{format, private};
|
||||
|
||||
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase", default)]
|
||||
|
||||
Reference in new issue
Block a user