Author: Kevin Schoon [me@kevinschoon.com]
Hash: d6a94c7be254f4ce9fe35b3c1c80a2e557b5ec9d
Timestamp: Sun, 17 Mar 2024 08:56:50 +0000 (3 months ago)

+1957 -2337 +/-49 browse
Replace capnp with tarpc
Replace capnp with tarpc

This replaces the capnp RPC implementation with tarpc. Tarpc requires far less
boilerplate and simplifies the codebase considerably.
1diff --git a/Cargo.lock b/Cargo.lock
2index eb34e63..814f8c7 100644
3--- a/Cargo.lock
4+++ b/Cargo.lock
5 @@ -505,8 +505,6 @@ dependencies = [
6 "ayllu_database",
7 "ayllu_git",
8 "ayllu_rpc",
9- "capnp",
10- "capnp-rpc",
11 "cc",
12 "clap 4.5.3",
13 "comrak",
14 @@ -528,6 +526,7 @@ dependencies = [
15 "serde_json",
16 "sqlx",
17 "tabwriter",
18+ "tarpc",
19 "tera",
20 "time",
21 "time-macros",
22 @@ -556,8 +555,6 @@ dependencies = [
23 "ayllu_database",
24 "ayllu_git",
25 "ayllu_rpc",
26- "capnp",
27- "capnp-rpc",
28 "clap 4.5.3",
29 "clap_complete",
30 "nickel-lang-core",
31 @@ -580,8 +577,6 @@ dependencies = [
32 "ayllu_api",
33 "ayllu_config",
34 "ayllu_rpc",
35- "capnp",
36- "capnp-rpc",
37 "clap 4.5.3",
38 "clap_complete",
39 "futures",
40 @@ -603,8 +598,6 @@ dependencies = [
41 "ayllu_config",
42 "ayllu_database",
43 "ayllu_rpc",
44- "capnp",
45- "capnp-rpc",
46 "clap 4.5.3",
47 "futures",
48 "rand",
49 @@ -622,11 +615,9 @@ dependencies = [
50 name = "ayllu_api"
51 version = "0.2.1"
52 dependencies = [
53- "capnp",
54- "capnp-futures",
55- "capnp-rpc",
56- "capnpc",
57 "serde",
58+ "tarpc",
59+ "thiserror",
60 ]
61
62 [[package]]
63 @@ -665,10 +656,8 @@ name = "ayllu_rpc"
64 version = "0.2.1"
65 dependencies = [
66 "async-trait",
67- "capnp",
68- "capnp-rpc",
69- "capnpc",
70 "futures",
71+ "tarpc",
72 "tokio",
73 "tokio-util",
74 "tracing",
75 @@ -834,45 +823,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
76 checksum = "a2bd12c1caf447e69cd4528f47f94d203fd2582878ecb9e9465484c4148a8223"
77
78 [[package]]
79- name = "capnp"
80- version = "0.18.13"
81- source = "registry+https://github.com/rust-lang/crates.io-index"
82- checksum = "384b671a5b39eadb909b0c2b81d22a400d446526e651e64be9eb6674b33644a4"
83- dependencies = [
84- "embedded-io",
85- ]
86-
87- [[package]]
88- name = "capnp-futures"
89- version = "0.18.2"
90- source = "registry+https://github.com/rust-lang/crates.io-index"
91- checksum = "b8697b857f5b014ff378f02817426d3b6fb90a69f32e330fe010f24fe10cf8f1"
92- dependencies = [
93- "capnp",
94- "futures",
95- ]
96-
97- [[package]]
98- name = "capnp-rpc"
99- version = "0.18.0"
100- source = "registry+https://github.com/rust-lang/crates.io-index"
101- checksum = "94b87a9e0f74600628e227d39b79ef8652c558a408999ac46ba22b19dbad0010"
102- dependencies = [
103- "capnp",
104- "capnp-futures",
105- "futures",
106- ]
107-
108- [[package]]
109- name = "capnpc"
110- version = "0.18.1"
111- source = "registry+https://github.com/rust-lang/crates.io-index"
112- checksum = "a642faaaa78187e70bdcc0014c593c213553cfeda3b15054426d0d596048b131"
113- dependencies = [
114- "capnp",
115- ]
116-
117- [[package]]
118 name = "cc"
119 version = "1.0.90"
120 source = "registry+https://github.com/rust-lang/crates.io-index"
121 @@ -1427,6 +1377,18 @@ dependencies = [
122 ]
123
124 [[package]]
125+ name = "educe"
126+ version = "0.4.23"
127+ source = "registry+https://github.com/rust-lang/crates.io-index"
128+ checksum = "0f0042ff8246a363dbe77d2ceedb073339e85a804b9a47636c6e016a9a32c05f"
129+ dependencies = [
130+ "enum-ordinalize",
131+ "proc-macro2",
132+ "quote",
133+ "syn 1.0.109",
134+ ]
135+
136+ [[package]]
137 name = "either"
138 version = "1.10.0"
139 source = "registry+https://github.com/rust-lang/crates.io-index"
140 @@ -1436,12 +1398,6 @@ dependencies = [
141 ]
142
143 [[package]]
144- name = "embedded-io"
145- version = "0.5.0"
146- source = "registry+https://github.com/rust-lang/crates.io-index"
147- checksum = "658bbadc628dc286b9ae02f0cb0f5411c056eb7487b72f0083203f115de94060"
148-
149- [[package]]
150 name = "ena"
151 version = "0.14.2"
152 source = "registry+https://github.com/rust-lang/crates.io-index"
153 @@ -1551,6 +1507,19 @@ dependencies = [
154 ]
155
156 [[package]]
157+ name = "enum-ordinalize"
158+ version = "3.1.15"
159+ source = "registry+https://github.com/rust-lang/crates.io-index"
160+ checksum = "1bf1fa3f06bbff1ea5b1a9c7b14aa992a39657db60a2759457328d7e058f49ee"
161+ dependencies = [
162+ "num-bigint",
163+ "num-traits",
164+ "proc-macro2",
165+ "quote",
166+ "syn 2.0.52",
167+ ]
168+
169+ [[package]]
170 name = "env_logger"
171 version = "0.8.4"
172 source = "registry+https://github.com/rust-lang/crates.io-index"
173 @@ -3639,6 +3608,49 @@ dependencies = [
174 ]
175
176 [[package]]
177+ name = "opentelemetry"
178+ version = "0.18.0"
179+ source = "registry+https://github.com/rust-lang/crates.io-index"
180+ checksum = "69d6c3d7288a106c0a363e4b0e8d308058d56902adefb16f4936f417ffef086e"
181+ dependencies = [
182+ "opentelemetry_api",
183+ "opentelemetry_sdk",
184+ ]
185+
186+ [[package]]
187+ name = "opentelemetry_api"
188+ version = "0.18.0"
189+ source = "registry+https://github.com/rust-lang/crates.io-index"
190+ checksum = "c24f96e21e7acc813c7a8394ee94978929db2bcc46cf6b5014fc612bf7760c22"
191+ dependencies = [
192+ "futures-channel",
193+ "futures-util",
194+ "indexmap 1.9.3",
195+ "js-sys",
196+ "once_cell",
197+ "pin-project-lite",
198+ "thiserror",
199+ ]
200+
201+ [[package]]
202+ name = "opentelemetry_sdk"
203+ version = "0.18.0"
204+ source = "registry+https://github.com/rust-lang/crates.io-index"
205+ checksum = "1ca41c4933371b61c2a2f214bf16931499af4ec90543604ec828f7a625c09113"
206+ dependencies = [
207+ "async-trait",
208+ "crossbeam-channel",
209+ "futures-channel",
210+ "futures-executor",
211+ "futures-util",
212+ "once_cell",
213+ "opentelemetry_api",
214+ "percent-encoding",
215+ "rand",
216+ "thiserror",
217+ ]
218+
219+ [[package]]
220 name = "overload"
221 version = "0.1.1"
222 source = "registry+https://github.com/rust-lang/crates.io-index"
223 @@ -5203,6 +5215,41 @@ dependencies = [
224 ]
225
226 [[package]]
227+ name = "tarpc"
228+ version = "0.34.0"
229+ source = "registry+https://github.com/rust-lang/crates.io-index"
230+ checksum = "93a1870169fb9490fb3b37df7f50782986475c33cb90955f9f9b9ae659124200"
231+ dependencies = [
232+ "anyhow",
233+ "fnv",
234+ "futures",
235+ "humantime",
236+ "opentelemetry",
237+ "pin-project",
238+ "rand",
239+ "serde",
240+ "static_assertions",
241+ "tarpc-plugins",
242+ "thiserror",
243+ "tokio",
244+ "tokio-serde",
245+ "tokio-util",
246+ "tracing",
247+ "tracing-opentelemetry",
248+ ]
249+
250+ [[package]]
251+ name = "tarpc-plugins"
252+ version = "0.13.1"
253+ source = "registry+https://github.com/rust-lang/crates.io-index"
254+ checksum = "ad8302bea2fb8a2b01b025d23414b0b4ed32a783b95e5d818c3320a8bc4baada"
255+ dependencies = [
256+ "proc-macro2",
257+ "quote",
258+ "syn 1.0.109",
259+ ]
260+
261+ [[package]]
262 name = "tempfile"
263 version = "3.10.1"
264 source = "registry+https://github.com/rust-lang/crates.io-index"
265 @@ -5439,6 +5486,22 @@ dependencies = [
266 ]
267
268 [[package]]
269+ name = "tokio-serde"
270+ version = "0.8.0"
271+ source = "registry+https://github.com/rust-lang/crates.io-index"
272+ checksum = "911a61637386b789af998ee23f50aa30d5fd7edcec8d6d3dedae5e5815205466"
273+ dependencies = [
274+ "bincode",
275+ "bytes",
276+ "educe",
277+ "futures-core",
278+ "futures-sink",
279+ "pin-project",
280+ "serde",
281+ "serde_json",
282+ ]
283+
284+ [[package]]
285 name = "tokio-stream"
286 version = "0.1.15"
287 source = "registry+https://github.com/rust-lang/crates.io-index"
288 @@ -5460,6 +5523,7 @@ dependencies = [
289 "futures-io",
290 "futures-sink",
291 "pin-project-lite",
292+ "slab",
293 "tokio",
294 "tracing",
295 ]
296 @@ -5622,6 +5686,19 @@ dependencies = [
297 ]
298
299 [[package]]
300+ name = "tracing-opentelemetry"
301+ version = "0.18.0"
302+ source = "registry+https://github.com/rust-lang/crates.io-index"
303+ checksum = "21ebb87a95ea13271332df069020513ab70bdb5637ca42d6e492dc3bbbad48de"
304+ dependencies = [
305+ "once_cell",
306+ "opentelemetry",
307+ "tracing",
308+ "tracing-core",
309+ "tracing-subscriber",
310+ ]
311+
312+ [[package]]
313 name = "tracing-subscriber"
314 version = "0.3.18"
315 source = "registry+https://github.com/rust-lang/crates.io-index"
316 diff --git a/README.md b/README.md
317index fbd911c..810ac35 100644
318--- a/README.md
319+++ b/README.md
320 @@ -81,7 +81,6 @@ Ayllu is written in [rust](https://www.rust-lang.org/) and you'll need its
321 compiler to build the project. Additionally you also must have
322
323 - [git](https://git-scm.com)
324- - [capnproto](https://capnproto.org/install.html)
325 - [sqlx-cli](https://github.com/launchbadge/sqlx/tree/main/sqlx-cli)
326 - [libgit2](https://libgit2.org/)
327
328 diff --git a/ayllu-build/Cargo.toml b/ayllu-build/Cargo.toml
329index 72373d8..0cdede2 100644
330--- a/ayllu-build/Cargo.toml
331+++ b/ayllu-build/Cargo.toml
332 @@ -21,8 +21,6 @@ rand = "0.8.5"
333 serde = { version = "1.0", features = ["derive"] }
334 tracing = "0.1.40"
335 tracing-subscriber = "0.3.18"
336- capnp-rpc = "0.18.0"
337- capnp = "0.18.9"
338 async-trait = "0.1.74"
339 sqlx = { version = "0.7.3", features = ["sqlite", "macros"] }
340 tokio = { version = "1.34.0", features = ["full"] }
341 diff --git a/ayllu-build/src/rpc_server.rs b/ayllu-build/src/rpc_server.rs
342index 10fe521..b703541 100644
343--- a/ayllu-build/src/rpc_server.rs
344+++ b/ayllu-build/src/rpc_server.rs
345 @@ -1,49 +1,55 @@
346+ use std::path::Path;
347+
348 use anyhow::Result;
349- use capnp::{capability::Promise, Error as CapnpError};
350+ use tracing::log::info;
351
352- use crate::config::Builder as Config;
353- use ayllu_api::build_capnp::worker::{
354- Client, ListManifestsParams, ListManifestsResults, ReadManifestParams, ReadManifestResults,
355- Server, SubmitParams, SubmitResults,
356+ use ayllu_api::{
357+ build::{Event, Manifest, Server},
358+ error::ApiError,
359 };
360-
361 use ayllu_database::{Builder, Wrapper as Database};
362- use ayllu_rpc::Server as RpcHelper;
363+ use ayllu_rpc::{
364+ futures::prelude::*,
365+ init_socket, spawn,
366+ tarpc::{
367+ context::Context,
368+ serde_transport::unix,
369+ server::{BaseChannel, Channel},
370+ tokio_serde::formats::Bincode,
371+ },
372+ };
373+
374+ use crate::config::Builder as Config;
375
376+ #[derive(Clone)]
377 #[allow(dead_code)]
378 struct ServerImpl {
379 db: Database,
380 }
381
382 impl Server for ServerImpl {
383- fn submit(
384- &mut self,
385- _params: SubmitParams,
386- _results: SubmitResults,
387- ) -> Promise<(), CapnpError> {
388- Promise::err(CapnpError::unimplemented(
389- "method worker::Server::submit not implemented".to_string(),
390- ))
391+ #[doc = r" submit a new event which may result in manifests being run"]
392+ async fn submit(self, _context: Context, _event: Event) -> Result<(), ApiError> {
393+ todo!()
394 }
395
396- fn list_manifests(
397- &mut self,
398- _: ListManifestsParams,
399- _: ListManifestsResults,
400- ) -> ::capnp::capability::Promise<(), ::capnp::Error> {
401- ::capnp::capability::Promise::err(::capnp::Error::unimplemented(
402- "method worker::Server::list_manifests not implemented".to_string(),
403- ))
404+ #[doc = r" list manifests available on the worker"]
405+ async fn list_manifests(
406+ self,
407+ _context: Context,
408+ _offset: i64,
409+ _limit: i64,
410+ ) -> Result<Vec<Manifest>, ApiError> {
411+ todo!()
412 }
413
414- fn read_manifest(
415- &mut self,
416- _: ReadManifestParams,
417- _: ReadManifestResults,
418- ) -> ::capnp::capability::Promise<(), ::capnp::Error> {
419- ::capnp::capability::Promise::err(::capnp::Error::unimplemented(
420- "method worker::Server::read_manifest not implemented".to_string(),
421- ))
422+ #[doc = r" read a manifest including it's status and all job output"]
423+ async fn read_manifest(
424+ self,
425+ _context: Context,
426+ _manifest_id: String,
427+ ) -> Result<Manifest, ApiError> {
428+ todo!()
429 }
430 }
431
432 @@ -53,8 +59,22 @@ pub async fn serve(cfg: &Config) -> Result<()> {
433 .migrations("./migrations")
434 .build()
435 .await?;
436- let server = ServerImpl { db };
437- let runtime = RpcHelper::<Client, ServerImpl>::new(&cfg.address, server);
438- runtime.serve().await?;
439+ info!("build server listening @ {}", cfg.address);
440+ let socket_path = Path::new(&cfg.address);
441+ init_socket(socket_path)?;
442+ let mut listener = unix::listen(socket_path, Bincode::default).await?;
443+ listener.config_mut().max_frame_length(usize::MAX);
444+ listener
445+ // Ignore accept errors.
446+ .filter_map(|r| future::ready(r.ok()))
447+ .map(BaseChannel::with_defaults)
448+ .map(move |channel| {
449+ let server = ServerImpl { db: db.clone() };
450+ channel.execute(server.serve()).for_each(spawn)
451+ })
452+ // Max 10 channels.
453+ .buffer_unordered(10)
454+ .for_each(|_| async {})
455+ .await;
456 Ok(())
457 }
458 diff --git a/ayllu-mail/Cargo.toml b/ayllu-mail/Cargo.toml
459index 855fa64..dc22d8c 100644
460--- a/ayllu-mail/Cargo.toml
461+++ b/ayllu-mail/Cargo.toml
462 @@ -16,9 +16,7 @@ clap = { version = "4.4.6", features = ["derive"] }
463 tracing-subscriber = { version = "0.3.17", features = ["env-filter"] }
464 tracing = "0.1.37"
465 tokio = { version = "1.33.0", features = ["full"] }
466- capnp-rpc = "0.18.0"
467 futures = "0.3.28"
468- capnp = "0.18.1"
469 melib = "0.8.2"
470 mailpot = { git = "https://ayllu-forge.org/forks/mailpot", branch = "ayllu-dev"}
471 clap_complete = "4.4.5"
472 diff --git a/ayllu-mail/src/server.rs b/ayllu-mail/src/server.rs
473index d77357f..efc135d 100644
474--- a/ayllu-mail/src/server.rs
475+++ b/ayllu-mail/src/server.rs
476 @@ -1,262 +1,234 @@
477 use std::error::Error as StdError;
478+ use std::path::Path;
479
480- use anyhow::{format_err, Result};
481- use capnp::{capability::Promise, Error as CapnpError};
482- use capnp_rpc::pry;
483+ use anyhow::Result;
484 use mailpot::{
485 models::{DbVal, Post},
486- Connection, Error as MailpotError,
487+ Configuration, Connection, Error as MailpotError,
488 };
489 use melib::Envelope;
490- use tracing::log;
491+ use tracing::log::info;
492
493 use crate::config::Config;
494- use ayllu_api::mail_capnp::server::{
495- Client, ExportParams, ExportResults, ListThreadsParams, ListThreadsResults, ListsParams,
496- ListsResults, ReadPostParams, ReadPostResults, ReadThreadParams, ReadThreadResults, Server,
497+
498+ use ayllu_api::{
499+ error::ApiError,
500+ mail::{MailingList, Message, Server as MailServer, Thread},
501+ };
502+
503+ use ayllu_rpc::{
504+ futures::prelude::*,
505+ init_socket, spawn,
506+ tarpc::{
507+ context::Context,
508+ serde_transport::unix,
509+ server::{BaseChannel, Channel},
510+ tokio_serde::formats::Bincode,
511+ },
512 };
513- use ayllu_rpc::Server as RpcHelper;
514
515 fn is_patch(subject: &str) -> bool {
516 subject.contains("[PATCH]") || subject.contains("[patch]")
517 }
518
519+ fn to_api_error(e: MailpotError) -> ApiError {
520+ ApiError::Message(e.to_string())
521+ }
522+
523+ fn to_message(post: &DbVal<Post>) -> Message {
524+ let envelope = Envelope::from_bytes(&post.message, None).unwrap();
525+ let text = envelope.body_bytes(&post.message).text();
526+ let body = String::from_utf8(post.message.to_vec()).unwrap();
527+ Message {
528+ id: post.pk,
529+ message_id: post.message_id.clone(),
530+ timestamp: post.timestamp,
531+ from: envelope.from[0].get_email(),
532+ address: post.address.clone(),
533+ body,
534+ text,
535+ is_patch: envelope.subject.map_or(false, |subject| is_patch(&subject)),
536+ }
537+ }
538+
539+ #[derive(Clone)]
540 struct ServerImpl {
541- db: Connection,
542+ mailpot_config: Configuration,
543 }
544
545- impl Server for ServerImpl {
546- fn list_threads(
547- &mut self,
548- params: ListThreadsParams,
549- results: ListThreadsResults,
550- ) -> Promise<(), CapnpError> {
551- let list_id = pry!(pry!(params.get()).get_id()).to_string().unwrap();
552- let list_threads = |mut results: ListThreadsResults| {
553- let list = self
554- .db
555- .list_by_id(&list_id)?
556- .ok_or(format_err!("no mailing list with id: {}", &list_id))?;
557- let posts = self.db.list_posts(list.pk, None)?;
558- // TODO: add a new database method to only return posts without
559- // reply headers to save cpu cycles
560- let posts: Vec<&DbVal<Post>> = posts
561- .iter()
562- .filter(|post| {
563- let envelope = Envelope::from_bytes(&post.0.message, None).unwrap();
564- envelope.in_reply_to.is_none()
565- })
566- .collect();
567- let mut threads = results.get().init_threads(posts.len() as u32);
568- for (i, post) in posts.iter().enumerate() {
569- let envelope = Envelope::from_bytes(&post.0.message, None).unwrap();
570- let message_text = envelope.body_bytes(&post.0.message).text();
571- let replies = self.db.list_thread(list.pk, &post.message_id)?;
572- let mut thread = threads.reborrow().get(i as u32);
573- thread.reborrow().set_n_replies(replies.len() as i64);
574- let mut first = thread.get_first().unwrap();
575- first.set_from(envelope.from[0].get_email().as_str().into());
576- first.set_id(post.pk);
577- first.set_address(post.0.address.as_str().into());
578- first.set_message_id(post.0.message_id.as_str().into());
579- first.set_timestamp(post.0.timestamp as i64);
580- if let Some(subject) = envelope.subject {
581- first.set_subject(subject.as_str().into());
582- first.set_is_patch(is_patch(&subject))
583- }
584- let message = String::from_utf8(post.0.message.clone()).unwrap();
585- first.set_body(message.as_str().into());
586- first.set_text(message_text.as_str().into());
587- }
588- Ok::<(), MailpotError>(())
589- };
590- match list_threads(results) {
591- Ok(_) => Promise::ok(()),
592- Err(e) => Promise::err(CapnpError::failed(e.to_string())),
593- }
594+ // BUG: these are all read-only functions but mailpot requires trusted()
595+
596+ impl MailServer for ServerImpl {
597+ #[doc = r" return all managed mailing lists"]
598+ async fn lists(self, _context: Context) -> Result<Vec<MailingList>, ApiError> {
599+ let db =
600+ Connection::open_or_create_db(self.mailpot_config.clone()).map_err(to_api_error)?.trusted();
601+ Ok(db
602+ .lists()
603+ .map_err(|e| ApiError::Message(e.to_string()))?
604+ .iter()
605+ .map(|db_val| MailingList {
606+ id: db_val.id.clone(),
607+ name: db_val.name.clone(),
608+ request_address: db_val.request_subaddr().clone(),
609+ address: db_val.address.clone(),
610+ description: db_val
611+ .description
612+ .as_ref()
613+ .map_or(String::new(), |desc| desc.clone()),
614+ topics: db_val.topics.clone(),
615+ })
616+ .collect())
617 }
618
619- fn read_thread(
620- &mut self,
621- params: ReadThreadParams,
622- results: ReadThreadResults,
623- ) -> Promise<(), CapnpError> {
624- let params = pry!(params.get());
625- let list_id = pry!(pry!(params.get_id()).to_string());
626- let message_id = pry!(pry!(params.get_message_id()).to_string());
627- let read_thread = |mut results: ReadThreadResults| {
628- let list = self
629- .db
630- .list_by_id(&list_id)?
631- .ok_or(format_err!("no mailing list with id: {}", list_id))?;
632- let first_post = self
633- .db
634- .list_post_by_message_id(list.pk, &message_id)?
635- .ok_or(format_err!("cannot find post: {}", message_id))?;
636- let replies = self.db.list_thread(list.pk, &message_id)?;
637- let mut threads = results.get().init_thread(replies.len() as u32 + 1);
638- let mut first = threads.reborrow().get(0);
639- first.set_id(first_post.0.pk);
640- first.set_address(first_post.0.address.as_str().into());
641- first.set_message_id(first_post.0.message_id.as_str().into());
642- let envelope = Envelope::from_bytes(&first_post.0.message, None).unwrap();
643- let message_text = envelope.body_bytes(&first_post.0.message).text();
644- first.set_text(message_text.as_str().into());
645- let message_body = String::from_utf8(first_post.0.message).unwrap();
646- first.set_body(message_body.as_str().into());
647- first.set_timestamp(first_post.0.timestamp as i64);
648- if let Some(subject) = envelope.subject {
649- first.set_subject(subject.as_str().into());
650- first.set_is_patch(is_patch(subject.as_str()));
651- }
652- log::info!("read thread with {} replies", replies.len());
653- for (i, reply) in replies.iter().enumerate() {
654- let mut next = threads.reborrow().get(i as u32 + 1);
655- let envelope = Envelope::from_bytes(&reply.1.message, None).unwrap();
656- next.set_id(reply.1.pk);
657- next.set_address(reply.1.address.as_str().into());
658- next.set_message_id(reply.1 .0.message_id.as_str().into());
659- let message_body = String::from_utf8(reply.1.message.to_vec()).unwrap();
660- next.set_body(message_body.as_str().into());
661- let message_text = envelope.body_bytes(&reply.1.message).text();
662- if let Some(subject) = envelope.subject {
663- next.set_subject(subject.as_str().into());
664- next.set_is_patch(is_patch(subject.as_str()));
665- }
666- next.set_text(message_text.as_str().into());
667- next.set_timestamp(envelope.timestamp as i64);
668- }
669- Ok::<(), MailpotError>(())
670- };
671- match read_thread(results) {
672- Ok(_) => Promise::ok(()),
673- Err(e) => Promise::err(CapnpError::failed(e.to_string())),
674+ #[doc = r" list all of the threads associated with a mailing list"]
675+ async fn list_threads(
676+ self,
677+ _context: Context,
678+ id: String,
679+ _offset: i64,
680+ _limit: i64,
681+ ) -> Result<Vec<Thread>, ApiError> {
682+ let db =
683+ Connection::open_or_create_db(self.mailpot_config.clone()).map_err(to_api_error)?.trusted();
684+ let list = db
685+ .list_by_id(&id)
686+ .map_err(to_api_error)?
687+ .ok_or(ApiError::NotFound(format!(
688+ "no mailing list with id {} exists",
689+ id
690+ )))?;
691+ let posts = db.list_posts(list.pk, None).map_err(to_api_error)?;
692+ // TODO: add a new database method to only return posts without
693+ // reply headers to save cpu cycles
694+ let posts: Vec<&DbVal<Post>> = posts
695+ .iter()
696+ .filter(|post| {
697+ let envelope = Envelope::from_bytes(&post.0.message, None).unwrap();
698+ envelope.in_reply_to.is_none()
699+ })
700+ .collect();
701+ let mut threads: Vec<Thread> = Vec::with_capacity(posts.len());
702+ for post in posts.iter() {
703+ let replies = db
704+ .list_thread(list.pk, &post.message_id)
705+ .map_err(to_api_error)?;
706+ let first = to_message(post);
707+ let has_patch = first.is_patch;
708+ threads.push(Thread {
709+ first,
710+ n_replies: replies.len() as i64,
711+ has_patch,
712+ });
713 }
714+ Ok(threads)
715 }
716
717- fn read_post(
718- &mut self,
719- params: ReadPostParams,
720- results: ReadPostResults,
721- ) -> Promise<(), CapnpError> {
722- let params = pry!(params.get());
723- let list_id = pry!(pry!(params.get_id()).to_string());
724- let message_id = pry!(pry!(params.get_message_id()).to_string());
725- let read_post = |mut results: ReadPostResults| {
726- let list = self
727- .db
728- .list_by_id(&list_id)?
729- .ok_or(format_err!("no mailing list with id: {}", list_id))?;
730- let post = self
731- .db
732- .list_post_by_message_id(list.pk, &message_id)?
733- .ok_or(format_err!("failed to find post with id: {}", message_id))?;
734- let mut message = results.get();
735- let envelope = Envelope::from_bytes(&post.message, None).unwrap();
736- message.set_id(post.pk);
737- message.set_address(post.address.as_str().into());
738- if let Some(from) = post.envelope_from.as_ref() {
739- message.set_from(from.as_str().into());
740- }
741- let message_text = envelope.body_bytes(&post.message).text();
742- let message_body = String::from_utf8(post.message.to_vec()).unwrap();
743- message.set_message_id(envelope.message_id.to_string().as_str().into());
744- message.set_body(message_body.as_str().into());
745- message.set_text(message_text.as_str().into());
746- message.set_timestamp(envelope.timestamp as i64);
747- if let Some(subject) = envelope.subject {
748- message.set_subject(subject.as_str().into());
749- message.set_is_patch(is_patch(subject.as_str()));
750- }
751-
752- Ok::<(), MailpotError>(())
753- };
754- match read_post(results) {
755- Ok(_) => Promise::ok(()),
756- Err(e) => Promise::err(CapnpError::failed(e.to_string())),
757+ #[doc = r" list all of the responses associated with a post starting from messageId"]
758+ async fn read_thread(
759+ self,
760+ _context: Context,
761+ id: String,
762+ message_id: String,
763+ _offset: i64,
764+ _limit: i64,
765+ ) -> Result<Vec<Message>, ApiError> {
766+ let db =
767+ Connection::open_or_create_db(self.mailpot_config.clone()).map_err(to_api_error)?.trusted();
768+ let list = db
769+ .list_by_id(&id)
770+ .map_err(to_api_error)?
771+ .ok_or(ApiError::NotFound(format!(
772+ "no mailing list with id {} exists",
773+ id
774+ )))?;
775+ let mut posts: Vec<Message> = Vec::new();
776+ // Read the initial post
777+ let first_post = db
778+ .list_post_by_message_id(list.pk, &message_id)
779+ .map_err(to_api_error)?
780+ .ok_or(ApiError::NotFound(format!(
781+ "no post with id {} exists",
782+ list.pk
783+ )))?;
784+ posts.push(to_message(&first_post));
785+ // then all of it's replies
786+ let replies = db.list_thread(list.pk, &message_id).map_err(to_api_error)?;
787+ for (_, post, _, _) in replies.iter() {
788+ posts.push(to_message(post));
789 }
790+ Ok(posts)
791 }
792
793- fn export(&mut self, params: ExportParams, results: ExportResults) -> Promise<(), CapnpError> {
794- let params = pry!(params.get());
795- let list_id = pry!(params.get_id()).to_string().unwrap();
796- let message_id = if params.has_message_id() {
797- let message_id = pry!(params.get_message_id()).to_string().unwrap();
798- Some(message_id)
799- } else {
800- None
801- };
802- let read_mbox = |mut results: ExportResults| {
803- let list = self
804- .db
805- .list_by_id(&list_id)?
806- .ok_or(format_err!("no mailing list with id: {}", list_id))?;
807- let buf = self.db.export_mbox(
808- list.pk,
809- message_id.as_deref(),
810- params.get_as_thread(),
811- )?;
812- results.get().set_thread(&buf);
813- Ok::<(), MailpotError>(())
814- };
815- match read_mbox(results) {
816- Ok(_) => Promise::ok(()),
817- Err(e) => Promise::err(CapnpError::failed(e.to_string())),
818- }
819+ #[doc = r" read a single post"]
820+ async fn read_post(
821+ self,
822+ _context: Context,
823+ id: String,
824+ message_id: String,
825+ ) -> Result<Message, ApiError> {
826+ let db =
827+ Connection::open_or_create_db(self.mailpot_config.clone()).map_err(to_api_error)?.trusted();
828+ let list = db
829+ .list_by_id(&id)
830+ .map_err(to_api_error)?
831+ .ok_or(ApiError::NotFound(format!(
832+ "no mailing list with id {} exists",
833+ id
834+ )))?;
835+ let post = db
836+ .list_post_by_message_id(list.pk, &message_id)
837+ .map_err(to_api_error)?
838+ .ok_or(ApiError::NotFound(format!(
839+ "no post with id {}",
840+ message_id
841+ )))?;
842+ Ok(to_message(&post))
843 }
844
845- fn lists(
846- &mut self,
847- _: ListsParams,
848- results: ListsResults,
849- ) -> ::capnp::capability::Promise<(), ::capnp::Error> {
850- let read_lists = |mut results: ListsResults| {
851- let lists = self.db.lists()?;
852- let mut lists_array = results.get().init_lists(lists.len() as u32);
853- for (i, list) in lists.iter().enumerate() {
854- lists_array
855- .reborrow()
856- .get(i as u32)
857- .set_id(list.id.as_str().into());
858- lists_array
859- .reborrow()
860- .get(i as u32)
861- .set_name(list.name.as_str().into());
862- lists_array
863- .reborrow()
864- .get(i as u32)
865- .set_address(list.address.as_str().into());
866- lists_array
867- .reborrow()
868- .get(i as u32)
869- .set_request_address(list.request_subaddr().as_str().into());
870- if let Some(description) = &list.description {
871- lists_array
872- .reborrow()
873- .get(i as u32)
874- .set_name(description.as_str().into());
875- }
876- let mut topics = lists_array
877- .reborrow()
878- .get(i as u32)
879- .init_topics(list.topics.len() as u32);
880- for (j, topic) in list.topics.iter().enumerate() {
881- topics.set(j as u32, topic.as_str().into());
882- }
883- }
884- Ok::<(), MailpotError>(())
885- };
886- match read_lists(results) {
887- Ok(_) => Promise::ok(()),
888- Err(e) => Promise::err(CapnpError::failed(e.to_string())),
889- }
890+ #[doc = r" export one or more messages in mbox format"]
891+ async fn export(
892+ self,
893+ _context: Context,
894+ id: String,
895+ message_id: Option<String>,
896+ as_thread: bool,
897+ ) -> Result<Vec<u8>, ApiError> {
898+ let db =
899+ Connection::open_or_create_db(self.mailpot_config.clone()).map_err(to_api_error)?.trusted();
900+ let list = db
901+ .list_by_id(&id)
902+ .map_err(to_api_error)?
903+ .ok_or(ApiError::NotFound(format!(
904+ "no mailing list with id {} exists",
905+ id
906+ )))?;
907+ let buf = db
908+ .export_mbox(list.pk, message_id.as_deref(), as_thread)
909+ .map_err(to_api_error)?;
910+ Ok(buf)
911 }
912 }
913
914 pub async fn serve(cfg: &Config) -> Result<(), Box<dyn StdError>> {
915- let db = Connection::open_or_create_db(cfg.mailpot_config())?.trusted();
916- let server = ServerImpl { db };
917- let runtime = RpcHelper::<Client, ServerImpl>::new(&cfg.mail.socket_path, server);
918- runtime.serve().await?;
919+ let socket_path = Path::new(&cfg.mail.socket_path);
920+ init_socket(socket_path)?;
921+ info!("mail server listening @ {:?}", socket_path);
922+ let mut listener = unix::listen(socket_path, Bincode::default).await?;
923+ listener.config_mut().max_frame_length(usize::MAX);
924+ listener
925+ // Ignore accept errors.
926+ .filter_map(|r| future::ready(r.ok()))
927+ .map(BaseChannel::with_defaults)
928+ .map(move |channel| {
929+ let server = ServerImpl {
930+ mailpot_config: cfg.mailpot_config(),
931+ };
932+ channel.execute(server.serve()).for_each(spawn)
933+ })
934+ // Max 10 channels.
935+ .buffer_unordered(10)
936+ .for_each(|_| async {})
937+ .await;
938 Ok(())
939 }
940 diff --git a/ayllu-xmpp/Cargo.toml b/ayllu-xmpp/Cargo.toml
941index 54105eb..7a5e342 100644
942--- a/ayllu-xmpp/Cargo.toml
943+++ b/ayllu-xmpp/Cargo.toml
944 @@ -19,8 +19,6 @@ anyhow = "1.0.75"
945 tokio-xmpp = "3.4.0"
946 futures = "0.3.28"
947 rand = "0.8.5"
948- capnp = "0.18.3"
949- capnp-rpc = "0.18.0"
950 tracing = { version = "0.1.40", features = ["log"] }
951 async-trait = "0.1.74"
952 sqlx = { version = "0.7.3", features = [ "sqlite", "macros", ] }
953 diff --git a/ayllu-xmpp/src/main.rs b/ayllu-xmpp/src/main.rs
954index 3b8b21c..7a6a30a 100644
955--- a/ayllu-xmpp/src/main.rs
956+++ b/ayllu-xmpp/src/main.rs
957 @@ -76,8 +76,6 @@ fn main() -> Result<()> {
958 .build()
959 .unwrap()
960 .block_on(async move {
961- // Capnp RPC server implementation with a read-only database
962- // connection
963 LocalSet::new().run_until(rpc_server::serve(config)).await
964 })
965 .unwrap();
966 diff --git a/ayllu-xmpp/src/rpc_server.rs b/ayllu-xmpp/src/rpc_server.rs
967index 122bff0..9d4f638 100644
968--- a/ayllu-xmpp/src/rpc_server.rs
969+++ b/ayllu-xmpp/src/rpc_server.rs
970 @@ -1,101 +1,102 @@
971 use std::error::Error as StdError;
972+ use std::path::Path;
973
974- use capnp::{capability::Promise, Error as CapnpError};
975- use capnp_rpc::pry;
976- use tracing::log;
977+ use tracing::log::info;
978
979 use crate::config::Xmpp as Config;
980 use crate::database_ext::XmppExt;
981- use ayllu_api::xmpp_capnp::server::{
982- Client, MessagesParams, MessagesResults, Server, StatsParams, StatsResults,
983+ use ayllu_api::{
984+ error::ApiError,
985+ xmpp::{ChannelStat, Message, Server},
986+ };
987+ use ayllu_database::{Builder, Wrapper as Database};
988+ use ayllu_rpc::{
989+ futures::prelude::*,
990+ init_socket, spawn,
991+ tarpc::{
992+ context::Context,
993+ serde_transport::unix,
994+ server::{BaseChannel, Channel},
995+ tokio_serde::formats::Bincode,
996+ },
997 };
998- use ayllu_database::{Wrapper as Database, Builder};
999- use ayllu_rpc::Server as RpcHelper;
1000
1001+ #[derive(Clone)]
1002 struct ServerImpl {
1003 db: Database,
1004 }
1005
1006 impl Server for ServerImpl {
1007- fn stats(&mut self, params: StatsParams, mut results: StatsResults) -> Promise<(), CapnpError> {
1008- let db = self.db.clone();
1009- let mut channels: Vec<String> = Vec::new();
1010- for channel_name in pry!(pry!(params.get()).get_channels()) {
1011- let channel_name = pry!(channel_name).to_string().unwrap();
1012- channels.push(channel_name);
1013+ #[doc = r" return stats for the given channel names"]
1014+ async fn stats(
1015+ self,
1016+ _context: Context,
1017+ channels: Vec<String>,
1018+ ) -> Result<Vec<ChannelStat>, ApiError> {
1019+ info!("looking up channels: {:?}", channels);
1020+ let stats = self.db.read_stats(channels).await.unwrap();
1021+ // sqlx can't use WHERE $ IN (..) ..
1022+ let mut results: Vec<ChannelStat> = Vec::with_capacity(stats.len());
1023+ for count in stats.iter() {
1024+ info!(
1025+ "got channel: name={} users={} msgs={}",
1026+ count.0, count.1, count.2,
1027+ );
1028+ results.push(ChannelStat {
1029+ name: count.0.clone(),
1030+ users_online: count.1,
1031+ num_messages: count.2,
1032+ });
1033 }
1034- Promise::from_future(async move {
1035- log::info!("looking up channels: {:?}", channels);
1036- let stats = db.read_stats(channels).await.unwrap();
1037- // sqlx can't use WHERE $ IN (..) ..
1038- let mut results = results.get().init_stats(stats.len() as u32);
1039- for (i, count) in stats.iter().enumerate() {
1040- log::info!(
1041- "got channel: name={} users={} msgs={}",
1042- count.0,
1043- count.1,
1044- count.2
1045- );
1046- results
1047- .reborrow()
1048- .get(i as u32)
1049- .set_name(count.0.as_str().into());
1050- results.reborrow().get(i as u32).set_users_online(count.1);
1051- results.reborrow().get(i as u32).set_n_messages(count.2);
1052- }
1053- Ok(())
1054- })
1055+ Ok(results)
1056 }
1057
1058- fn messages(
1059- &mut self,
1060- params: MessagesParams,
1061- mut results: MessagesResults,
1062- ) -> Promise<(), CapnpError> {
1063- let db = self.db.clone();
1064- let params = pry!(params.get());
1065- let channel_name = pry!(params.get_channel_name()).to_string().unwrap();
1066- let last_message = if params.has_last_message() {
1067- Some(pry!(params.get_last_message()).to_string().unwrap())
1068- } else {
1069- None
1070- };
1071- let limit = params.get_limit();
1072- Promise::from_future(async move {
1073- log::info!("looking up messages for channel: {:?}", channel_name);
1074- let messages = db
1075- .list_messages(&channel_name, last_message.as_deref(), limit)
1076- .await
1077- .unwrap();
1078- log::info!("db returned {} messages", messages.len());
1079- let mut results = results.get().init_messages(messages.len() as u32);
1080- for (i, message) in messages.iter().enumerate() {
1081- results
1082- .reborrow()
1083- .get(i as u32)
1084- .set_message_id(message.xmpp_id.as_str().into());
1085- results
1086- .reborrow()
1087- .get(i as u32)
1088- .set_timestamp(message.xmpp_timestamp.as_str().into());
1089- results
1090- .reborrow()
1091- .get(i as u32)
1092- .set_nickname(message.xmpp_nickname.as_str().into());
1093- results
1094- .reborrow()
1095- .get(i as u32)
1096- .set_body(message.xmpp_body.as_str().into());
1097- }
1098- Ok(())
1099- })
1100+ #[doc = r" return public messages for the channel"]
1101+ async fn messages(
1102+ self,
1103+ _context: Context,
1104+ channel_name: String,
1105+ last_message: Option<String>,
1106+ limit: i64,
1107+ ) -> Result<Vec<Message>, ApiError> {
1108+ info!("looking up messages for channel: {:?}", channel_name);
1109+ let messages = self
1110+ .db
1111+ .list_messages(&channel_name, last_message.as_deref(), limit)
1112+ .await
1113+ .unwrap();
1114+ info!("db returned {} messages", messages.len());
1115+ let mut results: Vec<Message> = Vec::with_capacity(messages.len());
1116+ for db_message in messages.iter() {
1117+ results.push(Message {
1118+ message_id: db_message.xmpp_id.clone(),
1119+ timestamp: db_message.xmpp_timestamp.clone(),
1120+ nickname: db_message.xmpp_nickname.clone(),
1121+ body: db_message.xmpp_body.clone(),
1122+ })
1123+ }
1124+ Ok(results)
1125 }
1126 }
1127
1128 pub async fn serve(cfg: Config) -> Result<(), Box<dyn StdError>> {
1129 let db = Builder::default().url(&cfg.database.path).build().await?;
1130- let server = ServerImpl { db };
1131- let runtime = RpcHelper::<Client, ServerImpl>::new(&cfg.socket_path, server);
1132- runtime.serve().await?;
1133+ let socket_path = Path::new(&cfg.socket_path);
1134+ init_socket(socket_path)?;
1135+ info!("xmpp server listening @ {:?}", socket_path);
1136+ let mut listener = unix::listen(socket_path, Bincode::default).await?;
1137+ listener.config_mut().max_frame_length(usize::MAX);
1138+ listener
1139+ // Ignore accept errors.
1140+ .filter_map(|r| future::ready(r.ok()))
1141+ .map(BaseChannel::with_defaults)
1142+ .map(move |channel| {
1143+ let server = ServerImpl { db: db.clone() };
1144+ channel.execute(server.serve()).for_each(spawn)
1145+ })
1146+ // Max 10 channels.
1147+ .buffer_unordered(10)
1148+ .for_each(|_| async {})
1149+ .await;
1150 Ok(())
1151 }
1152 diff --git a/ayllu/Cargo.toml b/ayllu/Cargo.toml
1153index 2b1b749..4188ab4 100644
1154--- a/ayllu/Cargo.toml
1155+++ b/ayllu/Cargo.toml
1156 @@ -58,10 +58,9 @@ tracing-subscriber = { version = "0.3.17", features = ["env-filter"] }
1157 tracing = "0.1.40"
1158 tower = { version = "0.4.13", features = ["util", "timeout", "tracing"] }
1159 mime = "0.3.17"
1160- capnp-rpc = "0.18.0"
1161- capnp = "0.18.1"
1162 async-trait = "0.1.74"
1163 webfinger = "0.5.1"
1164+ tarpc = { version = "0.34.0", features = ["full"] }
1165
1166 # NOTE: this must be cautiously updated along with sqlx and rusqlite.
1167 [dependencies.libsqlite3-sys]
1168 diff --git a/ayllu/src/job_server/commands.rs b/ayllu/src/job_server/commands.rs
1169index da7c748..2f7db87 100644
1170--- a/ayllu/src/job_server/commands.rs
1171+++ b/ayllu/src/job_server/commands.rs
1172 @@ -1,6 +1,7 @@
1173 use std::fs::canonicalize;
1174 use std::io::Write;
1175 use std::path::{Path, PathBuf};
1176+ use std::time::{Duration as StdDuration, SystemTime};
1177
1178 use anyhow::{format_err, Result};
1179 use tabwriter::TabWriter;
1180 @@ -8,10 +9,10 @@ use time::Duration;
1181
1182 use crate::config::Config;
1183 use crate::time::friendly;
1184- use ayllu_api::jobs_capnp::server::Client;
1185- use ayllu_api::models::{Job, Kind};
1186+ use ayllu_api::jobs::{Kind, ServerClient as JobsClient};
1187 use ayllu_git::git_dir;
1188- use ayllu_rpc::Client as RpcHelper;
1189+
1190+ use tarpc::{context, serde_transport::unix, tokio_serde::formats::Bincode};
1191
1192 fn name(repo_path: Option<String>) -> String {
1193 match repo_path {
1194 @@ -40,6 +41,12 @@ fn resolve_path(path: Option<&PathBuf>) -> Result<PathBuf> {
1195 }
1196 }
1197
1198+ async fn jobs_client(socket_path: &str) -> Result<JobsClient> {
1199+ let conn = unix::connect(Path::new(socket_path), Bincode::default).await?;
1200+ let client = JobsClient::new(tarpc::client::Config::default(), conn).spawn();
1201+ Ok(client)
1202+ }
1203+
1204 /// Run a job against a specific repository path, if kind is not specified all
1205 /// jobs will be ran.
1206 pub async fn run_one(
1207 @@ -52,20 +59,16 @@ pub async fn run_one(
1208 vec![kind.to_string().into()]
1209 });
1210 let repo_path = resolve_path(repo_path.as_ref())?;
1211- RpcHelper::new(&config.jobs_socket_path)
1212- .invoke(move |client: Client| async move {
1213- for kind in kinds {
1214- let mut request = client.submit_request();
1215- // FIXME: set_repo_path must take Path/PathBuf
1216- request
1217- .get()
1218- .set_repo_path(repo_path.display().to_string().as_str().into());
1219- request.get().set_kind(kind.into());
1220- request.send().promise.await?;
1221- }
1222- Ok(())
1223- })
1224- .await?;
1225+ let client = jobs_client(&config.jobs_socket_path).await?;
1226+ for kind in kinds {
1227+ let mut ctx = context::current();
1228+ ctx.deadline = SystemTime::now()
1229+ .checked_add(StdDuration::from_secs(1200))
1230+ .unwrap();
1231+ client
1232+ .submit(ctx, kind, repo_path.display().to_string())
1233+ .await?;
1234+ }
1235 Ok(())
1236 }
1237
1238 @@ -73,31 +76,12 @@ pub async fn list(config: Config, repo_path: Option<PathBuf>) -> Result<()> {
1239 let repo_path = repo_path
1240 .map(|repo_path| resolve_path(Some(repo_path).as_ref()))
1241 .transpose()?;
1242- let jobs = RpcHelper::new(&config.jobs_socket_path)
1243- .invoke(move |client: Client| async move {
1244- let mut request = client.list_request();
1245- if let Some(repo_path) = repo_path {
1246- // FIXME: set_repo_path must take Path/PathBuf
1247- request
1248- .get()
1249- .set_repo_path(repo_path.display().to_string().as_str().into());
1250- }
1251- let result = request.send().promise.await?;
1252- let jobs: Vec<Job> = result
1253- .get()?
1254- .get_jobs()?
1255- .iter()
1256- .map(|item| Job {
1257- id: item.get_id(),
1258- repo_path: String::from_utf8(item.get_repo_path().unwrap().0.to_vec()).unwrap(),
1259- created_at: item.get_created_at(),
1260- runtime: item.get_runtime(),
1261- kind: Kind::from(item.get_kind().unwrap()),
1262- commits: item.get_commits(),
1263- })
1264- .collect();
1265- Ok(jobs)
1266- })
1267+ let client = jobs_client(&config.jobs_socket_path).await?;
1268+ let jobs = client
1269+ .list(
1270+ context::current(),
1271+ repo_path.map(|path| path.display().to_string()),
1272+ )
1273 .await?;
1274 let mut tw = TabWriter::new(vec![]);
1275 writeln!(&mut tw, "name\ttime\truntime\tkind\tcommits")?;
1276 @@ -119,16 +103,9 @@ pub async fn list(config: Config, repo_path: Option<PathBuf>) -> Result<()> {
1277
1278 pub async fn purge(config: Config, repo_path: Option<PathBuf>) -> Result<()> {
1279 let repo_path = resolve_path(repo_path.as_ref())?;
1280- RpcHelper::new(&config.jobs_socket_path)
1281- .invoke(move |client: Client| async move {
1282- let mut request = client.purge_request();
1283- // FIXME: set_repo_path must take Path/PathBuf
1284- request
1285- .get()
1286- .set_repo_path(repo_path.display().to_string().as_str().into());
1287- request.send().promise.await?;
1288- Ok(())
1289- })
1290+ let client = jobs_client(&config.jobs_socket_path).await?;
1291+ client
1292+ .purge(context::current(), repo_path.display().to_string())
1293 .await?;
1294 Ok(())
1295 }
1296 diff --git a/ayllu/src/job_server/jobs/cloc.rs b/ayllu/src/job_server/jobs/cloc.rs
1297index 73a1d64..df3f8c5 100644
1298--- a/ayllu/src/job_server/jobs/cloc.rs
1299+++ b/ayllu/src/job_server/jobs/cloc.rs
1300 @@ -1,3 +1,4 @@
1301+ use std::path::Path;
1302 use std::sync::Arc;
1303 use std::time::{Duration, SystemTime};
1304
1305 @@ -5,52 +6,47 @@ use anyhow::Result;
1306 use tokei::{Config as TokeiConfig, Languages};
1307
1308 use crate::database_ext::{jobs::JobsExt, langauges::LanguagesExt};
1309- use ayllu_api::models::{Job as JobModel, Kind};
1310+ use ayllu_api::jobs::Kind;
1311 use ayllu_database::Wrapper as Database;
1312 use ayllu_git::Wrapper as Repository;
1313
1314- pub struct Job {
1315- pub repository: Repository,
1316- pub database: Arc<Database>,
1317- pub model: JobModel,
1318- }
1319-
1320- impl Job {
1321- pub async fn invoke(&self, commits: Vec<String>) -> Result<Duration> {
1322- let start = SystemTime::now();
1323- let repo_path = self.repository.path();
1324- for (i, commit) in commits.iter().enumerate() {
1325- if i % 50 == 0 && i != 0 {
1326- log::info!("processed {}/{} commits", i, commits.len());
1327- }
1328- let mut stats: Vec<(&str, i64)> = Vec::new();
1329- let git_hash = commit.to_string();
1330- self.repository
1331- .with_worktree("/tmp", Some(git_hash.as_str()), |tree_path| {
1332- let config = TokeiConfig::default();
1333- let mut languages = Languages::new();
1334- let paths = &[tree_path];
1335- let exclude = &[];
1336- languages.get_statistics(paths, exclude, &config);
1337- for language in languages {
1338- let name = language.0.name();
1339- let loc = language.1.code;
1340- stats.push((name, loc.try_into().unwrap()));
1341- }
1342- Ok(())
1343- })?;
1344- self.database
1345- .language_create(&repo_path, git_hash.as_str(), stats)
1346- .await?;
1347- self.database
1348- .create_hash(
1349- &repo_path,
1350- git_hash.as_str(),
1351- Kind::Cloc.to_string().as_str(),
1352- self.model.id,
1353- )
1354- .await?;
1355+ pub async fn invoke(
1356+ db: Arc<Database>,
1357+ repo_path: &Path,
1358+ commits: Vec<String>,
1359+ job_id: i64,
1360+ ) -> Result<Duration> {
1361+ let repository = Repository::new(repo_path)?;
1362+ let start = SystemTime::now();
1363+ let repo_path = repository.path();
1364+ for (i, commit) in commits.iter().enumerate() {
1365+ if i % 50 == 0 && i != 0 {
1366+ log::info!("processed {}/{} commits", i, commits.len());
1367 }
1368- Ok(start.elapsed().unwrap())
1369+ let mut stats: Vec<(&str, i64)> = Vec::new();
1370+ let git_hash = commit.to_string();
1371+ repository.with_worktree("/tmp", Some(git_hash.as_str()), |tree_path| {
1372+ let config = TokeiConfig::default();
1373+ let mut languages = Languages::new();
1374+ let paths = &[tree_path];
1375+ let exclude = &[];
1376+ languages.get_statistics(paths, exclude, &config);
1377+ for language in languages {
1378+ let name = language.0.name();
1379+ let loc = language.1.code;
1380+ stats.push((name, loc.try_into().unwrap()));
1381+ }
1382+ Ok(())
1383+ })?;
1384+ db.language_create(&repo_path, git_hash.as_str(), stats)
1385+ .await?;
1386+ db.create_hash(
1387+ &repo_path,
1388+ git_hash.as_str(),
1389+ Kind::Cloc.to_string().as_str(),
1390+ job_id,
1391+ )
1392+ .await?;
1393 }
1394+ Ok(start.elapsed().unwrap())
1395 }
1396 diff --git a/ayllu/src/job_server/jobs/contributors.rs b/ayllu/src/job_server/jobs/contributors.rs
1397index cc5e9a4..555e4a0 100644
1398--- a/ayllu/src/job_server/jobs/contributors.rs
1399+++ b/ayllu/src/job_server/jobs/contributors.rs
1400 @@ -1,11 +1,12 @@
1401 use std::sync::Arc;
1402+ use std::path::Path;
1403 use std::time::{Duration, SystemTime};
1404 use time::OffsetDateTime;
1405
1406 use anyhow::Result;
1407
1408 use crate::database_ext::{contributors::ContributorsExt, jobs::JobsExt};
1409- use ayllu_api::models::{Job as JobModel, Kind};
1410+ use ayllu_api::jobs::Kind;
1411 use ayllu_database::Wrapper as Database;
1412 use ayllu_git::Wrapper as Repository;
1413
1414 @@ -18,53 +19,48 @@ struct Contribution {
1415 lines_removed: usize,
1416 }
1417
1418- pub struct Job {
1419- pub repository: Repository,
1420- pub database: Arc<Database>,
1421- pub model: JobModel,
1422- }
1423-
1424- impl Job {
1425- pub async fn invoke(&self, commits: Vec<String>) -> Result<Duration> {
1426- let start = SystemTime::now();
1427- let repo_path = self.repository.path();
1428- for (i, commit) in commits.iter().enumerate() {
1429- if i % 50 == 0 && i != 0 {
1430- log::info!("processed {}/{} commits", i, commits.len());
1431- }
1432- let mut contribution = Contribution::default();
1433- let git_hash = commit.to_string();
1434- let commit = self.repository.commit(Some(git_hash.clone()))?.unwrap();
1435- let stats = self.repository.stats(git_hash.clone().as_str())?;
1436- contribution.name = commit.author_name;
1437- contribution.email = commit.author_email;
1438- contribution.time = commit.epoch;
1439- contribution.lines_added = stats.insertions;
1440- contribution.lines_removed = stats.deletions;
1441- let timestamp = OffsetDateTime::from_unix_timestamp(contribution.time).unwrap();
1442- let author_id = self
1443- .database
1444- .upsert_author(contribution.name.as_str(), contribution.email.as_str())
1445- .await?;
1446- self.database
1447- .contribution_add(
1448- author_id,
1449- &repo_path,
1450- git_hash.as_str(),
1451- timestamp,
1452- contribution.lines_added as i64,
1453- contribution.lines_removed as i64,
1454- )
1455- .await?;
1456- self.database
1457- .create_hash(
1458- &repo_path,
1459- git_hash.as_str(),
1460- Kind::Contributors.to_string().as_str(),
1461- self.model.id,
1462- )
1463- .await?;
1464+ pub async fn invoke(
1465+ db: Arc<Database>,
1466+ repo_path: &Path,
1467+ commits: Vec<String>,
1468+ job_id: i64,
1469+ ) -> Result<Duration> {
1470+ let repository = Repository::new(repo_path)?;
1471+ let start = SystemTime::now();
1472+ let repo_path = repository.path();
1473+ for (i, commit) in commits.iter().enumerate() {
1474+ if i % 50 == 0 && i != 0 {
1475+ log::info!("processed {}/{} commits", i, commits.len());
1476 }
1477- Ok(start.elapsed().unwrap())
1478+ let mut contribution = Contribution::default();
1479+ let git_hash = commit.to_string();
1480+ let commit = repository.commit(Some(git_hash.clone()))?.unwrap();
1481+ let stats = repository.stats(git_hash.clone().as_str())?;
1482+ contribution.name = commit.author_name;
1483+ contribution.email = commit.author_email;
1484+ contribution.time = commit.epoch;
1485+ contribution.lines_added = stats.insertions;
1486+ contribution.lines_removed = stats.deletions;
1487+ let timestamp = OffsetDateTime::from_unix_timestamp(contribution.time).unwrap();
1488+ let author_id = db
1489+ .upsert_author(contribution.name.as_str(), contribution.email.as_str())
1490+ .await?;
1491+ db.contribution_add(
1492+ author_id,
1493+ &repo_path,
1494+ git_hash.as_str(),
1495+ timestamp,
1496+ contribution.lines_added as i64,
1497+ contribution.lines_removed as i64,
1498+ )
1499+ .await?;
1500+ db.create_hash(
1501+ &repo_path,
1502+ git_hash.as_str(),
1503+ Kind::Contributors.to_string().as_str(),
1504+ job_id,
1505+ )
1506+ .await?;
1507 }
1508+ Ok(start.elapsed().unwrap())
1509 }
1510 diff --git a/ayllu/src/job_server/jobs/mod.rs b/ayllu/src/job_server/jobs/mod.rs
1511index eb3ca38..9a63152 100644
1512--- a/ayllu/src/job_server/jobs/mod.rs
1513+++ b/ayllu/src/job_server/jobs/mod.rs
1514 @@ -1,37 +1,2 @@
1515- use std::sync::Arc;
1516- use std::time::Duration;
1517-
1518- use anyhow::Result;
1519-
1520- use ayllu_api::models::{Job, Kind};
1521- use ayllu_database::Wrapper as Database;
1522- use ayllu_git::Wrapper as Repository;
1523-
1524- mod cloc;
1525- mod contributors;
1526-
1527- pub async fn invoke(
1528- commits: Vec<String>,
1529- job: Job,
1530- repository: Repository,
1531- database: Arc<Database>,
1532- ) -> Result<Duration> {
1533- match job.kind {
1534- Kind::Contributors => {
1535- let job = contributors::Job {
1536- repository,
1537- database,
1538- model: job,
1539- };
1540- job.invoke(commits).await
1541- }
1542- Kind::Cloc => {
1543- let job = cloc::Job {
1544- repository,
1545- database,
1546- model: job,
1547- };
1548- job.invoke(commits).await
1549- }
1550- }
1551- }
1552+ pub mod cloc;
1553+ pub mod contributors;
1554 diff --git a/ayllu/src/job_server/mod.rs b/ayllu/src/job_server/mod.rs
1555index 0cceca5..374fac6 100644
1556--- a/ayllu/src/job_server/mod.rs
1557+++ b/ayllu/src/job_server/mod.rs
1558 @@ -1,7 +1,48 @@
1559+ use std::error::Error;
1560+ use std::path::Path;
1561+
1562+ use tarpc::serde_transport::unix;
1563+
1564+ use tracing::info;
1565+
1566+ use crate::config::Config;
1567+ use ayllu_api::jobs::Server;
1568+ use ayllu_database::Builder;
1569+ use ayllu_rpc::{
1570+ futures::prelude::*,
1571+ init_socket, spawn,
1572+ tarpc::{
1573+ server::{BaseChannel, Channel},
1574+ tokio_serde::formats::Bincode,
1575+ },
1576+ };
1577+
1578 pub use commands::*;
1579- pub use server::serve;
1580
1581 mod commands;
1582 mod jobs;
1583 mod runner;
1584 mod server;
1585+
1586+ pub async fn serve(cfg: &Config) -> Result<(), Box<dyn Error>> {
1587+ let db = Builder::default().url(&cfg.database.path).build().await?;
1588+ // run server
1589+ let socket_path = Path::new(&cfg.jobs_socket_path);
1590+ init_socket(socket_path)?;
1591+ info!("job server listening @ {:?}", socket_path);
1592+ let mut listener = unix::listen(socket_path, Bincode::default).await?;
1593+ listener.config_mut().max_frame_length(usize::MAX);
1594+ listener
1595+ // Ignore accept errors.
1596+ .filter_map(|r| future::ready(r.ok()))
1597+ .map(BaseChannel::with_defaults)
1598+ .map(|channel| {
1599+ let server = server::Server::new(db.clone().into());
1600+ channel.execute(server.serve()).for_each(spawn)
1601+ })
1602+ // Max 10 channels.
1603+ .buffer_unordered(10)
1604+ .for_each(|_| async {})
1605+ .await;
1606+ Ok(())
1607+ }
1608 diff --git a/ayllu/src/job_server/runner.rs b/ayllu/src/job_server/runner.rs
1609index b5a7570..3c3fd09 100644
1610--- a/ayllu/src/job_server/runner.rs
1611+++ b/ayllu/src/job_server/runner.rs
1612 @@ -1,12 +1,13 @@
1613 use std::path::Path;
1614 use std::sync::Arc;
1615+ use std::time::Duration;
1616
1617 use anyhow::Result;
1618 use tracing::log;
1619
1620 use crate::database_ext::jobs::JobsExt;
1621- use crate::job_server::jobs::invoke;
1622- use ayllu_api::models::{Job, Kind};
1623+ use crate::job_server::jobs;
1624+ use ayllu_api::jobs::{Job, Kind};
1625 use ayllu_database::Wrapper as Database;
1626 use ayllu_git::Wrapper as Repository;
1627
1628 @@ -25,7 +26,21 @@ impl Runner {
1629 }
1630 }
1631
1632- async fn commits(&self, kind: Kind, repository: &Repository) -> Result<Vec<String>> {
1633+ pub async fn invoke(
1634+ &self,
1635+ commits: Vec<String>,
1636+ job: Job,
1637+ repo_path: &Path,
1638+ ) -> Result<Duration> {
1639+ let database = self.database.clone();
1640+ match job.kind {
1641+ Kind::Contributors => jobs::contributors::invoke(database, repo_path, commits, job.id).await,
1642+ Kind::Cloc => jobs::cloc::invoke(database, repo_path, commits, job.id).await,
1643+ }
1644+ }
1645+
1646+ async fn commits(&self, kind: Kind, repo_path: &Path) -> Result<Vec<String>> {
1647+ let repository = Repository::new(repo_path)?;
1648 let repo_path = repository.path();
1649 let latest_db_hash = self
1650 .database
1651 @@ -64,9 +79,12 @@ impl Runner {
1652
1653 let mut job = self.job.clone();
1654 job.id = job_id;
1655+ let repo_path = job.repo_path.clone();
1656
1657- let repository = Repository::new(Path::new(&job.repo_path))?;
1658- let commits = self.commits(job.kind.clone(), &repository).await?;
1659+ // let repository = Repository::new(Path::new(&job.repo_path))?;
1660+ let commits = self
1661+ .commits(job.kind.clone(), Path::new(&repo_path))
1662+ .await?;
1663 log::info!("processing {} commits", commits.len());
1664 if commits.is_empty() {
1665 // nothing to do
1666 @@ -76,7 +94,7 @@ impl Runner {
1667 return Ok(());
1668 }
1669
1670- let runtime = invoke(commits, job, repository, self.database.clone()).await?;
1671+ let runtime = self.invoke(commits, job, Path::new(&repo_path)).await?;
1672
1673 let duration = runtime.as_millis() as i64;
1674 log::info!("runtime {}ms", duration);
1675 diff --git a/ayllu/src/job_server/server.rs b/ayllu/src/job_server/server.rs
1676index c66afcb..3975b5c 100644
1677--- a/ayllu/src/job_server/server.rs
1678+++ b/ayllu/src/job_server/server.rs
1679 @@ -1,112 +1,65 @@
1680- use std::error::Error as StdError;
1681 use std::sync::Arc;
1682
1683- use capnp::{capability::Promise, Error};
1684- use capnp_rpc::pry;
1685-
1686- use crate::config::Config;
1687 use crate::database_ext::jobs::JobsExt;
1688 use crate::job_server::runner::Runner;
1689- use ayllu_api::jobs_capnp::server::{
1690- Client, ListParams, ListResults, PurgeParams, PurgeResults, Server, SubmitParams, SubmitResults,
1691- };
1692- use ayllu_api::models::{Job, Kind};
1693- use ayllu_database::{Wrapper as Database, Builder};
1694- use ayllu_rpc::Server as CapnpServerHelper;
1695-
1696+ use ayllu_api::jobs::{Job, Kind, Server as Api};
1697+ use ayllu_database::Wrapper as Database;
1698+ use ayllu_rpc::tarpc::context::Context;
1699
1700 #[derive(Clone)]
1701- pub struct ServerImpl {
1702+ pub struct Server {
1703 db: Arc<Database>,
1704 }
1705
1706- impl Server for ServerImpl {
1707- fn submit(
1708- &mut self,
1709- params: SubmitParams,
1710- _: SubmitResults,
1711- ) -> ::capnp::capability::Promise<(), ::capnp::Error> {
1712- let repo_path = pry!(pry!(pry!(params.get()).get_repo_path()).to_string());
1713- let kind = match pry!(pry!(params.get()).get_kind()) {
1714- ayllu_api::jobs_capnp::Kind::Contributors => String::from("contributors"),
1715- ayllu_api::jobs_capnp::Kind::Cloc => String::from("cloc"),
1716- };
1717- let db = self.db.clone();
1718- Promise::from_future(async move {
1719- let job = Job {
1720- id: 0,
1721- repo_path: repo_path.clone(),
1722- created_at: 0,
1723- runtime: 0,
1724- kind: Kind::from(kind),
1725- commits: 0,
1726- };
1727- match Runner::new(job, db).run().await {
1728- Ok(()) => Ok(()),
1729- Err(e) => Err(Error {
1730- kind: capnp::ErrorKind::Failed,
1731- extra: e.to_string(),
1732- }),
1733- }
1734- })
1735+ impl Server {
1736+ pub fn new(db: Arc<Database>) -> Self {
1737+ Server { db }
1738 }
1739+ }
1740
1741- fn list(
1742- &mut self,
1743- _: ListParams,
1744- mut result: ListResults,
1745- ) -> ::capnp::capability::Promise<(), ::capnp::Error> {
1746+ impl Api for Server {
1747+ #[doc = r" Submit a new job to be run in the background"]
1748+ async fn submit(self, _context: Context, kind: Kind, repo_path: String) {
1749 let db = self.db.clone();
1750- Promise::from_future(async move {
1751- match db.list_jobs(None).await {
1752- Ok(jobs) => {
1753- let mut jobs_result = result.get().init_jobs(jobs.len() as u32);
1754- for (i, job) in jobs.iter().enumerate() {
1755- let mut job_result = jobs_result.reborrow().get(i as u32);
1756- job_result.set_created_at(job.created_at.unix_timestamp());
1757- if job.repo_path.is_some() {
1758- let repo_path = job.repo_path.as_ref().unwrap();
1759- job_result.set_repo_path(repo_path.as_str().into())
1760- }
1761- if job.runtime.is_some() {
1762- job_result.set_runtime(job.runtime.unwrap() as i64)
1763- }
1764- job_result.set_commits(job.commits as i64);
1765- job_result.set_kind(Kind::from(job.kind.clone()).into());
1766- job_result.set_success(job.success.is_some_and(|success| success))
1767- }
1768- Ok(())
1769- }
1770- Err(e) => Err(Error {
1771- kind: capnp::ErrorKind::Failed,
1772- extra: e.to_string(),
1773- }),
1774- }
1775- })
1776+ let job = Job {
1777+ id: 0,
1778+ created_at: 0,
1779+ kind,
1780+ repo_path,
1781+ runtime: 0,
1782+ success: false,
1783+ commits: 0,
1784+ };
1785+ Runner::new(job, db).run().await.expect("failed to run job");
1786 }
1787
1788- fn purge(
1789- &mut self,
1790- params: PurgeParams,
1791- _: PurgeResults,
1792- ) -> ::capnp::capability::Promise<(), ::capnp::Error> {
1793- let repo_path = pry!(pry!(pry!(params.get()).get_repo_path()).to_string());
1794+ #[doc = r" List submitted and complete jobs"]
1795+ async fn list(self, _context: Context, repo_path: Option<String>) -> Vec<Job> {
1796 let db = self.db.clone();
1797- Promise::from_future(async move {
1798- match db.purge(&repo_path).await {
1799- Ok(_) => Ok(()),
1800- Err(e) => Err(Error {
1801- kind: capnp::ErrorKind::Failed,
1802- extra: e.to_string(),
1803- }),
1804- }
1805- })
1806+ let jobs = db
1807+ .list_jobs(repo_path.as_deref())
1808+ .await
1809+ .expect("failed to list jobs");
1810+ jobs.iter()
1811+ .map(|job| Job {
1812+ id: job.id,
1813+ created_at: job.created_at.unix_timestamp(),
1814+ kind: job.kind.clone().into(),
1815+ // FIXME
1816+ repo_path: job
1817+ .repo_path
1818+ .clone()
1819+ .expect("the database only supports absolute paths"),
1820+ runtime: job.runtime.map_or(0, |n| n as i64),
1821+ success: job.success.unwrap_or(false),
1822+ commits: job.commits as i64,
1823+ })
1824+ .collect()
1825 }
1826- }
1827
1828- pub async fn serve(cfg: &Config) -> Result<(), Box<dyn StdError>> {
1829- let db = Builder::default().url(&cfg.database.path).build().await?;
1830- let server = ServerImpl { db: Arc::new(db) };
1831- let runtime = CapnpServerHelper::<Client, ServerImpl>::new(&cfg.jobs_socket_path, server);
1832- runtime.serve().await.map_err(|e| e.into())
1833+ #[doc = r" Purge all jobs associated with the given repository"]
1834+ async fn purge(self, _context: Context, repo_path: String) {
1835+ let db = self.db.clone();
1836+ db.purge(&repo_path).await.expect("failed to purge jobs");
1837+ }
1838 }
1839 diff --git a/ayllu/src/web2/error.rs b/ayllu/src/web2/error.rs
1840index 38d078e..1f7d718 100644
1841--- a/ayllu/src/web2/error.rs
1842+++ b/ayllu/src/web2/error.rs
1843 @@ -5,9 +5,10 @@ use std::io::Error as IoError;
1844 use axum::{body::Body, response::IntoResponse, response::Response};
1845 use tera::Error as TeraError;
1846
1847+ use ayllu_api::error::ApiError;
1848 use ayllu_database::Error as SqlError;
1849 use ayllu_git::Error as GitError;
1850- use ayllu_rpc::Error as RpcError;
1851+ use ayllu_rpc::tarpc::client::RpcError;
1852
1853 /// Error maps known error types into errors that can be translated into HTTP
1854 /// status codes, e.g. Io::NotFound -> 404
1855 @@ -32,6 +33,12 @@ impl From<Box<dyn StdError>> for Error {
1856 }
1857 }
1858
1859+ impl From<ApiError> for Error {
1860+ fn from(value: ApiError) -> Self {
1861+ Error::Message(format!("RPC [ayllu]: {:?}", value))
1862+ }
1863+ }
1864+
1865 impl From<RpcError> for Error {
1866 fn from(value: RpcError) -> Self {
1867 Error::Message(format!("RPC: {:?}", value))
1868 diff --git a/ayllu/src/web2/middleware/rpc_initiator.rs b/ayllu/src/web2/middleware/rpc_initiator.rs
1869index cdc455c..0ef7534 100644
1870--- a/ayllu/src/web2/middleware/rpc_initiator.rs
1871+++ b/ayllu/src/web2/middleware/rpc_initiator.rs
1872 @@ -1,3 +1,4 @@
1873+ use std::path::Path;
1874 use std::sync::Arc;
1875
1876 use crate::web2::terautil::{Loader, Options};
1877 @@ -15,7 +16,8 @@ use tracing::log;
1878 use crate::config::Config;
1879 use crate::web2::config::Config as ClientConfig;
1880 use crate::web2::extractors::config::ConfigReader;
1881- use ayllu_rpc::Client;
1882+ use ayllu_api::{mail::ServerClient as MailClient, xmpp::ServerClient as XmppClient};
1883+ use ayllu_rpc::tarpc::{client, serde_transport::unix, tokio_serde::formats::Bincode};
1884
1885 pub type State = Arc<(Config, Vec<(String, Tera)>, &'static [Kind])>;
1886
1887 @@ -25,18 +27,12 @@ pub enum Kind {
1888 }
1889
1890 #[derive(Clone, Default)]
1891+ /// Initiator contains zero or more active RPC connections to various plugin
1892+ /// servers. Pages that mark plugins as required will always have them
1893+ /// configured and they can be safely unwrapped.
1894 pub struct Initiator {
1895- mail: Option<Client>,
1896- xmpp: Option<Client>,
1897- }
1898-
1899- impl Initiator {
1900- pub fn client(self, kind: Kind) -> Option<Client> {
1901- match kind {
1902- Kind::Mail => self.mail,
1903- Kind::Xmpp => self.xmpp,
1904- }
1905- }
1906+ pub mail: Option<MailClient>,
1907+ pub xmpp: Option<XmppClient>,
1908 }
1909
1910 fn plugin_not_enabled(
1911 @@ -72,22 +68,28 @@ fn plugin_not_enabled(
1912 .unwrap()
1913 }
1914
1915- /// configure an optinoal plugin for use in a handler
1916+ /// configure an optional plugin for use in a handler
1917 pub async fn optional(
1918 extract::State(cfg): extract::State<Arc<Config>>,
1919 mut req: extract::Request,
1920 next: Next,
1921 ) -> Response {
1922- req.extensions_mut().insert(Initiator {
1923- mail: cfg
1924- .mail
1925- .as_ref()
1926- .map(|mail_cfg| Client::new(&mail_cfg.socket_path)),
1927- xmpp: cfg
1928- .xmpp
1929- .as_ref()
1930- .map(|xmpp_cfg| Client::new(&xmpp_cfg.socket_path)),
1931- });
1932+ let mut initator = Initiator::default();
1933+ if let Some(mail_cfg) = cfg.mail.as_ref() {
1934+ // FIXME
1935+ let conn = unix::connect(Path::new(&mail_cfg.socket_path), Bincode::default)
1936+ .await
1937+ .unwrap();
1938+ initator.mail = Some(MailClient::new(client::Config::default(), conn).spawn());
1939+ };
1940+ if let Some(xmpp_cfg) = cfg.xmpp.as_ref() {
1941+ // FIXME
1942+ let conn = unix::connect(Path::new(&xmpp_cfg.socket_path), Bincode::default)
1943+ .await
1944+ .unwrap();
1945+ initator.xmpp = Some(XmppClient::new(client::Config::default(), conn).spawn());
1946+ };
1947+ req.extensions_mut().insert(initator);
1948 next.run(req).await
1949 }
1950
1951 @@ -104,13 +106,21 @@ pub async fn required(
1952 match kind {
1953 Kind::Mail => match &state.0.mail {
1954 Some(mail_cfg) => {
1955- initiator.mail = Some(Client::new(&mail_cfg.socket_path));
1956+ // FIXME
1957+ let conn = unix::connect(Path::new(&mail_cfg.socket_path), Bincode::default)
1958+ .await
1959+ .unwrap();
1960+ initiator.mail = Some(MailClient::new(client::Config::default(), conn).spawn());
1961 }
1962 None => return plugin_not_enabled("ayllu-mail", &state.0, client_config, &state.1),
1963 },
1964 Kind::Xmpp => match &state.0.xmpp {
1965 Some(xmpp_cfg) => {
1966- initiator.xmpp = Some(Client::new(&xmpp_cfg.socket_path));
1967+ // FIXME
1968+ let conn = unix::connect(Path::new(&xmpp_cfg.socket_path), Bincode::default)
1969+ .await
1970+ .unwrap();
1971+ initiator.xmpp = Some(XmppClient::new(client::Config::default(), conn).spawn());
1972 }
1973 None => return plugin_not_enabled("ayllu-xmpp", &state.0, client_config, &state.1),
1974 },
1975 diff --git a/ayllu/src/web2/routes/mail.rs b/ayllu/src/web2/routes/mail.rs
1976index e6a4977..199f1de 100644
1977--- a/ayllu/src/web2/routes/mail.rs
1978+++ b/ayllu/src/web2/routes/mail.rs
1979 @@ -7,14 +7,11 @@ use axum::{
1980 };
1981 use serde::{Deserialize, Serialize};
1982
1983- use crate::languages::Hint;
1984 use crate::web2::error::Error;
1985- use crate::web2::highlight::Highlighter;
1986- use crate::web2::middleware::rpc_initiator::{Initiator, Kind as InitiatorKind};
1987+ use crate::web2::middleware::rpc_initiator::Initiator;
1988 use crate::web2::middleware::template::Template;
1989 use crate::web2::navigation;
1990- use ayllu_api::mail_capnp::server::Client as MailClient;
1991- use ayllu_rpc::Client;
1992+ use ayllu_rpc::tarpc::context;
1993
1994 #[derive(Deserialize)]
1995 pub struct Params {
1996 @@ -55,73 +52,15 @@ struct List {
1997 pub request_address: String,
1998 }
1999
2000- async fn read_list(mail_client: Client, list_id: String) -> Result<Option<List>, Error> {
2001- let list = mail_client
2002- .invoke(move |c: MailClient| async move {
2003- let req = c.lists_request();
2004- let result = req.send().promise.await?;
2005- let rpc_lists = result.get()?.get_lists()?;
2006- for list in rpc_lists.iter() {
2007- let id = list.get_id()?.to_string().unwrap();
2008- let topics: Vec<String> = list
2009- .get_topics()?
2010- .iter()
2011- .map(|topic| topic.unwrap().to_string().unwrap())
2012- .collect();
2013- if list_id == id {
2014- return Ok(Some(List {
2015- id: list_id.clone(),
2016- name: list.get_name()?.to_string().unwrap(),
2017- description: None,
2018- address: list.get_address()?.to_string().unwrap(),
2019- topics,
2020- request_address: list.get_request_address()?.to_string().unwrap(),
2021- }));
2022- }
2023- }
2024- Ok(None)
2025- })
2026- .await?;
2027- Ok(list)
2028- }
2029-
2030 pub async fn lists(
2031 Extension(initiator): Extension<Initiator>,
2032 Extension((templates, mut ctx)): Extension<Template>,
2033 ) -> Result<Html<String>, Error> {
2034 ctx.insert("title", "lists");
2035 ctx.insert("nav_elements", &navigation::global("mail", true));
2036- let mail_client = initiator.client(InitiatorKind::Mail).unwrap();
2037- let lists = mail_client
2038- .invoke(move |c: MailClient| async move {
2039- let mut lists: Vec<List> = Vec::new();
2040- let req = c.lists_request();
2041- let result = req.send().promise.await?;
2042- let rpc_lists = result.get()?.get_lists()?;
2043- for list in rpc_lists.iter() {
2044- let description = if list.has_description() {
2045- Some(list.get_description()?.to_string().unwrap())
2046- } else {
2047- None
2048- };
2049- let topics: Vec<String> = list
2050- .get_topics()?
2051- .iter()
2052- .map(|topic| topic.unwrap().to_string().unwrap())
2053- .collect();
2054- lists.push(List {
2055- id: list.get_id()?.to_string().unwrap(),
2056- name: list.get_name()?.to_string().unwrap(),
2057- description,
2058- address: list.get_name()?.to_string().unwrap(),
2059- topics,
2060- request_address: list.get_request_address()?.to_string().unwrap(),
2061- })
2062- }
2063- Ok(lists)
2064- })
2065- .await?;
2066- ctx.insert("lists", &lists);
2067+ let mail_client = initiator.mail.unwrap();
2068+ let rpc_lists = mail_client.lists(context::current()).await??;
2069+ ctx.insert("lists", &rpc_lists);
2070 // ctx.insert("lists", &cfg.mail.unwrap().lists);
2071 let body = templates.render("lists.html", &ctx)?;
2072 Ok(Html(body))
2073 @@ -134,8 +73,9 @@ pub async fn threads(
2074 Extension(initiator): Extension<Initiator>,
2075 Extension((templates, mut ctx)): Extension<Template>,
2076 ) -> Result<Html<String>, Error> {
2077- let client = initiator.client(InitiatorKind::Mail).unwrap();
2078- let mailing_list = read_list(client.clone(), params.list_id.clone()).await?;
2079+ let client = initiator.mail.unwrap();
2080+ let lists = client.lists(context::current()).await??;
2081+ let mailing_list = lists.iter().find(|list| list.id == params.list_id);
2082 if let Some(mailing_list) = mailing_list {
2083 ctx.insert("title", &format!("list {}", mailing_list.name));
2084 ctx.insert("nav_elements", &navigation::global("mail", true));
2085 @@ -143,29 +83,11 @@ pub async fn threads(
2086 ctx.insert("title", &format!("list {}", mailing_list.address));
2087 ctx.insert("nav_elements", &navigation::global("mail", true));
2088 ctx.insert("list", &mailing_list);
2089- let mut threads = client
2090- .invoke(move |c: MailClient| async move {
2091- let mut threads: Vec<Thread> = Vec::new();
2092- let mut req = c.list_threads_request();
2093- req.get().set_id(params.list_id.as_str().into());
2094- let result = req.send().promise.await?;
2095- let rpc_threads = result.get()?.get_threads()?;
2096- for thread in rpc_threads.iter() {
2097- let message = thread.get_first()?;
2098- let message_id = message.get_message_id()?.to_string().unwrap();
2099- threads.push(Thread {
2100- id: message.get_id(),
2101- message_id,
2102- from: message.get_from()?.to_string().unwrap(),
2103- subject: message.get_subject()?.to_string().unwrap(),
2104- n_replies: thread.get_n_replies(),
2105- timestamp: thread.get_first().unwrap().get_timestamp(),
2106- })
2107- }
2108- Ok(threads)
2109- })
2110- .await?;
2111- threads.sort_by(|first, second| second.timestamp.cmp(&first.timestamp));
2112+ let threads = client
2113+ .list_threads(context::current(), params.list_id, 0, 0)
2114+ .await??;
2115+ // FIXME
2116+ // threads.sort_by(|first, second| second.timestamp.cmp(&first.timestamp));
2117 ctx.insert("threads", &threads);
2118 ctx.insert("request_address", &mailing_list.request_address);
2119 let body = templates.render("threads.html", &ctx)?;
2120 @@ -181,56 +103,31 @@ pub async fn threads(
2121 pub async fn thread(
2122 Path(params): Path<Params>,
2123 Extension(initiator): Extension<Initiator>,
2124- Extension(highlighter): Extension<Highlighter>,
2125+ // Extension(highlighter): Extension<Highlighter>,
2126 Extension((templates, mut ctx)): Extension<Template>,
2127 ) -> Result<Html<String>, Error> {
2128- let client = initiator.client(InitiatorKind::Mail).unwrap();
2129- let mailing_list = read_list(client.clone(), params.list_id.clone()).await?;
2130+ let client = initiator.mail.unwrap();
2131+ let lists = client.lists(context::current()).await??;
2132+ let mailing_list = lists.iter().find(|list| list.id == params.list_id);
2133 if let Some(mailing_list) = mailing_list {
2134- let thread_id = params.thread_id.clone();
2135 let messages = client
2136- .invoke(move |c: MailClient| async move {
2137- let mut messages: Vec<Message> = Vec::new();
2138- let mut req = c.read_thread_request();
2139- req.get().set_id(params.list_id.as_str().into());
2140- req.get().set_message_id(thread_id.unwrap().as_str().into());
2141- let result = req.send().promise.await?;
2142- for message in result.get()?.get_thread()? {
2143- let text = message.get_text().unwrap().to_string().unwrap();
2144- let text = if message.get_is_patch() {
2145- let (_, diff) = highlighter.highlight(
2146- &text,
2147- None,
2148- None,
2149- Some(Hint::from("DIFF")),
2150- false,
2151- );
2152- diff
2153- } else {
2154- text
2155- };
2156- messages.push(Message {
2157- id: message.get_id().to_string(),
2158- subject: message.get_subject()?.to_string().unwrap(),
2159- message_id: message.get_message_id()?.to_string().unwrap(),
2160- created_at: message.get_timestamp(),
2161- from_address: message.get_address()?.to_string().unwrap(),
2162- body: message.get_body()?.to_string().unwrap(),
2163- text,
2164- is_patch: message.get_is_patch(),
2165- })
2166- }
2167- Ok(messages)
2168- })
2169- .await?;
2170- let subject = messages.first().map(|message| message.subject.clone());
2171+ .read_thread(
2172+ context::current(),
2173+ params.thread_id.as_ref().unwrap().clone(),
2174+ params.message_id.unwrap().clone(),
2175+ 1000,
2176+ 0,
2177+ )
2178+ .await??;
2179+ // FIXME
2180+ // let subject = messages.first().map(|message| message.subject.clone());
2181 ctx.insert("title", &format!("list {}", mailing_list.address));
2182 ctx.insert("nav_elements", &navigation::global("mail", true));
2183 ctx.insert("list", &mailing_list);
2184 ctx.insert("list_id", &mailing_list.id);
2185 ctx.insert("thread_id", &params.thread_id.clone());
2186 ctx.insert("messages", &messages);
2187- ctx.insert("subject", &subject);
2188+ // ctx.insert("subject", &subject);
2189 let body = templates.render("thread.html", &ctx)?;
2190 Ok(Html(body))
2191 } else {
2192 @@ -244,41 +141,19 @@ pub async fn thread(
2193 pub async fn message(
2194 Path(params): Path<Params>,
2195 Extension(initiator): Extension<Initiator>,
2196- Extension(highlighter): Extension<Highlighter>,
2197 Extension((templates, mut ctx)): Extension<Template>,
2198 ) -> Result<Html<String>, Error> {
2199- let client = initiator.client(InitiatorKind::Mail).unwrap();
2200- let mailing_list = read_list(client.clone(), params.list_id.clone()).await?;
2201+ let client = initiator.mail.unwrap();
2202+ let lists = client.lists(context::current()).await??;
2203+ let mailing_list = lists.iter().find(|list| list.id == params.list_id);
2204 if let Some(mailing_list) = mailing_list {
2205 let message = client
2206- .invoke(move |c: MailClient| async move {
2207- let mut req = c.read_post_request();
2208- req.get().set_id(params.list_id.as_str().into());
2209- req.get()
2210- .set_message_id(params.message_id.unwrap().as_str().into());
2211- // TODO: detect a missing message and return 404 appropriately
2212- let result = req.send().promise.await?;
2213- let message = result.get()?;
2214- let text = message.get_text().unwrap().to_string().unwrap();
2215- let text = if message.get_is_patch() {
2216- let (_, diff) =
2217- highlighter.highlight(&text, None, None, Some(Hint::from("DIFF")), false);
2218- diff
2219- } else {
2220- text
2221- };
2222- Ok(Message {
2223- id: message.get_id().to_string(),
2224- subject: message.get_subject()?.to_string().unwrap(),
2225- message_id: message.get_message_id()?.to_string().unwrap(),
2226- created_at: message.get_timestamp(),
2227- body: message.get_body()?.to_string().unwrap(),
2228- text,
2229- from_address: message.get_address()?.to_string().unwrap(),
2230- is_patch: message.get_is_patch(),
2231- })
2232- })
2233- .await?;
2234+ .read_post(
2235+ context::current(),
2236+ params.list_id,
2237+ params.message_id.unwrap(),
2238+ )
2239+ .await??;
2240 ctx.insert("title", &format!("list {}", mailing_list.name));
2241 ctx.insert("nav_elements", &navigation::global("mail", true));
2242 ctx.insert("list", &mailing_list);
2243 @@ -299,24 +174,19 @@ pub async fn export(
2244 Path(params): Path<Params>,
2245 Extension(initiator): Extension<Initiator>,
2246 ) -> Result<Response, Error> {
2247- let client = initiator.client(InitiatorKind::Mail).unwrap();
2248- let mailing_list = read_list(client.clone(), params.list_id.clone()).await?;
2249+ let client = initiator.mail.unwrap();
2250+ let lists = client.lists(context::current()).await??;
2251+ let mailing_list = lists.iter().find(|list| list.id == params.list_id);
2252 if let Some(mailing_list) = mailing_list {
2253- let list_id = mailing_list.id.clone();
2254- let thread = client
2255- .invoke(move |c: MailClient| async move {
2256- let mut req = c.export_request();
2257- req.get().set_id(list_id.as_str().into());
2258- if let Some(message_id) = params.message_id {
2259- req.get().set_message_id(message_id.as_str().into());
2260- req.get().set_as_thread(true);
2261- }
2262- let result = req.send().promise.await?;
2263- let post = result.get()?.get_thread()?;
2264- Ok(post.to_vec())
2265- })
2266- .await?;
2267- let mut response = Bytes::from(thread).into_response();
2268+ let blob = client
2269+ .export(
2270+ context::current(),
2271+ mailing_list.id.clone(),
2272+ params.message_id,
2273+ true,
2274+ )
2275+ .await??;
2276+ let mut response = Bytes::from(blob).into_response();
2277 response
2278 .headers_mut()
2279 .insert(CONTENT_TYPE, "application/mbox".parse().unwrap());
2280 diff --git a/ayllu/src/web2/routes/repo.rs b/ayllu/src/web2/routes/repo.rs
2281index 31b8779..aa98bb7 100644
2282--- a/ayllu/src/web2/routes/repo.rs
2283+++ b/ayllu/src/web2/routes/repo.rs
2284 @@ -22,13 +22,15 @@ use crate::web2::charts;
2285 use crate::web2::error::Error;
2286 use crate::web2::highlight::TreeSitterAdapter;
2287 use crate::web2::middleware::repository::Preamble;
2288- use crate::web2::middleware::rpc_initiator::{Initiator, Kind as InitiatorKind};
2289+ use crate::web2::middleware::rpc_initiator::Initiator;
2290 use crate::web2::middleware::template::Template;
2291 use crate::web2::navigation;
2292 use crate::web2::util;
2293- use ayllu_api::xmpp_capnp::server::Client as XmppClient;
2294+
2295+ use ayllu_api::xmpp::ChannelStat;
2296 use ayllu_database::Wrapper as Database;
2297 use ayllu_git::{ChatKind, Commit, Config as GitConfig, TreeEntry, Wrapper};
2298+ use ayllu_rpc::tarpc::context;
2299
2300 const README_FILES: [&str; 6] = [
2301 "readme.md",
2302 @@ -117,75 +119,20 @@ pub struct ChatLink {
2303 pub users_online: Option<i64>,
2304 }
2305
2306- async fn managed_chats(cfg: &Config, git_cfg: &GitConfig, initiator: Initiator) -> Vec<ChatLink> {
2307- let managed_xmpp_channels: Vec<String> = cfg.xmpp.clone().map_or(Vec::new(), |link| {
2308- link.channels.iter().map(|item| item.jid.clone()).collect()
2309- });
2310- let mut links: Vec<ChatLink> = Vec::new();
2311- if git_cfg.chat.is_some() {
2312- let mut to_lookup: Vec<String> = Vec::new();
2313- for link in git_cfg.chat.clone().unwrap() {
2314- match link.kind {
2315- ChatKind::Xmpp => {
2316- if managed_xmpp_channels.contains(&link.url) {
2317- to_lookup.push(link.url.clone())
2318- } else {
2319- links.push(ChatLink {
2320- kind: format!("{:?}", link.kind),
2321- url: link.url.clone(),
2322- description: None,
2323- users_online: None,
2324- })
2325- };
2326- }
2327- ChatKind::Irc => links.push(ChatLink {
2328- kind: format!("{:?}", link.kind),
2329- url: link.url.clone(),
2330- description: None,
2331- users_online: None,
2332- }),
2333- }
2334- }
2335- let xmpp_client = initiator.client(InitiatorKind::Xmpp);
2336- if let Some(xmpp_client) = xmpp_client {
2337- match xmpp_client
2338- .invoke(move |client: XmppClient| async move {
2339- let mut remote_channels: Vec<ChatLink> = Vec::new();
2340- let mut request = client.stats_request();
2341- let mut channels = request
2342- .get()
2343- .init_channels(managed_xmpp_channels.len() as u32);
2344- for (i, lookup) in to_lookup.iter().enumerate() {
2345- channels.set(i as u32, lookup.as_str().into());
2346- }
2347- let status = request.send().promise.await?;
2348- let results = status.get()?;
2349- let stats = results.get_stats()?;
2350- for stat in stats {
2351- remote_channels.push(ChatLink {
2352- kind: format!("{:?}", ChatKind::Xmpp),
2353- url: stat.get_name()?.to_string().unwrap(),
2354- description: None,
2355- users_online: Some(stat.get_users_online()),
2356- })
2357- }
2358- Ok(remote_channels)
2359- })
2360- .await
2361- {
2362- Ok(remote_links) => {
2363- links.extend(remote_links);
2364- }
2365- Err(err) => {
2366- log::error!(
2367- "failed to resolve discussion group status: {:?}",
2368- err.to_string()
2369- )
2370- }
2371- };
2372- }
2373+ fn chat_links(cfg: &GitConfig) -> Vec<ChatLink> {
2374+ if let Some(chats) = cfg.chat.as_ref() {
2375+ chats
2376+ .iter()
2377+ .map(|chat| ChatLink {
2378+ kind: chat.kind.to_string(),
2379+ url: chat.url.clone(),
2380+ description: None,
2381+ users_online: None,
2382+ })
2383+ .collect()
2384+ } else {
2385+ vec![]
2386 }
2387- links
2388 }
2389
2390 #[derive(Serialize, Clone)]
2391 @@ -240,37 +187,27 @@ pub async fn serve(
2392 }
2393 }
2394
2395- let chat_links = managed_chats(&cfg, &preamble.config, initiator).await;
2396- ctx.insert("chat_links", &chat_links);
2397-
2398- // find any associated mailing lists associated with this repository
2399- /*
2400- let email_links = preamble.config.clone().mail.map(|mail| {
2401- let links: Vec<EmailLink> = mail
2402- .iter()
2403- .map(|address| {
2404- let git_address = address.0.clone();
2405- let description = cfg
2406- .mail
2407- .clone()
2408- .and_then(|mail_cfg| {
2409- mail_cfg
2410- .lists
2411- .iter()
2412- .find(|entry| entry.address == git_address)
2413- .map(|entry| entry.address.clone())
2414- });
2415- EmailLink {
2416- url: address.0.clone(),
2417- description,
2418+ let mut chats = chat_links(&preamble.config);
2419+ if !chats.is_empty() && initiator.xmpp.is_some() {
2420+ let stats = initiator
2421+ .xmpp
2422+ .unwrap()
2423+ .stats(
2424+ context::current(),
2425+ chats.iter().map(|chat| chat.url.clone()).collect(),
2426+ )
2427+ .await??;
2428+ for chat in &mut chats {
2429+ for stat in &stats {
2430+ // FIXME name --> url wrong?
2431+ if stat.name == chat.url {
2432+ chat.users_online = Some(stat.users_online);
2433 }
2434- })
2435- .collect();
2436- links
2437- });
2438+ }
2439+ }
2440+ }
2441
2442- ctx.insert("email_links", &email_links);
2443- */
2444+ ctx.insert("chats", &chats);
2445
2446 let materialized = make_view(
2447 &db,
2448 @@ -316,11 +253,7 @@ pub async fn serve(
2449 ctx.insert("show_details", &preamble.file_path.is_none());
2450 ctx.insert(
2451 "nav_elements",
2452- &navigation::primary(
2453- "project",
2454- &preamble.collection_name,
2455- &preamble.repo_name,
2456- ),
2457+ &navigation::primary("project", &preamble.collection_name, &preamble.repo_name),
2458 );
2459 ctx.insert("commit_count", &materialized.commit_count);
2460 ctx.insert("latest_commit", &preamble.latest_commit);
2461 diff --git a/ayllu/src/web2/routes/xmpp.rs b/ayllu/src/web2/routes/xmpp.rs
2462index 749bdce..7131691 100644
2463--- a/ayllu/src/web2/routes/xmpp.rs
2464+++ b/ayllu/src/web2/routes/xmpp.rs
2465 @@ -6,11 +6,10 @@ use serde::{Deserialize, Serialize};
2466
2467 use crate::config::Config;
2468 use crate::web2::error::Error;
2469- use crate::web2::extractors::config::ConfigReader;
2470- use crate::web2::middleware::rpc_initiator::{Initiator, Kind as InitiatorKind};
2471+ use crate::web2::middleware::rpc_initiator::Initiator;
2472 use crate::web2::middleware::template::Template;
2473 use crate::web2::navigation;
2474- use ayllu_api::xmpp_capnp::server::Client as XmppClient;
2475+ use ayllu_rpc::tarpc::context;
2476
2477 #[derive(Debug, Serialize)]
2478 struct ChannelWithStats {
2479 @@ -34,23 +33,9 @@ pub async fn channels(
2480 ) -> Result<Html<String>, Error> {
2481 ctx.insert("title", "Discussions");
2482 ctx.insert("nav_elements", &navigation::global("xmpp", true));
2483- let xmpp_client = initiator.client(InitiatorKind::Xmpp).unwrap();
2484- let channels = xmpp_client
2485- .invoke(move |c: XmppClient| async move {
2486- let mut channels: Vec<ChannelWithStats> = Vec::new();
2487- let req = c.stats_request();
2488- let stats = req.send().promise.await?;
2489- for stat in stats.get()?.get_stats()? {
2490- channels.push(ChannelWithStats {
2491- name: stat.get_name()?.to_string().unwrap(),
2492- n_users: stat.get_users_online(),
2493- n_messages: stat.get_n_messages(),
2494- online: true,
2495- })
2496- }
2497- Ok(channels)
2498- })
2499- .await?;
2500+ let client = initiator.xmpp.unwrap();
2501+ // FIXME
2502+ let channels = client.stats(context::current(), vec![]).await??;
2503 ctx.insert("channels", &channels);
2504 let body = templates.render("channels.html", &ctx)?;
2505 Ok(Html(body))
2506 @@ -65,35 +50,21 @@ pub struct ChannelParams {
2507 pub async fn channel(
2508 Path(params): Path<ChannelParams>,
2509 Extension(_cfg): Extension<Config>,
2510- ConfigReader(config): ConfigReader,
2511 Extension((templates, mut ctx)): Extension<Template>,
2512 Extension(initiator): Extension<Initiator>,
2513 ) -> Result<Html<String>, Error> {
2514 ctx.insert("title", "lists");
2515 ctx.insert("nav_elements", &navigation::global("xmpp", true));
2516 ctx.insert("channel", &params.channel);
2517- let xmpp_client = initiator.client(InitiatorKind::Xmpp).unwrap();
2518- let messages = xmpp_client
2519- .invoke(move |c: XmppClient| async move {
2520- let mut xmpp_messages: Vec<XmppMessage> = Vec::new();
2521- let mut req = c.messages_request();
2522- req.get().set_channel_name(params.channel.as_str().into());
2523- req.get().set_limit(config.items_per_page as i64);
2524- if let Some(id) = params.last_message {
2525- req.get().set_last_message(id.as_str().into());
2526- }
2527- let messages = req.send().promise.await?;
2528- for message in messages.get()?.get_messages()? {
2529- xmpp_messages.push(XmppMessage {
2530- id: message.get_message_id()?.to_string().unwrap(),
2531- nickname: message.get_nickname()?.to_string().unwrap(),
2532- timestamp: message.get_timestamp()?.to_string().unwrap(),
2533- body: message.get_body()?.to_string().unwrap(),
2534- })
2535- }
2536- Ok(xmpp_messages)
2537- })
2538- .await?;
2539+ let client = initiator.xmpp.unwrap();
2540+ let messages = client
2541+ .messages(
2542+ context::current(),
2543+ params.channel,
2544+ params.last_message,
2545+ 1000,
2546+ )
2547+ .await??;
2548 ctx.insert("messages", &messages);
2549 // ctx.insert("lists", &cfg.mail.unwrap().lists);
2550 let body = templates.render("channel.html", &ctx)?;
2551 diff --git a/crates/api/Cargo.toml b/crates/api/Cargo.toml
2552index c83cb5f..b849c96 100644
2553--- a/crates/api/Cargo.toml
2554+++ b/crates/api/Cargo.toml
2555 @@ -6,10 +6,6 @@ edition = "2021"
2556 # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
2557
2558 [dependencies]
2559- capnp = "0.18.1"
2560- capnp-futures = "0.18.0"
2561- capnp-rpc = "0.18.0"
2562 serde = { version = "1.0", features = ["derive"] }
2563-
2564- [build-dependencies]
2565- capnpc = "0.18.0"
2566+ tarpc = { version = "0.34.0", features = ["full"] }
2567+ thiserror = "1.0.58"
2568 diff --git a/crates/api/README.md b/crates/api/README.md
2569index e119d65..7eeb16a 100644
2570--- a/crates/api/README.md
2571+++ b/crates/api/README.md
2572 @@ -1,6 +1,6 @@
2573 # api
2574
2575- capnp plugin interfaces for ayllu.
2576+ Tarpc plugin interfaces for ayllu.
2577
2578 There are two types of plugins:
2579
2580 diff --git a/crates/api/build.rs b/crates/api/build.rs
2581deleted file mode 100644
2582index b491b06..0000000
2583--- a/crates/api/build.rs
2584+++ /dev/null
2585 @@ -1,10 +0,0 @@
2586- fn main() {
2587- capnpc::CompilerCommand::new()
2588- .src_prefix("v1")
2589- .file("v1/build.capnp")
2590- .file("v1/jobs.capnp")
2591- .file("v1/mail.capnp")
2592- .file("v1/xmpp.capnp")
2593- .run()
2594- .expect("compiling schema");
2595- }
2596 diff --git a/crates/api/src/build.rs b/crates/api/src/build.rs
2597new file mode 100644
2598index 0000000..ef6ab84
2599--- /dev/null
2600+++ b/crates/api/src/build.rs
2601 @@ -0,0 +1,76 @@
2602+ use serde::{Deserialize, Serialize};
2603+ use tarpc::service;
2604+
2605+ use crate::error::ApiError;
2606+
2607+ #[derive(Debug, Deserialize, Serialize)]
2608+ /// Defines some kind of event that occurs within a repository
2609+ pub enum Kind {
2610+ Commit,
2611+ Tag,
2612+ }
2613+
2614+ #[derive(Debug, Deserialize, Serialize)]
2615+ pub struct Event {
2616+ pub repository_url: String,
2617+ pub commit_hash: String,
2618+ pub kind: Kind,
2619+ }
2620+
2621+ #[derive(Debug, Deserialize, Serialize)]
2622+ /// array of samples taken to construct informative charts in the UI
2623+ pub struct Samples {
2624+ load_1m: Vec<f32>,
2625+ load_5m: Vec<f32>,
2626+ load_15m: Vec<f32>,
2627+ disk_total_bytes: Vec<i64>,
2628+ disk_free_bytes: Vec<i64>,
2629+ net_sent_bytes: Vec<i64>,
2630+ net_received_bytes: Vec<i64>,
2631+ net_sent_packets: Vec<i64>,
2632+ net_received_packets: Vec<i64>,
2633+ mem_total_bytes: Vec<i64>,
2634+ mem_free_bytes: Vec<i64>,
2635+ mem_available_bytes: Vec<i64>,
2636+ }
2637+
2638+ #[derive(Debug, Deserialize, Serialize)]
2639+ pub struct Step {
2640+ pub id: i64,
2641+ pub input: String,
2642+ pub shell: String,
2643+ }
2644+
2645+ #[derive(Debug, Deserialize, Serialize)]
2646+ pub struct Job {
2647+ pub id: i64,
2648+ pub name: String,
2649+ pub steps: Vec<Step>,
2650+ }
2651+
2652+ #[derive(Debug, Deserialize, Serialize)]
2653+ pub struct Manifest {
2654+ pub id: i64,
2655+ repository_url: String,
2656+ git_hash: String,
2657+ /// DOT representation of DAG rendered via manifest
2658+ dot_chart: String,
2659+ created_at: i64,
2660+ started_at: i64,
2661+ finished_at: i64,
2662+ success: bool,
2663+ // not included on manfest list calls otherwise ordered in the way
2664+ // that they are evaluated via dependency graph
2665+ jobs: Vec<Job>,
2666+ }
2667+
2668+ #[service]
2669+ /// running service that will respond to events
2670+ pub trait Server {
2671+ /// submit a new event which may result in manifests being run
2672+ async fn submit(event: Event) -> Result<(), ApiError>;
2673+ /// list manifests available on the worker
2674+ async fn list_manifests(offset: i64, limit: i64) -> Result<Vec<Manifest>, ApiError>;
2675+ /// read a manifest including it's status and all job output
2676+ async fn read_manifest(manifest_id: String) -> Result<Manifest, ApiError>;
2677+ }
2678 diff --git a/crates/api/src/error.rs b/crates/api/src/error.rs
2679new file mode 100644
2680index 0000000..15298d6
2681--- /dev/null
2682+++ b/crates/api/src/error.rs
2683 @@ -0,0 +1,10 @@
2684+ use serde::{Deserialize, Serialize};
2685+ use thiserror::Error;
2686+
2687+ #[derive(Error, Debug, Serialize, Deserialize)]
2688+ pub enum ApiError {
2689+ #[error("generic api error occurred")]
2690+ Message(String),
2691+ #[error("resource not found")]
2692+ NotFound(String),
2693+ }
2694 diff --git a/crates/api/src/jobs.rs b/crates/api/src/jobs.rs
2695new file mode 100644
2696index 0000000..00e07b9
2697--- /dev/null
2698+++ b/crates/api/src/jobs.rs
2699 @@ -0,0 +1,53 @@
2700+ use std::fmt::Display;
2701+
2702+ use serde::{Deserialize, Serialize};
2703+ use tarpc::service;
2704+
2705+ #[derive(Debug, Deserialize, Serialize, Clone)]
2706+ /// defines the type of job being ran
2707+ pub enum Kind {
2708+ /// store commit level contribution data
2709+ Contributors,
2710+ /// count lines of code
2711+ Cloc,
2712+ }
2713+
2714+ impl Display for Kind {
2715+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
2716+ match self {
2717+ Kind::Contributors => f.write_str("Contributors"),
2718+ Kind::Cloc => f.write_str("Cloc"),
2719+ }
2720+ }
2721+ }
2722+ impl From<String> for Kind {
2723+ fn from(value: String) -> Self {
2724+ match value.as_str() {
2725+ "CONTRIBUTORS" | "Contributors" | "contributors" => Kind::Contributors,
2726+ "CLOC" | "Cloc" | "cloc" => Kind::Cloc,
2727+ _ => panic!("invalid kind"),
2728+ }
2729+ }
2730+ }
2731+
2732+ #[derive(Debug, Deserialize, Serialize, Clone)]
2733+ /// generic definition of a "job" with a related "kind"
2734+ pub struct Job {
2735+ pub id: i64,
2736+ pub created_at: i64,
2737+ pub kind: Kind,
2738+ pub repo_path: String,
2739+ pub runtime: i64,
2740+ pub success: bool,
2741+ pub commits: i64,
2742+ }
2743+
2744+ #[service]
2745+ pub trait Server {
2746+ /// Submit a new job to be run in the background
2747+ async fn submit(kind: Kind, repo_path: String);
2748+ /// List submitted and complete jobs
2749+ async fn list(repo_path: Option<String>) -> Vec<Job>;
2750+ /// Purge all jobs associated with the given repository
2751+ async fn purge(repo_path: String);
2752+ }
2753 diff --git a/crates/api/src/lib.rs b/crates/api/src/lib.rs
2754index 3721061..1daadf3 100644
2755--- a/crates/api/src/lib.rs
2756+++ b/crates/api/src/lib.rs
2757 @@ -1,76 +1,5 @@
2758- pub mod models {
2759-
2760- use crate::jobs_capnp;
2761- use std::fmt;
2762-
2763- use serde::{Deserialize, Serialize};
2764-
2765- #[derive(Serialize, Deserialize, Clone, Debug)]
2766- pub enum Kind {
2767- Contributors,
2768- Cloc,
2769- }
2770-
2771- impl fmt::Display for Kind {
2772- fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
2773- match self {
2774- Kind::Contributors => write!(f, "CONTRIBUTORS"),
2775- Kind::Cloc => write!(f, "CLOC"),
2776- }
2777- }
2778- }
2779-
2780- impl From<String> for Kind {
2781- fn from(value: String) -> Self {
2782- match value.as_str() {
2783- "CONTRIBUTORS" | "Contributors" | "contributors" => Kind::Contributors,
2784- "CLOC" | "Cloc" | "cloc" => Kind::Cloc,
2785- _ => panic!("invalid kind"),
2786- }
2787- }
2788- }
2789-
2790- impl From<jobs_capnp::Kind> for Kind {
2791- fn from(value: jobs_capnp::Kind) -> Self {
2792- match value {
2793- jobs_capnp::Kind::Contributors => Kind::Contributors,
2794- jobs_capnp::Kind::Cloc => Kind::Cloc,
2795- }
2796- }
2797- }
2798-
2799- impl From<Kind> for jobs_capnp::Kind {
2800- fn from(val: Kind) -> Self {
2801- match val {
2802- Kind::Contributors => jobs_capnp::Kind::Contributors,
2803- Kind::Cloc => jobs_capnp::Kind::Cloc,
2804- }
2805- }
2806- }
2807-
2808- #[derive(Serialize, Deserialize, Clone, Debug)]
2809- pub struct Job {
2810- pub id: i64,
2811- pub created_at: i64,
2812- pub repo_path: String,
2813- pub runtime: i64,
2814- pub kind: Kind,
2815- pub commits: i64,
2816- }
2817- }
2818-
2819- pub mod jobs_capnp {
2820- include!(concat!(env!("OUT_DIR"), "/jobs_capnp.rs"));
2821- }
2822-
2823- pub mod mail_capnp {
2824- include!(concat!(env!("OUT_DIR"), "/mail_capnp.rs"));
2825- }
2826-
2827- pub mod xmpp_capnp {
2828- include!(concat!(env!("OUT_DIR"), "/xmpp_capnp.rs"));
2829- }
2830-
2831- pub mod build_capnp {
2832- include!(concat!(env!("OUT_DIR"), "/build_capnp.rs"));
2833- }
2834+ pub mod build;
2835+ pub mod error;
2836+ pub mod jobs;
2837+ pub mod mail;
2838+ pub mod xmpp;
2839 diff --git a/crates/api/src/mail.rs b/crates/api/src/mail.rs
2840new file mode 100644
2841index 0000000..963822c
2842--- /dev/null
2843+++ b/crates/api/src/mail.rs
2844 @@ -0,0 +1,66 @@
2845+ use serde::{Deserialize, Serialize};
2846+ use tarpc::service;
2847+
2848+ use crate::error::ApiError;
2849+
2850+ #[derive(Debug, Deserialize, Serialize)]
2851+ /// A single email message
2852+ pub struct Message {
2853+ pub id: i64,
2854+ pub message_id: String,
2855+ /// epoch time
2856+ pub timestamp: u64,
2857+ pub from: String,
2858+ pub address: String,
2859+ /// entire email including headers
2860+ pub body: String,
2861+ /// text of just the email message
2862+ pub text: String,
2863+ /// true if the message is a patch
2864+ pub is_patch: bool,
2865+ }
2866+
2867+ #[derive(Debug, Deserialize, Serialize)]
2868+ pub struct Thread {
2869+ /// initial message sent to the mailing list
2870+ pub first: Message,
2871+ /// number of follow up messages associated with thread
2872+ pub n_replies: i64,
2873+ /// true if the thread contains one or more patch
2874+ pub has_patch: bool,
2875+ }
2876+
2877+ #[derive(Debug, Deserialize, Serialize)]
2878+ pub struct MailingList {
2879+ /// unique list identifier
2880+ pub id: String,
2881+ /// friendly mailing list name
2882+ pub name: String,
2883+ /// address to send "commands" to
2884+ pub request_address: String,
2885+ /// RFC5322 email address
2886+ pub address: String,
2887+ /// description of the mailing list purpose
2888+ pub description: String,
2889+ /// Free form array of tags
2890+ pub topics: Vec<String>,
2891+ }
2892+
2893+ #[service]
2894+ pub trait Server {
2895+ /// return all managed mailing lists
2896+ async fn lists() -> Result<Vec<MailingList>, ApiError>;
2897+ /// list all of the threads associated with a mailing list
2898+ async fn list_threads(id: String, offset: i64, limit: i64) -> Result<Vec<Thread>, ApiError>;
2899+ /// list all of the responses associated with a post starting from messageId
2900+ async fn read_thread(
2901+ id: String,
2902+ message_id: String,
2903+ offset: i64,
2904+ limit: i64,
2905+ ) -> Result<Vec<Message>, ApiError>;
2906+ /// read a single post
2907+ async fn read_post(id: String, message_id: String) -> Result<Message, ApiError>;
2908+ /// export one or more messages in mbox format
2909+ async fn export(id: String, message_id: Option<String>, as_thread: bool) -> Result<Vec<u8>, ApiError>;
2910+ }
2911 diff --git a/crates/api/src/xmpp.rs b/crates/api/src/xmpp.rs
2912new file mode 100644
2913index 0000000..c827b23
2914--- /dev/null
2915+++ b/crates/api/src/xmpp.rs
2916 @@ -0,0 +1,39 @@
2917+ use serde::{Deserialize, Serialize};
2918+ use tarpc::service;
2919+
2920+ use crate::error::ApiError;
2921+
2922+ #[derive(Debug, Deserialize, Serialize)]
2923+ pub struct ChannelStat {
2924+ /// name of the channel
2925+ pub name: String,
2926+ /// number of users currently online
2927+ pub users_online: i64,
2928+ /// number of messages ever sent to the channel since we've benen recording
2929+ pub num_messages: i64,
2930+ }
2931+
2932+ #[derive(Debug, Deserialize, Serialize)]
2933+ /// chat message sent in the channel
2934+ pub struct Message {
2935+ /// unique message identifier
2936+ pub message_id: String,
2937+ /// tiemstamp the message was sent
2938+ pub timestamp: String,
2939+ /// user nickname as part of the channel
2940+ pub nickname: String,
2941+ /// body containing the message text
2942+ pub body: String,
2943+ }
2944+
2945+ #[service]
2946+ pub trait Server {
2947+ /// return stats for the given channel names
2948+ async fn stats(channels: Vec<String>) -> Result<Vec<ChannelStat>, ApiError>;
2949+ /// return public messages for the channel
2950+ async fn messages(
2951+ channel_name: String,
2952+ last_message: Option<String>,
2953+ limit: i64,
2954+ ) -> Result<Vec<Message>, ApiError>;
2955+ }
2956 diff --git a/crates/api/v1/build.capnp b/crates/api/v1/build.capnp
2957deleted file mode 100644
2958index 4124839..0000000
2959--- a/crates/api/v1/build.capnp
2960+++ /dev/null
2961 @@ -1,69 +0,0 @@
2962- @0xeb9468d06a22ac45;
2963-
2964- # defines some kind of event that occurs against a repository
2965- enum Kind {
2966- commit @0;
2967- tag @1;
2968- }
2969-
2970- struct Event {
2971- repositoryUrl @0 :Text;
2972- commitHash @1 :Text;
2973- kind @2 :Kind;
2974- }
2975-
2976- # array of samples taken to construct informative charts in the UI
2977- struct Samples {
2978- load1m @0 :List(Float32);
2979- load5m @1 :List(Float32);
2980- load15m @2 :List(Float32);
2981- diskTotalBytes @3 :List(Int64);
2982- diskFreeBytes @4 :List(Int64);
2983- netSentBytes @5 :List(Int64);
2984- netReceivedBytes @6 :List(Int64);
2985- netSentPackets @7 :List(Int64);
2986- netReceivedPackets @8 :List(Int64);
2987- memTotalBytes @9 :List(Int64);
2988- memFreeBytes @10 :List(Int64);
2989- memAvailableBytes @11 :List(Int64);
2990- }
2991-
2992- struct Step {
2993- id @0 :Int64;
2994- input @1 :Text;
2995- shell @2 :Text;
2996- }
2997-
2998- struct Job {
2999- id @0 :Int64;
3000- name @1 :Text;
3001- # ordered in the way they were evaluated via dependency graph
3002- steps @2 :List(Step);
3003- }
3004-
3005- struct Manifest {
3006- id @0 :Int64;
3007- repositoryUrl @1 :Text;
3008- gitHash @2 :Text;
3009- # DOT representation of DAG rendered via manifest
3010- dotChart @3 :Text;
3011- # Resource usage samples taken during build
3012- samples @4 :Samples;
3013- createdAt @5 :Int64;
3014- startedAt @6 :Int64;
3015- finishedAt @7 :Int64;
3016- success @8 :Bool;
3017- # not included on manfest list calls otherwise ordered in the way
3018- # that they are evaluated via dependency graph
3019- jobs @9 :List(Job);
3020- }
3021-
3022- # running service that will respond to events
3023- interface Worker {
3024- # submit a new event which may result in manifests being run
3025- submit @0 (event: Event);
3026- # list manifests available on the worker
3027- listManifests @1 (offset: Int64, limit: Int64) -> (manifests: List(Manifest));
3028- # read a manifest including it's status and all job output
3029- readManifest @2 (manifest_id: Text) -> (manifest: Manifest);
3030- }
3031 diff --git a/crates/api/v1/jobs.capnp b/crates/api/v1/jobs.capnp
3032deleted file mode 100644
3033index 50a41ae..0000000
3034--- a/crates/api/v1/jobs.capnp
3035+++ /dev/null
3036 @@ -1,30 +0,0 @@
3037- @0xaa252f70d9fb4cd5;
3038-
3039- # defines the type of job being ran
3040- enum Kind {
3041- # store commit level contribution data
3042- contributors @0;
3043- # count lines of code
3044- cloc @1;
3045- }
3046-
3047- # generic definition of a "job" with a related "kind"
3048- struct Job {
3049- id @0 :Int64;
3050- createdAt @1 :Int64;
3051- kind @2 :Kind;
3052- repoPath @3 :Text;
3053- runtime @4 :Int64;
3054- success @5 :Bool;
3055- commits @6 :Int64;
3056- }
3057-
3058- interface Server {
3059- # submit a new job of associated kind for the given repository to be ran in
3060- # the background.
3061- submit @0 (kind :Kind, repo_path :Text);
3062- # list submitted and complete jobs
3063- list @1 (repo_path :Text) -> (jobs :List(Job));
3064- # purge all jobs and computations associated with the given repository
3065- purge @2 (repo_path :Text);
3066- }
3067 diff --git a/crates/api/v1/mail.capnp b/crates/api/v1/mail.capnp
3068deleted file mode 100644
3069index 8e7c791..0000000
3070--- a/crates/api/v1/mail.capnp
3071+++ /dev/null
3072 @@ -1,55 +0,0 @@
3073- @0xe282ac1f72de3195;
3074-
3075- # a single email
3076- struct Message {
3077- # primary key of the message in the mailpot db
3078- id @0 :Int64;
3079- messageId @1 :Text;
3080- timestamp @2 :Int64;
3081- from @3 :Text;
3082- address @4 :Text;
3083- subject @5 :Text;
3084- # entire email including headers
3085- body @6 :Text;
3086- # text of just the email message
3087- text @7 :Text;
3088- # true if the message is a patch
3089- isPatch @8 :Bool;
3090- }
3091-
3092- struct Thread {
3093- # initial message sent to the mailing list
3094- first @0 :Message;
3095- # number of follow up messages associated with thread
3096- nReplies @1 :Int64;
3097- # true if the thread contains one or more patch
3098- hasPatch @2 :Bool;
3099- }
3100-
3101- struct MailingList {
3102- # unique list identifier
3103- id @0 :Text;
3104- # freindly mailing list name
3105- name @1 :Text;
3106- # address to send "commands" to
3107- requestAddress @2 :Text;
3108- # RFC 5322 email address
3109- address @3 :Text;
3110- # description of the mailing list purpose
3111- description @4 :Text;
3112- # free form array of tags
3113- topics @5 :List(Text);
3114- }
3115-
3116- interface Server {
3117- # return all managed mailing lists
3118- lists @0 () -> (lists: List(MailingList));
3119- # list all of the threads associated with a mailing list
3120- listThreads @1 (id: Text, offset: Int64, limit: Int64) -> (threads: List(Thread));
3121- # list all of the responses associated with a post starting from messageId
3122- readThread @2 (id: Text, messageId: Text, offset: Int64, limit: Int64) -> (thread: List(Message));
3123- # read a single post
3124- readPost @3 (id: Text, messageId: Text) -> Message;
3125- # export one or more messages in mbox format
3126- export @4 (id: Text, messageId: Text, asThread: Bool) -> (thread :Data);
3127- }
3128 diff --git a/crates/api/v1/xmpp.capnp b/crates/api/v1/xmpp.capnp
3129deleted file mode 100644
3130index bc938be..0000000
3131--- a/crates/api/v1/xmpp.capnp
3132+++ /dev/null
3133 @@ -1,29 +0,0 @@
3134- @0xe9c7e2def07a9ac5;
3135-
3136- struct ChannelStat {
3137- # name of the channel
3138- name @0 :Text;
3139- # number of users currently active in channel
3140- usersOnline @1 :Int64;
3141- # number of messages ever sent in the channel
3142- nMessages @2 :Int64;
3143- }
3144-
3145- # chat message sent in a chnanel
3146- struct Message {
3147- # unique message identifier
3148- messageId @0 :Text;
3149- # time the message was sent
3150- timestamp @1 :Text;
3151- # users nickname as part of the channel
3152- nickname @2 :Text;
3153- # body containing the message text
3154- body @3 :Text;
3155- }
3156-
3157- interface Server {
3158- # return stats for the given channel names
3159- stats @0 (channels: List(Text)) -> (stats: List(ChannelStat));
3160- # return public messages from the channel
3161- messages @1 (channel_name: Text, last_message: Text, limit: Int64) -> (messages: List(Message));
3162- }
3163 diff --git a/crates/git/src/config.rs b/crates/git/src/config.rs
3164index 17f2ee8..aa5794c 100644
3165--- a/crates/git/src/config.rs
3166+++ b/crates/git/src/config.rs
3167 @@ -1,3 +1,5 @@
3168+ use std::fmt::Display;
3169+
3170 use git2::Config as GitConfig;
3171 use serde::Serialize;
3172
3173 @@ -19,6 +21,15 @@ pub enum ChatKind {
3174 Irc,
3175 }
3176
3177+ impl Display for ChatKind {
3178+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
3179+ match self {
3180+ ChatKind::Xmpp => f.write_str("Xmpp"),
3181+ ChatKind::Irc => f.write_str("Irc"),
3182+ }
3183+ }
3184+ }
3185+
3186 #[derive(Serialize, Clone, Debug)]
3187 pub struct ChatLink {
3188 pub url: String,
3189 diff --git a/crates/rpc/Cargo.toml b/crates/rpc/Cargo.toml
3190index 3133248..810eab6 100644
3191--- a/crates/rpc/Cargo.toml
3192+++ b/crates/rpc/Cargo.toml
3193 @@ -7,13 +7,8 @@ edition = "2021"
3194
3195 [dependencies]
3196 async-trait = "0.1.74"
3197- capnp = "0.18.1"
3198- capnp-rpc = "0.18.0"
3199- capnpc = "0.18.0"
3200 futures = "0.3.28"
3201+ tarpc = { version = "0.34.0", features = ["full"] }
3202 tokio = { version = "1.33.0", features = ["full"] }
3203 tokio-util = { version = "0.7.9", features = ["compat"] }
3204 tracing = { version = "0.1.40", features = ["log"] }
3205-
3206- [build-dependencies]
3207- capnpc = "0.18.0"
3208 diff --git a/crates/rpc/README.md b/crates/rpc/README.md
3209index 88d593a..8fb3e29 100644
3210--- a/crates/rpc/README.md
3211+++ b/crates/rpc/README.md
3212 @@ -1,3 +1,3 @@
3213 # rpc
3214
3215- capnp helper code to reduce boilerplate
3216+ Helper code to reduce boilerplate
3217 diff --git a/crates/rpc/build.rs b/crates/rpc/build.rs
3218deleted file mode 100644
3219index 4c3beee..0000000
3220--- a/crates/rpc/build.rs
3221+++ /dev/null
3222 @@ -1,7 +0,0 @@
3223- fn main() {
3224- capnpc::CompilerCommand::new()
3225- .src_prefix("tests")
3226- .file("tests/hello.capnp")
3227- .run()
3228- .expect("compiling schema");
3229- }
3230 diff --git a/crates/rpc/src/lib.rs b/crates/rpc/src/lib.rs
3231index 618b648..752b26c 100644
3232--- a/crates/rpc/src/lib.rs
3233+++ b/crates/rpc/src/lib.rs
3234 @@ -1,218 +1,24 @@
3235- use std::error::Error as StdError;
3236- use std::fmt::Display;
3237- use std::fs::{metadata, remove_file};
3238+ use std::fs::remove_file;
3239 use std::future::Future;
3240 use std::io::Error as IoError;
3241- use std::marker::PhantomData;
3242 use std::path::Path;
3243
3244- use capnp::capability::Client as CapabilityClient;
3245- pub use capnp::capability::FromClientHook;
3246- use capnp::Error as CapnpError;
3247- use capnp_rpc::{rpc_twoparty_capnp::Side, twoparty::VatNetwork, RpcSystem};
3248- use futures::AsyncReadExt;
3249- use tokio::net::{UnixListener, UnixStream};
3250- use tokio::runtime::Handle;
3251- use tokio::task::{spawn_blocking, LocalSet};
3252- use tokio_util::compat::TokioAsyncReadCompatExt;
3253- use tracing::log;
3254+ // TODO: due to https://github.com/google/tarpc/issues/421 it's not currently
3255+ // possible to generalize serving into a helper function. For now I am only
3256+ // re-exporting tarpc for ease of use in downstream server implementations.
3257
3258- pub enum Kind {
3259- Xmpp,
3260- Mail,
3261- }
3262-
3263- #[derive(Debug)]
3264- pub enum Error {
3265- CapnpError(CapnpError),
3266- IoError(IoError),
3267- }
3268-
3269- impl StdError for Error {
3270- fn source(&self) -> Option<&(dyn StdError + 'static)> {
3271- match self {
3272- Error::CapnpError(e) => Some(e),
3273- Error::IoError(e) => Some(e),
3274- }
3275- }
3276- }
3277-
3278- impl Display for Error {
3279- fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
3280- match self {
3281- Error::CapnpError(e) => e.fmt(f),
3282- Error::IoError(e) => e.fmt(f),
3283- }
3284- }
3285- }
3286-
3287- impl From<IoError> for Error {
3288- fn from(value: IoError) -> Self {
3289- Error::IoError(value)
3290- }
3291- }
3292-
3293- impl From<CapnpError> for Error {
3294- fn from(value: CapnpError) -> Self {
3295- Error::CapnpError(value)
3296- }
3297- }
3298-
3299- /// reduce some boilerplate required to setup capnp servers and clients
3300- pub struct Server<C, S> {
3301- server: S,
3302- socket_path: String,
3303- client_type: PhantomData<C>,
3304- }
3305-
3306- impl<C, S> Server<C, S>
3307- where
3308- C: capnp::capability::FromServer<S> + Clone,
3309- {
3310- pub fn new(socket_path: &str, server: S) -> Self {
3311- Server {
3312- server,
3313- socket_path: socket_path.to_string(),
3314- client_type: PhantomData {},
3315- }
3316- }
3317-
3318- pub async fn serve(self) -> Result<(), Error> {
3319- let socket_path = Path::new(&self.socket_path);
3320- if metadata(socket_path).is_ok() {
3321- remove_file(socket_path)?;
3322- };
3323- // TODO: need to support both sockets and TCP connections for build
3324- // servers since they will cross network boundaries on QEMU, etc.
3325- log::info!("listening @ {}", self.socket_path);
3326- let listener = UnixListener::bind(socket_path)?;
3327- let client: C = capnp_rpc::new_client(self.server);
3328- loop {
3329- log::debug!("starting rpc loop");
3330- let (stream, _) = listener.accept().await?;
3331- let (reader, writer) = TokioAsyncReadCompatExt::compat(stream).split();
3332- let network = VatNetwork::new(reader, writer, Side::Server, Default::default());
3333- let next = CapabilityClient::new(client.clone().into_client_hook());
3334- let rpc_system = RpcSystem::new(Box::new(network), Some(next));
3335- tokio::task::spawn_local(rpc_system);
3336- log::debug!("rpc request finished");
3337- }
3338- }
3339- }
3340-
3341- #[derive(Clone)]
3342- pub struct Client {
3343- socket_path: String,
3344- }
3345-
3346- impl Client {
3347- pub fn new(socket_path: &str) -> Self {
3348- Client {
3349- socket_path: socket_path.to_string(),
3350- }
3351- }
3352- pub async fn invoke<C, T, F, Fut>(self, f: F) -> Result<T, Error>
3353- where
3354- C: capnp::capability::FromClientHook,
3355- T: Send + 'static,
3356- F: (FnOnce(C) -> Fut) + Send + 'static,
3357- Fut: Future<Output = Result<T, Error>>,
3358- {
3359- let result = spawn_blocking(move || {
3360- Handle::current().block_on(async move {
3361- LocalSet::new()
3362- .run_until(async move {
3363- log::info!("connecting to rpc service: @ {}", self.socket_path);
3364- let stream = UnixStream::connect(self.socket_path).await?;
3365- let (reader, writer) = TokioAsyncReadCompatExt::compat(stream).split();
3366- let rpc_network = Box::new(VatNetwork::new(
3367- reader,
3368- writer,
3369- Side::Client,
3370- Default::default(),
3371- ));
3372- let mut rpc_system = RpcSystem::new(rpc_network, None);
3373- let jobs_client: C = rpc_system.bootstrap(Side::Server);
3374- tokio::task::spawn_local(rpc_system);
3375- f(jobs_client).await
3376- })
3377- .await
3378- })
3379- })
3380- .await
3381- .unwrap()?;
3382- log::info!("finished rpc request");
3383- Ok(result)
3384- }
3385- }
3386+ pub use futures;
3387+ pub use tarpc;
3388
3389- pub mod helpers {
3390- pub fn string(result: capnp::Result<capnp::text::Reader>) -> String {
3391- result.map_or(String::new(), |result| result.to_string().unwrap())
3392- }
3393+ /// Spawn a future in a separate thread
3394+ pub async fn spawn(fut: impl Future<Output = ()> + Send + 'static) {
3395+ tokio::spawn(fut);
3396 }
3397
3398- /// service for testing only
3399- #[allow(dead_code)]
3400- mod hello_capnp {
3401- include!(concat!(env!("OUT_DIR"), "/hello_capnp.rs"));
3402- }
3403-
3404- #[cfg(test)]
3405- mod tests {
3406- use super::*;
3407-
3408- use std::thread::spawn;
3409- use tokio::runtime::Builder;
3410- use tokio::task::LocalSet;
3411-
3412- #[derive(Clone)]
3413- struct TestImpl;
3414-
3415- impl hello_capnp::server::Server for TestImpl {
3416- fn greet(
3417- &mut self,
3418- params: hello_capnp::server::GreetParams,
3419- mut results: hello_capnp::server::GreetResults,
3420- ) -> capnp::capability::Promise<(), capnp::Error> {
3421- let name = params.get().unwrap().get_name().unwrap().to_string();
3422- results
3423- .get()
3424- .set_greeting(format!("Hello: {}", name.unwrap()).as_str().into());
3425- capnp::capability::Promise::ok(())
3426- }
3427- }
3428-
3429- #[test]
3430- fn test_client_call() {
3431- spawn(move || {
3432- let server_runtime = Builder::new_current_thread().enable_all().build().unwrap();
3433- server_runtime.block_on(async {
3434- let server = Server::<hello_capnp::server::Client, TestImpl>::new(
3435- "/tmp/rpc_test.sock",
3436- TestImpl,
3437- );
3438- LocalSet::new().run_until(server.serve()).await.unwrap()
3439- })
3440- });
3441- let client_runtime = Builder::new_current_thread().enable_all().build().unwrap();
3442- client_runtime.block_on(async {
3443- let client = Client::new("/tmp/rpc_test.sock");
3444- LocalSet::new()
3445- .run_until(async {
3446- let greeting = client
3447- .invoke(move |c: hello_capnp::server::Client| async move {
3448- let mut req = c.greet_request();
3449- req.get().set_name("fuubar".into());
3450- let response = req.send().promise.await.unwrap();
3451- let greeting = response.get().unwrap().get_greeting().unwrap();
3452- Ok(greeting.to_string().unwrap())
3453- })
3454- .await
3455- .unwrap();
3456- assert!(greeting == "Hello: fuubar")
3457- })
3458- .await;
3459- });
3460+ /// Initialize the socket for listening
3461+ pub fn init_socket(path: &Path) -> Result<(), IoError> {
3462+ if path.exists() {
3463+ remove_file(path)?;
3464 }
3465+ Ok(())
3466 }
3467 diff --git a/crates/rpc/tests/hello.capnp b/crates/rpc/tests/hello.capnp
3468deleted file mode 100644
3469index cfccf8c..0000000
3470--- a/crates/rpc/tests/hello.capnp
3471+++ /dev/null
3472 @@ -1,7 +0,0 @@
3473- # test file for capnp rpc helpers
3474- @0x9168d956160fde90;
3475-
3476- interface Server {
3477- # return a friendly greeting
3478- greet @0 (name: Text) -> (greeting :Text);
3479- }
3480 diff --git a/scripts/check_build_dependencies.sh b/scripts/check_build_dependencies.sh
3481index 8cc6dfd..cfb4893 100755
3482--- a/scripts/check_build_dependencies.sh
3483+++ b/scripts/check_build_dependencies.sh
3484 @@ -30,7 +30,6 @@ check_cmd "pkg-config"
3485 check_cmd "sassc"
3486 check_cmd "sqlx"
3487 check_cmd "sqlite3"
3488- check_cmd "capnpc"
3489 check_cmd "npm"
3490
3491 # check for openssl which annoyingly is required to build sqlx-cli if that is
3492 diff --git a/www/content/_index.md b/www/content/_index.md
3493index ea236ea..f2cabc5 100644
3494--- a/www/content/_index.md
3495+++ b/www/content/_index.md
3496 @@ -57,8 +57,7 @@ and/or cron to extend forge functionality.
3497
3498 #### Extensible Plugin System
3499
3500- An extensible CapnProto based plugin system allows adding large external
3501- integrations via RPC calls.
3502+ An extensible RPC based plugin system allows adding large external integrations.
3503
3504 </article>
3505
3506 diff --git a/www/content/docs/architecture.md b/www/content/docs/architecture.md
3507index 5eb2bdf..8dd6597 100644
3508--- a/www/content/docs/architecture.md
3509+++ b/www/content/docs/architecture.md
3510 @@ -8,7 +8,7 @@ weight = 0
3511
3512 It's basic architecture is that of a single binary providing a web interface to
3513 browse and interact with Git repositories as well as an RPC server based on
3514- [Cap'n Proto](https://capnproto.org/) with an embedded client. The RPC server
3515+ [tarpc](https://github.com/google/tarpc) with an embedded client. The RPC server
3516 running in the core Ayllu binary is called the `job_server` and it provides an
3517 interface to interact with Ayllu for administrators. Much of the functionality
3518 of Ayllu is implemented via `RPC extensions` which are minimal binaries that
3519 diff --git a/www/design.nomnom b/www/design.nomnom
3520new file mode 100644
3521index 0000000..6f755b6
3522--- /dev/null
3523+++ b/www/design.nomnom
3524 @@ -0,0 +1,51 @@
3525+ [reverse_proxy]
3526+
3527+ [reverse_proxy]<-->[Rudolfs (LFS API)]
3528+
3529+ [reverse_proxy]<-->[ayllu-core |
3530+ [axum http interface]
3531+ [sites static hosting]
3532+ [tarpc-server (job_server)]
3533+ [git repositories via libgit2]
3534+ [<database> sqlite]
3535+ ]
3536+
3537+ [ayllu-core] <--> [
3538+ ayllu-mail|
3539+ [tarpc-server]
3540+ [mailpot]
3541+ [postfix]
3542+ [<database> sqlite]
3543+ ] <-- SMTP [<actor> contributor-2]
3544+
3545+ [ayllu-core] <--> [
3546+ ayllu-xmpp |
3547+ [tarpc-server]
3548+ [chat interface/bot]
3549+ [<database> sqlite]
3550+ ] <-- XMPP [<actor>; contributor-2]
3551+
3552+ [ayllu-core] <--> [
3553+ ayllu-build |
3554+ [tarpc-server]
3555+ [<database> sqlite]
3556+ ]
3557+
3558+ [ayllu-core] <--> [ayllu-shell] <-- SSH [<actor> contributor]
3559+ [ayllu-core] <--> [ayllu-cli] <-- [<actor> admin]
3560+
3561+
3562+ [ayllu-build]<-->[
3563+ ayllu-builder-1 |
3564+ [<package>qemu-x86_64| tarpc-server | executor]
3565+ ]
3566+
3567+ [ayllu-build]<-->[
3568+ ayllu-builder-2 |
3569+ [<package>qemu-aarch64| tarpc-server | exectuor]
3570+ ]
3571+
3572+ [ayllu-build]<-->[
3573+ ayllu-builder-n |
3574+ [<package>lxc| tarpc-server | executor]
3575+ ] --> [ayllu-core]
3576 diff --git a/www/public/assets/architecture.svg b/www/public/assets/architecture.svg
3577index 2de2a37..e05c148 100644
3578--- a/www/public/assets/architecture.svg
3579+++ b/www/public/assets/architecture.svg
3580 @@ -1,35 +1,34 @@
3581- <svg version="1.1" baseProfile="full" width="1938.0" height="655.0" viewbox="0 0 1938 655" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:ev="http://www.w3.org/2001/xml-events">
3582- <desc >
3583- [reverse_proxy]
3584+ <svg version="1.1" baseProfile="full" width="1817.5" height="664.0" viewBox="0 0 1817.5 664" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:ev="http://www.w3.org/2001/xml-events">
3585+ <desc >[reverse_proxy]
3586
3587 [reverse_proxy]&lt;--&gt;[Rudolfs (LFS API)]
3588
3589 [reverse_proxy]&lt;--&gt;[ayllu-core |
3590- [axum http interface]
3591+ [axum http interface]
3592 [sites static hosting]
3593- [capnp-rpc-server (job_server)]
3594+ [tarpc-server (job_server)]
3595 [git repositories via libgit2]
3596 [&lt;database&gt; sqlite]
3597 ]
3598
3599 [ayllu-core] &lt;--&gt; [
3600 ayllu-mail|
3601- [capnp-rpc-server]
3602+ [tarpc-server]
3603 [mailpot]
3604 [postfix]
3605 [&lt;database&gt; sqlite]
3606 ] &lt;-- SMTP [&lt;actor&gt; contributor-2]
3607
3608 [ayllu-core] &lt;--&gt; [
3609- ayllu-xmpp |
3610- [capnp-rpc-server]
3611- [chat interface/bot]
3612+ ayllu-xmpp |
3613+ [tarpc-server]
3614+ [chat interface/bot]
3615 [&lt;database&gt; sqlite]
3616- ] &lt;-- XMPP [&lt;actor&gt; contributor-2]
3617+ ] &lt;-- XMPP [&lt;actor&gt;; contributor-2]
3618
3619 [ayllu-core] &lt;--&gt; [
3620- ayllu-build |
3621- [capnp-rpc-server]
3622+ ayllu-build |
3623+ [tarpc-server]
3624 [&lt;database&gt; sqlite]
3625 ]
3626
3627 @@ -38,553 +37,553 @@
3628
3629
3630 [ayllu-build]&lt;--&gt;[
3631- ayllu-builder-1 |
3632- [&lt;package&gt;qemu-x86_64| capnp-rpc-server | executor]
3633+ ayllu-builder-1 |
3634+ [&lt;package&gt;qemu-x86_64| tarpc-server | executor]
3635 ]
3636
3637 [ayllu-build]&lt;--&gt;[
3638- ayllu-builder-2 |
3639- [&lt;package&gt;qemu-aarch64| capnp-rpc-server | exectuor]
3640+ ayllu-builder-2 |
3641+ [&lt;package&gt;qemu-aarch64| tarpc-server | exectuor]
3642 ]
3643
3644 [ayllu-build]&lt;--&gt;[
3645- ayllu-builder-n |
3646- [&lt;package&gt;lxc| capnp-rpc-server | executor]
3647- ] --&gt; [ayllu-core]</desc>
3648+ ayllu-builder-n |
3649+ [&lt;package&gt;lxc| tarpc-server | executor]
3650+ ] --&gt; [ayllu-core]
3651+ </desc>
3652 <g stroke-width="1.0" text-align="left" font="12pt Helvetica, Arial, sans-serif" font-size="12pt" font-family="Helvetica" font-weight="normal" font-style="normal">
3653- <g font-family="Helvetica" font-size="12pt" font-weight="bold" font-style="normal" stroke-width="3.0" stroke-linejoin="round" stroke-linecap="round" stroke="#33322E">
3654- <g stroke="transparent" fill="transparent">
3655- <rect x="0.0" y="0.0" height="655.0" width="1938.0" stroke="none"></rect>
3656- </g>
3657+ <g font-family="Helvetica" font-size="12pt" font-weight="bold" font-style="normal" stroke-width="3.0" stroke-linejoin="round" stroke-linecap="round" stroke="#33322E">
3658+ <g stroke="transparent" fill="transparent">
3659+ <rect x="0.0" y="0.0" height="664.0" width="1817.5" stroke="none"></rect>
3660+ </g>
3661 <g transform="translate(8, 8)" fill="#33322E">
3662- <g transform="translate(20, 20)" fill="#33322E" font-family="Helvetica" font-size="12pt" font-weight="normal" font-style="normal">
3663- <g stroke-dasharray="6 6">
3664- <path d="M965.1 23.1 L713 51 L713 115.83333333333333 L713.0 115.8 " fill="none"></path>
3665- </g>
3666- <path d="M707.7 109.2 L713.0 115.8 L718.3 109.2 L713.0 122.5 Z"></path>
3667- <path d="M959.1 29.2 L965.1 23.1 L957.9 18.6 L971.8 22.4 Z"></path>
3668+ <g transform="translate(20, 20)" fill="#33322E" font-family="Helvetica" font-size="12pt" font-weight="normal" font-style="normal">
3669 <g stroke-dasharray="6 6">
3670- <path d="M1103.4 23.1 L1355.5 51 L1355.5 64.33333333333333 L1355.5 64.3 " fill="none"></path>
3671- </g>
3672- <path d="M1350.2 57.7 L1355.5 64.3 L1360.8 57.7 L1355.5 71.0 Z"></path>
3673- <path d="M1110.6 18.6 L1103.4 23.1 L1109.4 29.2 L1096.8 22.4 Z"></path>
3674+ <path d="M873.9 24.0 L631.5 52 L631.5 117.33333333333333 L631.5 117.3 " fill="none"></path>
3675+ </g>
3676+ <path d="M626.2 110.7 L631.5 117.3 L636.8 110.7 L631.5 124.0 Z"></path>
3677+ <path d="M867.9 30.1 L873.9 24.0 L866.6 19.5 L880.5 23.2 Z"></path>
3678 <g stroke-dasharray="6 6">
3679- <path d="M822.4 180.4 L261 225 L261 238.33333333333334 L261.0 238.3 " fill="none"></path>
3680- </g>
3681- <path d="M255.7 231.7 L261.0 238.3 L266.3 231.7 L261.0 245.0 Z"></path>
3682- <path d="M816.1 186.2 L822.4 180.4 L815.3 175.6 L829.0 179.9 Z"></path>
3683- <text x="758.0" y="489.4" stroke="none">SMTP</text>
3684+ <path d="M1012.1 24.0 L1254.5 52 L1254.5 65.33333333333333 L1254.5 65.3 " fill="none"></path>
3685+ </g>
3686+ <path d="M1249.2 58.7 L1254.5 65.3 L1259.8 58.7 L1254.5 72.0 Z"></path>
3687+ <path d="M1019.4 19.5 L1012.1 24.0 L1018.1 30.1 L1005.5 23.2 Z"></path>
3688 <g stroke-dasharray="6 6">
3689- <path d="M261.0 385.7 L261 399 L750 497.4263494967978 L750.0 497.4 " fill="none"></path>
3690- </g>
3691- <path d="M266.3 392.3 L261.0 385.7 L255.7 392.3 L261.0 379.0 Z"></path>
3692+ <path d="M740.9 184.6 L242 228 L242 241.33333333333334 L242.0 241.3 " fill="none"></path>
3693+ </g>
3694+ <path d="M236.7 234.7 L242.0 241.3 L247.3 234.7 L242.0 248.0 Z"></path>
3695+ <path d="M734.7 190.5 L740.9 184.6 L733.8 179.9 L747.5 184.1 Z"></path>
3696+ <text x="698.3" y="495.3" stroke="none">SMTP</text>
3697 <g stroke-dasharray="6 6">
3698- <path d="M926.9 206.0 L807.5 225 L807.5 238.33333333333334 L807.5 238.3 " fill="none"></path>
3699- </g>
3700- <path d="M802.2 231.7 L807.5 238.3 L812.8 231.7 L807.5 245.0 Z"></path>
3701- <path d="M921.1 212.4 L926.9 206.0 L919.5 201.8 L933.5 205.0 Z"></path>
3702- <text x="754.7" y="473.5" stroke="none">XMPP</text>
3703+ <path d="M242.0 390.7 L242 404 L690.25 503.2664359861592 L690.3 503.3 " fill="none"></path>
3704+ </g>
3705+ <path d="M247.3 397.3 L242.0 390.7 L236.7 397.3 L242.0 384.0 Z"></path>
3706 <g stroke-dasharray="6 6">
3707- <path d="M807.5 385.7 L807.5 399 L807.5 481.5 L807.5 481.5 " fill="none"></path>
3708- </g>
3709- <path d="M812.8 392.3 L807.5 385.7 L802.2 392.3 L807.5 379.0 Z"></path>
3710+ <path d="M858.5 209.1 L750.5 228 L750.5 241.33333333333334 L750.5 241.3 " fill="none"></path>
3711+ </g>
3712+ <path d="M745.2 234.7 L750.5 241.3 L755.8 234.7 L750.5 248.0 Z"></path>
3713+ <path d="M852.8 215.5 L858.5 209.1 L851.0 205.0 L865.0 208.0 Z"></path>
3714+ <text x="695.6" y="480.0" stroke="none">XMPP</text>
3715 <g stroke-dasharray="6 6">
3716- <path d="M1276.2 209.5 L1259 225 L1259 238.33333333333334 L1259.0 238.3 " fill="none"></path>
3717- </g>
3718- <path d="M1253.7 231.7 L1259.0 238.3 L1264.3 231.7 L1259.0 245.0 Z"></path>
3719- <path d="M1274.9 217.9 L1276.2 209.5 L1267.7 210.0 L1281.2 205.0 Z"></path>
3720+ <path d="M750.5 390.7 L750.5 404 L748.4375 488 L748.4 488.0 " fill="none"></path>
3721+ </g>
3722+ <path d="M755.8 397.3 L750.5 390.7 L745.2 397.3 L750.5 384.0 Z"></path>
3723 <g stroke-dasharray="6 6">
3724- <path d="M1595.6 206.8 L1659 225 L1659 289.8333333333333 L1659.0 289.8 " fill="none"></path>
3725- </g>
3726- <path d="M1653.7 283.2 L1659.0 289.8 L1664.3 283.2 L1659.0 296.5 Z"></path>
3727- <path d="M1603.5 203.5 L1595.6 206.8 L1600.6 213.8 L1589.2 205.0 Z"></path>
3728- <text x="1618.3" y="473.5" stroke="none">SSH</text>
3729+ <path d="M1183.6 212.8 L1168.75 228 L1168.75 241.33333333333334 L1168.8 241.3 " fill="none"></path>
3730+ </g>
3731+ <path d="M1163.4 234.7 L1168.8 241.3 L1174.1 234.7 L1168.8 248.0 Z"></path>
3732+ <path d="M1182.8 221.3 L1183.6 212.8 L1175.1 213.8 L1188.2 208.0 Z"></path>
3733 <g stroke-dasharray="6 6">
3734- <path d="M1659.0 334.2 L1659 399 L1659 481.5 L1659.0 481.5 " fill="none"></path>
3735- </g>
3736- <path d="M1664.3 340.8 L1659.0 334.2 L1653.7 340.8 L1659.0 327.5 Z"></path>
3737+ <path d="M1472.2 210.0 L1528 228 L1528 293.3333333333333 L1528.0 293.3 " fill="none"></path>
3738+ </g>
3739+ <path d="M1522.7 286.7 L1528.0 293.3 L1533.3 286.7 L1528.0 300.0 Z"></path>
3740+ <path d="M1480.2 207.0 L1472.2 210.0 L1476.9 217.2 L1465.8 208.0 Z"></path>
3741+ <text x="1487.3" y="480.0" stroke="none">SSH</text>
3742 <g stroke-dasharray="6 6">
3743- <path d="M1691.6 206.3 L1783.5 225 L1783.5 289.8333333333333 L1783.5 289.8 " fill="none"></path>
3744- </g>
3745- <path d="M1778.2 283.2 L1783.5 289.8 L1788.8 283.2 L1783.5 296.5 Z"></path>
3746- <path d="M1699.2 202.4 L1691.6 206.3 L1697.1 212.9 L1685.1 205.0 Z"></path>
3747+ <path d="M1528.0 338.7 L1528 404 L1528 488 L1528.0 488.0 " fill="none"></path>
3748+ </g>
3749+ <path d="M1533.3 345.3 L1528.0 338.7 L1522.7 345.3 L1528.0 332.0 Z"></path>
3750 <g stroke-dasharray="6 6">
3751- <path d="M1783.5 334.2 L1783.5 399 L1783.5 481.5 L1783.5 481.5 " fill="none"></path>
3752- </g>
3753- <path d="M1788.8 340.8 L1783.5 334.2 L1778.2 340.8 L1783.5 327.5 Z"></path>
3754+ <path d="M1568.6 209.4 L1652.5 228 L1652.5 293.3333333333333 L1652.5 293.3 " fill="none"></path>
3755+ </g>
3756+ <path d="M1647.2 286.7 L1652.5 293.3 L1657.8 286.7 L1652.5 300.0 Z"></path>
3757+ <path d="M1576.2 205.7 L1568.6 209.4 L1573.9 216.1 L1562.0 208.0 Z"></path>
3758 <g stroke-dasharray="6 6">
3759- <path d="M1102.2 365.9 L1006 399 L1006 412.3333333333333 L1006.0 412.3 " fill="none"></path>
3760- </g>
3761- <path d="M1000.7 405.7 L1006.0 412.3 L1011.3 405.7 L1006.0 419.0 Z"></path>
3762- <path d="M1097.6 373.1 L1102.2 365.9 L1094.2 363.0 L1108.5 363.8 Z"></path>
3763+ <path d="M1652.5 338.7 L1652.5 404 L1652.5 488 L1652.5 488.0 " fill="none"></path>
3764+ </g>
3765+ <path d="M1657.8 345.3 L1652.5 338.7 L1647.2 345.3 L1652.5 332.0 Z"></path>
3766 <g stroke-dasharray="6 6">
3767- <path d="M1242.1 385.5 L1239 399 L1239 412.3333333333333 L1239.0 412.3 " fill="none"></path>
3768- </g>
3769- <path d="M1233.7 405.7 L1239.0 412.3 L1244.3 405.7 L1239.0 419.0 Z"></path>
3770- <path d="M1245.8 393.2 L1242.1 385.5 L1235.4 390.8 L1243.6 379.0 Z"></path>
3771+ <path d="M1031.0 367.4 L933 404 L933 417.3333333333333 L933.0 417.3 " fill="none"></path>
3772+ </g>
3773+ <path d="M927.7 410.7 L933.0 417.3 L938.3 410.7 L933.0 424.0 Z"></path>
3774+ <path d="M1026.6 374.7 L1031.0 367.4 L1022.9 364.8 L1037.3 365.1 Z"></path>
3775 <g stroke-dasharray="6 6">
3776- <path d="M1354.1 383.0 L1375.5 399 L1388.6489765726408 413.9884707045646 L1388.6 414.0 " fill="none"></path>
3777- </g>
3778- <path d="M1380.2 412.5 L1388.6 414.0 L1388.3 405.5 L1393.0 419.0 Z"></path>
3779- <path d="M1362.6 382.7 L1354.1 383.0 L1356.2 391.3 L1348.7 379.0 Z"></path>
3780+ <path d="M1152.9 390.5 L1150 404 L1150 417.3333333333333 L1150.0 417.3 " fill="none"></path>
3781+ </g>
3782+ <path d="M1144.7 410.7 L1150.0 417.3 L1155.3 410.7 L1150.0 424.0 Z"></path>
3783+ <path d="M1156.7 398.2 L1152.9 390.5 L1146.3 395.9 L1154.3 384.0 Z"></path>
3784 <g stroke-dasharray="6 6">
3785- <path d="M1512.9 419.0 L1522 399 L1522 312 L1522 312 L1522 225 L1522 225 L1489.6328041618644 208.08741118367695 L1489.6 208.1 " fill="none"></path>
3786- </g>
3787- <path d="M1498.0 206.4 L1489.6 208.1 L1493.1 215.9 L1483.7 205.0 Z"></path>
3788+ <path d="M1255.6 388.3 L1274.5 404 L1285.5740965207704 418.6780924298968 L1285.6 418.7 " fill="none"></path>
3789+ </g>
3790+ <path d="M1277.3 416.6 L1285.6 418.7 L1285.8 410.1 L1289.6 424.0 Z"></path>
3791+ <path d="M1264.1 388.4 L1255.6 388.3 L1257.3 396.6 L1250.5 384.0 Z"></path>
3792+ <g stroke-dasharray="6 6">
3793+ <path d="M1392.7 424.0 L1400 404 L1400 316 L1400 316 L1400 228 L1400 228 L1372.6362943277163 211.45012990267378 L1372.6 211.5 " fill="none"></path>
3794+ </g>
3795+ <path d="M1381.1 210.3 L1372.6 211.5 L1375.6 219.5 L1366.9 208.0 Z"></path>
3796 <g data-name="reverse_proxy">
3797- <g fill="#eee8d5" stroke="#33322E" data-name="reverse_proxy">
3798- <rect x="971.8" y="0.0" height="31.0" width="125.0" data-name="reverse_proxy"></rect>
3799- </g>
3800- <g transform="translate(971.75, 0)" font-family="Helvetica" font-size="12pt" font-weight="bold" font-style="normal" data-name="reverse_proxy">
3801- <g transform="translate(8, 8)" fill="#33322E" text-align="center" data-name="reverse_proxy">
3802- <text x="54.5" y="13.5" stroke="none" text-anchor="middle" data-name="reverse_proxy">reverse_proxy</text>
3803+ <g fill="#eee8d5" stroke="#33322E" data-name="reverse_proxy">
3804+ <rect x="880.5" y="0.0" height="32.0" width="125.0" data-name="reverse_proxy"></rect>
3805+ </g>
3806+ <g transform="translate(880.5, 0)" font-family="Helvetica" font-size="12pt" font-weight="bold" font-style="normal" data-name="reverse_proxy">
3807+ <g transform="translate(8, 8)" fill="#33322E" text-align="center" data-name="reverse_proxy">
3808+ <text x="54.5" y="14.1" stroke="none" text-anchor="middle" data-name="reverse_proxy">reverse_proxy</text>
3809
3810- </g>
3811 </g>
3812 </g>
3813+ </g>
3814 <g data-name="Rudolfs (LFS API)">
3815- <g fill="#eee8d5" stroke="#33322E" data-name="Rudolfs (LFS API)">
3816- <rect x="637.0" y="122.5" height="31.0" width="152.0" data-name="Rudolfs (LFS API)"></rect>
3817- </g>
3818- <g transform="translate(637, 122.5)" font-family="Helvetica" font-size="12pt" font-weight="bold" font-style="normal" data-name="Rudolfs (LFS API)">
3819- <g transform="translate(8, 8)" fill="#33322E" text-align="center" data-name="Rudolfs (LFS API)">
3820- <text x="68.0" y="13.5" stroke="none" text-anchor="middle" data-name="Rudolfs (LFS API)">Rudolfs (LFS API)</text>
3821+ <g fill="#eee8d5" stroke="#33322E" data-name="Rudolfs (LFS API)">
3822+ <rect x="555.5" y="124.0" height="32.0" width="152.0" data-name="Rudolfs (LFS API)"></rect>
3823+ </g>
3824+ <g transform="translate(555.5, 124)" font-family="Helvetica" font-size="12pt" font-weight="bold" font-style="normal" data-name="Rudolfs (LFS API)">
3825+ <g transform="translate(8, 8)" fill="#33322E" text-align="center" data-name="Rudolfs (LFS API)">
3826+ <text x="68.0" y="14.1" stroke="none" text-anchor="middle" data-name="Rudolfs (LFS API)">Rudolfs (LFS API)</text>
3827
3828- </g>
3829 </g>
3830 </g>
3831+ </g>
3832 <g data-name="ayllu-core">
3833- <g fill="#eee8d5" stroke="#33322E" data-name="ayllu-core">
3834- <rect x="829.0" y="71.0" height="134.0" width="1053.0" data-name="ayllu-core"></rect>
3835- <path d="M829.0 102.0 L1882.0 102.0" fill="none" data-name="ayllu-core"></path>
3836- </g>
3837- <g transform="translate(829, 71)" font-family="Helvetica" font-size="12pt" font-weight="bold" font-style="normal" data-name="ayllu-core">
3838- <g transform="translate(8, 8)" fill="#33322E" text-align="center" data-name="ayllu-core">
3839- <text x="518.5" y="13.5" stroke="none" text-anchor="middle" data-name="ayllu-core">ayllu-core</text>
3840+ <g fill="#eee8d5" stroke="#33322E" data-name="ayllu-core">
3841+ <rect x="747.5" y="72.0" height="136.0" width="1014.0" data-name="ayllu-core"></rect>
3842+ <path d="M747.5 104.0 L1761.5 104.0" fill="none" data-name="ayllu-core"></path>
3843+ </g>
3844+ <g transform="translate(747.5, 72)" font-family="Helvetica" font-size="12pt" font-weight="bold" font-style="normal" data-name="ayllu-core">
3845+ <g transform="translate(8, 8)" fill="#33322E" text-align="center" data-name="ayllu-core">
3846+ <text x="499.0" y="14.1" stroke="none" text-anchor="middle" data-name="ayllu-core">ayllu-core</text>
3847
3848- </g>
3849 </g>
3850- <g transform="translate(829, 102)" font-family="Helvetica" font-size="12pt" font-weight="normal" font-style="normal" data-name="ayllu-core">
3851- <g transform="translate(8, 8)" fill="#33322E" data-name="ayllu-core">
3852- <g transform="translate(20, 20)" data-name="ayllu-core">
3853- <g data-name="axum http interface">
3854- <g fill="#fdf6e3" stroke="#33322E" data-name="axum http interface">
3855- <rect x="0.0" y="8.0" height="31.0" width="164.0" data-name="axum http interface"></rect>
3856- </g>
3857+ </g>
3858+ <g transform="translate(747.5, 104)" font-family="Helvetica" font-size="12pt" font-weight="normal" font-style="normal" data-name="ayllu-core">
3859+ <g transform="translate(8, 8)" fill="#33322E" data-name="ayllu-core">
3860+ <g transform="translate(20, 20)" data-name="ayllu-core">
3861+ <g data-name="axum http interface">
3862+ <g fill="#fdf6e3" stroke="#33322E" data-name="axum http interface">
3863+ <rect x="0.0" y="8.0" height="32.0" width="164.0" data-name="axum http interface"></rect>
3864+ </g>
3865 <g transform="translate(0, 8)" font-family="Helvetica" font-size="12pt" font-weight="bold" font-style="normal" data-name="axum http interface">
3866- <g transform="translate(8, 8)" fill="#33322E" text-align="center" data-name="axum http interface">
3867- <text x="74.0" y="13.5" stroke="none" text-anchor="middle" data-name="axum http interface">axum http interface</text>
3868+ <g transform="translate(8, 8)" fill="#33322E" text-align="center" data-name="axum http interface">
3869+ <text x="74.0" y="14.1" stroke="none" text-anchor="middle" data-name="axum http interface">axum http interface</text>
3870
3871- </g>
3872 </g>
3873 </g>
3874+ </g>
3875 <g data-name="sites static hosting">
3876- <g fill="#fdf6e3" stroke="#33322E" data-name="sites static hosting">
3877- <rect x="204.0" y="8.0" height="31.0" width="161.0" data-name="sites static hosting"></rect>
3878- </g>
3879+ <g fill="#fdf6e3" stroke="#33322E" data-name="sites static hosting">
3880+ <rect x="204.0" y="8.0" height="32.0" width="161.0" data-name="sites static hosting"></rect>
3881+ </g>
3882 <g transform="translate(204, 8)" font-family="Helvetica" font-size="12pt" font-weight="bold" font-style="normal" data-name="sites static hosting">
3883- <g transform="translate(8, 8)" fill="#33322E" text-align="center" data-name="sites static hosting">
3884- <text x="72.5" y="13.5" stroke="none" text-anchor="middle" data-name="sites static hosting">sites static hosting</text>
3885+ <g transform="translate(8, 8)" fill="#33322E" text-align="center" data-name="sites static hosting">
3886+ <text x="72.5" y="14.1" stroke="none" text-anchor="middle" data-name="sites static hosting">sites static hosting</text>
3887
3888- </g>
3889 </g>
3890 </g>
3891- <g data-name="capnp-rpc-server (job_server)">
3892- <g fill="#fdf6e3" stroke="#33322E" data-name="capnp-rpc-server (job_server)">
3893- <rect x="405.0" y="8.0" height="31.0" width="244.0" data-name="capnp-rpc-server (job_server)"></rect>
3894- </g>
3895- <g transform="translate(405, 8)" font-family="Helvetica" font-size="12pt" font-weight="bold" font-style="normal" data-name="capnp-rpc-server (job_server)">
3896- <g transform="translate(8, 8)" fill="#33322E" text-align="center" data-name="capnp-rpc-server (job_server)">
3897- <text x="114.0" y="13.5" stroke="none" text-anchor="middle" data-name="capnp-rpc-server (job_server)">capnp-rpc-server (job_server)</text>
3898+ </g>
3899+ <g data-name="tarpc-server (job_server)">
3900+ <g fill="#fdf6e3" stroke="#33322E" data-name="tarpc-server (job_server)">
3901+ <rect x="405.0" y="8.0" height="32.0" width="205.0" data-name="tarpc-server (job_server)"></rect>
3902+ </g>
3903+ <g transform="translate(405, 8)" font-family="Helvetica" font-size="12pt" font-weight="bold" font-style="normal" data-name="tarpc-server (job_server)">
3904+ <g transform="translate(8, 8)" fill="#33322E" text-align="center" data-name="tarpc-server (job_server)">
3905+ <text x="94.5" y="14.1" stroke="none" text-anchor="middle" data-name="tarpc-server (job_server)">tarpc-server (job_server)</text>
3906
3907- </g>
3908 </g>
3909 </g>
3910+ </g>
3911 <g data-name="git repositories via libgit2">
3912- <g fill="#fdf6e3" stroke="#33322E" data-name="git repositories via libgit2">
3913- <rect x="689.0" y="8.0" height="31.0" width="210.0" data-name="git repositories via libgit2"></rect>
3914- </g>
3915- <g transform="translate(689, 8)" font-family="Helvetica" font-size="12pt" font-weight="bold" font-style="normal" data-name="git repositories via libgit2">
3916- <g transform="translate(8, 8)" fill="#33322E" text-align="center" data-name="git repositories via libgit2">
3917- <text x="97.0" y="13.5" stroke="none" text-anchor="middle" data-name="git repositories via libgit2">git repositories via libgit2</text>
3918+ <g fill="#fdf6e3" stroke="#33322E" data-name="git repositories via libgit2">
3919+ <rect x="650.0" y="8.0" height="32.0" width="210.0" data-name="git repositories via libgit2"></rect>
3920+ </g>
3921+ <g transform="translate(650, 8)" font-family="Helvetica" font-size="12pt" font-weight="bold" font-style="normal" data-name="git repositories via libgit2">
3922+ <g transform="translate(8, 8)" fill="#33322E" text-align="center" data-name="git repositories via libgit2">
3923+ <text x="97.0" y="14.1" stroke="none" text-anchor="middle" data-name="git repositories via libgit2">git repositories via libgit2</text>
3924
3925- </g>
3926 </g>
3927 </g>
3928+ </g>
3929 <g data-name="sqlite">
3930- <g fill="#fdf6e3" stroke="#33322E" data-name="sqlite">
3931- <rect x="939.0" y="8.0" height="31.0" width="58.0" stroke="none" data-name="sqlite"></rect>
3932- <path d="M939.0 8.0 L939.0 39.0" fill="none" data-name="sqlite"></path>
3933- <path d="M997.0 8.0 L997.0 39.0" fill="none" data-name="sqlite"></path>
3934- <ellipse cx="968.0" cy="8.0" rx="29.0" ry="6.0" data-name="sqlite"></ellipse>
3935- <path d="M997.0 39.0 L997.0 39.3 L996.9 39.6 L996.7 39.9 L996.4 40.2 L996.1 40.5 L995.7 40.8 L995.3 41.1 L994.7 41.3 L994.1 41.6 L993.5 41.9 L992.7 42.1 L992.0 42.4 L991.1 42.6 L990.2 42.9 L989.3 43.1 L988.2 43.3 L987.2 43.5 L986.1 43.7 L984.9 43.9 L983.7 44.0 L982.5 44.2 L981.2 44.3 L979.9 44.5 L978.6 44.6 L977.2 44.7 L975.9 44.8 L974.5 44.8 L973.0 44.9 L971.6 45.0 L970.2 45.0 L968.7 45.0 L967.3 45.0 L965.8 45.0 L964.4 45.0 L963.0 44.9 L961.5 44.8 L960.1 44.8 L958.8 44.7 L957.4 44.6 L956.1 44.5 L954.8 44.3 L953.5 44.2 L952.3 44.0 L951.1 43.9 L949.9 43.7 L948.8 43.5 L947.8 43.3 L946.7 43.1 L945.8 42.9 L944.9 42.6 L944.0 42.4 L943.3 42.1 L942.5 41.9 L941.9 41.6 L941.3 41.3 L940.7 41.1 L940.3 40.8 L939.9 40.5 L939.6 40.2 L939.3 39.9 L939.1 39.6 L939.0 39.3 L939.0 39.0" data-name="sqlite"></path>
3936- </g>
3937- <g transform="translate(939, 12)" font-family="Helvetica" font-size="12pt" font-weight="bold" font-style="normal" data-name="sqlite">
3938- <g transform="translate(8, 8)" fill="#33322E" text-align="center" data-name="sqlite">
3939- <text x="21.0" y="13.5" stroke="none" text-anchor="middle" data-name="sqlite">sqlite</text>
3940+ <g fill="#fdf6e3" stroke="#33322E" data-name="sqlite">
3941+ <rect x="900.0" y="8.0" height="32.0" width="58.0" stroke="none" data-name="sqlite"></rect>
3942+ <path d="M900.0 8.0 L900.0 40.0" fill="none" data-name="sqlite"></path>
3943+ <path d="M958.0 8.0 L958.0 40.0" fill="none" data-name="sqlite"></path>
3944+ <ellipse cx="929.0" cy="8.0" rx="29.0" ry="6.0" data-name="sqlite"></ellipse>
3945+ <path d="M958.0 40.0 L958.0 40.3 L957.9 40.6 L957.7 40.9 L957.4 41.2 L957.1 41.5 L956.7 41.8 L956.3 42.1 L955.7 42.3 L955.1 42.6 L954.5 42.9 L953.7 43.1 L953.0 43.4 L952.1 43.6 L951.2 43.9 L950.3 44.1 L949.2 44.3 L948.2 44.5 L947.1 44.7 L945.9 44.9 L944.7 45.0 L943.5 45.2 L942.2 45.3 L940.9 45.5 L939.6 45.6 L938.2 45.7 L936.9 45.8 L935.5 45.8 L934.0 45.9 L932.6 46.0 L931.2 46.0 L929.7 46.0 L928.3 46.0 L926.8 46.0 L925.4 46.0 L924.0 45.9 L922.5 45.8 L921.1 45.8 L919.8 45.7 L918.4 45.6 L917.1 45.5 L915.8 45.3 L914.5 45.2 L913.3 45.0 L912.1 44.9 L910.9 44.7 L909.8 44.5 L908.8 44.3 L907.7 44.1 L906.8 43.9 L905.9 43.6 L905.0 43.4 L904.3 43.1 L903.5 42.9 L902.9 42.6 L902.3 42.3 L901.7 42.1 L901.3 41.8 L900.9 41.5 L900.6 41.2 L900.3 40.9 L900.1 40.6 L900.0 40.3 L900.0 40.0" data-name="sqlite"></path>
3946+ </g>
3947+ <g transform="translate(900, 12)" font-family="Helvetica" font-size="12pt" font-weight="bold" font-style="normal" data-name="sqlite">
3948+ <g transform="translate(8, 8)" fill="#33322E" text-align="center" data-name="sqlite">
3949+ <text x="21.0" y="14.1" stroke="none" text-anchor="middle" data-name="sqlite">sqlite</text>
3950
3951- </g>
3952 </g>
3953 </g>
3954 </g>
3955 </g>
3956 </g>
3957 </g>
3958+ </g>
3959 <g data-name="ayllu-mail">
3960- <g fill="#eee8d5" stroke="#33322E" data-name="ayllu-mail">
3961- <rect x="0.0" y="245.0" height="134.0" width="522.0" data-name="ayllu-mail"></rect>
3962- <path d="M0.0 276.0 L522.0 276.0" fill="none" data-name="ayllu-mail"></path>
3963- </g>
3964- <g transform="translate(0, 245)" font-family="Helvetica" font-size="12pt" font-weight="bold" font-style="normal" data-name="ayllu-mail">
3965- <g transform="translate(8, 8)" fill="#33322E" text-align="center" data-name="ayllu-mail">
3966- <text x="253.0" y="13.5" stroke="none" text-anchor="middle" data-name="ayllu-mail">ayllu-mail</text>
3967+ <g fill="#eee8d5" stroke="#33322E" data-name="ayllu-mail">
3968+ <rect x="0.0" y="248.0" height="136.0" width="484.0" data-name="ayllu-mail"></rect>
3969+ <path d="M0.0 280.0 L484.0 280.0" fill="none" data-name="ayllu-mail"></path>
3970+ </g>
3971+ <g transform="translate(0, 248)" font-family="Helvetica" font-size="12pt" font-weight="bold" font-style="normal" data-name="ayllu-mail">
3972+ <g transform="translate(8, 8)" fill="#33322E" text-align="center" data-name="ayllu-mail">
3973+ <text x="234.0" y="14.1" stroke="none" text-anchor="middle" data-name="ayllu-mail">ayllu-mail</text>
3974
3975- </g>
3976 </g>
3977- <g transform="translate(0, 276)" font-family="Helvetica" font-size="12pt" font-weight="normal" font-style="normal" data-name="ayllu-mail">
3978- <g transform="translate(8, 8)" fill="#33322E" data-name="ayllu-mail">
3979- <g transform="translate(20, 20)" data-name="ayllu-mail">
3980- <g data-name="capnp-rpc-server">
3981- <g fill="#fdf6e3" stroke="#33322E" data-name="capnp-rpc-server">
3982- <rect x="0.0" y="8.0" height="31.0" width="147.0" data-name="capnp-rpc-server"></rect>
3983- </g>
3984- <g transform="translate(0, 8)" font-family="Helvetica" font-size="12pt" font-weight="bold" font-style="normal" data-name="capnp-rpc-server">
3985- <g transform="translate(8, 8)" fill="#33322E" text-align="center" data-name="capnp-rpc-server">
3986- <text x="65.5" y="13.5" stroke="none" text-anchor="middle" data-name="capnp-rpc-server">capnp-rpc-server</text>
3987+ </g>
3988+ <g transform="translate(0, 280)" font-family="Helvetica" font-size="12pt" font-weight="normal" font-style="normal" data-name="ayllu-mail">
3989+ <g transform="translate(8, 8)" fill="#33322E" data-name="ayllu-mail">
3990+ <g transform="translate(20, 20)" data-name="ayllu-mail">
3991+ <g data-name="tarpc-server">
3992+ <g fill="#fdf6e3" stroke="#33322E" data-name="tarpc-server">
3993+ <rect x="0.0" y="8.0" height="32.0" width="109.0" data-name="tarpc-server"></rect>
3994+ </g>
3995+ <g transform="translate(0, 8)" font-family="Helvetica" font-size="12pt" font-weight="bold" font-style="normal" data-name="tarpc-server">
3996+ <g transform="translate(8, 8)" fill="#33322E" text-align="center" data-name="tarpc-server">
3997+ <text x="46.5" y="14.1" stroke="none" text-anchor="middle" data-name="tarpc-server">tarpc-server</text>
3998
3999- </g>
4000 </g>
4001 </g>
4002+ </g>
4003 <g data-name="mailpot">
4004- <g fill="#fdf6e3" stroke="#33322E" data-name="mailpot">
4005- <rect x="187.0" y="8.0" height="31.0" width="73.0" data-name="mailpot"></rect>
4006- </g>
4007- <g transform="translate(187, 8)" font-family="Helvetica" font-size="12pt" font-weight="bold" font-style="normal" data-name="mailpot">
4008- <g transform="translate(8, 8)" fill="#33322E" text-align="center" data-name="mailpot">
4009- <text x="28.5" y="13.5" stroke="none" text-anchor="middle" data-name="mailpot">mailpot</text>
4010+ <g fill="#fdf6e3" stroke="#33322E" data-name="mailpot">
4011+ <rect x="149.0" y="8.0" height="32.0" width="73.0" data-name="mailpot"></rect>
4012+ </g>
4013+ <g transform="translate(149, 8)" font-family="Helvetica" font-size="12pt" font-weight="bold" font-style="normal" data-name="mailpot">
4014+ <g transform="translate(8, 8)" fill="#33322E" text-align="center" data-name="mailpot">
4015+ <text x="28.5" y="14.1" stroke="none" text-anchor="middle" data-name="mailpot">mailpot</text>
4016
4017- </g>
4018 </g>
4019 </g>
4020+ </g>
4021 <g data-name="postfix">
4022- <g fill="#fdf6e3" stroke="#33322E" data-name="postfix">
4023- <rect x="300.0" y="8.0" height="31.0" width="68.0" data-name="postfix"></rect>
4024- </g>
4025- <g transform="translate(300, 8)" font-family="Helvetica" font-size="12pt" font-weight="bold" font-style="normal" data-name="postfix">
4026- <g transform="translate(8, 8)" fill="#33322E" text-align="center" data-name="postfix">
4027- <text x="26.0" y="13.5" stroke="none" text-anchor="middle" data-name="postfix">postfix</text>
4028+ <g fill="#fdf6e3" stroke="#33322E" data-name="postfix">
4029+ <rect x="262.0" y="8.0" height="32.0" width="68.0" data-name="postfix"></rect>
4030+ </g>
4031+ <g transform="translate(262, 8)" font-family="Helvetica" font-size="12pt" font-weight="bold" font-style="normal" data-name="postfix">
4032+ <g transform="translate(8, 8)" fill="#33322E" text-align="center" data-name="postfix">
4033+ <text x="26.0" y="14.1" stroke="none" text-anchor="middle" data-name="postfix">postfix</text>
4034
4035- </g>
4036 </g>
4037 </g>
4038+ </g>
4039 <g data-name="sqlite">
4040- <g fill="#fdf6e3" stroke="#33322E" data-name="sqlite">
4041- <rect x="408.0" y="8.0" height="31.0" width="58.0" stroke="none" data-name="sqlite"></rect>
4042- <path d="M408.0 8.0 L408.0 39.0" fill="none" data-name="sqlite"></path>
4043- <path d="M466.0 8.0 L466.0 39.0" fill="none" data-name="sqlite"></path>
4044- <ellipse cx="437.0" cy="8.0" rx="29.0" ry="6.0" data-name="sqlite"></ellipse>
4045- <path d="M466.0 39.0 L466.0 39.3 L465.9 39.6 L465.7 39.9 L465.4 40.2 L465.1 40.5 L464.7 40.8 L464.3 41.1 L463.7 41.3 L463.1 41.6 L462.5 41.9 L461.7 42.1 L461.0 42.4 L460.1 42.6 L459.2 42.9 L458.3 43.1 L457.2 43.3 L456.2 43.5 L455.1 43.7 L453.9 43.9 L452.7 44.0 L451.5 44.2 L450.2 44.3 L448.9 44.5 L447.6 44.6 L446.2 44.7 L444.9 44.8 L443.5 44.8 L442.0 44.9 L440.6 45.0 L439.2 45.0 L437.7 45.0 L436.3 45.0 L434.8 45.0 L433.4 45.0 L432.0 44.9 L430.5 44.8 L429.1 44.8 L427.8 44.7 L426.4 44.6 L425.1 44.5 L423.8 44.3 L422.5 44.2 L421.3 44.0 L420.1 43.9 L418.9 43.7 L417.8 43.5 L416.8 43.3 L415.7 43.1 L414.8 42.9 L413.9 42.6 L413.0 42.4 L412.3 42.1 L411.5 41.9 L410.9 41.6 L410.3 41.3 L409.7 41.1 L409.3 40.8 L408.9 40.5 L408.6 40.2 L408.3 39.9 L408.1 39.6 L408.0 39.3 L408.0 39.0" data-name="sqlite"></path>
4046- </g>
4047- <g transform="translate(408, 12)" font-family="Helvetica" font-size="12pt" font-weight="bold" font-style="normal" data-name="sqlite">
4048- <g transform="translate(8, 8)" fill="#33322E" text-align="center" data-name="sqlite">
4049- <text x="21.0" y="13.5" stroke="none" text-anchor="middle" data-name="sqlite">sqlite</text>
4050+ <g fill="#fdf6e3" stroke="#33322E" data-name="sqlite">
4051+ <rect x="370.0" y="8.0" height="32.0" width="58.0" stroke="none" data-name="sqlite"></rect>
4052+ <path d="M370.0 8.0 L370.0 40.0" fill="none" data-name="sqlite"></path>
4053+ <path d="M428.0 8.0 L428.0 40.0" fill="none" data-name="sqlite"></path>
4054+ <ellipse cx="399.0" cy="8.0" rx="29.0" ry="6.0" data-name="sqlite"></ellipse>
4055+ <path d="M428.0 40.0 L428.0 40.3 L427.9 40.6 L427.7 40.9 L427.4 41.2 L427.1 41.5 L426.7 41.8 L426.3 42.1 L425.7 42.3 L425.1 42.6 L424.5 42.9 L423.7 43.1 L423.0 43.4 L422.1 43.6 L421.2 43.9 L420.3 44.1 L419.2 44.3 L418.2 44.5 L417.1 44.7 L415.9 44.9 L414.7 45.0 L413.5 45.2 L412.2 45.3 L410.9 45.5 L409.6 45.6 L408.2 45.7 L406.9 45.8 L405.5 45.8 L404.0 45.9 L402.6 46.0 L401.2 46.0 L399.7 46.0 L398.3 46.0 L396.8 46.0 L395.4 46.0 L394.0 45.9 L392.5 45.8 L391.1 45.8 L389.8 45.7 L388.4 45.6 L387.1 45.5 L385.8 45.3 L384.5 45.2 L383.3 45.0 L382.1 44.9 L380.9 44.7 L379.8 44.5 L378.8 44.3 L377.7 44.1 L376.8 43.9 L375.9 43.6 L375.0 43.4 L374.3 43.1 L373.5 42.9 L372.9 42.6 L372.3 42.3 L371.7 42.1 L371.3 41.8 L370.9 41.5 L370.6 41.2 L370.3 40.9 L370.1 40.6 L370.0 40.3 L370.0 40.0" data-name="sqlite"></path>
4056+ </g>
4057+ <g transform="translate(370, 12)" font-family="Helvetica" font-size="12pt" font-weight="bold" font-style="normal" data-name="sqlite">
4058+ <g transform="translate(8, 8)" fill="#33322E" text-align="center" data-name="sqlite">
4059+ <text x="21.0" y="14.1" stroke="none" text-anchor="middle" data-name="sqlite">sqlite</text>
4060
4061- </g>
4062 </g>
4063 </g>
4064 </g>
4065 </g>
4066 </g>
4067 </g>
4068+ </g>
4069 <g data-name="contributor-2">
4070- <g fill="#eee8d5" stroke="#33322E" data-name="contributor-2">
4071- <circle r="4.0" cx="807.5" cy="493.5" data-name="contributor-2"></circle>
4072- <path d="M807.5 497.5 L807.5 505.5" fill="none" data-name="contributor-2"></path>
4073- <path d="M803.5 501.5 L811.5 501.5" fill="none" data-name="contributor-2"></path>
4074- <path d="M803.5 509.5 L807.5 505.5 L811.5 509.5" fill="none" data-name="contributor-2"></path>
4075- </g>
4076- <g transform="translate(750, 505.5)" font-family="Helvetica" font-size="12pt" font-weight="normal" font-style="normal" data-name="contributor-2">
4077- <g transform="translate(8, 8)" fill="#33322E" text-align="center" data-name="contributor-2">
4078- <text x="49.5" y="13.5" stroke="none" text-anchor="middle" data-name="contributor-2">contributor-2</text>
4079+ <g fill="#eee8d5" stroke="#33322E" data-name="contributor-2">
4080+ <circle r="4.0" cx="747.8" cy="500.0" data-name="contributor-2"></circle>
4081+ <path d="M747.8 504.0 L747.8 512.0" fill="none" data-name="contributor-2"></path>
4082+ <path d="M743.8 508.0 L751.8 508.0" fill="none" data-name="contributor-2"></path>
4083+ <path d="M743.8 516.0 L747.8 512.0 L751.8 516.0" fill="none" data-name="contributor-2"></path>
4084+ </g>
4085+ <g transform="translate(690.25, 512)" font-family="Helvetica" font-size="12pt" font-weight="normal" font-style="normal" data-name="contributor-2">
4086+ <g transform="translate(8, 8)" fill="#33322E" text-align="center" data-name="contributor-2">
4087+ <text x="49.5" y="14.1" stroke="none" text-anchor="middle" data-name="contributor-2">contributor-2</text>
4088
4089- </g>
4090 </g>
4091 </g>
4092+ </g>
4093 <g data-name="ayllu-xmpp">
4094- <g fill="#eee8d5" stroke="#33322E" data-name="ayllu-xmpp">
4095- <rect x="562.0" y="245.0" height="134.0" width="491.0" data-name="ayllu-xmpp"></rect>
4096- <path d="M562.0 276.0 L1053.0 276.0" fill="none" data-name="ayllu-xmpp"></path>
4097- </g>
4098- <g transform="translate(562, 245)" font-family="Helvetica" font-size="12pt" font-weight="bold" font-style="normal" data-name="ayllu-xmpp">
4099- <g transform="translate(8, 8)" fill="#33322E" text-align="center" data-name="ayllu-xmpp">
4100- <text x="237.5" y="13.5" stroke="none" text-anchor="middle" data-name="ayllu-xmpp">ayllu-xmpp</text>
4101+ <g fill="#eee8d5" stroke="#33322E" data-name="ayllu-xmpp">
4102+ <rect x="524.0" y="248.0" height="136.0" width="453.0" data-name="ayllu-xmpp"></rect>
4103+ <path d="M524.0 280.0 L977.0 280.0" fill="none" data-name="ayllu-xmpp"></path>
4104+ </g>
4105+ <g transform="translate(524, 248)" font-family="Helvetica" font-size="12pt" font-weight="bold" font-style="normal" data-name="ayllu-xmpp">
4106+ <g transform="translate(8, 8)" fill="#33322E" text-align="center" data-name="ayllu-xmpp">
4107+ <text x="218.5" y="14.1" stroke="none" text-anchor="middle" data-name="ayllu-xmpp">ayllu-xmpp</text>
4108
4109- </g>
4110 </g>
4111- <g transform="translate(562, 276)" font-family="Helvetica" font-size="12pt" font-weight="normal" font-style="normal" data-name="ayllu-xmpp">
4112- <g transform="translate(8, 8)" fill="#33322E" data-name="ayllu-xmpp">
4113- <g transform="translate(20, 20)" data-name="ayllu-xmpp">
4114- <g data-name="capnp-rpc-server">
4115- <g fill="#fdf6e3" stroke="#33322E" data-name="capnp-rpc-server">
4116- <rect x="0.0" y="8.0" height="31.0" width="147.0" data-name="capnp-rpc-server"></rect>
4117- </g>
4118- <g transform="translate(0, 8)" font-family="Helvetica" font-size="12pt" font-weight="bold" font-style="normal" data-name="capnp-rpc-server">
4119- <g transform="translate(8, 8)" fill="#33322E" text-align="center" data-name="capnp-rpc-server">
4120- <text x="65.5" y="13.5" stroke="none" text-anchor="middle" data-name="capnp-rpc-server">capnp-rpc-server</text>
4121+ </g>
4122+ <g transform="translate(524, 280)" font-family="Helvetica" font-size="12pt" font-weight="normal" font-style="normal" data-name="ayllu-xmpp">
4123+ <g transform="translate(8, 8)" fill="#33322E" data-name="ayllu-xmpp">
4124+ <g transform="translate(20, 20)" data-name="ayllu-xmpp">
4125+ <g data-name="tarpc-server">
4126+ <g fill="#fdf6e3" stroke="#33322E" data-name="tarpc-server">
4127+ <rect x="0.0" y="8.0" height="32.0" width="109.0" data-name="tarpc-server"></rect>
4128+ </g>
4129+ <g transform="translate(0, 8)" font-family="Helvetica" font-size="12pt" font-weight="bold" font-style="normal" data-name="tarpc-server">
4130+ <g transform="translate(8, 8)" fill="#33322E" text-align="center" data-name="tarpc-server">
4131+ <text x="46.5" y="14.1" stroke="none" text-anchor="middle" data-name="tarpc-server">tarpc-server</text>
4132
4133- </g>
4134 </g>
4135 </g>
4136+ </g>
4137 <g data-name="chat interface/bot">
4138- <g fill="#fdf6e3" stroke="#33322E" data-name="chat interface/bot">
4139- <rect x="187.0" y="8.0" height="31.0" width="150.0" data-name="chat interface/bot"></rect>
4140- </g>
4141- <g transform="translate(187, 8)" font-family="Helvetica" font-size="12pt" font-weight="bold" font-style="normal" data-name="chat interface/bot">
4142- <g transform="translate(8, 8)" fill="#33322E" text-align="center" data-name="chat interface/bot">
4143- <text x="67.0" y="13.5" stroke="none" text-anchor="middle" data-name="chat interface/bot">chat interface/bot</text>
4144+ <g fill="#fdf6e3" stroke="#33322E" data-name="chat interface/bot">
4145+ <rect x="149.0" y="8.0" height="32.0" width="150.0" data-name="chat interface/bot"></rect>
4146+ </g>
4147+ <g transform="translate(149, 8)" font-family="Helvetica" font-size="12pt" font-weight="bold" font-style="normal" data-name="chat interface/bot">
4148+ <g transform="translate(8, 8)" fill="#33322E" text-align="center" data-name="chat interface/bot">
4149+ <text x="67.0" y="14.1" stroke="none" text-anchor="middle" data-name="chat interface/bot">chat interface/bot</text>
4150
4151- </g>
4152 </g>
4153 </g>
4154+ </g>
4155 <g data-name="sqlite">
4156- <g fill="#fdf6e3" stroke="#33322E" data-name="sqlite">
4157- <rect x="377.0" y="8.0" height="31.0" width="58.0" stroke="none" data-name="sqlite"></rect>
4158- <path d="M377.0 8.0 L377.0 39.0" fill="none" data-name="sqlite"></path>
4159- <path d="M435.0 8.0 L435.0 39.0" fill="none" data-name="sqlite"></path>
4160- <ellipse cx="406.0" cy="8.0" rx="29.0" ry="6.0" data-name="sqlite"></ellipse>
4161- <path d="M435.0 39.0 L435.0 39.3 L434.9 39.6 L434.7 39.9 L434.4 40.2 L434.1 40.5 L433.7 40.8 L433.3 41.1 L432.7 41.3 L432.1 41.6 L431.5 41.9 L430.7 42.1 L430.0 42.4 L429.1 42.6 L428.2 42.9 L427.3 43.1 L426.2 43.3 L425.2 43.5 L424.1 43.7 L422.9 43.9 L421.7 44.0 L420.5 44.2 L419.2 44.3 L417.9 44.5 L416.6 44.6 L415.2 44.7 L413.9 44.8 L412.5 44.8 L411.0 44.9 L409.6 45.0 L408.2 45.0 L406.7 45.0 L405.3 45.0 L403.8 45.0 L402.4 45.0 L401.0 44.9 L399.5 44.8 L398.1 44.8 L396.8 44.7 L395.4 44.6 L394.1 44.5 L392.8 44.3 L391.5 44.2 L390.3 44.0 L389.1 43.9 L387.9 43.7 L386.8 43.5 L385.8 43.3 L384.7 43.1 L383.8 42.9 L382.9 42.6 L382.0 42.4 L381.3 42.1 L380.5 41.9 L379.9 41.6 L379.3 41.3 L378.7 41.1 L378.3 40.8 L377.9 40.5 L377.6 40.2 L377.3 39.9 L377.1 39.6 L377.0 39.3 L377.0 39.0" data-name="sqlite"></path>
4162- </g>
4163- <g transform="translate(377, 12)" font-family="Helvetica" font-size="12pt" font-weight="bold" font-style="normal" data-name="sqlite">
4164- <g transform="translate(8, 8)" fill="#33322E" text-align="center" data-name="sqlite">
4165- <text x="21.0" y="13.5" stroke="none" text-anchor="middle" data-name="sqlite">sqlite</text>
4166+ <g fill="#fdf6e3" stroke="#33322E" data-name="sqlite">
4167+ <rect x="339.0" y="8.0" height="32.0" width="58.0" stroke="none" data-name="sqlite"></rect>
4168+ <path d="M339.0 8.0 L339.0 40.0" fill="none" data-name="sqlite"></path>
4169+ <path d="M397.0 8.0 L397.0 40.0" fill="none" data-name="sqlite"></path>
4170+ <ellipse cx="368.0" cy="8.0" rx="29.0" ry="6.0" data-name="sqlite"></ellipse>
4171+ <path d="M397.0 40.0 L397.0 40.3 L396.9 40.6 L396.7 40.9 L396.4 41.2 L396.1 41.5 L395.7 41.8 L395.3 42.1 L394.7 42.3 L394.1 42.6 L393.5 42.9 L392.7 43.1 L392.0 43.4 L391.1 43.6 L390.2 43.9 L389.3 44.1 L388.2 44.3 L387.2 44.5 L386.1 44.7 L384.9 44.9 L383.7 45.0 L382.5 45.2 L381.2 45.3 L379.9 45.5 L378.6 45.6 L377.2 45.7 L375.9 45.8 L374.5 45.8 L373.0 45.9 L371.6 46.0 L370.2 46.0 L368.7 46.0 L367.3 46.0 L365.8 46.0 L364.4 46.0 L363.0 45.9 L361.5 45.8 L360.1 45.8 L358.8 45.7 L357.4 45.6 L356.1 45.5 L354.8 45.3 L353.5 45.2 L352.3 45.0 L351.1 44.9 L349.9 44.7 L348.8 44.5 L347.8 44.3 L346.7 44.1 L345.8 43.9 L344.9 43.6 L344.0 43.4 L343.3 43.1 L342.5 42.9 L341.9 42.6 L341.3 42.3 L340.7 42.1 L340.3 41.8 L339.9 41.5 L339.6 41.2 L339.3 40.9 L339.1 40.6 L339.0 40.3 L339.0 40.0" data-name="sqlite"></path>
4172+ </g>
4173+ <g transform="translate(339, 12)" font-family="Helvetica" font-size="12pt" font-weight="bold" font-style="normal" data-name="sqlite">
4174+ <g transform="translate(8, 8)" fill="#33322E" text-align="center" data-name="sqlite">
4175+ <text x="21.0" y="14.1" stroke="none" text-anchor="middle" data-name="sqlite">sqlite</text>
4176
4177- </g>
4178 </g>
4179 </g>
4180 </g>
4181 </g>
4182 </g>
4183 </g>
4184+ </g>
4185 <g data-name="ayllu-build">
4186- <g fill="#eee8d5" stroke="#33322E" data-name="ayllu-build">
4187- <rect x="1108.5" y="245.0" height="134.0" width="301.0" data-name="ayllu-build"></rect>
4188- <path d="M1108.5 276.0 L1409.5 276.0" fill="none" data-name="ayllu-build"></path>
4189- </g>
4190- <g transform="translate(1108.5, 245)" font-family="Helvetica" font-size="12pt" font-weight="bold" font-style="normal" data-name="ayllu-build">
4191- <g transform="translate(8, 8)" fill="#33322E" text-align="center" data-name="ayllu-build">
4192- <text x="142.5" y="13.5" stroke="none" text-anchor="middle" data-name="ayllu-build">ayllu-build</text>
4193+ <g fill="#eee8d5" stroke="#33322E" data-name="ayllu-build">
4194+ <rect x="1037.3" y="248.0" height="136.0" width="263.0" data-name="ayllu-build"></rect>
4195+ <path d="M1037.3 280.0 L1300.3 280.0" fill="none" data-name="ayllu-build"></path>
4196+ </g>
4197+ <g transform="translate(1037.25, 248)" font-family="Helvetica" font-size="12pt" font-weight="bold" font-style="normal" data-name="ayllu-build">
4198+ <g transform="translate(8, 8)" fill="#33322E" text-align="center" data-name="ayllu-build">
4199+ <text x="123.5" y="14.1" stroke="none" text-anchor="middle" data-name="ayllu-build">ayllu-build</text>
4200
4201- </g>
4202 </g>
4203- <g transform="translate(1108.5, 276)" font-family="Helvetica" font-size="12pt" font-weight="normal" font-style="normal" data-name="ayllu-build">
4204- <g transform="translate(8, 8)" fill="#33322E" data-name="ayllu-build">
4205- <g transform="translate(20, 20)" data-name="ayllu-build">
4206- <g data-name="capnp-rpc-server">
4207- <g fill="#fdf6e3" stroke="#33322E" data-name="capnp-rpc-server">
4208- <rect x="0.0" y="8.0" height="31.0" width="147.0" data-name="capnp-rpc-server"></rect>
4209- </g>
4210- <g transform="translate(0, 8)" font-family="Helvetica" font-size="12pt" font-weight="bold" font-style="normal" data-name="capnp-rpc-server">
4211- <g transform="translate(8, 8)" fill="#33322E" text-align="center" data-name="capnp-rpc-server">
4212- <text x="65.5" y="13.5" stroke="none" text-anchor="middle" data-name="capnp-rpc-server">capnp-rpc-server</text>
4213+ </g>
4214+ <g transform="translate(1037.25, 280)" font-family="Helvetica" font-size="12pt" font-weight="normal" font-style="normal" data-name="ayllu-build">
4215+ <g transform="translate(8, 8)" fill="#33322E" data-name="ayllu-build">
4216+ <g transform="translate(20, 20)" data-name="ayllu-build">
4217+ <g data-name="tarpc-server">
4218+ <g fill="#fdf6e3" stroke="#33322E" data-name="tarpc-server">
4219+ <rect x="0.0" y="8.0" height="32.0" width="109.0" data-name="tarpc-server"></rect>
4220+ </g>
4221+ <g transform="translate(0, 8)" font-family="Helvetica" font-size="12pt" font-weight="bold" font-style="normal" data-name="tarpc-server">
4222+ <g transform="translate(8, 8)" fill="#33322E" text-align="center" data-name="tarpc-server">
4223+ <text x="46.5" y="14.1" stroke="none" text-anchor="middle" data-name="tarpc-server">tarpc-server</text>
4224
4225- </g>
4226 </g>
4227 </g>
4228+ </g>
4229 <g data-name="sqlite">
4230- <g fill="#fdf6e3" stroke="#33322E" data-name="sqlite">
4231- <rect x="187.0" y="8.0" height="31.0" width="58.0" stroke="none" data-name="sqlite"></rect>
4232- <path d="M187.0 8.0 L187.0 39.0" fill="none" data-name="sqlite"></path>
4233- <path d="M245.0 8.0 L245.0 39.0" fill="none" data-name="sqlite"></path>
4234- <ellipse cx="216.0" cy="8.0" rx="29.0" ry="6.0" data-name="sqlite"></ellipse>
4235- <path d="M245.0 39.0 L245.0 39.3 L244.9 39.6 L244.7 39.9 L244.4 40.2 L244.1 40.5 L243.7 40.8 L243.3 41.1 L242.7 41.3 L242.1 41.6 L241.5 41.9 L240.7 42.1 L240.0 42.4 L239.1 42.6 L238.2 42.9 L237.3 43.1 L236.2 43.3 L235.2 43.5 L234.1 43.7 L232.9 43.9 L231.7 44.0 L230.5 44.2 L229.2 44.3 L227.9 44.5 L226.6 44.6 L225.2 44.7 L223.9 44.8 L222.5 44.8 L221.0 44.9 L219.6 45.0 L218.2 45.0 L216.7 45.0 L215.3 45.0 L213.8 45.0 L212.4 45.0 L211.0 44.9 L209.5 44.8 L208.1 44.8 L206.8 44.7 L205.4 44.6 L204.1 44.5 L202.8 44.3 L201.5 44.2 L200.3 44.0 L199.1 43.9 L197.9 43.7 L196.8 43.5 L195.8 43.3 L194.7 43.1 L193.8 42.9 L192.9 42.6 L192.0 42.4 L191.3 42.1 L190.5 41.9 L189.9 41.6 L189.3 41.3 L188.7 41.1 L188.3 40.8 L187.9 40.5 L187.6 40.2 L187.3 39.9 L187.1 39.6 L187.0 39.3 L187.0 39.0" data-name="sqlite"></path>
4236- </g>
4237- <g transform="translate(187, 12)" font-family="Helvetica" font-size="12pt" font-weight="bold" font-style="normal" data-name="sqlite">
4238- <g transform="translate(8, 8)" fill="#33322E" text-align="center" data-name="sqlite">
4239- <text x="21.0" y="13.5" stroke="none" text-anchor="middle" data-name="sqlite">sqlite</text>
4240+ <g fill="#fdf6e3" stroke="#33322E" data-name="sqlite">
4241+ <rect x="149.0" y="8.0" height="32.0" width="58.0" stroke="none" data-name="sqlite"></rect>
4242+ <path d="M149.0 8.0 L149.0 40.0" fill="none" data-name="sqlite"></path>
4243+ <path d="M207.0 8.0 L207.0 40.0" fill="none" data-name="sqlite"></path>
4244+ <ellipse cx="178.0" cy="8.0" rx="29.0" ry="6.0" data-name="sqlite"></ellipse>
4245+ <path d="M207.0 40.0 L207.0 40.3 L206.9 40.6 L206.7 40.9 L206.4 41.2 L206.1 41.5 L205.7 41.8 L205.3 42.1 L204.7 42.3 L204.1 42.6 L203.5 42.9 L202.7 43.1 L202.0 43.4 L201.1 43.6 L200.2 43.9 L199.3 44.1 L198.2 44.3 L197.2 44.5 L196.1 44.7 L194.9 44.9 L193.7 45.0 L192.5 45.2 L191.2 45.3 L189.9 45.5 L188.6 45.6 L187.2 45.7 L185.9 45.8 L184.5 45.8 L183.0 45.9 L181.6 46.0 L180.2 46.0 L178.7 46.0 L177.3 46.0 L175.8 46.0 L174.4 46.0 L173.0 45.9 L171.5 45.8 L170.1 45.8 L168.8 45.7 L167.4 45.6 L166.1 45.5 L164.8 45.3 L163.5 45.2 L162.3 45.0 L161.1 44.9 L159.9 44.7 L158.8 44.5 L157.8 44.3 L156.7 44.1 L155.8 43.9 L154.9 43.6 L154.0 43.4 L153.3 43.1 L152.5 42.9 L151.9 42.6 L151.3 42.3 L150.7 42.1 L150.3 41.8 L149.9 41.5 L149.6 41.2 L149.3 40.9 L149.1 40.6 L149.0 40.3 L149.0 40.0" data-name="sqlite"></path>
4246+ </g>
4247+ <g transform="translate(149, 12)" font-family="Helvetica" font-size="12pt" font-weight="bold" font-style="normal" data-name="sqlite">
4248+ <g transform="translate(8, 8)" fill="#33322E" text-align="center" data-name="sqlite">
4249+ <text x="21.0" y="14.1" stroke="none" text-anchor="middle" data-name="sqlite">sqlite</text>
4250
4251- </g>
4252 </g>
4253 </g>
4254 </g>
4255 </g>
4256 </g>
4257 </g>
4258+ </g>
4259 <g data-name="ayllu-shell">
4260- <g fill="#eee8d5" stroke="#33322E" data-name="ayllu-shell">
4261- <rect x="1612.0" y="296.5" height="31.0" width="94.0" data-name="ayllu-shell"></rect>
4262- </g>
4263- <g transform="translate(1612, 296.5)" font-family="Helvetica" font-size="12pt" font-weight="bold" font-style="normal" data-name="ayllu-shell">
4264- <g transform="translate(8, 8)" fill="#33322E" text-align="center" data-name="ayllu-shell">
4265- <text x="39.0" y="13.5" stroke="none" text-anchor="middle" data-name="ayllu-shell">ayllu-shell</text>
4266+ <g fill="#eee8d5" stroke="#33322E" data-name="ayllu-shell">
4267+ <rect x="1481.0" y="300.0" height="32.0" width="94.0" data-name="ayllu-shell"></rect>
4268+ </g>
4269+ <g transform="translate(1481, 300)" font-family="Helvetica" font-size="12pt" font-weight="bold" font-style="normal" data-name="ayllu-shell">
4270+ <g transform="translate(8, 8)" fill="#33322E" text-align="center" data-name="ayllu-shell">
4271+ <text x="39.0" y="14.1" stroke="none" text-anchor="middle" data-name="ayllu-shell">ayllu-shell</text>
4272
4273- </g>
4274 </g>
4275 </g>
4276+ </g>
4277 <g data-name="contributor">
4278- <g fill="#eee8d5" stroke="#33322E" data-name="contributor">
4279- <circle r="4.0" cx="1659.0" cy="493.5" data-name="contributor"></circle>
4280- <path d="M1659.0 497.5 L1659.0 505.5" fill="none" data-name="contributor"></path>
4281- <path d="M1655.0 501.5 L1663.0 501.5" fill="none" data-name="contributor"></path>
4282- <path d="M1655.0 509.5 L1659.0 505.5 L1663.0 509.5" fill="none" data-name="contributor"></path>
4283- </g>
4284- <g transform="translate(1608.5, 505.5)" font-family="Helvetica" font-size="12pt" font-weight="normal" font-style="normal" data-name="contributor">
4285- <g transform="translate(8, 8)" fill="#33322E" text-align="center" data-name="contributor">
4286- <text x="42.5" y="13.5" stroke="none" text-anchor="middle" data-name="contributor">contributor</text>
4287+ <g fill="#eee8d5" stroke="#33322E" data-name="contributor">
4288+ <circle r="4.0" cx="1528.0" cy="500.0" data-name="contributor"></circle>
4289+ <path d="M1528.0 504.0 L1528.0 512.0" fill="none" data-name="contributor"></path>
4290+ <path d="M1524.0 508.0 L1532.0 508.0" fill="none" data-name="contributor"></path>
4291+ <path d="M1524.0 516.0 L1528.0 512.0 L1532.0 516.0" fill="none" data-name="contributor"></path>
4292+ </g>
4293+ <g transform="translate(1477.5, 512)" font-family="Helvetica" font-size="12pt" font-weight="normal" font-style="normal" data-name="contributor">
4294+ <g transform="translate(8, 8)" fill="#33322E" text-align="center" data-name="contributor">
4295+ <text x="42.5" y="14.1" stroke="none" text-anchor="middle" data-name="contributor">contributor</text>
4296
4297- </g>
4298 </g>
4299 </g>
4300+ </g>
4301 <g data-name="ayllu-cli">
4302- <g fill="#eee8d5" stroke="#33322E" data-name="ayllu-cli">
4303- <rect x="1746.0" y="296.5" height="31.0" width="75.0" data-name="ayllu-cli"></rect>
4304- </g>
4305- <g transform="translate(1746, 296.5)" font-family="Helvetica" font-size="12pt" font-weight="bold" font-style="normal" data-name="ayllu-cli">
4306- <g transform="translate(8, 8)" fill="#33322E" text-align="center" data-name="ayllu-cli">
4307- <text x="29.5" y="13.5" stroke="none" text-anchor="middle" data-name="ayllu-cli">ayllu-cli</text>
4308+ <g fill="#eee8d5" stroke="#33322E" data-name="ayllu-cli">
4309+ <rect x="1615.0" y="300.0" height="32.0" width="75.0" data-name="ayllu-cli"></rect>
4310+ </g>
4311+ <g transform="translate(1615, 300)" font-family="Helvetica" font-size="12pt" font-weight="bold" font-style="normal" data-name="ayllu-cli">
4312+ <g transform="translate(8, 8)" fill="#33322E" text-align="center" data-name="ayllu-cli">
4313+ <text x="29.5" y="14.1" stroke="none" text-anchor="middle" data-name="ayllu-cli">ayllu-cli</text>
4314
4315- </g>
4316 </g>
4317 </g>
4318+ </g>
4319 <g data-name="admin">
4320- <g fill="#eee8d5" stroke="#33322E" data-name="admin">
4321- <circle r="4.0" cx="1783.5" cy="493.5" data-name="admin"></circle>
4322- <path d="M1783.5 497.5 L1783.5 505.5" fill="none" data-name="admin"></path>
4323- <path d="M1779.5 501.5 L1787.5 501.5" fill="none" data-name="admin"></path>
4324- <path d="M1779.5 509.5 L1783.5 505.5 L1787.5 509.5" fill="none" data-name="admin"></path>
4325- </g>
4326- <g transform="translate(1752, 505.5)" font-family="Helvetica" font-size="12pt" font-weight="normal" font-style="normal" data-name="admin">
4327- <g transform="translate(8, 8)" fill="#33322E" text-align="center" data-name="admin">
4328- <text x="23.5" y="13.5" stroke="none" text-anchor="middle" data-name="admin">admin</text>
4329+ <g fill="#eee8d5" stroke="#33322E" data-name="admin">
4330+ <circle r="4.0" cx="1652.5" cy="500.0" data-name="admin"></circle>
4331+ <path d="M1652.5 504.0 L1652.5 512.0" fill="none" data-name="admin"></path>
4332+ <path d="M1648.5 508.0 L1656.5 508.0" fill="none" data-name="admin"></path>
4333+ <path d="M1648.5 516.0 L1652.5 512.0 L1656.5 516.0" fill="none" data-name="admin"></path>
4334+ </g>
4335+ <g transform="translate(1621, 512)" font-family="Helvetica" font-size="12pt" font-weight="normal" font-style="normal" data-name="admin">
4336+ <g transform="translate(8, 8)" fill="#33322E" text-align="center" data-name="admin">
4337+ <text x="23.5" y="14.1" stroke="none" text-anchor="middle" data-name="admin">admin</text>
4338
4339- </g>
4340 </g>
4341 </g>
4342+ </g>
4343 <g data-name="ayllu-builder-1">
4344- <g fill="#eee8d5" stroke="#33322E" data-name="ayllu-builder-1">
4345- <rect x="909.5" y="419.0" height="180.0" width="193.0" data-name="ayllu-builder-1"></rect>
4346- <path d="M909.5 450.0 L1102.5 450.0" fill="none" data-name="ayllu-builder-1"></path>
4347- </g>
4348- <g transform="translate(909.5, 419)" font-family="Helvetica" font-size="12pt" font-weight="bold" font-style="normal" data-name="ayllu-builder-1">
4349- <g transform="translate(8, 8)" fill="#33322E" text-align="center" data-name="ayllu-builder-1">
4350- <text x="88.5" y="13.5" stroke="none" text-anchor="middle" data-name="ayllu-builder-1">ayllu-builder-1</text>
4351+ <g fill="#eee8d5" stroke="#33322E" data-name="ayllu-builder-1">
4352+ <rect x="846.5" y="424.0" height="184.0" width="173.0" data-name="ayllu-builder-1"></rect>
4353+ <path d="M846.5 456.0 L1019.5 456.0" fill="none" data-name="ayllu-builder-1"></path>
4354+ </g>
4355+ <g transform="translate(846.5, 424)" font-family="Helvetica" font-size="12pt" font-weight="bold" font-style="normal" data-name="ayllu-builder-1">
4356+ <g transform="translate(8, 8)" fill="#33322E" text-align="center" data-name="ayllu-builder-1">
4357+ <text x="78.5" y="14.1" stroke="none" text-anchor="middle" data-name="ayllu-builder-1">ayllu-builder-1</text>
4358
4359- </g>
4360 </g>
4361- <g transform="translate(909.5, 450)" font-family="Helvetica" font-size="12pt" font-weight="normal" font-style="normal" data-name="ayllu-builder-1">
4362- <g transform="translate(8, 8)" fill="#33322E" data-name="ayllu-builder-1">
4363- <g transform="translate(20, 20)" data-name="ayllu-builder-1">
4364- <g data-name="qemu-x86_64">
4365- <g fill="#fdf6e3" stroke="#33322E" data-name="qemu-x86_64">
4366- <rect x="0.0" y="31.0" height="62.0" width="137.0" data-name="qemu-x86_64"></rect>
4367- <path d="M0.0 31.0 L0.0 0.0 L113.1 0.0 L113.1 31.0 Z" data-name="qemu-x86_64"></path>
4368- <path d="M0.0 31.0 L137.0 31.0" fill="none" data-name="qemu-x86_64"></path>
4369- <path d="M0.0 62.0 L137.0 62.0" fill="none" data-name="qemu-x86_64"></path>
4370- </g>
4371+ </g>
4372+ <g transform="translate(846.5, 456)" font-family="Helvetica" font-size="12pt" font-weight="normal" font-style="normal" data-name="ayllu-builder-1">
4373+ <g transform="translate(8, 8)" fill="#33322E" data-name="ayllu-builder-1">
4374+ <g transform="translate(20, 20)" data-name="ayllu-builder-1">
4375+ <g data-name="qemu-x86_64">
4376+ <g fill="#fdf6e3" stroke="#33322E" data-name="qemu-x86_64">
4377+ <rect x="0.0" y="32.0" height="64.0" width="117.0" data-name="qemu-x86_64"></rect>
4378+ <path d="M0.0 32.0 L0.0 0.0 L113.1 0.0 L113.1 32.0 Z" data-name="qemu-x86_64"></path>
4379+ <path d="M0.0 32.0 L117.0 32.0" fill="none" data-name="qemu-x86_64"></path>
4380+ <path d="M0.0 64.0 L117.0 64.0" fill="none" data-name="qemu-x86_64"></path>
4381+ </g>
4382 <g transform="translate(0, 0)" font-family="Helvetica" font-size="12pt" font-weight="normal" font-style="normal" data-name="qemu-x86_64">
4383- <g transform="translate(8, 8)" fill="#33322E" text-align="left" data-name="qemu-x86_64">
4384- <text x="0.0" y="13.5" stroke="none" data-name="qemu-x86_64">qemu-x86_64</text>
4385+ <g transform="translate(8, 8)" fill="#33322E" text-align="left" data-name="qemu-x86_64">
4386+ <text x="0.0" y="14.1" stroke="none" data-name="qemu-x86_64">qemu-x86_64</text>
4387
4388- </g>
4389 </g>
4390- <g transform="translate(0, 31)" font-family="Helvetica" font-size="12pt" font-weight="normal" font-style="normal" data-name="qemu-x86_64">
4391- <g transform="translate(8, 8)" fill="#33322E" text-align="left" data-name="qemu-x86_64">
4392- <text x="0.0" y="13.5" stroke="none" data-name="qemu-x86_64">capnp-rpc-server</text>
4393+ </g>
4394+ <g transform="translate(0, 32)" font-family="Helvetica" font-size="12pt" font-weight="normal" font-style="normal" data-name="qemu-x86_64">
4395+ <g transform="translate(8, 8)" fill="#33322E" text-align="left" data-name="qemu-x86_64">
4396+ <text x="0.0" y="14.1" stroke="none" data-name="qemu-x86_64">tarpc-server</text>
4397
4398- </g>
4399 </g>
4400- <g transform="translate(0, 62)" font-family="Helvetica" font-size="12pt" font-weight="normal" font-style="normal" data-name="qemu-x86_64">
4401- <g transform="translate(8, 8)" fill="#33322E" text-align="left" data-name="qemu-x86_64">
4402- <text x="0.0" y="13.5" stroke="none" data-name="qemu-x86_64">executor</text>
4403+ </g>
4404+ <g transform="translate(0, 64)" font-family="Helvetica" font-size="12pt" font-weight="normal" font-style="normal" data-name="qemu-x86_64">
4405+ <g transform="translate(8, 8)" fill="#33322E" text-align="left" data-name="qemu-x86_64">
4406+ <text x="0.0" y="14.1" stroke="none" data-name="qemu-x86_64">executor</text>
4407
4408- </g>
4409 </g>
4410 </g>
4411 </g>
4412 </g>
4413 </g>
4414 </g>
4415+ </g>
4416 <g data-name="ayllu-builder-2">
4417- <g fill="#eee8d5" stroke="#33322E" data-name="ayllu-builder-2">
4418- <rect x="1142.5" y="419.0" height="180.0" width="193.0" data-name="ayllu-builder-2"></rect>
4419- <path d="M1142.5 450.0 L1335.5 450.0" fill="none" data-name="ayllu-builder-2"></path>
4420- </g>
4421- <g transform="translate(1142.5, 419)" font-family="Helvetica" font-size="12pt" font-weight="bold" font-style="normal" data-name="ayllu-builder-2">
4422- <g transform="translate(8, 8)" fill="#33322E" text-align="center" data-name="ayllu-builder-2">
4423- <text x="88.5" y="13.5" stroke="none" text-anchor="middle" data-name="ayllu-builder-2">ayllu-builder-2</text>
4424+ <g fill="#eee8d5" stroke="#33322E" data-name="ayllu-builder-2">
4425+ <rect x="1059.5" y="424.0" height="184.0" width="181.0" data-name="ayllu-builder-2"></rect>
4426+ <path d="M1059.5 456.0 L1240.5 456.0" fill="none" data-name="ayllu-builder-2"></path>
4427+ </g>
4428+ <g transform="translate(1059.5, 424)" font-family="Helvetica" font-size="12pt" font-weight="bold" font-style="normal" data-name="ayllu-builder-2">
4429+ <g transform="translate(8, 8)" fill="#33322E" text-align="center" data-name="ayllu-builder-2">
4430+ <text x="82.5" y="14.1" stroke="none" text-anchor="middle" data-name="ayllu-builder-2">ayllu-builder-2</text>
4431
4432- </g>
4433 </g>
4434- <g transform="translate(1142.5, 450)" font-family="Helvetica" font-size="12pt" font-weight="normal" font-style="normal" data-name="ayllu-builder-2">
4435- <g transform="translate(8, 8)" fill="#33322E" data-name="ayllu-builder-2">
4436- <g transform="translate(20, 20)" data-name="ayllu-builder-2">
4437- <g data-name="qemu-aarch64">
4438- <g fill="#fdf6e3" stroke="#33322E" data-name="qemu-aarch64">
4439- <rect x="0.0" y="31.0" height="62.0" width="137.0" data-name="qemu-aarch64"></rect>
4440- <path d="M0.0 31.0 L0.0 0.0 L118.9 0.0 L118.9 31.0 Z" data-name="qemu-aarch64"></path>
4441- <path d="M0.0 31.0 L137.0 31.0" fill="none" data-name="qemu-aarch64"></path>
4442- <path d="M0.0 62.0 L137.0 62.0" fill="none" data-name="qemu-aarch64"></path>
4443- </g>
4444+ </g>
4445+ <g transform="translate(1059.5, 456)" font-family="Helvetica" font-size="12pt" font-weight="normal" font-style="normal" data-name="ayllu-builder-2">
4446+ <g transform="translate(8, 8)" fill="#33322E" data-name="ayllu-builder-2">
4447+ <g transform="translate(20, 20)" data-name="ayllu-builder-2">
4448+ <g data-name="qemu-aarch64">
4449+ <g fill="#fdf6e3" stroke="#33322E" data-name="qemu-aarch64">
4450+ <rect x="0.0" y="32.0" height="64.0" width="125.0" data-name="qemu-aarch64"></rect>
4451+ <path d="M0.0 32.0 L0.0 0.0 L118.9 0.0 L118.9 32.0 Z" data-name="qemu-aarch64"></path>
4452+ <path d="M0.0 32.0 L125.0 32.0" fill="none" data-name="qemu-aarch64"></path>
4453+ <path d="M0.0 64.0 L125.0 64.0" fill="none" data-name="qemu-aarch64"></path>
4454+ </g>
4455 <g transform="translate(0, 0)" font-family="Helvetica" font-size="12pt" font-weight="normal" font-style="normal" data-name="qemu-aarch64">
4456- <g transform="translate(8, 8)" fill="#33322E" text-align="left" data-name="qemu-aarch64">
4457- <text x="0.0" y="13.5" stroke="none" data-name="qemu-aarch64">qemu-aarch64</text>
4458+ <g transform="translate(8, 8)" fill="#33322E" text-align="left" data-name="qemu-aarch64">
4459+ <text x="0.0" y="14.1" stroke="none" data-name="qemu-aarch64">qemu-aarch64</text>
4460
4461- </g>
4462 </g>
4463- <g transform="translate(0, 31)" font-family="Helvetica" font-size="12pt" font-weight="normal" font-style="normal" data-name="qemu-aarch64">
4464- <g transform="translate(8, 8)" fill="#33322E" text-align="left" data-name="qemu-aarch64">
4465- <text x="0.0" y="13.5" stroke="none" data-name="qemu-aarch64">capnp-rpc-server</text>
4466+ </g>
4467+ <g transform="translate(0, 32)" font-family="Helvetica" font-size="12pt" font-weight="normal" font-style="normal" data-name="qemu-aarch64">
4468+ <g transform="translate(8, 8)" fill="#33322E" text-align="left" data-name="qemu-aarch64">
4469+ <text x="0.0" y="14.1" stroke="none" data-name="qemu-aarch64">tarpc-server</text>
4470
4471- </g>
4472 </g>
4473- <g transform="translate(0, 62)" font-family="Helvetica" font-size="12pt" font-weight="normal" font-style="normal" data-name="qemu-aarch64">
4474- <g transform="translate(8, 8)" fill="#33322E" text-align="left" data-name="qemu-aarch64">
4475- <text x="0.0" y="13.5" stroke="none" data-name="qemu-aarch64">exectuor</text>
4476+ </g>
4477+ <g transform="translate(0, 64)" font-family="Helvetica" font-size="12pt" font-weight="normal" font-style="normal" data-name="qemu-aarch64">
4478+ <g transform="translate(8, 8)" fill="#33322E" text-align="left" data-name="qemu-aarch64">
4479+ <text x="0.0" y="14.1" stroke="none" data-name="qemu-aarch64">exectuor</text>
4480
4481- </g>
4482 </g>
4483 </g>
4484 </g>
4485 </g>
4486 </g>
4487 </g>
4488+ </g>
4489 <g data-name="ayllu-builder-n">
4490- <g fill="#eee8d5" stroke="#33322E" data-name="ayllu-builder-n">
4491- <rect x="1375.5" y="419.0" height="180.0" width="193.0" data-name="ayllu-builder-n"></rect>
4492- <path d="M1375.5 450.0 L1568.5 450.0" fill="none" data-name="ayllu-builder-n"></path>
4493- </g>
4494- <g transform="translate(1375.5, 419)" font-family="Helvetica" font-size="12pt" font-weight="bold" font-style="normal" data-name="ayllu-builder-n">
4495- <g transform="translate(8, 8)" fill="#33322E" text-align="center" data-name="ayllu-builder-n">
4496- <text x="88.5" y="13.5" stroke="none" text-anchor="middle" data-name="ayllu-builder-n">ayllu-builder-n</text>
4497+ <g fill="#eee8d5" stroke="#33322E" data-name="ayllu-builder-n">
4498+ <rect x="1280.5" y="424.0" height="184.0" width="157.0" data-name="ayllu-builder-n"></rect>
4499+ <path d="M1280.5 456.0 L1437.5 456.0" fill="none" data-name="ayllu-builder-n"></path>
4500+ </g>
4501+ <g transform="translate(1280.5, 424)" font-family="Helvetica" font-size="12pt" font-weight="bold" font-style="normal" data-name="ayllu-builder-n">
4502+ <g transform="translate(8, 8)" fill="#33322E" text-align="center" data-name="ayllu-builder-n">
4503+ <text x="70.5" y="14.1" stroke="none" text-anchor="middle" data-name="ayllu-builder-n">ayllu-builder-n</text>
4504
4505- </g>
4506 </g>
4507- <g transform="translate(1375.5, 450)" font-family="Helvetica" font-size="12pt" font-weight="normal" font-style="normal" data-name="ayllu-builder-n">
4508- <g transform="translate(8, 8)" fill="#33322E" data-name="ayllu-builder-n">
4509- <g transform="translate(20, 20)" data-name="ayllu-builder-n">
4510- <g data-name="lxc">
4511- <g fill="#fdf6e3" stroke="#33322E" data-name="lxc">
4512- <rect x="0.0" y="31.0" height="62.0" width="137.0" data-name="lxc"></rect>
4513- <path d="M0.0 31.0 L0.0 0.0 L35.2 0.0 L35.2 31.0 Z" data-name="lxc"></path>
4514- <path d="M0.0 31.0 L137.0 31.0" fill="none" data-name="lxc"></path>
4515- <path d="M0.0 62.0 L137.0 62.0" fill="none" data-name="lxc"></path>
4516- </g>
4517+ </g>
4518+ <g transform="translate(1280.5, 456)" font-family="Helvetica" font-size="12pt" font-weight="normal" font-style="normal" data-name="ayllu-builder-n">
4519+ <g transform="translate(8, 8)" fill="#33322E" data-name="ayllu-builder-n">
4520+ <g transform="translate(20, 20)" data-name="ayllu-builder-n">
4521+ <g data-name="lxc">
4522+ <g fill="#fdf6e3" stroke="#33322E" data-name="lxc">
4523+ <rect x="0.0" y="32.0" height="64.0" width="101.0" data-name="lxc"></rect>
4524+ <path d="M0.0 32.0 L0.0 0.0 L35.2 0.0 L35.2 32.0 Z" data-name="lxc"></path>
4525+ <path d="M0.0 32.0 L101.0 32.0" fill="none" data-name="lxc"></path>
4526+ <path d="M0.0 64.0 L101.0 64.0" fill="none" data-name="lxc"></path>
4527+ </g>
4528 <g transform="translate(0, 0)" font-family="Helvetica" font-size="12pt" font-weight="normal" font-style="normal" data-name="lxc">
4529- <g transform="translate(8, 8)" fill="#33322E" text-align="left" data-name="lxc">
4530- <text x="0.0" y="13.5" stroke="none" data-name="lxc">lxc</text>
4531+ <g transform="translate(8, 8)" fill="#33322E" text-align="left" data-name="lxc">
4532+ <text x="0.0" y="14.1" stroke="none" data-name="lxc">lxc</text>
4533
4534- </g>
4535 </g>
4536- <g transform="translate(0, 31)" font-family="Helvetica" font-size="12pt" font-weight="normal" font-style="normal" data-name="lxc">
4537- <g transform="translate(8, 8)" fill="#33322E" text-align="left" data-name="lxc">
4538- <text x="0.0" y="13.5" stroke="none" data-name="lxc">capnp-rpc-server</text>
4539+ </g>
4540+ <g transform="translate(0, 32)" font-family="Helvetica" font-size="12pt" font-weight="normal" font-style="normal" data-name="lxc">
4541+ <g transform="translate(8, 8)" fill="#33322E" text-align="left" data-name="lxc">
4542+ <text x="0.0" y="14.1" stroke="none" data-name="lxc">tarpc-server</text>
4543
4544- </g>
4545 </g>
4546- <g transform="translate(0, 62)" font-family="Helvetica" font-size="12pt" font-weight="normal" font-style="normal" data-name="lxc">
4547- <g transform="translate(8, 8)" fill="#33322E" text-align="left" data-name="lxc">
4548- <text x="0.0" y="13.5" stroke="none" data-name="lxc">executor</text>
4549+ </g>
4550+ <g transform="translate(0, 64)" font-family="Helvetica" font-size="12pt" font-weight="normal" font-style="normal" data-name="lxc">
4551+ <g transform="translate(8, 8)" fill="#33322E" text-align="left" data-name="lxc">
4552+ <text x="0.0" y="14.1" stroke="none" data-name="lxc">executor</text>
4553
4554- </g>
4555 </g>
4556 </g>
4557 </g>
4558 @@ -595,4 +594,5 @@
4559 </g>
4560 </g>
4561 </g>
4562- </svg>
4563\ No newline at end of file
4564+ </g>
4565+ </svg>
4566 diff --git a/www/public/docs/architecture/index.html b/www/public/docs/architecture/index.html
4567index 25dbdfb..bfeb01a 100644
4568--- a/www/public/docs/architecture/index.html
4569+++ b/www/public/docs/architecture/index.html
4570 @@ -1 +1 @@
4571- <!doctype html><html lang=en><head><meta charset=utf-8><link href=/main.css rel=stylesheet><link href=/assets/ayllu_logo.svg rel=icon type=image/svg+xml><link href=/assets/ayllu_logo.png rel=icon sizes=any type=image/png><title>Ayllu</title></head><nav><ul><li><a href=/> <img class=logo src=/assets/ayllu_logo.png> </a></ul><ul><li><a href=https://ayllu-forge.org/browse>browse</a><li><a href=/docs>docs</a></ul></nav><body class=container><div class=wrapper><main class=page-body><div class=documentation><div class=side-panel><ul><li class=active><a href=https://ayllu-forge.org/docs/architecture/>Architecture</a><li><a href=https://ayllu-forge.org/docs/builds/>Builds</a><li><a href=https://ayllu-forge.org/docs/configuration/>Configuration</a><li><a href=https://ayllu-forge.org/docs/developers/>Developers</a><li><a href=https://ayllu-forge.org/docs/installation/>Installation</a><li><a href=https://ayllu-forge.org/docs/faq/>FAQ</a><li><a href=https://ayllu-forge.org/docs/mail/>Mail</a></ul></div><div class=doc-content><h1>Architecture</h1><p><strong>Ayllu is a new software project and it's design is evolving.</strong><p>It's basic architecture is that of a single binary providing a web interface to browse and interact with Git repositories as well as an RPC server based on <a href=https://capnproto.org/>Cap'n Proto</a> with an embedded client. The RPC server running in the core Ayllu binary is called the <code>job_server</code> and it provides an interface to interact with Ayllu for administrators. Much of the functionality of Ayllu is implemented via <code>RPC extensions</code> which are minimal binaries that are used to extend the core functionality of Ayllu.<p>The current architecture of Ayllu is depicted below:<p><a href=/assets/architecture.svg><img class=diagram src=/assets/architecture.svg></a></div></div></main><footer class=page-footer>2024, <a href=/projects/ayllu/blob/main/ATTRIBUTIONS.md>attributions</a></footer></div>
4572\ No newline at end of file
4573+ <!doctype html><html lang=en><head><meta charset=utf-8><link href=/main.css rel=stylesheet><link href=/assets/ayllu_logo.svg rel=icon type=image/svg+xml><link href=/assets/ayllu_logo.png rel=icon sizes=any type=image/png><title>Ayllu</title></head><nav><ul><li><a href=/> <img class=logo src=/assets/ayllu_logo.png> </a></ul><ul><li><a href=https://ayllu-forge.org/browse>browse</a><li><a href=/docs>docs</a></ul></nav><body class=container><div class=wrapper><main class=page-body><div class=documentation><div class=side-panel><ul><li class=active><a href=https://ayllu-forge.org/docs/architecture/>Architecture</a><li><a href=https://ayllu-forge.org/docs/builds/>Builds</a><li><a href=https://ayllu-forge.org/docs/configuration/>Configuration</a><li><a href=https://ayllu-forge.org/docs/developers/>Developers</a><li><a href=https://ayllu-forge.org/docs/installation/>Installation</a><li><a href=https://ayllu-forge.org/docs/faq/>FAQ</a><li><a href=https://ayllu-forge.org/docs/mail/>Mail</a></ul></div><div class=doc-content><h1>Architecture</h1><p><strong>Ayllu is a new software project and it's design is evolving.</strong><p>It's basic architecture is that of a single binary providing a web interface to browse and interact with Git repositories as well as an RPC server based on <a href=https://github.com/google/tarpc>tarpc</a> with an embedded client. The RPC server running in the core Ayllu binary is called the <code>job_server</code> and it provides an interface to interact with Ayllu for administrators. Much of the functionality of Ayllu is implemented via <code>RPC extensions</code> which are minimal binaries that are used to extend the core functionality of Ayllu.<p>The current architecture of Ayllu is depicted below:<p><a href=/assets/architecture.svg><img class=diagram src=/assets/architecture.svg></a></div></div></main><footer class=page-footer>2024, <a href=/projects/ayllu/blob/main/ATTRIBUTIONS.md>attributions</a></footer></div>
4574\ No newline at end of file
4575 diff --git a/www/public/index.html b/www/public/index.html
4576index 1ab90ef..0188f0a 100644
4577--- a/www/public/index.html
4578+++ b/www/public/index.html
4579 @@ -1 +1 @@
4580- <!doctype html><html lang=en><head><meta charset=utf-8><link href=/main.css rel=stylesheet><link href=/assets/ayllu_logo.svg rel=icon type=image/svg+xml><link href=/assets/ayllu_logo.png rel=icon sizes=any type=image/png><title>Ayllu</title></head><nav><ul><li><a href=/> <img class=logo src=/assets/ayllu_logo.png> </a></ul><ul><li><a href=https://ayllu-forge.org/browse>browse</a><li><a href=/docs>docs</a></ul></nav><body class=container><div class=wrapper><main class=page-body><section class=home-header><div class=blurb><h1 id=hyper-performant-hackable-code-forge-built-on-open-standards>Hyper Performant & Hackable Code Forge Built on Open Standards</h1><p>Ayllu is a lightweight code forge designed to enable individuals and community projects develop software in collaboration across open internet standards.</p><a href=https://ayllu-forge.org/projects/ayllu> <button>source</button> </a></div><div class=screenshot><a href=https://ayllu-forge.org/projects/ayllu> <img class="screenshot night" src=/assets/images/ui.png></a><img class="screenshot day" src=/assets/images/ui-day.png></div></section><div class=home-wrapper><article><h4 id=general-purpose-git-ui>General Purpose Git UI</h4><p>General purpose web based UI for git. Browse hosted projects, view contributors, source code, blame, releases, etc.</article><article><h4 id=source-code-analysis>Source Code Analysis</h4><p>Super accurate syntax highlighting and code composition insight across project history.</article><article><h4 id=incremental-jobs>Incremental Jobs</h4><p>A built-in configurable incremental jobs system controlled via Git hooks and/or cron to extend forge functionality.</article><article><h4 id=extensible-plugin-system>Extensible Plugin System</h4><p>An extensible CapnProto based plugin system allows adding large external integrations via RPC calls.</article><article><h4 id=completely-themable>Completely Themable</h4><p>The web interface is fully customizable, a few themes <a href=https://ayllu-forge.org/config>already</a> exist and more are on the way.</article><article><h4 id=web-1-5>Web 1.5</h4><p>Super fast web frontend with first class support for RSS based project subscriptions. Ayllu loads faster than any other code forge in existence. <em>If it doesn't, it's a bug!</em></article><article><h4 id=static-hosting>Static Hosting</h4><p>Serve static websites directly from a git repository making project sites, documentation, or other assets simple to self-host.</article><article><h4 id=mailing-list-management>Mailing List Management</h4><p>Full featured email based software development workflows and mailing list management based on <a href=https://git.meli-email.org/meli/mailpot>Mailpot 🍯</a>.</article><article><h4 id=much-more>Much More</h4><p>New features planned such as continuous integration, mailing list support, external API, federation, and more.</article></div></main><footer class=page-footer>2024, <a href=/projects/ayllu/blob/main/ATTRIBUTIONS.md>attributions</a></footer></div>
4581\ No newline at end of file
4582+ <!doctype html><html lang=en><head><meta charset=utf-8><link href=/main.css rel=stylesheet><link href=/assets/ayllu_logo.svg rel=icon type=image/svg+xml><link href=/assets/ayllu_logo.png rel=icon sizes=any type=image/png><title>Ayllu</title></head><nav><ul><li><a href=/> <img class=logo src=/assets/ayllu_logo.png> </a></ul><ul><li><a href=https://ayllu-forge.org/browse>browse</a><li><a href=/docs>docs</a></ul></nav><body class=container><div class=wrapper><main class=page-body><section class=home-header><div class=blurb><h1 id=hyper-performant-hackable-code-forge-built-on-open-standards>Hyper Performant & Hackable Code Forge Built on Open Standards</h1><p>Ayllu is a lightweight code forge designed to enable individuals and community projects develop software in collaboration across open internet standards.</p><a href=https://ayllu-forge.org/projects/ayllu> <button>source</button> </a></div><div class=screenshot><a href=https://ayllu-forge.org/projects/ayllu> <img class="screenshot night" src=/assets/images/ui.png></a><img class="screenshot day" src=/assets/images/ui-day.png></div></section><div class=home-wrapper><article><h4 id=general-purpose-git-ui>General Purpose Git UI</h4><p>General purpose web based UI for git. Browse hosted projects, view contributors, source code, blame, releases, etc.</article><article><h4 id=source-code-analysis>Source Code Analysis</h4><p>Super accurate syntax highlighting and code composition insight across project history.</article><article><h4 id=incremental-jobs>Incremental Jobs</h4><p>A built-in configurable incremental jobs system controlled via Git hooks and/or cron to extend forge functionality.</article><article><h4 id=extensible-plugin-system>Extensible Plugin System</h4><p>An extensible RPC based plugin system allows adding large external integrations.</article><article><h4 id=completely-themable>Completely Themable</h4><p>The web interface is fully customizable, a few themes <a href=https://ayllu-forge.org/config>already</a> exist and more are on the way.</article><article><h4 id=web-1-5>Web 1.5</h4><p>Super fast web frontend with first class support for RSS based project subscriptions. Ayllu loads faster than any other code forge in existence. <em>If it doesn't, it's a bug!</em></article><article><h4 id=static-hosting>Static Hosting</h4><p>Serve static websites directly from a git repository making project sites, documentation, or other assets simple to self-host.</article><article><h4 id=mailing-list-management>Mailing List Management</h4><p>Full featured email based software development workflows and mailing list management based on <a href=https://git.meli-email.org/meli/mailpot>Mailpot 🍯</a>.</article><article><h4 id=much-more>Much More</h4><p>New features planned such as continuous integration, mailing list support, external API, federation, and more.</article></div></main><footer class=page-footer>2024, <a href=/projects/ayllu/blob/main/ATTRIBUTIONS.md>attributions</a></footer></div>
4583\ No newline at end of file
4584 diff --git a/www/static/assets/architecture.svg b/www/static/assets/architecture.svg
4585index 2de2a37..e05c148 100644
4586--- a/www/static/assets/architecture.svg
4587+++ b/www/static/assets/architecture.svg
4588 @@ -1,35 +1,34 @@
4589- <svg version="1.1" baseProfile="full" width="1938.0" height="655.0" viewbox="0 0 1938 655" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:ev="http://www.w3.org/2001/xml-events">
4590- <desc >
4591- [reverse_proxy]
4592+ <svg version="1.1" baseProfile="full" width="1817.5" height="664.0" viewBox="0 0 1817.5 664" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:ev="http://www.w3.org/2001/xml-events">
4593+ <desc >[reverse_proxy]
4594
4595 [reverse_proxy]&lt;--&gt;[Rudolfs (LFS API)]
4596
4597 [reverse_proxy]&lt;--&gt;[ayllu-core |
4598- [axum http interface]
4599+ [axum http interface]
4600 [sites static hosting]
4601- [capnp-rpc-server (job_server)]
4602+ [tarpc-server (job_server)]
4603 [git repositories via libgit2]
4604 [&lt;database&gt; sqlite]
4605 ]
4606
4607 [ayllu-core] &lt;--&gt; [
4608 ayllu-mail|
4609- [capnp-rpc-server]
4610+ [tarpc-server]
4611 [mailpot]
4612 [postfix]
4613 [&lt;database&gt; sqlite]
4614 ] &lt;-- SMTP [&lt;actor&gt; contributor-2]
4615
4616 [ayllu-core] &lt;--&gt; [
4617- ayllu-xmpp |
4618- [capnp-rpc-server]
4619- [chat interface/bot]
4620+ ayllu-xmpp |
4621+ [tarpc-server]
4622+ [chat interface/bot]
4623 [&lt;database&gt; sqlite]
4624- ] &lt;-- XMPP [&lt;actor&gt; contributor-2]
4625+ ] &lt;-- XMPP [&lt;actor&gt;; contributor-2]
4626
4627 [ayllu-core] &lt;--&gt; [
4628- ayllu-build |
4629- [capnp-rpc-server]
4630+ ayllu-build |
4631+ [tarpc-server]
4632 [&lt;database&gt; sqlite]
4633 ]
4634
4635 @@ -38,553 +37,553 @@
4636
4637
4638 [ayllu-build]&lt;--&gt;[
4639- ayllu-builder-1 |
4640- [&lt;package&gt;qemu-x86_64| capnp-rpc-server | executor]
4641+ ayllu-builder-1 |
4642+ [&lt;package&gt;qemu-x86_64| tarpc-server | executor]
4643 ]
4644
4645 [ayllu-build]&lt;--&gt;[
4646- ayllu-builder-2 |
4647- [&lt;package&gt;qemu-aarch64| capnp-rpc-server | exectuor]
4648+ ayllu-builder-2 |
4649+ [&lt;package&gt;qemu-aarch64| tarpc-server | exectuor]
4650 ]
4651
4652 [ayllu-build]&lt;--&gt;[
4653- ayllu-builder-n |
4654- [&lt;package&gt;lxc| capnp-rpc-server | executor]
4655- ] --&gt; [ayllu-core]</desc>
4656+ ayllu-builder-n |
4657+ [&lt;package&gt;lxc| tarpc-server | executor]
4658+ ] --&gt; [ayllu-core]
4659+ </desc>
4660 <g stroke-width="1.0" text-align="left" font="12pt Helvetica, Arial, sans-serif" font-size="12pt" font-family="Helvetica" font-weight="normal" font-style="normal">
4661- <g font-family="Helvetica" font-size="12pt" font-weight="bold" font-style="normal" stroke-width="3.0" stroke-linejoin="round" stroke-linecap="round" stroke="#33322E">
4662- <g stroke="transparent" fill="transparent">
4663- <rect x="0.0" y="0.0" height="655.0" width="1938.0" stroke="none"></rect>
4664- </g>
4665+ <g font-family="Helvetica" font-size="12pt" font-weight="bold" font-style="normal" stroke-width="3.0" stroke-linejoin="round" stroke-linecap="round" stroke="#33322E">
4666+ <g stroke="transparent" fill="transparent">
4667+ <rect x="0.0" y="0.0" height="664.0" width="1817.5" stroke="none"></rect>
4668+ </g>
4669 <g transform="translate(8, 8)" fill="#33322E">
4670- <g transform="translate(20, 20)" fill="#33322E" font-family="Helvetica" font-size="12pt" font-weight="normal" font-style="normal">
4671- <g stroke-dasharray="6 6">
4672- <path d="M965.1 23.1 L713 51 L713 115.83333333333333 L713.0 115.8 " fill="none"></path>
4673- </g>
4674- <path d="M707.7 109.2 L713.0 115.8 L718.3 109.2 L713.0 122.5 Z"></path>
4675- <path d="M959.1 29.2 L965.1 23.1 L957.9 18.6 L971.8 22.4 Z"></path>
4676+ <g transform="translate(20, 20)" fill="#33322E" font-family="Helvetica" font-size="12pt" font-weight="normal" font-style="normal">
4677 <g stroke-dasharray="6 6">
4678- <path d="M1103.4 23.1 L1355.5 51 L1355.5 64.33333333333333 L1355.5 64.3 " fill="none"></path>
4679- </g>
4680- <path d="M1350.2 57.7 L1355.5 64.3 L1360.8 57.7 L1355.5 71.0 Z"></path>
4681- <path d="M1110.6 18.6 L1103.4 23.1 L1109.4 29.2 L1096.8 22.4 Z"></path>
4682+ <path d="M873.9 24.0 L631.5 52 L631.5 117.33333333333333 L631.5 117.3 " fill="none"></path>
4683+ </g>
4684+ <path d="M626.2 110.7 L631.5 117.3 L636.8 110.7 L631.5 124.0 Z"></path>
4685+ <path d="M867.9 30.1 L873.9 24.0 L866.6 19.5 L880.5 23.2 Z"></path>
4686 <g stroke-dasharray="6 6">
4687- <path d="M822.4 180.4 L261 225 L261 238.33333333333334 L261.0 238.3 " fill="none"></path>
4688- </g>
4689- <path d="M255.7 231.7 L261.0 238.3 L266.3 231.7 L261.0 245.0 Z"></path>
4690- <path d="M816.1 186.2 L822.4 180.4 L815.3 175.6 L829.0 179.9 Z"></path>
4691- <text x="758.0" y="489.4" stroke="none">SMTP</text>
4692+ <path d="M1012.1 24.0 L1254.5 52 L1254.5 65.33333333333333 L1254.5 65.3 " fill="none"></path>
4693+ </g>
4694+ <path d="M1249.2 58.7 L1254.5 65.3 L1259.8 58.7 L1254.5 72.0 Z"></path>
4695+ <path d="M1019.4 19.5 L1012.1 24.0 L1018.1 30.1 L1005.5 23.2 Z"></path>
4696 <g stroke-dasharray="6 6">
4697- <path d="M261.0 385.7 L261 399 L750 497.4263494967978 L750.0 497.4 " fill="none"></path>
4698- </g>
4699- <path d="M266.3 392.3 L261.0 385.7 L255.7 392.3 L261.0 379.0 Z"></path>
4700+ <path d="M740.9 184.6 L242 228 L242 241.33333333333334 L242.0 241.3 " fill="none"></path>
4701+ </g>
4702+ <path d="M236.7 234.7 L242.0 241.3 L247.3 234.7 L242.0 248.0 Z"></path>
4703+ <path d="M734.7 190.5 L740.9 184.6 L733.8 179.9 L747.5 184.1 Z"></path>
4704+ <text x="698.3" y="495.3" stroke="none">SMTP</text>
4705 <g stroke-dasharray="6 6">
4706- <path d="M926.9 206.0 L807.5 225 L807.5 238.33333333333334 L807.5 238.3 " fill="none"></path>
4707- </g>
4708- <path d="M802.2 231.7 L807.5 238.3 L812.8 231.7 L807.5 245.0 Z"></path>
4709- <path d="M921.1 212.4 L926.9 206.0 L919.5 201.8 L933.5 205.0 Z"></path>
4710- <text x="754.7" y="473.5" stroke="none">XMPP</text>
4711+ <path d="M242.0 390.7 L242 404 L690.25 503.2664359861592 L690.3 503.3 " fill="none"></path>
4712+ </g>
4713+ <path d="M247.3 397.3 L242.0 390.7 L236.7 397.3 L242.0 384.0 Z"></path>
4714 <g stroke-dasharray="6 6">
4715- <path d="M807.5 385.7 L807.5 399 L807.5 481.5 L807.5 481.5 " fill="none"></path>
4716- </g>
4717- <path d="M812.8 392.3 L807.5 385.7 L802.2 392.3 L807.5 379.0 Z"></path>
4718+ <path d="M858.5 209.1 L750.5 228 L750.5 241.33333333333334 L750.5 241.3 " fill="none"></path>
4719+ </g>
4720+ <path d="M745.2 234.7 L750.5 241.3 L755.8 234.7 L750.5 248.0 Z"></path>
4721+ <path d="M852.8 215.5 L858.5 209.1 L851.0 205.0 L865.0 208.0 Z"></path>
4722+ <text x="695.6" y="480.0" stroke="none">XMPP</text>
4723 <g stroke-dasharray="6 6">
4724- <path d="M1276.2 209.5 L1259 225 L1259 238.33333333333334 L1259.0 238.3 " fill="none"></path>
4725- </g>
4726- <path d="M1253.7 231.7 L1259.0 238.3 L1264.3 231.7 L1259.0 245.0 Z"></path>
4727- <path d="M1274.9 217.9 L1276.2 209.5 L1267.7 210.0 L1281.2 205.0 Z"></path>
4728+ <path d="M750.5 390.7 L750.5 404 L748.4375 488 L748.4 488.0 " fill="none"></path>
4729+ </g>
4730+ <path d="M755.8 397.3 L750.5 390.7 L745.2 397.3 L750.5 384.0 Z"></path>
4731 <g stroke-dasharray="6 6">
4732- <path d="M1595.6 206.8 L1659 225 L1659 289.8333333333333 L1659.0 289.8 " fill="none"></path>
4733- </g>
4734- <path d="M1653.7 283.2 L1659.0 289.8 L1664.3 283.2 L1659.0 296.5 Z"></path>
4735- <path d="M1603.5 203.5 L1595.6 206.8 L1600.6 213.8 L1589.2 205.0 Z"></path>
4736- <text x="1618.3" y="473.5" stroke="none">SSH</text>
4737+ <path d="M1183.6 212.8 L1168.75 228 L1168.75 241.33333333333334 L1168.8 241.3 " fill="none"></path>
4738+ </g>
4739+ <path d="M1163.4 234.7 L1168.8 241.3 L1174.1 234.7 L1168.8 248.0 Z"></path>
4740+ <path d="M1182.8 221.3 L1183.6 212.8 L1175.1 213.8 L1188.2 208.0 Z"></path>
4741 <g stroke-dasharray="6 6">
4742- <path d="M1659.0 334.2 L1659 399 L1659 481.5 L1659.0 481.5 " fill="none"></path>
4743- </g>
4744- <path d="M1664.3 340.8 L1659.0 334.2 L1653.7 340.8 L1659.0 327.5 Z"></path>
4745+ <path d="M1472.2 210.0 L1528 228 L1528 293.3333333333333 L1528.0 293.3 " fill="none"></path>
4746+ </g>
4747+ <path d="M1522.7 286.7 L1528.0 293.3 L1533.3 286.7 L1528.0 300.0 Z"></path>
4748+ <path d="M1480.2 207.0 L1472.2 210.0 L1476.9 217.2 L1465.8 208.0 Z"></path>
4749+ <text x="1487.3" y="480.0" stroke="none">SSH</text>
4750 <g stroke-dasharray="6 6">
4751- <path d="M1691.6 206.3 L1783.5 225 L1783.5 289.8333333333333 L1783.5 289.8 " fill="none"></path>
4752- </g>
4753- <path d="M1778.2 283.2 L1783.5 289.8 L1788.8 283.2 L1783.5 296.5 Z"></path>
4754- <path d="M1699.2 202.4 L1691.6 206.3 L1697.1 212.9 L1685.1 205.0 Z"></path>
4755+ <path d="M1528.0 338.7 L1528 404 L1528 488 L1528.0 488.0 " fill="none"></path>
4756+ </g>
4757+ <path d="M1533.3 345.3 L1528.0 338.7 L1522.7 345.3 L1528.0 332.0 Z"></path>
4758 <g stroke-dasharray="6 6">
4759- <path d="M1783.5 334.2 L1783.5 399 L1783.5 481.5 L1783.5 481.5 " fill="none"></path>
4760- </g>
4761- <path d="M1788.8 340.8 L1783.5 334.2 L1778.2 340.8 L1783.5 327.5 Z"></path>
4762+ <path d="M1568.6 209.4 L1652.5 228 L1652.5 293.3333333333333 L1652.5 293.3 " fill="none"></path>
4763+ </g>
4764+ <path d="M1647.2 286.7 L1652.5 293.3 L1657.8 286.7 L1652.5 300.0 Z"></path>
4765+ <path d="M1576.2 205.7 L1568.6 209.4 L1573.9 216.1 L1562.0 208.0 Z"></path>
4766 <g stroke-dasharray="6 6">
4767- <path d="M1102.2 365.9 L1006 399 L1006 412.3333333333333 L1006.0 412.3 " fill="none"></path>
4768- </g>
4769- <path d="M1000.7 405.7 L1006.0 412.3 L1011.3 405.7 L1006.0 419.0 Z"></path>
4770- <path d="M1097.6 373.1 L1102.2 365.9 L1094.2 363.0 L1108.5 363.8 Z"></path>
4771+ <path d="M1652.5 338.7 L1652.5 404 L1652.5 488 L1652.5 488.0 " fill="none"></path>
4772+ </g>
4773+ <path d="M1657.8 345.3 L1652.5 338.7 L1647.2 345.3 L1652.5 332.0 Z"></path>
4774 <g stroke-dasharray="6 6">
4775- <path d="M1242.1 385.5 L1239 399 L1239 412.3333333333333 L1239.0 412.3 " fill="none"></path>
4776- </g>
4777- <path d="M1233.7 405.7 L1239.0 412.3 L1244.3 405.7 L1239.0 419.0 Z"></path>
4778- <path d="M1245.8 393.2 L1242.1 385.5 L1235.4 390.8 L1243.6 379.0 Z"></path>
4779+ <path d="M1031.0 367.4 L933 404 L933 417.3333333333333 L933.0 417.3 " fill="none"></path>
4780+ </g>
4781+ <path d="M927.7 410.7 L933.0 417.3 L938.3 410.7 L933.0 424.0 Z"></path>
4782+ <path d="M1026.6 374.7 L1031.0 367.4 L1022.9 364.8 L1037.3 365.1 Z"></path>
4783 <g stroke-dasharray="6 6">
4784- <path d="M1354.1 383.0 L1375.5 399 L1388.6489765726408 413.9884707045646 L1388.6 414.0 " fill="none"></path>
4785- </g>
4786- <path d="M1380.2 412.5 L1388.6 414.0 L1388.3 405.5 L1393.0 419.0 Z"></path>
4787- <path d="M1362.6 382.7 L1354.1 383.0 L1356.2 391.3 L1348.7 379.0 Z"></path>
4788+ <path d="M1152.9 390.5 L1150 404 L1150 417.3333333333333 L1150.0 417.3 " fill="none"></path>
4789+ </g>
4790+ <path d="M1144.7 410.7 L1150.0 417.3 L1155.3 410.7 L1150.0 424.0 Z"></path>
4791+ <path d="M1156.7 398.2 L1152.9 390.5 L1146.3 395.9 L1154.3 384.0 Z"></path>
4792 <g stroke-dasharray="6 6">
4793- <path d="M1512.9 419.0 L1522 399 L1522 312 L1522 312 L1522 225 L1522 225 L1489.6328041618644 208.08741118367695 L1489.6 208.1 " fill="none"></path>
4794- </g>
4795- <path d="M1498.0 206.4 L1489.6 208.1 L1493.1 215.9 L1483.7 205.0 Z"></path>
4796+ <path d="M1255.6 388.3 L1274.5 404 L1285.5740965207704 418.6780924298968 L1285.6 418.7 " fill="none"></path>
4797+ </g>
4798+ <path d="M1277.3 416.6 L1285.6 418.7 L1285.8 410.1 L1289.6 424.0 Z"></path>
4799+ <path d="M1264.1 388.4 L1255.6 388.3 L1257.3 396.6 L1250.5 384.0 Z"></path>
4800+ <g stroke-dasharray="6 6">
4801+ <path d="M1392.7 424.0 L1400 404 L1400 316 L1400 316 L1400 228 L1400 228 L1372.6362943277163 211.45012990267378 L1372.6 211.5 " fill="none"></path>
4802+ </g>
4803+ <path d="M1381.1 210.3 L1372.6 211.5 L1375.6 219.5 L1366.9 208.0 Z"></path>
4804 <g data-name="reverse_proxy">
4805- <g fill="#eee8d5" stroke="#33322E" data-name="reverse_proxy">
4806- <rect x="971.8" y="0.0" height="31.0" width="125.0" data-name="reverse_proxy"></rect>
4807- </g>
4808- <g transform="translate(971.75, 0)" font-family="Helvetica" font-size="12pt" font-weight="bold" font-style="normal" data-name="reverse_proxy">
4809- <g transform="translate(8, 8)" fill="#33322E" text-align="center" data-name="reverse_proxy">
4810- <text x="54.5" y="13.5" stroke="none" text-anchor="middle" data-name="reverse_proxy">reverse_proxy</text>
4811+ <g fill="#eee8d5" stroke="#33322E" data-name="reverse_proxy">
4812+ <rect x="880.5" y="0.0" height="32.0" width="125.0" data-name="reverse_proxy"></rect>
4813+ </g>
4814+ <g transform="translate(880.5, 0)" font-family="Helvetica" font-size="12pt" font-weight="bold" font-style="normal" data-name="reverse_proxy">
4815+ <g transform="translate(8, 8)" fill="#33322E" text-align="center" data-name="reverse_proxy">
4816+ <text x="54.5" y="14.1" stroke="none" text-anchor="middle" data-name="reverse_proxy">reverse_proxy</text>
4817
4818- </g>
4819 </g>
4820 </g>
4821+ </g>
4822 <g data-name="Rudolfs (LFS API)">
4823- <g fill="#eee8d5" stroke="#33322E" data-name="Rudolfs (LFS API)">
4824- <rect x="637.0" y="122.5" height="31.0" width="152.0" data-name="Rudolfs (LFS API)"></rect>
4825- </g>
4826- <g transform="translate(637, 122.5)" font-family="Helvetica" font-size="12pt" font-weight="bold" font-style="normal" data-name="Rudolfs (LFS API)">
4827- <g transform="translate(8, 8)" fill="#33322E" text-align="center" data-name="Rudolfs (LFS API)">
4828- <text x="68.0" y="13.5" stroke="none" text-anchor="middle" data-name="Rudolfs (LFS API)">Rudolfs (LFS API)</text>
4829+ <g fill="#eee8d5" stroke="#33322E" data-name="Rudolfs (LFS API)">
4830+ <rect x="555.5" y="124.0" height="32.0" width="152.0" data-name="Rudolfs (LFS API)"></rect>
4831+ </g>
4832+ <g transform="translate(555.5, 124)" font-family="Helvetica" font-size="12pt" font-weight="bold" font-style="normal" data-name="Rudolfs (LFS API)">
4833+ <g transform="translate(8, 8)" fill="#33322E" text-align="center" data-name="Rudolfs (LFS API)">
4834+ <text x="68.0" y="14.1" stroke="none" text-anchor="middle" data-name="Rudolfs (LFS API)">Rudolfs (LFS API)</text>
4835
4836- </g>
4837 </g>
4838 </g>
4839+ </g>
4840 <g data-name="ayllu-core">
4841- <g fill="#eee8d5" stroke="#33322E" data-name="ayllu-core">
4842- <rect x="829.0" y="71.0" height="134.0" width="1053.0" data-name="ayllu-core"></rect>
4843- <path d="M829.0 102.0 L1882.0 102.0" fill="none" data-name="ayllu-core"></path>
4844- </g>
4845- <g transform="translate(829, 71)" font-family="Helvetica" font-size="12pt" font-weight="bold" font-style="normal" data-name="ayllu-core">
4846- <g transform="translate(8, 8)" fill="#33322E" text-align="center" data-name="ayllu-core">
4847- <text x="518.5" y="13.5" stroke="none" text-anchor="middle" data-name="ayllu-core">ayllu-core</text>
4848+ <g fill="#eee8d5" stroke="#33322E" data-name="ayllu-core">
4849+ <rect x="747.5" y="72.0" height="136.0" width="1014.0" data-name="ayllu-core"></rect>
4850+ <path d="M747.5 104.0 L1761.5 104.0" fill="none" data-name="ayllu-core"></path>
4851+ </g>
4852+ <g transform="translate(747.5, 72)" font-family="Helvetica" font-size="12pt" font-weight="bold" font-style="normal" data-name="ayllu-core">
4853+ <g transform="translate(8, 8)" fill="#33322E" text-align="center" data-name="ayllu-core">
4854+ <text x="499.0" y="14.1" stroke="none" text-anchor="middle" data-name="ayllu-core">ayllu-core</text>
4855
4856- </g>
4857 </g>
4858- <g transform="translate(829, 102)" font-family="Helvetica" font-size="12pt" font-weight="normal" font-style="normal" data-name="ayllu-core">
4859- <g transform="translate(8, 8)" fill="#33322E" data-name="ayllu-core">
4860- <g transform="translate(20, 20)" data-name="ayllu-core">
4861- <g data-name="axum http interface">
4862- <g fill="#fdf6e3" stroke="#33322E" data-name="axum http interface">
4863- <rect x="0.0" y="8.0" height="31.0" width="164.0" data-name="axum http interface"></rect>
4864- </g>
4865+ </g>
4866+ <g transform="translate(747.5, 104)" font-family="Helvetica" font-size="12pt" font-weight="normal" font-style="normal" data-name="ayllu-core">
4867+ <g transform="translate(8, 8)" fill="#33322E" data-name="ayllu-core">
4868+ <g transform="translate(20, 20)" data-name="ayllu-core">
4869+ <g data-name="axum http interface">
4870+ <g fill="#fdf6e3" stroke="#33322E" data-name="axum http interface">
4871+ <rect x="0.0" y="8.0" height="32.0" width="164.0" data-name="axum http interface"></rect>
4872+ </g>
4873 <g transform="translate(0, 8)" font-family="Helvetica" font-size="12pt" font-weight="bold" font-style="normal" data-name="axum http interface">
4874- <g transform="translate(8, 8)" fill="#33322E" text-align="center" data-name="axum http interface">
4875- <text x="74.0" y="13.5" stroke="none" text-anchor="middle" data-name="axum http interface">axum http interface</text>
4876+ <g transform="translate(8, 8)" fill="#33322E" text-align="center" data-name="axum http interface">
4877+ <text x="74.0" y="14.1" stroke="none" text-anchor="middle" data-name="axum http interface">axum http interface</text>
4878
4879- </g>
4880 </g>
4881 </g>
4882+ </g>
4883 <g data-name="sites static hosting">
4884- <g fill="#fdf6e3" stroke="#33322E" data-name="sites static hosting">
4885- <rect x="204.0" y="8.0" height="31.0" width="161.0" data-name="sites static hosting"></rect>
4886- </g>
4887+ <g fill="#fdf6e3" stroke="#33322E" data-name="sites static hosting">
4888+ <rect x="204.0" y="8.0" height="32.0" width="161.0" data-name="sites static hosting"></rect>
4889+ </g>
4890 <g transform="translate(204, 8)" font-family="Helvetica" font-size="12pt" font-weight="bold" font-style="normal" data-name="sites static hosting">
4891- <g transform="translate(8, 8)" fill="#33322E" text-align="center" data-name="sites static hosting">
4892- <text x="72.5" y="13.5" stroke="none" text-anchor="middle" data-name="sites static hosting">sites static hosting</text>
4893+ <g transform="translate(8, 8)" fill="#33322E" text-align="center" data-name="sites static hosting">
4894+ <text x="72.5" y="14.1" stroke="none" text-anchor="middle" data-name="sites static hosting">sites static hosting</text>
4895
4896- </g>
4897 </g>
4898 </g>
4899- <g data-name="capnp-rpc-server (job_server)">
4900- <g fill="#fdf6e3" stroke="#33322E" data-name="capnp-rpc-server (job_server)">
4901- <rect x="405.0" y="8.0" height="31.0" width="244.0" data-name="capnp-rpc-server (job_server)"></rect>
4902- </g>
4903- <g transform="translate(405, 8)" font-family="Helvetica" font-size="12pt" font-weight="bold" font-style="normal" data-name="capnp-rpc-server (job_server)">
4904- <g transform="translate(8, 8)" fill="#33322E" text-align="center" data-name="capnp-rpc-server (job_server)">
4905- <text x="114.0" y="13.5" stroke="none" text-anchor="middle" data-name="capnp-rpc-server (job_server)">capnp-rpc-server (job_server)</text>
4906+ </g>
4907+ <g data-name="tarpc-server (job_server)">
4908+ <g fill="#fdf6e3" stroke="#33322E" data-name="tarpc-server (job_server)">
4909+ <rect x="405.0" y="8.0" height="32.0" width="205.0" data-name="tarpc-server (job_server)"></rect>
4910+ </g>
4911+ <g transform="translate(405, 8)" font-family="Helvetica" font-size="12pt" font-weight="bold" font-style="normal" data-name="tarpc-server (job_server)">
4912+ <g transform="translate(8, 8)" fill="#33322E" text-align="center" data-name="tarpc-server (job_server)">
4913+ <text x="94.5" y="14.1" stroke="none" text-anchor="middle" data-name="tarpc-server (job_server)">tarpc-server (job_server)</text>
4914
4915- </g>
4916 </g>
4917 </g>
4918+ </g>
4919 <g data-name="git repositories via libgit2">
4920- <g fill="#fdf6e3" stroke="#33322E" data-name="git repositories via libgit2">
4921- <rect x="689.0" y="8.0" height="31.0" width="210.0" data-name="git repositories via libgit2"></rect>
4922- </g>
4923- <g transform="translate(689, 8)" font-family="Helvetica" font-size="12pt" font-weight="bold" font-style="normal" data-name="git repositories via libgit2">
4924- <g transform="translate(8, 8)" fill="#33322E" text-align="center" data-name="git repositories via libgit2">
4925- <text x="97.0" y="13.5" stroke="none" text-anchor="middle" data-name="git repositories via libgit2">git repositories via libgit2</text>
4926+ <g fill="#fdf6e3" stroke="#33322E" data-name="git repositories via libgit2">
4927+ <rect x="650.0" y="8.0" height="32.0" width="210.0" data-name="git repositories via libgit2"></rect>
4928+ </g>
4929+ <g transform="translate(650, 8)" font-family="Helvetica" font-size="12pt" font-weight="bold" font-style="normal" data-name="git repositories via libgit2">
4930+ <g transform="translate(8, 8)" fill="#33322E" text-align="center" data-name="git repositories via libgit2">
4931+ <text x="97.0" y="14.1" stroke="none" text-anchor="middle" data-name="git repositories via libgit2">git repositories via libgit2</text>
4932
4933- </g>
4934 </g>
4935 </g>
4936+ </g>
4937 <g data-name="sqlite">
4938- <g fill="#fdf6e3" stroke="#33322E" data-name="sqlite">
4939- <rect x="939.0" y="8.0" height="31.0" width="58.0" stroke="none" data-name="sqlite"></rect>
4940- <path d="M939.0 8.0 L939.0 39.0" fill="none" data-name="sqlite"></path>
4941- <path d="M997.0 8.0 L997.0 39.0" fill="none" data-name="sqlite"></path>
4942- <ellipse cx="968.0" cy="8.0" rx="29.0" ry="6.0" data-name="sqlite"></ellipse>
4943- <path d="M997.0 39.0 L997.0 39.3 L996.9 39.6 L996.7 39.9 L996.4 40.2 L996.1 40.5 L995.7 40.8 L995.3 41.1 L994.7 41.3 L994.1 41.6 L993.5 41.9 L992.7 42.1 L992.0 42.4 L991.1 42.6 L990.2 42.9 L989.3 43.1 L988.2 43.3 L987.2 43.5 L986.1 43.7 L984.9 43.9 L983.7 44.0 L982.5 44.2 L981.2 44.3 L979.9 44.5 L978.6 44.6 L977.2 44.7 L975.9 44.8 L974.5 44.8 L973.0 44.9 L971.6 45.0 L970.2 45.0 L968.7 45.0 L967.3 45.0 L965.8 45.0 L964.4 45.0 L963.0 44.9 L961.5 44.8 L960.1 44.8 L958.8 44.7 L957.4 44.6 L956.1 44.5 L954.8 44.3 L953.5 44.2 L952.3 44.0 L951.1 43.9 L949.9 43.7 L948.8 43.5 L947.8 43.3 L946.7 43.1 L945.8 42.9 L944.9 42.6 L944.0 42.4 L943.3 42.1 L942.5 41.9 L941.9 41.6 L941.3 41.3 L940.7 41.1 L940.3 40.8 L939.9 40.5 L939.6 40.2 L939.3 39.9 L939.1 39.6 L939.0 39.3 L939.0 39.0" data-name="sqlite"></path>
4944- </g>
4945- <g transform="translate(939, 12)" font-family="Helvetica" font-size="12pt" font-weight="bold" font-style="normal" data-name="sqlite">
4946- <g transform="translate(8, 8)" fill="#33322E" text-align="center" data-name="sqlite">
4947- <text x="21.0" y="13.5" stroke="none" text-anchor="middle" data-name="sqlite">sqlite</text>
4948+ <g fill="#fdf6e3" stroke="#33322E" data-name="sqlite">
4949+ <rect x="900.0" y="8.0" height="32.0" width="58.0" stroke="none" data-name="sqlite"></rect>
4950+ <path d="M900.0 8.0 L900.0 40.0" fill="none" data-name="sqlite"></path>
4951+ <path d="M958.0 8.0 L958.0 40.0" fill="none" data-name="sqlite"></path>
4952+ <ellipse cx="929.0" cy="8.0" rx="29.0" ry="6.0" data-name="sqlite"></ellipse>
4953+ <path d="M958.0 40.0 L958.0 40.3 L957.9 40.6 L957.7 40.9 L957.4 41.2 L957.1 41.5 L956.7 41.8 L956.3 42.1 L955.7 42.3 L955.1 42.6 L954.5 42.9 L953.7 43.1 L953.0 43.4 L952.1 43.6 L951.2 43.9 L950.3 44.1 L949.2 44.3 L948.2 44.5 L947.1 44.7 L945.9 44.9 L944.7 45.0 L943.5 45.2 L942.2 45.3 L940.9 45.5 L939.6 45.6 L938.2 45.7 L936.9 45.8 L935.5 45.8 L934.0 45.9 L932.6 46.0 L931.2 46.0 L929.7 46.0 L928.3 46.0 L926.8 46.0 L925.4 46.0 L924.0 45.9 L922.5 45.8 L921.1 45.8 L919.8 45.7 L918.4 45.6 L917.1 45.5 L915.8 45.3 L914.5 45.2 L913.3 45.0 L912.1 44.9 L910.9 44.7 L909.8 44.5 L908.8 44.3 L907.7 44.1 L906.8 43.9 L905.9 43.6 L905.0 43.4 L904.3 43.1 L903.5 42.9 L902.9 42.6 L902.3 42.3 L901.7 42.1 L901.3 41.8 L900.9 41.5 L900.6 41.2 L900.3 40.9 L900.1 40.6 L900.0 40.3 L900.0 40.0" data-name="sqlite"></path>
4954+ </g>
4955+ <g transform="translate(900, 12)" font-family="Helvetica" font-size="12pt" font-weight="bold" font-style="normal" data-name="sqlite">
4956+ <g transform="translate(8, 8)" fill="#33322E" text-align="center" data-name="sqlite">
4957+ <text x="21.0" y="14.1" stroke="none" text-anchor="middle" data-name="sqlite">sqlite</text>
4958
4959- </g>
4960 </g>
4961 </g>
4962 </g>
4963 </g>
4964 </g>
4965 </g>
4966+ </g>
4967 <g data-name="ayllu-mail">
4968- <g fill="#eee8d5" stroke="#33322E" data-name="ayllu-mail">
4969- <rect x="0.0" y="245.0" height="134.0" width="522.0" data-name="ayllu-mail"></rect>
4970- <path d="M0.0 276.0 L522.0 276.0" fill="none" data-name="ayllu-mail"></path>
4971- </g>
4972- <g transform="translate(0, 245)" font-family="Helvetica" font-size="12pt" font-weight="bold" font-style="normal" data-name="ayllu-mail">
4973- <g transform="translate(8, 8)" fill="#33322E" text-align="center" data-name="ayllu-mail">
4974- <text x="253.0" y="13.5" stroke="none" text-anchor="middle" data-name="ayllu-mail">ayllu-mail</text>
4975+ <g fill="#eee8d5" stroke="#33322E" data-name="ayllu-mail">
4976+ <rect x="0.0" y="248.0" height="136.0" width="484.0" data-name="ayllu-mail"></rect>
4977+ <path d="M0.0 280.0 L484.0 280.0" fill="none" data-name="ayllu-mail"></path>
4978+ </g>
4979+ <g transform="translate(0, 248)" font-family="Helvetica" font-size="12pt" font-weight="bold" font-style="normal" data-name="ayllu-mail">
4980+ <g transform="translate(8, 8)" fill="#33322E" text-align="center" data-name="ayllu-mail">
4981+ <text x="234.0" y="14.1" stroke="none" text-anchor="middle" data-name="ayllu-mail">ayllu-mail</text>
4982
4983- </g>
4984 </g>
4985- <g transform="translate(0, 276)" font-family="Helvetica" font-size="12pt" font-weight="normal" font-style="normal" data-name="ayllu-mail">
4986- <g transform="translate(8, 8)" fill="#33322E" data-name="ayllu-mail">
4987- <g transform="translate(20, 20)" data-name="ayllu-mail">
4988- <g data-name="capnp-rpc-server">
4989- <g fill="#fdf6e3" stroke="#33322E" data-name="capnp-rpc-server">
4990- <rect x="0.0" y="8.0" height="31.0" width="147.0" data-name="capnp-rpc-server"></rect>
4991- </g>
4992- <g transform="translate(0, 8)" font-family="Helvetica" font-size="12pt" font-weight="bold" font-style="normal" data-name="capnp-rpc-server">
4993- <g transform="translate(8, 8)" fill="#33322E" text-align="center" data-name="capnp-rpc-server">
4994- <text x="65.5" y="13.5" stroke="none" text-anchor="middle" data-name="capnp-rpc-server">capnp-rpc-server</text>
4995+ </g>
4996+ <g transform="translate(0, 280)" font-family="Helvetica" font-size="12pt" font-weight="normal" font-style="normal" data-name="ayllu-mail">
4997+ <g transform="translate(8, 8)" fill="#33322E" data-name="ayllu-mail">
4998+ <g transform="translate(20, 20)" data-name="ayllu-mail">
4999+ <g data-name="tarpc-server">
5000+ <g fill="#fdf6e3" stroke="#33322E" data-name="tarpc-server">
5001+ <rect x="0.0" y="8.0" height="32.0" width="109.0" data-name="tarpc-server"></rect>
5002+ </g>
5003+ <g transform="translate(0, 8)" font-family="Helvetica" font-size="12pt" font-weight="bold" font-style="normal" data-name="tarpc-server">
5004+ <g transform="translate(8, 8)" fill="#33322E" text-align="center" data-name="tarpc-server">
5005+ <text x="46.5" y="14.1" stroke="none" text-anchor="middle" data-name="tarpc-server">tarpc-server</text>
5006
5007- </g>
5008 </g>
5009 </g>
5010+ </g>
5011 <g data-name="mailpot">
5012- <g fill="#fdf6e3" stroke="#33322E" data-name="mailpot">
5013- <rect x="187.0" y="8.0" height="31.0" width="73.0" data-name="mailpot"></rect>
5014- </g>
5015- <g transform="translate(187, 8)" font-family="Helvetica" font-size="12pt" font-weight="bold" font-style="normal" data-name="mailpot">
5016- <g transform="translate(8, 8)" fill="#33322E" text-align="center" data-name="mailpot">
5017- <text x="28.5" y="13.5" stroke="none" text-anchor="middle" data-name="mailpot">mailpot</text>
5018+ <g fill="#fdf6e3" stroke="#33322E" data-name="mailpot">
5019+ <rect x="149.0" y="8.0" height="32.0" width="73.0" data-name="mailpot"></rect>
5020+ </g>
5021+ <g transform="translate(149, 8)" font-family="Helvetica" font-size="12pt" font-weight="bold" font-style="normal" data-name="mailpot">
5022+ <g transform="translate(8, 8)" fill="#33322E" text-align="center" data-name="mailpot">
5023+ <text x="28.5" y="14.1" stroke="none" text-anchor="middle" data-name="mailpot">mailpot</text>
5024
5025- </g>
5026 </g>
5027 </g>
5028+ </g>
5029 <g data-name="postfix">
5030- <g fill="#fdf6e3" stroke="#33322E" data-name="postfix">
5031- <rect x="300.0" y="8.0" height="31.0" width="68.0" data-name="postfix"></rect>
5032- </g>
5033- <g transform="translate(300, 8)" font-family="Helvetica" font-size="12pt" font-weight="bold" font-style="normal" data-name="postfix">
5034- <g transform="translate(8, 8)" fill="#33322E" text-align="center" data-name="postfix">
5035- <text x="26.0" y="13.5" stroke="none" text-anchor="middle" data-name="postfix">postfix</text>
5036+ <g fill="#fdf6e3" stroke="#33322E" data-name="postfix">
5037+ <rect x="262.0" y="8.0" height="32.0" width="68.0" data-name="postfix"></rect>
5038+ </g>
5039+ <g transform="translate(262, 8)" font-family="Helvetica" font-size="12pt" font-weight="bold" font-style="normal" data-name="postfix">
5040+ <g transform="translate(8, 8)" fill="#33322E" text-align="center" data-name="postfix">
5041+ <text x="26.0" y="14.1" stroke="none" text-anchor="middle" data-name="postfix">postfix</text>
5042
5043- </g>
5044 </g>
5045 </g>
5046+ </g>
5047 <g data-name="sqlite">
5048- <g fill="#fdf6e3" stroke="#33322E" data-name="sqlite">
5049- <rect x="408.0" y="8.0" height="31.0" width="58.0" stroke="none" data-name="sqlite"></rect>
5050- <path d="M408.0 8.0 L408.0 39.0" fill="none" data-name="sqlite"></path>
5051- <path d="M466.0 8.0 L466.0 39.0" fill="none" data-name="sqlite"></path>
5052- <ellipse cx="437.0" cy="8.0" rx="29.0" ry="6.0" data-name="sqlite"></ellipse>
5053- <path d="M466.0 39.0 L466.0 39.3 L465.9 39.6 L465.7 39.9 L465.4 40.2 L465.1 40.5 L464.7 40.8 L464.3 41.1 L463.7 41.3 L463.1 41.6 L462.5 41.9 L461.7 42.1 L461.0 42.4 L460.1 42.6 L459.2 42.9 L458.3 43.1 L457.2 43.3 L456.2 43.5 L455.1 43.7 L453.9 43.9 L452.7 44.0 L451.5 44.2 L450.2 44.3 L448.9 44.5 L447.6 44.6 L446.2 44.7 L444.9 44.8 L443.5 44.8 L442.0 44.9 L440.6 45.0 L439.2 45.0 L437.7 45.0 L436.3 45.0 L434.8 45.0 L433.4 45.0 L432.0 44.9 L430.5 44.8 L429.1 44.8 L427.8 44.7 L426.4 44.6 L425.1 44.5 L423.8 44.3 L422.5 44.2 L421.3 44.0 L420.1 43.9 L418.9 43.7 L417.8 43.5 L416.8 43.3 L415.7 43.1 L414.8 42.9 L413.9 42.6 L413.0 42.4 L412.3 42.1 L411.5 41.9 L410.9 41.6 L410.3 41.3 L409.7 41.1 L409.3 40.8 L408.9 40.5 L408.6 40.2 L408.3 39.9 L408.1 39.6 L408.0 39.3 L408.0 39.0" data-name="sqlite"></path>
5054- </g>
5055- <g transform="translate(408, 12)" font-family="Helvetica" font-size="12pt" font-weight="bold" font-style="normal" data-name="sqlite">
5056- <g transform="translate(8, 8)" fill="#33322E" text-align="center" data-name="sqlite">
5057- <text x="21.0" y="13.5" stroke="none" text-anchor="middle" data-name="sqlite">sqlite</text>
5058+ <g fill="#fdf6e3" stroke="#33322E" data-name="sqlite">
5059+ <rect x="370.0" y="8.0" height="32.0" width="58.0" stroke="none" data-name="sqlite"></rect>
5060+ <path d="M370.0 8.0 L370.0 40.0" fill="none" data-name="sqlite"></path>
5061+ <path d="M428.0 8.0 L428.0 40.0" fill="none" data-name="sqlite"></path>
5062+ <ellipse cx="399.0" cy="8.0" rx="29.0" ry="6.0" data-name="sqlite"></ellipse>
5063+ <path d="M428.0 40.0 L428.0 40.3 L427.9 40.6 L427.7 40.9 L427.4 41.2 L427.1 41.5 L426.7 41.8 L426.3 42.1 L425.7 42.3 L425.1 42.6 L424.5 42.9 L423.7 43.1 L423.0 43.4 L422.1 43.6 L421.2 43.9 L420.3 44.1 L419.2 44.3 L418.2 44.5 L417.1 44.7 L415.9 44.9 L414.7 45.0 L413.5 45.2 L412.2 45.3 L410.9 45.5 L409.6 45.6 L408.2 45.7 L406.9 45.8 L405.5 45.8 L404.0 45.9 L402.6 46.0 L401.2 46.0 L399.7 46.0 L398.3 46.0 L396.8 46.0 L395.4 46.0 L394.0 45.9 L392.5 45.8 L391.1 45.8 L389.8 45.7 L388.4 45.6 L387.1 45.5 L385.8 45.3 L384.5 45.2 L383.3 45.0 L382.1 44.9 L380.9 44.7 L379.8 44.5 L378.8 44.3 L377.7 44.1 L376.8 43.9 L375.9 43.6 L375.0 43.4 L374.3 43.1 L373.5 42.9 L372.9 42.6 L372.3 42.3 L371.7 42.1 L371.3 41.8 L370.9 41.5 L370.6 41.2 L370.3 40.9 L370.1 40.6 L370.0 40.3 L370.0 40.0" data-name="sqlite"></path>
5064+ </g>
5065+ <g transform="translate(370, 12)" font-family="Helvetica" font-size="12pt" font-weight="bold" font-style="normal" data-name="sqlite">
5066+ <g transform="translate(8, 8)" fill="#33322E" text-align="center" data-name="sqlite">
5067+ <text x="21.0" y="14.1" stroke="none" text-anchor="middle" data-name="sqlite">sqlite</text>
5068
5069- </g>
5070 </g>
5071 </g>
5072 </g>
5073 </g>
5074 </g>
5075 </g>
5076+ </g>
5077 <g data-name="contributor-2">
5078- <g fill="#eee8d5" stroke="#33322E" data-name="contributor-2">
5079- <circle r="4.0" cx="807.5" cy="493.5" data-name="contributor-2"></circle>
5080- <path d="M807.5 497.5 L807.5 505.5" fill="none" data-name="contributor-2"></path>
5081- <path d="M803.5 501.5 L811.5 501.5" fill="none" data-name="contributor-2"></path>
5082- <path d="M803.5 509.5 L807.5 505.5 L811.5 509.5" fill="none" data-name="contributor-2"></path>
5083- </g>
5084- <g transform="translate(750, 505.5)" font-family="Helvetica" font-size="12pt" font-weight="normal" font-style="normal" data-name="contributor-2">
5085- <g transform="translate(8, 8)" fill="#33322E" text-align="center" data-name="contributor-2">
5086- <text x="49.5" y="13.5" stroke="none" text-anchor="middle" data-name="contributor-2">contributor-2</text>
5087+ <g fill="#eee8d5" stroke="#33322E" data-name="contributor-2">
5088+ <circle r="4.0" cx="747.8" cy="500.0" data-name="contributor-2"></circle>
5089+ <path d="M747.8 504.0 L747.8 512.0" fill="none" data-name="contributor-2"></path>
5090+ <path d="M743.8 508.0 L751.8 508.0" fill="none" data-name="contributor-2"></path>
5091+ <path d="M743.8 516.0 L747.8 512.0 L751.8 516.0" fill="none" data-name="contributor-2"></path>
5092+ </g>
5093+ <g transform="translate(690.25, 512)" font-family="Helvetica" font-size="12pt" font-weight="normal" font-style="normal" data-name="contributor-2">
5094+ <g transform="translate(8, 8)" fill="#33322E" text-align="center" data-name="contributor-2">
5095+ <text x="49.5" y="14.1" stroke="none" text-anchor="middle" data-name="contributor-2">contributor-2</text>
5096
5097- </g>
5098 </g>
5099 </g>
5100+ </g>
5101 <g data-name="ayllu-xmpp">
5102- <g fill="#eee8d5" stroke="#33322E" data-name="ayllu-xmpp">
5103- <rect x="562.0" y="245.0" height="134.0" width="491.0" data-name="ayllu-xmpp"></rect>
5104- <path d="M562.0 276.0 L1053.0 276.0" fill="none" data-name="ayllu-xmpp"></path>
5105- </g>
5106- <g transform="translate(562, 245)" font-family="Helvetica" font-size="12pt" font-weight="bold" font-style="normal" data-name="ayllu-xmpp">
5107- <g transform="translate(8, 8)" fill="#33322E" text-align="center" data-name="ayllu-xmpp">
5108- <text x="237.5" y="13.5" stroke="none" text-anchor="middle" data-name="ayllu-xmpp">ayllu-xmpp</text>
5109+ <g fill="#eee8d5" stroke="#33322E" data-name="ayllu-xmpp">
5110+ <rect x="524.0" y="248.0" height="136.0" width="453.0" data-name="ayllu-xmpp"></rect>
5111+ <path d="M524.0 280.0 L977.0 280.0" fill="none" data-name="ayllu-xmpp"></path>
5112+ </g>
5113+ <g transform="translate(524, 248)" font-family="Helvetica" font-size="12pt" font-weight="bold" font-style="normal" data-name="ayllu-xmpp">
5114+ <g transform="translate(8, 8)" fill="#33322E" text-align="center" data-name="ayllu-xmpp">
5115+ <text x="218.5" y="14.1" stroke="none" text-anchor="middle" data-name="ayllu-xmpp">ayllu-xmpp</text>
5116
5117- </g>
5118 </g>
5119- <g transform="translate(562, 276)" font-family="Helvetica" font-size="12pt" font-weight="normal" font-style="normal" data-name="ayllu-xmpp">
5120- <g transform="translate(8, 8)" fill="#33322E" data-name="ayllu-xmpp">
5121- <g transform="translate(20, 20)" data-name="ayllu-xmpp">
5122- <g data-name="capnp-rpc-server">
5123- <g fill="#fdf6e3" stroke="#33322E" data-name="capnp-rpc-server">
5124- <rect x="0.0" y="8.0" height="31.0" width="147.0" data-name="capnp-rpc-server"></rect>
5125- </g>
5126- <g transform="translate(0, 8)" font-family="Helvetica" font-size="12pt" font-weight="bold" font-style="normal" data-name="capnp-rpc-server">
5127- <g transform="translate(8, 8)" fill="#33322E" text-align="center" data-name="capnp-rpc-server">
5128- <text x="65.5" y="13.5" stroke="none" text-anchor="middle" data-name="capnp-rpc-server">capnp-rpc-server</text>
5129+ </g>
5130+ <g transform="translate(524, 280)" font-family="Helvetica" font-size="12pt" font-weight="normal" font-style="normal" data-name="ayllu-xmpp">
5131+ <g transform="translate(8, 8)" fill="#33322E" data-name="ayllu-xmpp">
5132+ <g transform="translate(20, 20)" data-name="ayllu-xmpp">
5133+ <g data-name="tarpc-server">
5134+ <g fill="#fdf6e3" stroke="#33322E" data-name="tarpc-server">
5135+ <rect x="0.0" y="8.0" height="32.0" width="109.0" data-name="tarpc-server"></rect>
5136+ </g>
5137+ <g transform="translate(0, 8)" font-family="Helvetica" font-size="12pt" font-weight="bold" font-style="normal" data-name="tarpc-server">
5138+ <g transform="translate(8, 8)" fill="#33322E" text-align="center" data-name="tarpc-server">
5139+ <text x="46.5" y="14.1" stroke="none" text-anchor="middle" data-name="tarpc-server">tarpc-server</text>
5140
5141- </g>
5142 </g>
5143 </g>
5144+ </g>
5145 <g data-name="chat interface/bot">
5146- <g fill="#fdf6e3" stroke="#33322E" data-name="chat interface/bot">
5147- <rect x="187.0" y="8.0" height="31.0" width="150.0" data-name="chat interface/bot"></rect>
5148- </g>
5149- <g transform="translate(187, 8)" font-family="Helvetica" font-size="12pt" font-weight="bold" font-style="normal" data-name="chat interface/bot">
5150- <g transform="translate(8, 8)" fill="#33322E" text-align="center" data-name="chat interface/bot">
5151- <text x="67.0" y="13.5" stroke="none" text-anchor="middle" data-name="chat interface/bot">chat interface/bot</text>
5152+ <g fill="#fdf6e3" stroke="#33322E" data-name="chat interface/bot">
5153+ <rect x="149.0" y="8.0" height="32.0" width="150.0" data-name="chat interface/bot"></rect>
5154+ </g>
5155+ <g transform="translate(149, 8)" font-family="Helvetica" font-size="12pt" font-weight="bold" font-style="normal" data-name="chat interface/bot">
5156+ <g transform="translate(8, 8)" fill="#33322E" text-align="center" data-name="chat interface/bot">
5157+ <text x="67.0" y="14.1" stroke="none" text-anchor="middle" data-name="chat interface/bot">chat interface/bot</text>
5158
5159- </g>
5160 </g>
5161 </g>
5162+ </g>
5163 <g data-name="sqlite">
5164- <g fill="#fdf6e3" stroke="#33322E" data-name="sqlite">
5165- <rect x="377.0" y="8.0" height="31.0" width="58.0" stroke="none" data-name="sqlite"></rect>
5166- <path d="M377.0 8.0 L377.0 39.0" fill="none" data-name="sqlite"></path>
5167- <path d="M435.0 8.0 L435.0 39.0" fill="none" data-name="sqlite"></path>
5168- <ellipse cx="406.0" cy="8.0" rx="29.0" ry="6.0" data-name="sqlite"></ellipse>
5169- <path d="M435.0 39.0 L435.0 39.3 L434.9 39.6 L434.7 39.9 L434.4 40.2 L434.1 40.5 L433.7 40.8 L433.3 41.1 L432.7 41.3 L432.1 41.6 L431.5 41.9 L430.7 42.1 L430.0 42.4 L429.1 42.6 L428.2 42.9 L427.3 43.1 L426.2 43.3 L425.2 43.5 L424.1 43.7 L422.9 43.9 L421.7 44.0 L420.5 44.2 L419.2 44.3 L417.9 44.5 L416.6 44.6 L415.2 44.7 L413.9 44.8 L412.5 44.8 L411.0 44.9 L409.6 45.0 L408.2 45.0 L406.7 45.0 L405.3 45.0 L403.8 45.0 L402.4 45.0 L401.0 44.9 L399.5 44.8 L398.1 44.8 L396.8 44.7 L395.4 44.6 L394.1 44.5 L392.8 44.3 L391.5 44.2 L390.3 44.0 L389.1 43.9 L387.9 43.7 L386.8 43.5 L385.8 43.3 L384.7 43.1 L383.8 42.9 L382.9 42.6 L382.0 42.4 L381.3 42.1 L380.5 41.9 L379.9 41.6 L379.3 41.3 L378.7 41.1 L378.3 40.8 L377.9 40.5 L377.6 40.2 L377.3 39.9 L377.1 39.6 L377.0 39.3 L377.0 39.0" data-name="sqlite"></path>
5170- </g>
5171- <g transform="translate(377, 12)" font-family="Helvetica" font-size="12pt" font-weight="bold" font-style="normal" data-name="sqlite">
5172- <g transform="translate(8, 8)" fill="#33322E" text-align="center" data-name="sqlite">
5173- <text x="21.0" y="13.5" stroke="none" text-anchor="middle" data-name="sqlite">sqlite</text>
5174+ <g fill="#fdf6e3" stroke="#33322E" data-name="sqlite">
5175+ <rect x="339.0" y="8.0" height="32.0" width="58.0" stroke="none" data-name="sqlite"></rect>
5176+ <path d="M339.0 8.0 L339.0 40.0" fill="none" data-name="sqlite"></path>
5177+ <path d="M397.0 8.0 L397.0 40.0" fill="none" data-name="sqlite"></path>
5178+ <ellipse cx="368.0" cy="8.0" rx="29.0" ry="6.0" data-name="sqlite"></ellipse>
5179+ <path d="M397.0 40.0 L397.0 40.3 L396.9 40.6 L396.7 40.9 L396.4 41.2 L396.1 41.5 L395.7 41.8 L395.3 42.1 L394.7 42.3 L394.1 42.6 L393.5 42.9 L392.7 43.1 L392.0 43.4 L391.1 43.6 L390.2 43.9 L389.3 44.1 L388.2 44.3 L387.2 44.5 L386.1 44.7 L384.9 44.9 L383.7 45.0 L382.5 45.2 L381.2 45.3 L379.9 45.5 L378.6 45.6 L377.2 45.7 L375.9 45.8 L374.5 45.8 L373.0 45.9 L371.6 46.0 L370.2 46.0 L368.7 46.0 L367.3 46.0 L365.8 46.0 L364.4 46.0 L363.0 45.9 L361.5 45.8 L360.1 45.8 L358.8 45.7 L357.4 45.6 L356.1 45.5 L354.8 45.3 L353.5 45.2 L352.3 45.0 L351.1 44.9 L349.9 44.7 L348.8 44.5 L347.8 44.3 L346.7 44.1 L345.8 43.9 L344.9 43.6 L344.0 43.4 L343.3 43.1 L342.5 42.9 L341.9 42.6 L341.3 42.3 L340.7 42.1 L340.3 41.8 L339.9 41.5 L339.6 41.2 L339.3 40.9 L339.1 40.6 L339.0 40.3 L339.0 40.0" data-name="sqlite"></path>
5180+ </g>
5181+ <g transform="translate(339, 12)" font-family="Helvetica" font-size="12pt" font-weight="bold" font-style="normal" data-name="sqlite">
5182+ <g transform="translate(8, 8)" fill="#33322E" text-align="center" data-name="sqlite">
5183+ <text x="21.0" y="14.1" stroke="none" text-anchor="middle" data-name="sqlite">sqlite</text>
5184
5185- </g>
5186 </g>
5187 </g>
5188 </g>
5189 </g>
5190 </g>
5191 </g>
5192+ </g>
5193 <g data-name="ayllu-build">
5194- <g fill="#eee8d5" stroke="#33322E" data-name="ayllu-build">
5195- <rect x="1108.5" y="245.0" height="134.0" width="301.0" data-name="ayllu-build"></rect>
5196- <path d="M1108.5 276.0 L1409.5 276.0" fill="none" data-name="ayllu-build"></path>
5197- </g>
5198- <g transform="translate(1108.5, 245)" font-family="Helvetica" font-size="12pt" font-weight="bold" font-style="normal" data-name="ayllu-build">
5199- <g transform="translate(8, 8)" fill="#33322E" text-align="center" data-name="ayllu-build">
5200- <text x="142.5" y="13.5" stroke="none" text-anchor="middle" data-name="ayllu-build">ayllu-build</text>
5201+ <g fill="#eee8d5" stroke="#33322E" data-name="ayllu-build">
5202+ <rect x="1037.3" y="248.0" height="136.0" width="263.0" data-name="ayllu-build"></rect>
5203+ <path d="M1037.3 280.0 L1300.3 280.0" fill="none" data-name="ayllu-build"></path>
5204+ </g>
5205+ <g transform="translate(1037.25, 248)" font-family="Helvetica" font-size="12pt" font-weight="bold" font-style="normal" data-name="ayllu-build">
5206+ <g transform="translate(8, 8)" fill="#33322E" text-align="center" data-name="ayllu-build">
5207+ <text x="123.5" y="14.1" stroke="none" text-anchor="middle" data-name="ayllu-build">ayllu-build</text>
5208
5209- </g>
5210 </g>
5211- <g transform="translate(1108.5, 276)" font-family="Helvetica" font-size="12pt" font-weight="normal" font-style="normal" data-name="ayllu-build">
5212- <g transform="translate(8, 8)" fill="#33322E" data-name="ayllu-build">
5213- <g transform="translate(20, 20)" data-name="ayllu-build">
5214- <g data-name="capnp-rpc-server">
5215- <g fill="#fdf6e3" stroke="#33322E" data-name="capnp-rpc-server">
5216- <rect x="0.0" y="8.0" height="31.0" width="147.0" data-name="capnp-rpc-server"></rect>
5217- </g>
5218- <g transform="translate(0, 8)" font-family="Helvetica" font-size="12pt" font-weight="bold" font-style="normal" data-name="capnp-rpc-server">
5219- <g transform="translate(8, 8)" fill="#33322E" text-align="center" data-name="capnp-rpc-server">
5220- <text x="65.5" y="13.5" stroke="none" text-anchor="middle" data-name="capnp-rpc-server">capnp-rpc-server</text>
5221+ </g>
5222+ <g transform="translate(1037.25, 280)" font-family="Helvetica" font-size="12pt" font-weight="normal" font-style="normal" data-name="ayllu-build">
5223+ <g transform="translate(8, 8)" fill="#33322E" data-name="ayllu-build">
5224+ <g transform="translate(20, 20)" data-name="ayllu-build">
5225+ <g data-name="tarpc-server">
5226+ <g fill="#fdf6e3" stroke="#33322E" data-name="tarpc-server">
5227+ <rect x="0.0" y="8.0" height="32.0" width="109.0" data-name="tarpc-server"></rect>
5228+ </g>
5229+ <g transform="translate(0, 8)" font-family="Helvetica" font-size="12pt" font-weight="bold" font-style="normal" data-name="tarpc-server">
5230+ <g transform="translate(8, 8)" fill="#33322E" text-align="center" data-name="tarpc-server">
5231+ <text x="46.5" y="14.1" stroke="none" text-anchor="middle" data-name="tarpc-server">tarpc-server</text>
5232
5233- </g>
5234 </g>
5235 </g>
5236+ </g>
5237 <g data-name="sqlite">
5238- <g fill="#fdf6e3" stroke="#33322E" data-name="sqlite">
5239- <rect x="187.0" y="8.0" height="31.0" width="58.0" stroke="none" data-name="sqlite"></rect>
5240- <path d="M187.0 8.0 L187.0 39.0" fill="none" data-name="sqlite"></path>
5241- <path d="M245.0 8.0 L245.0 39.0" fill="none" data-name="sqlite"></path>
5242- <ellipse cx="216.0" cy="8.0" rx="29.0" ry="6.0" data-name="sqlite"></ellipse>
5243- <path d="M245.0 39.0 L245.0 39.3 L244.9 39.6 L244.7 39.9 L244.4 40.2 L244.1 40.5 L243.7 40.8 L243.3 41.1 L242.7 41.3 L242.1 41.6 L241.5 41.9 L240.7 42.1 L240.0 42.4 L239.1 42.6 L238.2 42.9 L237.3 43.1 L236.2 43.3 L235.2 43.5 L234.1 43.7 L232.9 43.9 L231.7 44.0 L230.5 44.2 L229.2 44.3 L227.9 44.5 L226.6 44.6 L225.2 44.7 L223.9 44.8 L222.5 44.8 L221.0 44.9 L219.6 45.0 L218.2 45.0 L216.7 45.0 L215.3 45.0 L213.8 45.0 L212.4 45.0 L211.0 44.9 L209.5 44.8 L208.1 44.8 L206.8 44.7 L205.4 44.6 L204.1 44.5 L202.8 44.3 L201.5 44.2 L200.3 44.0 L199.1 43.9 L197.9 43.7 L196.8 43.5 L195.8 43.3 L194.7 43.1 L193.8 42.9 L192.9 42.6 L192.0 42.4 L191.3 42.1 L190.5 41.9 L189.9 41.6 L189.3 41.3 L188.7 41.1 L188.3 40.8 L187.9 40.5 L187.6 40.2 L187.3 39.9 L187.1 39.6 L187.0 39.3 L187.0 39.0" data-name="sqlite"></path>
5244- </g>
5245- <g transform="translate(187, 12)" font-family="Helvetica" font-size="12pt" font-weight="bold" font-style="normal" data-name="sqlite">
5246- <g transform="translate(8, 8)" fill="#33322E" text-align="center" data-name="sqlite">
5247- <text x="21.0" y="13.5" stroke="none" text-anchor="middle" data-name="sqlite">sqlite</text>
5248+ <g fill="#fdf6e3" stroke="#33322E" data-name="sqlite">
5249+ <rect x="149.0" y="8.0" height="32.0" width="58.0" stroke="none" data-name="sqlite"></rect>
5250+ <path d="M149.0 8.0 L149.0 40.0" fill="none" data-name="sqlite"></path>
5251+ <path d="M207.0 8.0 L207.0 40.0" fill="none" data-name="sqlite"></path>
5252+ <ellipse cx="178.0" cy="8.0" rx="29.0" ry="6.0" data-name="sqlite"></ellipse>
5253+ <path d="M207.0 40.0 L207.0 40.3 L206.9 40.6 L206.7 40.9 L206.4 41.2 L206.1 41.5 L205.7 41.8 L205.3 42.1 L204.7 42.3 L204.1 42.6 L203.5 42.9 L202.7 43.1 L202.0 43.4 L201.1 43.6 L200.2 43.9 L199.3 44.1 L198.2 44.3 L197.2 44.5 L196.1 44.7 L194.9 44.9 L193.7 45.0 L192.5 45.2 L191.2 45.3 L189.9 45.5 L188.6 45.6 L187.2 45.7 L185.9 45.8 L184.5 45.8 L183.0 45.9 L181.6 46.0 L180.2 46.0 L178.7 46.0 L177.3 46.0 L175.8 46.0 L174.4 46.0 L173.0 45.9 L171.5 45.8 L170.1 45.8 L168.8 45.7 L167.4 45.6 L166.1 45.5 L164.8 45.3 L163.5 45.2 L162.3 45.0 L161.1 44.9 L159.9 44.7 L158.8 44.5 L157.8 44.3 L156.7 44.1 L155.8 43.9 L154.9 43.6 L154.0 43.4 L153.3 43.1 L152.5 42.9 L151.9 42.6 L151.3 42.3 L150.7 42.1 L150.3 41.8 L149.9 41.5 L149.6 41.2 L149.3 40.9 L149.1 40.6 L149.0 40.3 L149.0 40.0" data-name="sqlite"></path>
5254+ </g>
5255+ <g transform="translate(149, 12)" font-family="Helvetica" font-size="12pt" font-weight="bold" font-style="normal" data-name="sqlite">
5256+ <g transform="translate(8, 8)" fill="#33322E" text-align="center" data-name="sqlite">
5257+ <text x="21.0" y="14.1" stroke="none" text-anchor="middle" data-name="sqlite">sqlite</text>
5258
5259- </g>
5260 </g>
5261 </g>
5262 </g>
5263 </g>
5264 </g>
5265 </g>
5266+ </g>
5267 <g data-name="ayllu-shell">
5268- <g fill="#eee8d5" stroke="#33322E" data-name="ayllu-shell">
5269- <rect x="1612.0" y="296.5" height="31.0" width="94.0" data-name="ayllu-shell"></rect>
5270- </g>
5271- <g transform="translate(1612, 296.5)" font-family="Helvetica" font-size="12pt" font-weight="bold" font-style="normal" data-name="ayllu-shell">
5272- <g transform="translate(8, 8)" fill="#33322E" text-align="center" data-name="ayllu-shell">
5273- <text x="39.0" y="13.5" stroke="none" text-anchor="middle" data-name="ayllu-shell">ayllu-shell</text>
5274+ <g fill="#eee8d5" stroke="#33322E" data-name="ayllu-shell">
5275+ <rect x="1481.0" y="300.0" height="32.0" width="94.0" data-name="ayllu-shell"></rect>
5276+ </g>
5277+ <g transform="translate(1481, 300)" font-family="Helvetica" font-size="12pt" font-weight="bold" font-style="normal" data-name="ayllu-shell">
5278+ <g transform="translate(8, 8)" fill="#33322E" text-align="center" data-name="ayllu-shell">
5279+ <text x="39.0" y="14.1" stroke="none" text-anchor="middle" data-name="ayllu-shell">ayllu-shell</text>
5280
5281- </g>
5282 </g>
5283 </g>
5284+ </g>
5285 <g data-name="contributor">
5286- <g fill="#eee8d5" stroke="#33322E" data-name="contributor">
5287- <circle r="4.0" cx="1659.0" cy="493.5" data-name="contributor"></circle>
5288- <path d="M1659.0 497.5 L1659.0 505.5" fill="none" data-name="contributor"></path>
5289- <path d="M1655.0 501.5 L1663.0 501.5" fill="none" data-name="contributor"></path>
5290- <path d="M1655.0 509.5 L1659.0 505.5 L1663.0 509.5" fill="none" data-name="contributor"></path>
5291- </g>
5292- <g transform="translate(1608.5, 505.5)" font-family="Helvetica" font-size="12pt" font-weight="normal" font-style="normal" data-name="contributor">
5293- <g transform="translate(8, 8)" fill="#33322E" text-align="center" data-name="contributor">
5294- <text x="42.5" y="13.5" stroke="none" text-anchor="middle" data-name="contributor">contributor</text>
5295+ <g fill="#eee8d5" stroke="#33322E" data-name="contributor">
5296+ <circle r="4.0" cx="1528.0" cy="500.0" data-name="contributor"></circle>
5297+ <path d="M1528.0 504.0 L1528.0 512.0" fill="none" data-name="contributor"></path>
5298+ <path d="M1524.0 508.0 L1532.0 508.0" fill="none" data-name="contributor"></path>
5299+ <path d="M1524.0 516.0 L1528.0 512.0 L1532.0 516.0" fill="none" data-name="contributor"></path>
5300+ </g>
5301+ <g transform="translate(1477.5, 512)" font-family="Helvetica" font-size="12pt" font-weight="normal" font-style="normal" data-name="contributor">
5302+ <g transform="translate(8, 8)" fill="#33322E" text-align="center" data-name="contributor">
5303+ <text x="42.5" y="14.1" stroke="none" text-anchor="middle" data-name="contributor">contributor</text>
5304
5305- </g>
5306 </g>
5307 </g>
5308+ </g>
5309 <g data-name="ayllu-cli">
5310- <g fill="#eee8d5" stroke="#33322E" data-name="ayllu-cli">
5311- <rect x="1746.0" y="296.5" height="31.0" width="75.0" data-name="ayllu-cli"></rect>
5312- </g>
5313- <g transform="translate(1746, 296.5)" font-family="Helvetica" font-size="12pt" font-weight="bold" font-style="normal" data-name="ayllu-cli">
5314- <g transform="translate(8, 8)" fill="#33322E" text-align="center" data-name="ayllu-cli">
5315- <text x="29.5" y="13.5" stroke="none" text-anchor="middle" data-name="ayllu-cli">ayllu-cli</text>
5316+ <g fill="#eee8d5" stroke="#33322E" data-name="ayllu-cli">
5317+ <rect x="1615.0" y="300.0" height="32.0" width="75.0" data-name="ayllu-cli"></rect>
5318+ </g>
5319+ <g transform="translate(1615, 300)" font-family="Helvetica" font-size="12pt" font-weight="bold" font-style="normal" data-name="ayllu-cli">
5320+ <g transform="translate(8, 8)" fill="#33322E" text-align="center" data-name="ayllu-cli">
5321+ <text x="29.5" y="14.1" stroke="none" text-anchor="middle" data-name="ayllu-cli">ayllu-cli</text>
5322
5323- </g>
5324 </g>
5325 </g>
5326+ </g>
5327 <g data-name="admin">
5328- <g fill="#eee8d5" stroke="#33322E" data-name="admin">
5329- <circle r="4.0" cx="1783.5" cy="493.5" data-name="admin"></circle>
5330- <path d="M1783.5 497.5 L1783.5 505.5" fill="none" data-name="admin"></path>
5331- <path d="M1779.5 501.5 L1787.5 501.5" fill="none" data-name="admin"></path>
5332- <path d="M1779.5 509.5 L1783.5 505.5 L1787.5 509.5" fill="none" data-name="admin"></path>
5333- </g>
5334- <g transform="translate(1752, 505.5)" font-family="Helvetica" font-size="12pt" font-weight="normal" font-style="normal" data-name="admin">
5335- <g transform="translate(8, 8)" fill="#33322E" text-align="center" data-name="admin">
5336- <text x="23.5" y="13.5" stroke="none" text-anchor="middle" data-name="admin">admin</text>
5337+ <g fill="#eee8d5" stroke="#33322E" data-name="admin">
5338+ <circle r="4.0" cx="1652.5" cy="500.0" data-name="admin"></circle>
5339+ <path d="M1652.5 504.0 L1652.5 512.0" fill="none" data-name="admin"></path>
5340+ <path d="M1648.5 508.0 L1656.5 508.0" fill="none" data-name="admin"></path>
5341+ <path d="M1648.5 516.0 L1652.5 512.0 L1656.5 516.0" fill="none" data-name="admin"></path>
5342+ </g>
5343+ <g transform="translate(1621, 512)" font-family="Helvetica" font-size="12pt" font-weight="normal" font-style="normal" data-name="admin">
5344+ <g transform="translate(8, 8)" fill="#33322E" text-align="center" data-name="admin">
5345+ <text x="23.5" y="14.1" stroke="none" text-anchor="middle" data-name="admin">admin</text>
5346
5347- </g>
5348 </g>
5349 </g>
5350+ </g>
5351 <g data-name="ayllu-builder-1">
5352- <g fill="#eee8d5" stroke="#33322E" data-name="ayllu-builder-1">
5353- <rect x="909.5" y="419.0" height="180.0" width="193.0" data-name="ayllu-builder-1"></rect>
5354- <path d="M909.5 450.0 L1102.5 450.0" fill="none" data-name="ayllu-builder-1"></path>
5355- </g>
5356- <g transform="translate(909.5, 419)" font-family="Helvetica" font-size="12pt" font-weight="bold" font-style="normal" data-name="ayllu-builder-1">
5357- <g transform="translate(8, 8)" fill="#33322E" text-align="center" data-name="ayllu-builder-1">
5358- <text x="88.5" y="13.5" stroke="none" text-anchor="middle" data-name="ayllu-builder-1">ayllu-builder-1</text>
5359+ <g fill="#eee8d5" stroke="#33322E" data-name="ayllu-builder-1">
5360+ <rect x="846.5" y="424.0" height="184.0" width="173.0" data-name="ayllu-builder-1"></rect>
5361+ <path d="M846.5 456.0 L1019.5 456.0" fill="none" data-name="ayllu-builder-1"></path>
5362+ </g>
5363+ <g transform="translate(846.5, 424)" font-family="Helvetica" font-size="12pt" font-weight="bold" font-style="normal" data-name="ayllu-builder-1">
5364+ <g transform="translate(8, 8)" fill="#33322E" text-align="center" data-name="ayllu-builder-1">
5365+ <text x="78.5" y="14.1" stroke="none" text-anchor="middle" data-name="ayllu-builder-1">ayllu-builder-1</text>
5366
5367- </g>
5368 </g>
5369- <g transform="translate(909.5, 450)" font-family="Helvetica" font-size="12pt" font-weight="normal" font-style="normal" data-name="ayllu-builder-1">
5370- <g transform="translate(8, 8)" fill="#33322E" data-name="ayllu-builder-1">
5371- <g transform="translate(20, 20)" data-name="ayllu-builder-1">
5372- <g data-name="qemu-x86_64">
5373- <g fill="#fdf6e3" stroke="#33322E" data-name="qemu-x86_64">
5374- <rect x="0.0" y="31.0" height="62.0" width="137.0" data-name="qemu-x86_64"></rect>
5375- <path d="M0.0 31.0 L0.0 0.0 L113.1 0.0 L113.1 31.0 Z" data-name="qemu-x86_64"></path>
5376- <path d="M0.0 31.0 L137.0 31.0" fill="none" data-name="qemu-x86_64"></path>
5377- <path d="M0.0 62.0 L137.0 62.0" fill="none" data-name="qemu-x86_64"></path>
5378- </g>
5379+ </g>
5380+ <g transform="translate(846.5, 456)" font-family="Helvetica" font-size="12pt" font-weight="normal" font-style="normal" data-name="ayllu-builder-1">
5381+ <g transform="translate(8, 8)" fill="#33322E" data-name="ayllu-builder-1">
5382+ <g transform="translate(20, 20)" data-name="ayllu-builder-1">
5383+ <g data-name="qemu-x86_64">
5384+ <g fill="#fdf6e3" stroke="#33322E" data-name="qemu-x86_64">
5385+ <rect x="0.0" y="32.0" height="64.0" width="117.0" data-name="qemu-x86_64"></rect>
5386+ <path d="M0.0 32.0 L0.0 0.0 L113.1 0.0 L113.1 32.0 Z" data-name="qemu-x86_64"></path>
5387+ <path d="M0.0 32.0 L117.0 32.0" fill="none" data-name="qemu-x86_64"></path>
5388+ <path d="M0.0 64.0 L117.0 64.0" fill="none" data-name="qemu-x86_64"></path>
5389+ </g>
5390 <g transform="translate(0, 0)" font-family="Helvetica" font-size="12pt" font-weight="normal" font-style="normal" data-name="qemu-x86_64">
5391- <g transform="translate(8, 8)" fill="#33322E" text-align="left" data-name="qemu-x86_64">
5392- <text x="0.0" y="13.5" stroke="none" data-name="qemu-x86_64">qemu-x86_64</text>
5393+ <g transform="translate(8, 8)" fill="#33322E" text-align="left" data-name="qemu-x86_64">
5394+ <text x="0.0" y="14.1" stroke="none" data-name="qemu-x86_64">qemu-x86_64</text>
5395
5396- </g>
5397 </g>
5398- <g transform="translate(0, 31)" font-family="Helvetica" font-size="12pt" font-weight="normal" font-style="normal" data-name="qemu-x86_64">
5399- <g transform="translate(8, 8)" fill="#33322E" text-align="left" data-name="qemu-x86_64">
5400- <text x="0.0" y="13.5" stroke="none" data-name="qemu-x86_64">capnp-rpc-server</text>
5401+ </g>
5402+ <g transform="translate(0, 32)" font-family="Helvetica" font-size="12pt" font-weight="normal" font-style="normal" data-name="qemu-x86_64">
5403+ <g transform="translate(8, 8)" fill="#33322E" text-align="left" data-name="qemu-x86_64">
5404+ <text x="0.0" y="14.1" stroke="none" data-name="qemu-x86_64">tarpc-server</text>
5405
5406- </g>
5407 </g>
5408- <g transform="translate(0, 62)" font-family="Helvetica" font-size="12pt" font-weight="normal" font-style="normal" data-name="qemu-x86_64">
5409- <g transform="translate(8, 8)" fill="#33322E" text-align="left" data-name="qemu-x86_64">
5410- <text x="0.0" y="13.5" stroke="none" data-name="qemu-x86_64">executor</text>
5411+ </g>
5412+ <g transform="translate(0, 64)" font-family="Helvetica" font-size="12pt" font-weight="normal" font-style="normal" data-name="qemu-x86_64">
5413+ <g transform="translate(8, 8)" fill="#33322E" text-align="left" data-name="qemu-x86_64">
5414+ <text x="0.0" y="14.1" stroke="none" data-name="qemu-x86_64">executor</text>
5415
5416- </g>
5417 </g>
5418 </g>
5419 </g>
5420 </g>
5421 </g>
5422 </g>
5423+ </g>
5424 <g data-name="ayllu-builder-2">
5425- <g fill="#eee8d5" stroke="#33322E" data-name="ayllu-builder-2">
5426- <rect x="1142.5" y="419.0" height="180.0" width="193.0" data-name="ayllu-builder-2"></rect>
5427- <path d="M1142.5 450.0 L1335.5 450.0" fill="none" data-name="ayllu-builder-2"></path>
5428- </g>
5429- <g transform="translate(1142.5, 419)" font-family="Helvetica" font-size="12pt" font-weight="bold" font-style="normal" data-name="ayllu-builder-2">
5430- <g transform="translate(8, 8)" fill="#33322E" text-align="center" data-name="ayllu-builder-2">
5431- <text x="88.5" y="13.5" stroke="none" text-anchor="middle" data-name="ayllu-builder-2">ayllu-builder-2</text>
5432+ <g fill="#eee8d5" stroke="#33322E" data-name="ayllu-builder-2">
5433+ <rect x="1059.5" y="424.0" height="184.0" width="181.0" data-name="ayllu-builder-2"></rect>
5434+ <path d="M1059.5 456.0 L1240.5 456.0" fill="none" data-name="ayllu-builder-2"></path>
5435+ </g>
5436+ <g transform="translate(1059.5, 424)" font-family="Helvetica" font-size="12pt" font-weight="bold" font-style="normal" data-name="ayllu-builder-2">
5437+ <g transform="translate(8, 8)" fill="#33322E" text-align="center" data-name="ayllu-builder-2">
5438+ <text x="82.5" y="14.1" stroke="none" text-anchor="middle" data-name="ayllu-builder-2">ayllu-builder-2</text>
5439
5440- </g>
5441 </g>
5442- <g transform="translate(1142.5, 450)" font-family="Helvetica" font-size="12pt" font-weight="normal" font-style="normal" data-name="ayllu-builder-2">
5443- <g transform="translate(8, 8)" fill="#33322E" data-name="ayllu-builder-2">
5444- <g transform="translate(20, 20)" data-name="ayllu-builder-2">
5445- <g data-name="qemu-aarch64">
5446- <g fill="#fdf6e3" stroke="#33322E" data-name="qemu-aarch64">
5447- <rect x="0.0" y="31.0" height="62.0" width="137.0" data-name="qemu-aarch64"></rect>
5448- <path d="M0.0 31.0 L0.0 0.0 L118.9 0.0 L118.9 31.0 Z" data-name="qemu-aarch64"></path>
5449- <path d="M0.0 31.0 L137.0 31.0" fill="none" data-name="qemu-aarch64"></path>
5450- <path d="M0.0 62.0 L137.0 62.0" fill="none" data-name="qemu-aarch64"></path>
5451- </g>
5452+ </g>
5453+ <g transform="translate(1059.5, 456)" font-family="Helvetica" font-size="12pt" font-weight="normal" font-style="normal" data-name="ayllu-builder-2">
5454+ <g transform="translate(8, 8)" fill="#33322E" data-name="ayllu-builder-2">
5455+ <g transform="translate(20, 20)" data-name="ayllu-builder-2">
5456+ <g data-name="qemu-aarch64">
5457+ <g fill="#fdf6e3" stroke="#33322E" data-name="qemu-aarch64">
5458+ <rect x="0.0" y="32.0" height="64.0" width="125.0" data-name="qemu-aarch64"></rect>
5459+ <path d="M0.0 32.0 L0.0 0.0 L118.9 0.0 L118.9 32.0 Z" data-name="qemu-aarch64"></path>
5460+ <path d="M0.0 32.0 L125.0 32.0" fill="none" data-name="qemu-aarch64"></path>
5461+ <path d="M0.0 64.0 L125.0 64.0" fill="none" data-name="qemu-aarch64"></path>
5462+ </g>
5463 <g transform="translate(0, 0)" font-family="Helvetica" font-size="12pt" font-weight="normal" font-style="normal" data-name="qemu-aarch64">
5464- <g transform="translate(8, 8)" fill="#33322E" text-align="left" data-name="qemu-aarch64">
5465- <text x="0.0" y="13.5" stroke="none" data-name="qemu-aarch64">qemu-aarch64</text>
5466+ <g transform="translate(8, 8)" fill="#33322E" text-align="left" data-name="qemu-aarch64">
5467+ <text x="0.0" y="14.1" stroke="none" data-name="qemu-aarch64">qemu-aarch64</text>
5468
5469- </g>
5470 </g>
5471- <g transform="translate(0, 31)" font-family="Helvetica" font-size="12pt" font-weight="normal" font-style="normal" data-name="qemu-aarch64">
5472- <g transform="translate(8, 8)" fill="#33322E" text-align="left" data-name="qemu-aarch64">
5473- <text x="0.0" y="13.5" stroke="none" data-name="qemu-aarch64">capnp-rpc-server</text>
5474+ </g>
5475+ <g transform="translate(0, 32)" font-family="Helvetica" font-size="12pt" font-weight="normal" font-style="normal" data-name="qemu-aarch64">
5476+ <g transform="translate(8, 8)" fill="#33322E" text-align="left" data-name="qemu-aarch64">
5477+ <text x="0.0" y="14.1" stroke="none" data-name="qemu-aarch64">tarpc-server</text>
5478
5479- </g>
5480 </g>
5481- <g transform="translate(0, 62)" font-family="Helvetica" font-size="12pt" font-weight="normal" font-style="normal" data-name="qemu-aarch64">
5482- <g transform="translate(8, 8)" fill="#33322E" text-align="left" data-name="qemu-aarch64">
5483- <text x="0.0" y="13.5" stroke="none" data-name="qemu-aarch64">exectuor</text>
5484+ </g>
5485+ <g transform="translate(0, 64)" font-family="Helvetica" font-size="12pt" font-weight="normal" font-style="normal" data-name="qemu-aarch64">
5486+ <g transform="translate(8, 8)" fill="#33322E" text-align="left" data-name="qemu-aarch64">
5487+ <text x="0.0" y="14.1" stroke="none" data-name="qemu-aarch64">exectuor</text>
5488
5489- </g>
5490 </g>
5491 </g>
5492 </g>
5493 </g>
5494 </g>
5495 </g>
5496+ </g>
5497 <g data-name="ayllu-builder-n">
5498- <g fill="#eee8d5" stroke="#33322E" data-name="ayllu-builder-n">
5499- <rect x="1375.5" y="419.0" height="180.0" width="193.0" data-name="ayllu-builder-n"></rect>
5500- <path d="M1375.5 450.0 L1568.5 450.0" fill="none" data-name="ayllu-builder-n"></path>
5501- </g>
5502- <g transform="translate(1375.5, 419)" font-family="Helvetica" font-size="12pt" font-weight="bold" font-style="normal" data-name="ayllu-builder-n">
5503- <g transform="translate(8, 8)" fill="#33322E" text-align="center" data-name="ayllu-builder-n">
5504- <text x="88.5" y="13.5" stroke="none" text-anchor="middle" data-name="ayllu-builder-n">ayllu-builder-n</text>
5505+ <g fill="#eee8d5" stroke="#33322E" data-name="ayllu-builder-n">
5506+ <rect x="1280.5" y="424.0" height="184.0" width="157.0" data-name="ayllu-builder-n"></rect>
5507+ <path d="M1280.5 456.0 L1437.5 456.0" fill="none" data-name="ayllu-builder-n"></path>
5508+ </g>
5509+ <g transform="translate(1280.5, 424)" font-family="Helvetica" font-size="12pt" font-weight="bold" font-style="normal" data-name="ayllu-builder-n">
5510+ <g transform="translate(8, 8)" fill="#33322E" text-align="center" data-name="ayllu-builder-n">
5511+ <text x="70.5" y="14.1" stroke="none" text-anchor="middle" data-name="ayllu-builder-n">ayllu-builder-n</text>
5512
5513- </g>
5514 </g>
5515- <g transform="translate(1375.5, 450)" font-family="Helvetica" font-size="12pt" font-weight="normal" font-style="normal" data-name="ayllu-builder-n">
5516- <g transform="translate(8, 8)" fill="#33322E" data-name="ayllu-builder-n">
5517- <g transform="translate(20, 20)" data-name="ayllu-builder-n">
5518- <g data-name="lxc">
5519- <g fill="#fdf6e3" stroke="#33322E" data-name="lxc">
5520- <rect x="0.0" y="31.0" height="62.0" width="137.0" data-name="lxc"></rect>
5521- <path d="M0.0 31.0 L0.0 0.0 L35.2 0.0 L35.2 31.0 Z" data-name="lxc"></path>
5522- <path d="M0.0 31.0 L137.0 31.0" fill="none" data-name="lxc"></path>
5523- <path d="M0.0 62.0 L137.0 62.0" fill="none" data-name="lxc"></path>
5524- </g>
5525+ </g>
5526+ <g transform="translate(1280.5, 456)" font-family="Helvetica" font-size="12pt" font-weight="normal" font-style="normal" data-name="ayllu-builder-n">
5527+ <g transform="translate(8, 8)" fill="#33322E" data-name="ayllu-builder-n">
5528+ <g transform="translate(20, 20)" data-name="ayllu-builder-n">
5529+ <g data-name="lxc">
5530+ <g fill="#fdf6e3" stroke="#33322E" data-name="lxc">
5531+ <rect x="0.0" y="32.0" height="64.0" width="101.0" data-name="lxc"></rect>
5532+ <path d="M0.0 32.0 L0.0 0.0 L35.2 0.0 L35.2 32.0 Z" data-name="lxc"></path>
5533+ <path d="M0.0 32.0 L101.0 32.0" fill="none" data-name="lxc"></path>
5534+ <path d="M0.0 64.0 L101.0 64.0" fill="none" data-name="lxc"></path>
5535+ </g>
5536 <g transform="translate(0, 0)" font-family="Helvetica" font-size="12pt" font-weight="normal" font-style="normal" data-name="lxc">
5537- <g transform="translate(8, 8)" fill="#33322E" text-align="left" data-name="lxc">
5538- <text x="0.0" y="13.5" stroke="none" data-name="lxc">lxc</text>
5539+ <g transform="translate(8, 8)" fill="#33322E" text-align="left" data-name="lxc">
5540+ <text x="0.0" y="14.1" stroke="none" data-name="lxc">lxc</text>
5541
5542- </g>
5543 </g>
5544- <g transform="translate(0, 31)" font-family="Helvetica" font-size="12pt" font-weight="normal" font-style="normal" data-name="lxc">
5545- <g transform="translate(8, 8)" fill="#33322E" text-align="left" data-name="lxc">
5546- <text x="0.0" y="13.5" stroke="none" data-name="lxc">capnp-rpc-server</text>
5547+ </g>
5548+ <g transform="translate(0, 32)" font-family="Helvetica" font-size="12pt" font-weight="normal" font-style="normal" data-name="lxc">
5549+ <g transform="translate(8, 8)" fill="#33322E" text-align="left" data-name="lxc">
5550+ <text x="0.0" y="14.1" stroke="none" data-name="lxc">tarpc-server</text>
5551
5552- </g>
5553 </g>
5554- <g transform="translate(0, 62)" font-family="Helvetica" font-size="12pt" font-weight="normal" font-style="normal" data-name="lxc">
5555- <g transform="translate(8, 8)" fill="#33322E" text-align="left" data-name="lxc">
5556- <text x="0.0" y="13.5" stroke="none" data-name="lxc">executor</text>
5557+ </g>
5558+ <g transform="translate(0, 64)" font-family="Helvetica" font-size="12pt" font-weight="normal" font-style="normal" data-name="lxc">
5559+ <g transform="translate(8, 8)" fill="#33322E" text-align="left" data-name="lxc">
5560+ <text x="0.0" y="14.1" stroke="none" data-name="lxc">executor</text>
5561
5562- </g>
5563 </g>
5564 </g>
5565 </g>
5566 @@ -595,4 +594,5 @@
5567 </g>
5568 </g>
5569 </g>
5570- </svg>
5571\ No newline at end of file
5572+ </g>
5573+ </svg>