···15 --wrap-pg "postgresql://user:pass@pg-host:5432/plc-db"
16 ```
17000000000018- Wrap a plc server, maximalist edition:
1920 ```bash
···89- [ ] experimental: websocket version of /export
90- [x] experimental: accept writes by forwarding them upstream
91- [ ] experimental: serve a tlog
92-- [ ] experimental: embed a log database directly for fast and efficient mirroring
93- [ ] experimental: support multiple upstreams?
9495- [ ] new command todo: `zip` or `check` or `diff`: compare two plc logs over some time range
···15 --wrap-pg "postgresql://user:pass@pg-host:5432/plc-db"
16 ```
1718+- Run a fully self-contained mirror using an embedded fjall database (no postgres needed):
19+20+ ```bash
21+ # backfill first
22+ allegedly backfill --to-fjall ./plc-data
23+24+ # then run the mirror
25+ allegedly mirror --wrap-fjall ./plc-data
26+ ```
27+28- Wrap a plc server, maximalist edition:
2930 ```bash
···99- [ ] experimental: websocket version of /export
100- [x] experimental: accept writes by forwarding them upstream
101- [ ] experimental: serve a tlog
102+- [x] experimental: embed a log database directly for fast and efficient mirroring
103- [ ] experimental: support multiple upstreams?
104105- [ ] new command todo: `zip` or `check` or `diff`: compare two plc logs over some time range
+30-3
src/bin/backfill.rs
···1use allegedly::{
2- Db, Dt, ExportPage, FolderSource, HttpSource, backfill, backfill_to_pg,
03 bin::{GlobalArgs, bin_init},
4- full_pages, logo, pages_to_pg, pages_to_stdout, poll_upstream,
5};
6use clap::Parser;
7use reqwest::Url;
···45 /// only used if `--to-postgres` is present
46 #[arg(long, action)]
47 postgres_reset: bool,
000000000048 /// Stop at the week ending before this date
49 #[arg(long)]
50 until: Option<Dt>,
···66 to_postgres,
67 postgres_cert,
68 postgres_reset,
0069 until,
70 catch_up,
71 }: Args,
···143 }
144145 // set up sinks
146- if let Some(pg_url) = to_postgres {
00000000000000147 log::trace!("connecting to postgres...");
148 let db = Db::new(pg_url.as_str(), postgres_cert).await?;
149 log::trace!("connected to postgres");
···1use allegedly::{
2+ Db, Dt, ExportPage, FjallDb, FolderSource, HttpSource, backfill, backfill_to_fjall,
3+ backfill_to_pg,
4 bin::{GlobalArgs, bin_init},
5+ full_pages, logo, pages_to_fjall, pages_to_pg, pages_to_stdout, poll_upstream,
6};
7use clap::Parser;
8use reqwest::Url;
···46 /// only used if `--to-postgres` is present
47 #[arg(long, action)]
48 postgres_reset: bool,
49+ /// Bulk load into a local fjall embedded database
50+ ///
51+ /// Pass a directory path for the fjall database
52+ #[arg(long, conflicts_with_all = ["to_postgres", "postgres_cert", "postgres_reset"])]
53+ to_fjall: Option<PathBuf>,
54+ /// Delete all operations from the fjall db before starting
55+ ///
56+ /// only used if `--to-fjall` is present
57+ #[arg(long, action, requires = "to_fjall")]
58+ fjall_reset: bool,
59 /// Stop at the week ending before this date
60 #[arg(long)]
61 until: Option<Dt>,
···77 to_postgres,
78 postgres_cert,
79 postgres_reset,
80+ to_fjall,
81+ fjall_reset,
82 until,
83 catch_up,
84 }: Args,
···156 }
157158 // set up sinks
159+ if let Some(fjall_path) = to_fjall {
160+ log::trace!("opening fjall db at {fjall_path:?}...");
161+ let db = FjallDb::open(&fjall_path)?;
162+ log::trace!("opened fjall db");
163+164+ tasks.spawn(backfill_to_fjall(
165+ db.clone(),
166+ fjall_reset,
167+ bulk_out,
168+ found_last_tx,
169+ ));
170+ if catch_up {
171+ tasks.spawn(pages_to_fjall(db, full_out));
172+ }
173+ } else if let Some(pg_url) = to_postgres {
174 log::trace!("connecting to postgres...");
175 let db = Db::new(pg_url.as_str(), postgres_cert).await?;
176 log::trace!("connected to postgres");
+46-25
src/bin/mirror.rs
···1use allegedly::{
2- Db, ExperimentalConf, ListenConf,
3 bin::{GlobalArgs, InstrumentationArgs, bin_init},
4- logo, pages_to_pg, poll_upstream, serve,
5};
6use clap::Parser;
7use reqwest::Url;
···1011#[derive(Debug, clap::Args)]
12pub struct Args {
13- /// the wrapped did-method-plc server
14 #[arg(long, env = "ALLEGEDLY_WRAP")]
15- wrap: Url,
16 /// the wrapped did-method-plc server's database (write access required)
17- #[arg(long, env = "ALLEGEDLY_WRAP_PG")]
18 wrap_pg: Option<Url>,
19 /// path to tls cert for the wrapped postgres db, if needed
20 #[arg(long, env = "ALLEGEDLY_WRAP_PG_CERT")]
21 wrap_pg_cert: Option<PathBuf>,
00022 /// wrapping server listen address
23 #[arg(short, long, env = "ALLEGEDLY_BIND")]
24 #[clap(default_value = "127.0.0.1:8000")]
···70 wrap,
71 wrap_pg,
72 wrap_pg_cert,
073 bind,
74 acme_domain,
75 acme_cache_path,
···106107 let mut tasks = JoinSet::new();
108109- let db = if sync {
110- let wrap_pg = wrap_pg.ok_or(anyhow::anyhow!(
111- "a wrapped reference postgres must be provided to sync"
112- ))?;
113- let db = Db::new(wrap_pg.as_str(), wrap_pg_cert).await?;
114115- // TODO: allow starting up with polling backfill from beginning?
116- log::debug!("getting the latest op from the db...");
117 let latest = db
118- .get_latest()
119- .await?
120 .expect("there to be at least one op in the db. did you backfill?");
121122 let (send_page, recv_page) = mpsc::channel(8);
···126 let throttle = Duration::from_millis(upstream_throttle_ms);
127128 tasks.spawn(poll_upstream(Some(latest), poll_url, throttle, send_page));
129- tasks.spawn(pages_to_pg(db.clone(), recv_page));
130- Some(db)
0131 } else {
132- None
133- };
00000000000000000000000000134135- tasks.spawn(serve(
136- upstream,
137- wrap,
138- listen_conf,
139- experimental_conf,
140- db.clone(),
141- ));
142143 while let Some(next) = tasks.join_next().await {
144 match next {
···1use allegedly::{
2+ Db, ExperimentalConf, FjallDb, ListenConf,
3 bin::{GlobalArgs, InstrumentationArgs, bin_init},
4+ logo, pages_to_fjall, pages_to_pg, poll_upstream, serve, serve_fjall,
5};
6use clap::Parser;
7use reqwest::Url;
···1011#[derive(Debug, clap::Args)]
12pub struct Args {
13+ /// the wrapped did-method-plc server (not needed when using --wrap-fjall)
14 #[arg(long, env = "ALLEGEDLY_WRAP")]
15+ wrap: Option<Url>,
16 /// the wrapped did-method-plc server's database (write access required)
17+ #[arg(long, env = "ALLEGEDLY_WRAP_PG", conflicts_with = "wrap_fjall")]
18 wrap_pg: Option<Url>,
19 /// path to tls cert for the wrapped postgres db, if needed
20 #[arg(long, env = "ALLEGEDLY_WRAP_PG_CERT")]
21 wrap_pg_cert: Option<PathBuf>,
22+ /// path to a local fjall database directory (alternative to postgres)
23+ #[arg(long, env = "ALLEGEDLY_WRAP_FJALL", conflicts_with_all = ["wrap_pg", "wrap_pg_cert"])]
24+ wrap_fjall: Option<PathBuf>,
25 /// wrapping server listen address
26 #[arg(short, long, env = "ALLEGEDLY_BIND")]
27 #[clap(default_value = "127.0.0.1:8000")]
···73 wrap,
74 wrap_pg,
75 wrap_pg_cert,
76+ wrap_fjall,
77 bind,
78 acme_domain,
79 acme_cache_path,
···110111 let mut tasks = JoinSet::new();
112113+ if let Some(fjall_path) = wrap_fjall {
114+ let db = FjallDb::open(&fjall_path)?;
000115116+ log::debug!("getting the latest op from fjall...");
0117 let latest = db
118+ .get_latest()?
0119 .expect("there to be at least one op in the db. did you backfill?");
120121 let (send_page, recv_page) = mpsc::channel(8);
···125 let throttle = Duration::from_millis(upstream_throttle_ms);
126127 tasks.spawn(poll_upstream(Some(latest), poll_url, throttle, send_page));
128+ tasks.spawn(pages_to_fjall(db.clone(), recv_page));
129+130+ tasks.spawn(serve_fjall(upstream, listen_conf, experimental_conf, db));
131 } else {
132+ let wrap = wrap.ok_or(anyhow::anyhow!(
133+ "--wrap is required unless using --wrap-fjall"
134+ ))?;
135+136+ let db: Option<Db> = if sync {
137+ let wrap_pg = wrap_pg.ok_or(anyhow::anyhow!(
138+ "a wrapped reference postgres (--wrap-pg) or fjall db (--wrap-fjall) must be provided to sync"
139+ ))?;
140+ let db = Db::new(wrap_pg.as_str(), wrap_pg_cert).await?;
141+142+ log::debug!("getting the latest op from the db...");
143+ let latest = db
144+ .get_latest()
145+ .await?
146+ .expect("there to be at least one op in the db. did you backfill?");
147+148+ let (send_page, recv_page) = mpsc::channel(8);
149+150+ let mut poll_url = upstream.clone();
151+ poll_url.set_path("/export");
152+ let throttle = Duration::from_millis(upstream_throttle_ms);
153+154+ tasks.spawn(poll_upstream(Some(latest), poll_url, throttle, send_page));
155+ tasks.spawn(pages_to_pg(db.clone(), recv_page));
156+ Some(db)
157+ } else {
158+ None
159+ };
160161+ tasks.spawn(serve(upstream, wrap, listen_conf, experimental_conf, db));
162+ }
00000163164 while let Some(next) = tasks.join_next().await {
165 match next {