···3636 --experimental-write-upstream
3737 ```
38383939+- Reverse-proxy to any PLC server, terminating TLS and forwarding writes upstream
4040+4141+ ```bash
4242+ sudo allegedly wrap \
4343+ --wrap "http://127.0.0.1:3000" \
4444+ --acme-ipv6 \
4545+ --acme-cache-path ./acme-cache \
4646+ --acme-domain "plc.wtf" \
4747+ --experimental-acme-domain "experimental.plc.wtf" \
4848+ --experimental-write-upstream \
4949+ --upstream "https://plc.wtf" \
5050+ ```
5151+39524053add `--help` to any command for more info about it
4154···6679- monitoring of the various tasks
6780- health check pings
6881- expose metrics/tracing
6969-- read-only flag for mirror wrapper
8282+- [x] read-only flag for mirror wrapper
7083- bundle: write directly to s3-compatible object storage
7184- helpers for automating periodic `bundle` runs
7285
+7-1
src/bin/allegedly.rs
···4949 #[command(flatten)]
5050 args: mirror::Args,
5151 },
5252+ /// Wrap any did-method-plc server, without syncing upstream (read-only)
5353+ Wrap {
5454+ #[command(flatten)]
5555+ args: mirror::Args,
5656+ },
5257 /// Poll an upstream PLC server and log new ops to stdout
5358 Tail {
5459 /// Begin tailing from a specific timestamp for replay or wait-until
···9196 .await
9297 .expect("to write bundles to output files");
9398 }
9494- Commands::Mirror { args } => mirror::run(globals, args).await?,
9999+ Commands::Mirror { args } => mirror::run(globals, args, true).await?,
100100+ Commands::Wrap { args } => mirror::run(globals, args, false).await?,
95101 Commands::Tail { after } => {
96102 let mut url = globals.upstream;
97103 url.set_path("/export");
+30-17
src/bin/mirror.rs
···1313 wrap: Url,
1414 /// the wrapped did-method-plc server's database (write access required)
1515 #[arg(long, env = "ALLEGEDLY_WRAP_PG")]
1616- wrap_pg: Url,
1616+ wrap_pg: Option<Url>,
1717 /// path to tls cert for the wrapped postgres db, if needed
1818 #[arg(long, env = "ALLEGEDLY_WRAP_PG_CERT")]
1919 wrap_pg_cert: Option<PathBuf>,
···7676 experimental_acme_domain,
7777 experimental_write_upstream,
7878 }: Args,
7979+ sync: bool,
7980) -> anyhow::Result<()> {
8080- let db = Db::new(wrap_pg.as_str(), wrap_pg_cert).await?;
8181-8282- // TODO: allow starting up with polling backfill from beginning?
8383- log::debug!("getting the latest op from the db...");
8484- let latest = db
8585- .get_latest()
8686- .await?
8787- .expect("there to be at least one op in the db. did you backfill?");
8888-8981 let listen_conf = match (bind, acme_domain.is_empty(), acme_cache_path) {
9082 (_, false, Some(cache_path)) => {
9183 create_dir_all(&cache_path).await?;
···112104113105 let mut tasks = JoinSet::new();
114106115115- let (send_page, recv_page) = mpsc::channel(8);
107107+ let db = if sync {
108108+ let wrap_pg = wrap_pg.ok_or(anyhow::anyhow!(
109109+ "a wrapped reference postgres must be provided to sync"
110110+ ))?;
111111+ let db = Db::new(wrap_pg.as_str(), wrap_pg_cert).await?;
112112+113113+ // TODO: allow starting up with polling backfill from beginning?
114114+ log::debug!("getting the latest op from the db...");
115115+ let latest = db
116116+ .get_latest()
117117+ .await?
118118+ .expect("there to be at least one op in the db. did you backfill?");
116119117117- let mut poll_url = upstream.clone();
118118- poll_url.set_path("/export");
119119- let throttle = Duration::from_millis(upstream_throttle_ms);
120120+ let (send_page, recv_page) = mpsc::channel(8);
120121121121- tasks.spawn(poll_upstream(Some(latest), poll_url, throttle, send_page));
122122- tasks.spawn(pages_to_pg(db.clone(), recv_page));
122122+ let mut poll_url = upstream.clone();
123123+ poll_url.set_path("/export");
124124+ let throttle = Duration::from_millis(upstream_throttle_ms);
125125+126126+ tasks.spawn(poll_upstream(Some(latest), poll_url, throttle, send_page));
127127+ tasks.spawn(pages_to_pg(db.clone(), recv_page));
128128+ Some(db)
129129+ } else {
130130+ None
131131+ };
132132+123133 tasks.spawn(serve(
124134 upstream,
125135 wrap,
···157167 globals: GlobalArgs,
158168 #[command(flatten)]
159169 args: Args,
170170+ /// Run the mirror in wrap mode, no upstream synchronization (read-only)
171171+ #[arg(long, action)]
172172+ wrap_mode: bool,
160173}
161174162175#[allow(dead_code)]
···164177async fn main() -> anyhow::Result<()> {
165178 let args = CliArgs::parse();
166179 bin_init("mirror");
167167- run(args.globals, args.args).await?;
180180+ run(args.globals, args.args, !args.wrap_mode).await?;
168181 Ok(())
169182}
+80-34
src/mirror.rs
···1919 client: Client,
2020 plc: Url,
2121 upstream: Url,
2222+ sync_info: Option<SyncInfo>,
2323+ experimental: ExperimentalConf,
2424+}
2525+2626+/// server info that only applies in mirror (synchronizing) mode
2727+#[derive(Clone)]
2828+struct SyncInfo {
2229 latest_at: CachedValue<Dt, GetLatestAt>,
2330 upstream_status: CachedValue<PlcStatus, CheckUpstream>,
2424- experimental: ExperimentalConf,
2531}
26322733#[handler]
2834fn hello(
2935 Data(State {
3636+ sync_info,
3037 upstream,
3138 experimental: exp,
3239 ..
3340 }): Data<&State>,
3441 req: &Request,
3542) -> String {
4343+ // let mode = if sync_info.is_some() { "mirror" } else { "wrap" };
4444+ let pre_info = if sync_info.is_some() {
4545+ format!(
4646+ r#"
4747+This is a PLC[1] mirror running Allegedly in mirror mode. Mirror mode wraps and
4848+synchronizes a local PLC reference server instance[2] (why?[3]).
4949+5050+5151+Configured upstream:
5252+5353+ {upstream}
5454+5555+"#
5656+ )
5757+ } else {
5858+ format!(
5959+ r#"
6060+This is a PLC[1] mirror running Allegedly in wrap mode. Wrap mode reverse-
6161+proxies requests to a PLC server and can terminate TLS, like NGINX or Caddy.
6262+6363+6464+Configured upstream (only used if experimental op forwarding is enabled):
6565+6666+ {upstream}
6767+6868+"#
6969+ )
7070+ };
7171+3672 let post_info = match (exp.write_upstream, &exp.acme_domain, req.uri().host()) {
3773 (false, _, _) => " - POST /* Always rejected. This is a mirror.".to_string(),
3874 (_, None, _) => {
···49855086 format!(
5187 r#"{}
5252-5353-This is a PLC[1] mirror running Allegedly in mirror mode. Mirror mode wraps and
5454-synchronizes a local PLC reference server instance[2] (why?[3]).
5555-5656-5757-Configured upstream:
5858-5959- {upstream}
6060-8888+{pre_info}
61896290Available APIs:
6391···176204 Data(State {
177205 plc,
178206 client,
179179- latest_at,
180180- upstream_status,
207207+ sync_info,
181208 ..
182209 }): Data<&State>,
183210) -> impl IntoResponse {
···186213 if !ok {
187214 overall_status = StatusCode::BAD_GATEWAY;
188215 }
189189- let (ok, upstream_status) = upstream_status.get().await.expect("plc_status infallible");
190190- if !ok {
191191- overall_status = StatusCode::BAD_GATEWAY;
216216+ if let Some(SyncInfo {
217217+ latest_at,
218218+ upstream_status,
219219+ }) = sync_info
220220+ {
221221+ // mirror mode
222222+ let (ok, upstream_status) = upstream_status.get().await.expect("plc_status infallible");
223223+ if !ok {
224224+ overall_status = StatusCode::BAD_GATEWAY;
225225+ }
226226+ let latest = latest_at.get().await.ok();
227227+ (
228228+ overall_status,
229229+ Json(serde_json::json!({
230230+ "server": "allegedly (mirror)",
231231+ "version": env!("CARGO_PKG_VERSION"),
232232+ "wrapped_plc": wrapped_status,
233233+ "upstream_plc": upstream_status,
234234+ "latest_at": latest,
235235+ })),
236236+ )
237237+ } else {
238238+ // wrap mode
239239+ (
240240+ overall_status,
241241+ Json(serde_json::json!({
242242+ "server": "allegedly (mirror)",
243243+ "version": env!("CARGO_PKG_VERSION"),
244244+ "wrapped_plc": wrapped_status,
245245+ })),
246246+ )
192247 }
193193- let latest = latest_at.get().await.ok();
194194- (
195195- overall_status,
196196- Json(serde_json::json!({
197197- "server": "allegedly (mirror)",
198198- "version": env!("CARGO_PKG_VERSION"),
199199- "wrapped_plc": wrapped_status,
200200- "upstream_plc": upstream_status,
201201- "latest_at": latest,
202202- })),
203203- )
204248}
205249206250fn proxy_response(res: reqwest::Response) -> Response {
···344388 plc: Url,
345389 listen: ListenConf,
346390 experimental: ExperimentalConf,
347347- db: Db,
391391+ db: Option<Db>,
348392) -> anyhow::Result<&'static str> {
349393 log::info!("starting server...");
350394···355399 .build()
356400 .expect("reqwest client to build");
357401358358- let latest_at = CachedValue::new(GetLatestAt(db), Duration::from_secs(2));
359359- let upstream_status = CachedValue::new(
360360- CheckUpstream(upstream.clone(), client.clone()),
361361- Duration::from_secs(6),
362362- );
402402+ // when `db` is None, we're running in wrap mode. no db access, no upstream sync
403403+ let sync_info = db.map(|db| SyncInfo {
404404+ latest_at: CachedValue::new(GetLatestAt(db), Duration::from_secs(2)),
405405+ upstream_status: CachedValue::new(
406406+ CheckUpstream(upstream.clone(), client.clone()),
407407+ Duration::from_secs(6),
408408+ ),
409409+ });
363410364411 let state = State {
365412 client,
366413 plc,
367414 upstream: upstream.clone(),
368368- latest_at,
369369- upstream_status,
415415+ sync_info,
370416 experimental: experimental.clone(),
371417 };
372418