Commit
Author: Kevin Schoon [me@kevinschoon.com]
Hash: 5e49307e3177cd306d26e7f21fceec130ae061e6
Timestamp: Tue, 11 Jun 2024 14:50:12 +0000 (5 months ago)

+1903 -186 +/-56 browse
several new architectual changes for mailpot integration
several new architectual changes for mailpot integration

Updates the container to better support postfix integration with ayllu-mail.
Adds crony system for hanging e-mail messages.
Fixup a few scripts and add shellcheck support.
1diff --git a/Cargo.lock b/Cargo.lock
2index 57224f0..65845cf 100644
3--- a/Cargo.lock
4+++ b/Cargo.lock
5 @@ -3,6 +3,15 @@
6 version = 3
7
8 [[package]]
9+ name = "abnf-core"
10+ version = "0.6.0"
11+ source = "registry+https://github.com/rust-lang/crates.io-index"
12+ checksum = "ec182d1f071b906a9f59269c89af101515a5cbe58f723eb6717e7fe7445c0dea"
13+ dependencies = [
14+ "nom",
15+ ]
16+
17+ [[package]]
18 name = "addr2line"
19 version = "0.21.0"
20 source = "registry+https://github.com/rust-lang/crates.io-index"
21 @@ -37,6 +46,7 @@ dependencies = [
22 "cfg-if",
23 "getrandom",
24 "once_cell",
25+ "serde",
26 "version_check",
27 "zerocopy",
28 ]
29 @@ -150,6 +160,187 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
30 checksum = "96d30a06541fbafbc7f82ed10c06164cfbd2c401138f6addd8404629c4b16711"
31
32 [[package]]
33+ name = "async-channel"
34+ version = "1.9.0"
35+ source = "registry+https://github.com/rust-lang/crates.io-index"
36+ checksum = "81953c529336010edd6d8e358f886d9581267795c61b19475b71314bffa46d35"
37+ dependencies = [
38+ "concurrent-queue",
39+ "event-listener 2.5.3",
40+ "futures-core",
41+ ]
42+
43+ [[package]]
44+ name = "async-channel"
45+ version = "2.3.1"
46+ source = "registry+https://github.com/rust-lang/crates.io-index"
47+ checksum = "89b47800b0be77592da0afd425cc03468052844aff33b84e33cc696f64e77b6a"
48+ dependencies = [
49+ "concurrent-queue",
50+ "event-listener-strategy",
51+ "futures-core",
52+ "pin-project-lite",
53+ ]
54+
55+ [[package]]
56+ name = "async-executor"
57+ version = "1.12.0"
58+ source = "registry+https://github.com/rust-lang/crates.io-index"
59+ checksum = "c8828ec6e544c02b0d6691d21ed9f9218d0384a82542855073c2a3f58304aaf0"
60+ dependencies = [
61+ "async-task",
62+ "concurrent-queue",
63+ "fastrand 2.0.1",
64+ "futures-lite 2.3.0",
65+ "slab",
66+ ]
67+
68+ [[package]]
69+ name = "async-fs"
70+ version = "1.6.0"
71+ source = "registry+https://github.com/rust-lang/crates.io-index"
72+ checksum = "279cf904654eeebfa37ac9bb1598880884924aab82e290aa65c9e77a0e142e06"
73+ dependencies = [
74+ "async-lock 2.8.0",
75+ "autocfg",
76+ "blocking",
77+ "futures-lite 1.13.0",
78+ ]
79+
80+ [[package]]
81+ name = "async-io"
82+ version = "1.13.0"
83+ source = "registry+https://github.com/rust-lang/crates.io-index"
84+ checksum = "0fc5b45d93ef0529756f812ca52e44c221b35341892d3dcc34132ac02f3dd2af"
85+ dependencies = [
86+ "async-lock 2.8.0",
87+ "autocfg",
88+ "cfg-if",
89+ "concurrent-queue",
90+ "futures-lite 1.13.0",
91+ "log",
92+ "parking",
93+ "polling 2.8.0",
94+ "rustix 0.37.27",
95+ "slab",
96+ "socket2 0.4.10",
97+ "waker-fn",
98+ ]
99+
100+ [[package]]
101+ name = "async-io"
102+ version = "2.3.3"
103+ source = "registry+https://github.com/rust-lang/crates.io-index"
104+ checksum = "0d6baa8f0178795da0e71bc42c9e5d13261aac7ee549853162e66a241ba17964"
105+ dependencies = [
106+ "async-lock 3.4.0",
107+ "cfg-if",
108+ "concurrent-queue",
109+ "futures-io",
110+ "futures-lite 2.3.0",
111+ "parking",
112+ "polling 3.7.1",
113+ "rustix 0.38.31",
114+ "slab",
115+ "tracing",
116+ "windows-sys 0.52.0",
117+ ]
118+
119+ [[package]]
120+ name = "async-lock"
121+ version = "2.8.0"
122+ source = "registry+https://github.com/rust-lang/crates.io-index"
123+ checksum = "287272293e9d8c41773cec55e365490fe034813a2f172f502d6ddcf75b2f582b"
124+ dependencies = [
125+ "event-listener 2.5.3",
126+ ]
127+
128+ [[package]]
129+ name = "async-lock"
130+ version = "3.4.0"
131+ source = "registry+https://github.com/rust-lang/crates.io-index"
132+ checksum = "ff6e472cdea888a4bd64f342f09b3f50e1886d32afe8df3d663c01140b811b18"
133+ dependencies = [
134+ "event-listener 5.3.1",
135+ "event-listener-strategy",
136+ "pin-project-lite",
137+ ]
138+
139+ [[package]]
140+ name = "async-net"
141+ version = "1.8.0"
142+ source = "registry+https://github.com/rust-lang/crates.io-index"
143+ checksum = "0434b1ed18ce1cf5769b8ac540e33f01fa9471058b5e89da9e06f3c882a8c12f"
144+ dependencies = [
145+ "async-io 1.13.0",
146+ "blocking",
147+ "futures-lite 1.13.0",
148+ ]
149+
150+ [[package]]
151+ name = "async-process"
152+ version = "1.8.1"
153+ source = "registry+https://github.com/rust-lang/crates.io-index"
154+ checksum = "ea6438ba0a08d81529c69b36700fa2f95837bfe3e776ab39cde9c14d9149da88"
155+ dependencies = [
156+ "async-io 1.13.0",
157+ "async-lock 2.8.0",
158+ "async-signal",
159+ "blocking",
160+ "cfg-if",
161+ "event-listener 3.1.0",
162+ "futures-lite 1.13.0",
163+ "rustix 0.38.31",
164+ "windows-sys 0.48.0",
165+ ]
166+
167+ [[package]]
168+ name = "async-signal"
169+ version = "0.2.8"
170+ source = "registry+https://github.com/rust-lang/crates.io-index"
171+ checksum = "794f185324c2f00e771cd9f1ae8b5ac68be2ca7abb129a87afd6e86d228bc54d"
172+ dependencies = [
173+ "async-io 2.3.3",
174+ "async-lock 3.4.0",
175+ "atomic-waker",
176+ "cfg-if",
177+ "futures-core",
178+ "futures-io",
179+ "rustix 0.38.31",
180+ "signal-hook-registry",
181+ "slab",
182+ "windows-sys 0.52.0",
183+ ]
184+
185+ [[package]]
186+ name = "async-stream"
187+ version = "0.3.5"
188+ source = "registry+https://github.com/rust-lang/crates.io-index"
189+ checksum = "cd56dd203fef61ac097dd65721a419ddccb106b2d2b70ba60a6b529f03961a51"
190+ dependencies = [
191+ "async-stream-impl",
192+ "futures-core",
193+ "pin-project-lite",
194+ ]
195+
196+ [[package]]
197+ name = "async-stream-impl"
198+ version = "0.3.5"
199+ source = "registry+https://github.com/rust-lang/crates.io-index"
200+ checksum = "16e62a023e7c117e27523144c5d2459f4397fcc3cab0085af8e2224f643a0193"
201+ dependencies = [
202+ "proc-macro2",
203+ "quote",
204+ "syn 2.0.52",
205+ ]
206+
207+ [[package]]
208+ name = "async-task"
209+ version = "4.7.1"
210+ source = "registry+https://github.com/rust-lang/crates.io-index"
211+ checksum = "8b75356056920673b02621b35afd0f7dda9306d03c79a30f5c56c44cf256e3de"
212+
213+ [[package]]
214 name = "async-trait"
215 version = "0.1.77"
216 source = "registry+https://github.com/rust-lang/crates.io-index"
217 @@ -183,6 +374,12 @@ dependencies = [
218 ]
219
220 [[package]]
221+ name = "atomic-waker"
222+ version = "1.1.2"
223+ source = "registry+https://github.com/rust-lang/crates.io-index"
224+ checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0"
225+
226+ [[package]]
227 name = "atty"
228 version = "0.2.14"
229 source = "registry+https://github.com/rust-lang/crates.io-index"
230 @@ -316,7 +513,7 @@ dependencies = [
231 "httparse",
232 "include_dir",
233 "lazy_static",
234- "libloading",
235+ "libloading 0.8.3",
236 "libsqlite3-sys",
237 "lightningcss",
238 "log",
239 @@ -350,6 +547,25 @@ dependencies = [
240 ]
241
242 [[package]]
243+ name = "ayllu-mail"
244+ version = "0.2.1"
245+ dependencies = [
246+ "anyhow",
247+ "ayllu_api",
248+ "ayllu_config",
249+ "ayllu_rpc",
250+ "clap 4.5.3",
251+ "clap_complete",
252+ "futures",
253+ "mailpot",
254+ "melib",
255+ "serde",
256+ "tokio",
257+ "tracing",
258+ "tracing-subscriber",
259+ ]
260+
261+ [[package]]
262 name = "ayllu_api"
263 version = "0.2.1"
264 dependencies = [
265 @@ -425,6 +641,12 @@ dependencies = [
266
267 [[package]]
268 name = "base64"
269+ version = "0.13.1"
270+ source = "registry+https://github.com/rust-lang/crates.io-index"
271+ checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8"
272+
273+ [[package]]
274+ name = "base64"
275 version = "0.21.7"
276 source = "registry+https://github.com/rust-lang/crates.io-index"
277 checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567"
278 @@ -511,6 +733,19 @@ dependencies = [
279 ]
280
281 [[package]]
282+ name = "blocking"
283+ version = "1.6.1"
284+ source = "registry+https://github.com/rust-lang/crates.io-index"
285+ checksum = "703f41c54fc768e63e091340b424302bb1c29ef4aa0c7f10fe849dfb114d29ea"
286+ dependencies = [
287+ "async-channel 2.3.1",
288+ "async-task",
289+ "futures-io",
290+ "futures-lite 2.3.0",
291+ "piper",
292+ ]
293+
294+ [[package]]
295 name = "bstr"
296 version = "1.9.1"
297 source = "registry+https://github.com/rust-lang/crates.io-index"
298 @@ -549,6 +784,12 @@ dependencies = [
299 ]
300
301 [[package]]
302+ name = "bytecount"
303+ version = "0.6.8"
304+ source = "registry+https://github.com/rust-lang/crates.io-index"
305+ checksum = "5ce89b21cab1437276d2650d57e971f9d548a2d9037cc231abdc0562b97498ce"
306+
307+ [[package]]
308 name = "bytemuck"
309 version = "1.15.0"
310 source = "registry+https://github.com/rust-lang/crates.io-index"
311 @@ -592,6 +833,7 @@ dependencies = [
312 "iana-time-zone",
313 "js-sys",
314 "num-traits",
315+ "serde",
316 "wasm-bindgen",
317 "windows-targets 0.52.4",
318 ]
319 @@ -715,6 +957,15 @@ dependencies = [
320 ]
321
322 [[package]]
323+ name = "concurrent-queue"
324+ version = "2.5.0"
325+ source = "registry+https://github.com/rust-lang/crates.io-index"
326+ checksum = "4ca0197aee26d1ae37445ee532fefce43251d24cc7c166799f4d46817f1d3973"
327+ dependencies = [
328+ "crossbeam-utils",
329+ ]
330+
331+ [[package]]
332 name = "const-cstr"
333 version = "0.3.0"
334 source = "registry+https://github.com/rust-lang/crates.io-index"
335 @@ -1126,7 +1377,7 @@ version = "0.5.2"
336 source = "registry+https://github.com/rust-lang/crates.io-index"
337 checksum = "330c60081dcc4c72131f8eb70510f1ac07223e5d4163db481a04a0befcffa412"
338 dependencies = [
339- "libloading",
340+ "libloading 0.8.3",
341 ]
342
343 [[package]]
344 @@ -1184,6 +1435,70 @@ dependencies = [
345 ]
346
347 [[package]]
348+ name = "encoding"
349+ version = "0.2.33"
350+ source = "registry+https://github.com/rust-lang/crates.io-index"
351+ checksum = "6b0d943856b990d12d3b55b359144ff341533e516d94098b1d3fc1ac666d36ec"
352+ dependencies = [
353+ "encoding-index-japanese",
354+ "encoding-index-korean",
355+ "encoding-index-simpchinese",
356+ "encoding-index-singlebyte",
357+ "encoding-index-tradchinese",
358+ ]
359+
360+ [[package]]
361+ name = "encoding-index-japanese"
362+ version = "1.20141219.5"
363+ source = "registry+https://github.com/rust-lang/crates.io-index"
364+ checksum = "04e8b2ff42e9a05335dbf8b5c6f7567e5591d0d916ccef4e0b1710d32a0d0c91"
365+ dependencies = [
366+ "encoding_index_tests",
367+ ]
368+
369+ [[package]]
370+ name = "encoding-index-korean"
371+ version = "1.20141219.5"
372+ source = "registry+https://github.com/rust-lang/crates.io-index"
373+ checksum = "4dc33fb8e6bcba213fe2f14275f0963fd16f0a02c878e3095ecfdf5bee529d81"
374+ dependencies = [
375+ "encoding_index_tests",
376+ ]
377+
378+ [[package]]
379+ name = "encoding-index-simpchinese"
380+ version = "1.20141219.5"
381+ source = "registry+https://github.com/rust-lang/crates.io-index"
382+ checksum = "d87a7194909b9118fc707194baa434a4e3b0fb6a5a757c73c3adb07aa25031f7"
383+ dependencies = [
384+ "encoding_index_tests",
385+ ]
386+
387+ [[package]]
388+ name = "encoding-index-singlebyte"
389+ version = "1.20141219.5"
390+ source = "registry+https://github.com/rust-lang/crates.io-index"
391+ checksum = "3351d5acffb224af9ca265f435b859c7c01537c0849754d3db3fdf2bfe2ae84a"
392+ dependencies = [
393+ "encoding_index_tests",
394+ ]
395+
396+ [[package]]
397+ name = "encoding-index-tradchinese"
398+ version = "1.20141219.5"
399+ source = "registry+https://github.com/rust-lang/crates.io-index"
400+ checksum = "fd0e20d5688ce3cab59eb3ef3a2083a5c77bf496cb798dc6fcdb75f323890c18"
401+ dependencies = [
402+ "encoding_index_tests",
403+ ]
404+
405+ [[package]]
406+ name = "encoding_index_tests"
407+ version = "0.1.4"
408+ source = "registry+https://github.com/rust-lang/crates.io-index"
409+ checksum = "a246d82be1c9d791c5dfde9a2bd045fc3cbba3fa2b11ad558f27d01712f00569"
410+
411+ [[package]]
412 name = "encoding_rs"
413 version = "0.8.33"
414 source = "registry+https://github.com/rust-lang/crates.io-index"
415 @@ -1280,6 +1595,50 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
416 checksum = "0206175f82b8d6bf6652ff7d71a1e27fd2e4efde587fd368662814d6ec1d9ce0"
417
418 [[package]]
419+ name = "event-listener"
420+ version = "3.1.0"
421+ source = "registry+https://github.com/rust-lang/crates.io-index"
422+ checksum = "d93877bcde0eb80ca09131a08d23f0a5c18a620b01db137dba666d18cd9b30c2"
423+ dependencies = [
424+ "concurrent-queue",
425+ "parking",
426+ "pin-project-lite",
427+ ]
428+
429+ [[package]]
430+ name = "event-listener"
431+ version = "5.3.1"
432+ source = "registry+https://github.com/rust-lang/crates.io-index"
433+ checksum = "6032be9bd27023a771701cc49f9f053c751055f71efb2e0ae5c15809093675ba"
434+ dependencies = [
435+ "concurrent-queue",
436+ "parking",
437+ "pin-project-lite",
438+ ]
439+
440+ [[package]]
441+ name = "event-listener-strategy"
442+ version = "0.5.2"
443+ source = "registry+https://github.com/rust-lang/crates.io-index"
444+ checksum = "0f214dc438f977e6d4e3500aaa277f5ad94ca83fbbd9b1a15713ce2344ccc5a1"
445+ dependencies = [
446+ "event-listener 5.3.1",
447+ "pin-project-lite",
448+ ]
449+
450+ [[package]]
451+ name = "fallible-iterator"
452+ version = "0.3.0"
453+ source = "registry+https://github.com/rust-lang/crates.io-index"
454+ checksum = "2acce4a10f12dc2fb14a218589d4f1f62ef011b2d0cc4b3cb1bba8e94da14649"
455+
456+ [[package]]
457+ name = "fallible-streaming-iterator"
458+ version = "0.1.9"
459+ source = "registry+https://github.com/rust-lang/crates.io-index"
460+ checksum = "7360491ce676a36bf9bb3c56c1aa791658183a54d2744120f27285738d90465a"
461+
462+ [[package]]
463 name = "fancy-regex"
464 version = "0.11.0"
465 source = "registry+https://github.com/rust-lang/crates.io-index"
466 @@ -1291,6 +1650,15 @@ dependencies = [
467
468 [[package]]
469 name = "fastrand"
470+ version = "1.9.0"
471+ source = "registry+https://github.com/rust-lang/crates.io-index"
472+ checksum = "e51093e27b0797c359783294ca4f0a911c270184cb10f85783b118614a1501be"
473+ dependencies = [
474+ "instant",
475+ ]
476+
477+ [[package]]
478+ name = "fastrand"
479 version = "2.0.1"
480 source = "registry+https://github.com/rust-lang/crates.io-index"
481 checksum = "25cbce373ec4653f1a01a31e8a5e5ec0c622dc27ff9c4e6606eefef5cbbed4a5"
482 @@ -1314,6 +1682,18 @@ dependencies = [
483 ]
484
485 [[package]]
486+ name = "filetime"
487+ version = "0.2.23"
488+ source = "registry+https://github.com/rust-lang/crates.io-index"
489+ checksum = "1ee447700ac8aa0b2f2bd7bc4462ad686ba06baa6727ac149a2d6277f0d240fd"
490+ dependencies = [
491+ "cfg-if",
492+ "libc",
493+ "redox_syscall 0.4.1",
494+ "windows-sys 0.52.0",
495+ ]
496+
497+ [[package]]
498 name = "finl_unicode"
499 version = "1.2.0"
500 source = "registry+https://github.com/rust-lang/crates.io-index"
501 @@ -1402,6 +1782,16 @@ dependencies = [
502 ]
503
504 [[package]]
505+ name = "fraction"
506+ version = "0.13.1"
507+ source = "registry+https://github.com/rust-lang/crates.io-index"
508+ checksum = "3027ae1df8d41b4bed2241c8fdad4acc1e7af60c8e17743534b545e77182d678"
509+ dependencies = [
510+ "lazy_static",
511+ "num",
512+ ]
513+
514+ [[package]]
515 name = "freetype"
516 version = "0.7.1"
517 source = "registry+https://github.com/rust-lang/crates.io-index"
518 @@ -1423,6 +1813,15 @@ dependencies = [
519 ]
520
521 [[package]]
522+ name = "fsevent-sys"
523+ version = "4.1.0"
524+ source = "registry+https://github.com/rust-lang/crates.io-index"
525+ checksum = "76ee7a02da4d231650c7cea31349b889be2f45ddb3ef3032d2ec8185f6313fd2"
526+ dependencies = [
527+ "libc",
528+ ]
529+
530+ [[package]]
531 name = "funty"
532 version = "2.0.0"
533 source = "registry+https://github.com/rust-lang/crates.io-index"
534 @@ -1488,6 +1887,34 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
535 checksum = "a44623e20b9681a318efdd71c299b6b222ed6f231972bfe2f224ebad6311f0c1"
536
537 [[package]]
538+ name = "futures-lite"
539+ version = "1.13.0"
540+ source = "registry+https://github.com/rust-lang/crates.io-index"
541+ checksum = "49a9d51ce47660b1e808d3c990b4709f2f415d928835a17dfd16991515c46bce"
542+ dependencies = [
543+ "fastrand 1.9.0",
544+ "futures-core",
545+ "futures-io",
546+ "memchr",
547+ "parking",
548+ "pin-project-lite",
549+ "waker-fn",
550+ ]
551+
552+ [[package]]
553+ name = "futures-lite"
554+ version = "2.3.0"
555+ source = "registry+https://github.com/rust-lang/crates.io-index"
556+ checksum = "52527eb5074e35e9339c6b4e8d12600c7128b68fb25dcb9fa9dec18f7c25f3a5"
557+ dependencies = [
558+ "fastrand 2.0.1",
559+ "futures-core",
560+ "futures-io",
561+ "parking",
562+ "pin-project-lite",
563+ ]
564+
565+ [[package]]
566 name = "futures-macro"
567 version = "0.3.30"
568 source = "registry+https://github.com/rust-lang/crates.io-index"
569 @@ -1554,8 +1981,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
570 checksum = "190092ea657667030ac6a35e305e62fc4dd69fd98ac98631e5d3a2b1575a12b5"
571 dependencies = [
572 "cfg-if",
573+ "js-sys",
574 "libc",
575 "wasi",
576+ "wasm-bindgen",
577 ]
578
579 [[package]]
580 @@ -1896,7 +2325,7 @@ dependencies = [
581 "httpdate",
582 "itoa",
583 "pin-project-lite",
584- "socket2",
585+ "socket2 0.5.6",
586 "tokio",
587 "tower-service",
588 "tracing",
589 @@ -1966,7 +2395,7 @@ dependencies = [
590 "http-body 1.0.0",
591 "hyper 1.2.0",
592 "pin-project-lite",
593- "socket2",
594+ "socket2 0.5.6",
595 "tokio",
596 "tower",
597 "tower-service",
598 @@ -2043,6 +2472,32 @@ dependencies = [
599 ]
600
601 [[package]]
602+ name = "imap-codec"
603+ version = "1.0.0"
604+ source = "registry+https://github.com/rust-lang/crates.io-index"
605+ checksum = "e6222d4417bbb3448ed620046448c3ca2b947aa349a3c1ba0ca90ebf2af56831"
606+ dependencies = [
607+ "abnf-core",
608+ "base64 0.21.7",
609+ "chrono",
610+ "imap-types",
611+ "log",
612+ "nom",
613+ "thiserror",
614+ ]
615+
616+ [[package]]
617+ name = "imap-types"
618+ version = "1.0.0"
619+ source = "registry+https://github.com/rust-lang/crates.io-index"
620+ checksum = "8a799ec6e315b75b5e5d72b0b1403f8c05c625b98b860bbdf19a5a4e189ae061"
621+ dependencies = [
622+ "base64 0.21.7",
623+ "chrono",
624+ "thiserror",
625+ ]
626+
627+ [[package]]
628 name = "include_dir"
629 version = "0.7.3"
630 source = "registry+https://github.com/rust-lang/crates.io-index"
631 @@ -2070,6 +2525,7 @@ checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99"
632 dependencies = [
633 "autocfg",
634 "hashbrown 0.12.3",
635+ "serde",
636 ]
637
638 [[package]]
639 @@ -2083,6 +2539,26 @@ dependencies = [
640 ]
641
642 [[package]]
643+ name = "inotify"
644+ version = "0.9.6"
645+ source = "registry+https://github.com/rust-lang/crates.io-index"
646+ checksum = "f8069d3ec154eb856955c1c0fbffefbf5f3c40a104ec912d4797314c1801abff"
647+ dependencies = [
648+ "bitflags 1.3.2",
649+ "inotify-sys",
650+ "libc",
651+ ]
652+
653+ [[package]]
654+ name = "inotify-sys"
655+ version = "0.1.5"
656+ source = "registry+https://github.com/rust-lang/crates.io-index"
657+ checksum = "e05c02b5e89bff3b946cedeca278abc628fe811e604f027c45a8aa3cf793d0eb"
658+ dependencies = [
659+ "libc",
660+ ]
661+
662+ [[package]]
663 name = "instant"
664 version = "0.1.12"
665 source = "registry+https://github.com/rust-lang/crates.io-index"
666 @@ -2092,6 +2568,17 @@ dependencies = [
667 ]
668
669 [[package]]
670+ name = "io-lifetimes"
671+ version = "1.0.11"
672+ source = "registry+https://github.com/rust-lang/crates.io-index"
673+ checksum = "eae7b9aee968036d54dce06cebaefd919e4472e753296daccd6d344e3e2df0c2"
674+ dependencies = [
675+ "hermit-abi 0.3.9",
676+ "libc",
677+ "windows-sys 0.48.0",
678+ ]
679+
680+ [[package]]
681 name = "ipnet"
682 version = "2.9.0"
683 source = "registry+https://github.com/rust-lang/crates.io-index"
684 @@ -2109,6 +2596,15 @@ dependencies = [
685 ]
686
687 [[package]]
688+ name = "iso8601"
689+ version = "0.6.1"
690+ source = "registry+https://github.com/rust-lang/crates.io-index"
691+ checksum = "924e5d73ea28f59011fec52a0d12185d496a9b075d360657aed2a5707f701153"
692+ dependencies = [
693+ "nom",
694+ ]
695+
696+ [[package]]
697 name = "itertools"
698 version = "0.10.5"
699 source = "registry+https://github.com/rust-lang/crates.io-index"
700 @@ -2142,18 +2638,66 @@ dependencies = [
701 ]
702
703 [[package]]
704- name = "jpeg-decoder"
705- version = "0.3.1"
706+ name = "jpeg-decoder"
707+ version = "0.3.1"
708+ source = "registry+https://github.com/rust-lang/crates.io-index"
709+ checksum = "f5d4a7da358eff58addd2877a45865158f0d78c911d43a5784ceb7bbf52833b0"
710+
711+ [[package]]
712+ name = "js-sys"
713+ version = "0.3.69"
714+ source = "registry+https://github.com/rust-lang/crates.io-index"
715+ checksum = "29c15563dc2726973df627357ce0c9ddddbea194836909d655df6a75d2cf296d"
716+ dependencies = [
717+ "wasm-bindgen",
718+ ]
719+
720+ [[package]]
721+ name = "jsonschema"
722+ version = "0.17.1"
723+ source = "registry+https://github.com/rust-lang/crates.io-index"
724+ checksum = "2a071f4f7efc9a9118dfb627a0a94ef247986e1ab8606a4c806ae2b3aa3b6978"
725+ dependencies = [
726+ "ahash 0.8.11",
727+ "anyhow",
728+ "base64 0.21.7",
729+ "bytecount",
730+ "fancy-regex",
731+ "fraction",
732+ "getrandom",
733+ "iso8601",
734+ "itoa",
735+ "memchr",
736+ "num-cmp",
737+ "once_cell",
738+ "parking_lot 0.12.1",
739+ "percent-encoding",
740+ "regex",
741+ "serde",
742+ "serde_json",
743+ "time",
744+ "url",
745+ "uuid",
746+ ]
747+
748+ [[package]]
749+ name = "kqueue"
750+ version = "1.0.8"
751 source = "registry+https://github.com/rust-lang/crates.io-index"
752- checksum = "f5d4a7da358eff58addd2877a45865158f0d78c911d43a5784ceb7bbf52833b0"
753+ checksum = "7447f1ca1b7b563588a205fe93dea8df60fd981423a768bc1c0ded35ed147d0c"
754+ dependencies = [
755+ "kqueue-sys",
756+ "libc",
757+ ]
758
759 [[package]]
760- name = "js-sys"
761- version = "0.3.69"
762+ name = "kqueue-sys"
763+ version = "1.0.4"
764 source = "registry+https://github.com/rust-lang/crates.io-index"
765- checksum = "29c15563dc2726973df627357ce0c9ddddbea194836909d655df6a75d2cf296d"
766+ checksum = "ed9625ffda8729b85e45cf04090035ac368927b8cebc34898e7c120f52e4838b"
767 dependencies = [
768- "wasm-bindgen",
769+ "bitflags 1.3.2",
770+ "libc",
771 ]
772
773 [[package]]
774 @@ -2187,6 +2731,16 @@ dependencies = [
775
776 [[package]]
777 name = "libloading"
778+ version = "0.7.4"
779+ source = "registry+https://github.com/rust-lang/crates.io-index"
780+ checksum = "b67380fd3b2fbe7527a606e18729d21c6f3951633d0500574c4dc22d2d638b9f"
781+ dependencies = [
782+ "cfg-if",
783+ "winapi",
784+ ]
785+
786+ [[package]]
787+ name = "libloading"
788 version = "0.8.3"
789 source = "registry+https://github.com/rust-lang/crates.io-index"
790 checksum = "0c2a198fb6b0eada2a8df47933734e6d35d350665a33a3593d7164fa52c75c19"
791 @@ -2291,6 +2845,12 @@ checksum = "0717cef1bc8b636c6e1c1bbdefc09e6322da8a9321966e8928ef80d20f7f770f"
792
793 [[package]]
794 name = "linux-raw-sys"
795+ version = "0.3.8"
796+ source = "registry+https://github.com/rust-lang/crates.io-index"
797+ checksum = "ef53942eb7bf7ff43a617b3e2c1c4a5ecf5944a7c1bc12d7ee39bbb15e5c1519"
798+
799+ [[package]]
800+ name = "linux-raw-sys"
801 version = "0.4.13"
802 source = "registry+https://github.com/rust-lang/crates.io-index"
803 checksum = "01cda141df6706de531b6c46c3a33ecca755538219bd484262fa09410c13539c"
804 @@ -2312,6 +2872,27 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
805 checksum = "90ed8c1e510134f979dbc4f070f87d4313098b704861a105fe34231c70a3901c"
806
807 [[package]]
808+ name = "mailpot"
809+ version = "0.1.1"
810+ source = "git+https://ayllu-forge.org/ayllu/mailpot?branch=main#f425cf0198a098cdf1f2c6672a8e34c5b9bb3302"
811+ dependencies = [
812+ "anyhow",
813+ "chrono",
814+ "data-encoding",
815+ "jsonschema",
816+ "log",
817+ "melib",
818+ "minijinja",
819+ "percent-encoding",
820+ "rusqlite",
821+ "serde",
822+ "serde_json",
823+ "thiserror",
824+ "toml 0.8.14",
825+ "xdg",
826+ ]
827+
828+ [[package]]
829 name = "matchers"
830 version = "0.1.0"
831 source = "registry+https://github.com/rust-lang/crates.io-index"
832 @@ -2343,6 +2924,43 @@ dependencies = [
833 ]
834
835 [[package]]
836+ name = "melib"
837+ version = "0.8.6"
838+ source = "registry+https://github.com/rust-lang/crates.io-index"
839+ checksum = "4f233699ab6a71d41529624e3d9600c8a3a208874fcf4ec4a05778314afdd2e7"
840+ dependencies = [
841+ "async-stream",
842+ "base64 0.13.1",
843+ "bitflags 2.4.2",
844+ "data-encoding",
845+ "encoding",
846+ "encoding_rs",
847+ "flate2",
848+ "futures",
849+ "imap-codec",
850+ "indexmap 1.9.3",
851+ "libc",
852+ "libloading 0.7.4",
853+ "log",
854+ "native-tls",
855+ "nix",
856+ "nom",
857+ "notify",
858+ "polling 2.8.0",
859+ "regex",
860+ "serde",
861+ "serde_derive",
862+ "serde_json",
863+ "serde_path_to_error",
864+ "smallvec",
865+ "smol",
866+ "socket2 0.5.6",
867+ "unicode-segmentation",
868+ "uuid",
869+ "xdg",
870+ ]
871+
872+ [[package]]
873 name = "memchr"
874 version = "2.7.1"
875 source = "registry+https://github.com/rust-lang/crates.io-index"
876 @@ -2358,6 +2976,21 @@ dependencies = [
877 ]
878
879 [[package]]
880+ name = "memo-map"
881+ version = "0.3.2"
882+ source = "registry+https://github.com/rust-lang/crates.io-index"
883+ checksum = "374c335b2df19e62d4cb323103473cbc6510980253119180de862d89184f6a83"
884+
885+ [[package]]
886+ name = "memoffset"
887+ version = "0.9.1"
888+ source = "registry+https://github.com/rust-lang/crates.io-index"
889+ checksum = "488016bfae457b036d996092f6cb448677611ce4449e970ceaf42695203f218a"
890+ dependencies = [
891+ "autocfg",
892+ ]
893+
894+ [[package]]
895 name = "mime"
896 version = "0.3.17"
897 source = "registry+https://github.com/rust-lang/crates.io-index"
898 @@ -2374,6 +3007,17 @@ dependencies = [
899 ]
900
901 [[package]]
902+ name = "minijinja"
903+ version = "0.31.1"
904+ source = "registry+https://github.com/rust-lang/crates.io-index"
905+ checksum = "0b1dbc390e4447b2500c4071d7bc2a808cf07e925bae6b92db8a3c3eae773c58"
906+ dependencies = [
907+ "memo-map",
908+ "self_cell 0.10.3",
909+ "serde",
910+ ]
911+
912+ [[package]]
913 name = "minimal-lexical"
914 version = "0.2.1"
915 source = "registry+https://github.com/rust-lang/crates.io-index"
916 @@ -2396,6 +3040,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
917 checksum = "a4a650543ca06a924e8b371db273b2756685faae30f8487da1b56505a8f78b0c"
918 dependencies = [
919 "libc",
920+ "log",
921 "wasi",
922 "windows-sys 0.48.0",
923 ]
924 @@ -2425,6 +3070,18 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
925 checksum = "c96aba5aa877601bb3f6dd6a63a969e1f82e60646e81e71b14496995e9853c91"
926
927 [[package]]
928+ name = "nix"
929+ version = "0.27.1"
930+ source = "registry+https://github.com/rust-lang/crates.io-index"
931+ checksum = "2eb04e9c688eff1c89d72b407f168cf79bb9e867a9d3323ed6c01519eb9cc053"
932+ dependencies = [
933+ "bitflags 2.4.2",
934+ "cfg-if",
935+ "libc",
936+ "memoffset",
937+ ]
938+
939+ [[package]]
940 name = "nom"
941 version = "7.1.3"
942 source = "registry+https://github.com/rust-lang/crates.io-index"
943 @@ -2435,6 +3092,25 @@ dependencies = [
944 ]
945
946 [[package]]
947+ name = "notify"
948+ version = "6.1.1"
949+ source = "registry+https://github.com/rust-lang/crates.io-index"
950+ checksum = "6205bd8bb1e454ad2e27422015fb5e4f2bcc7e08fa8f27058670d208324a4d2d"
951+ dependencies = [
952+ "bitflags 2.4.2",
953+ "crossbeam-channel",
954+ "filetime",
955+ "fsevent-sys",
956+ "inotify",
957+ "kqueue",
958+ "libc",
959+ "log",
960+ "mio",
961+ "walkdir",
962+ "windows-sys 0.48.0",
963+ ]
964+
965+ [[package]]
966 name = "nu-ansi-term"
967 version = "0.46.0"
968 source = "registry+https://github.com/rust-lang/crates.io-index"
969 @@ -2445,12 +3121,25 @@ dependencies = [
970 ]
971
972 [[package]]
973+ name = "num"
974+ version = "0.4.3"
975+ source = "registry+https://github.com/rust-lang/crates.io-index"
976+ checksum = "35bd024e8b2ff75562e5f34e7f4905839deb4b22955ef5e73d2fea1b9813cb23"
977+ dependencies = [
978+ "num-bigint",
979+ "num-complex",
980+ "num-integer",
981+ "num-iter",
982+ "num-rational",
983+ "num-traits",
984+ ]
985+
986+ [[package]]
987 name = "num-bigint"
988- version = "0.4.4"
989+ version = "0.4.5"
990 source = "registry+https://github.com/rust-lang/crates.io-index"
991- checksum = "608e7659b5c3d7cba262d894801b9ec9d00de989e8a82bd4bef91d08da45cdc0"
992+ checksum = "c165a9ab64cf766f73521c0dd2cfdff64f488b8f0b3e621face3462d3db536d7"
993 dependencies = [
994- "autocfg",
995 "num-integer",
996 "num-traits",
997 ]
998 @@ -2473,6 +3162,21 @@ dependencies = [
999 ]
1000
1001 [[package]]
1002+ name = "num-cmp"
1003+ version = "0.1.0"
1004+ source = "registry+https://github.com/rust-lang/crates.io-index"
1005+ checksum = "63335b2e2c34fae2fb0aa2cecfd9f0832a1e24b3b32ecec612c3426d46dc8aaa"
1006+
1007+ [[package]]
1008+ name = "num-complex"
1009+ version = "0.4.6"
1010+ source = "registry+https://github.com/rust-lang/crates.io-index"
1011+ checksum = "73f88a1307638156682bada9d7604135552957b7818057dcef22705b4d509495"
1012+ dependencies = [
1013+ "num-traits",
1014+ ]
1015+
1016+ [[package]]
1017 name = "num-conv"
1018 version = "0.1.0"
1019 source = "registry+https://github.com/rust-lang/crates.io-index"
1020 @@ -2499,9 +3203,9 @@ dependencies = [
1021
1022 [[package]]
1023 name = "num-iter"
1024- version = "0.1.44"
1025+ version = "0.1.45"
1026 source = "registry+https://github.com/rust-lang/crates.io-index"
1027- checksum = "d869c01cc0c455284163fd0092f1f93835385ccab5a98a0dcc497b2f8bf055a9"
1028+ checksum = "1429034a0490724d0075ebb2bc9e875d6503c3cf69e235a8941aa757d83ef5bf"
1029 dependencies = [
1030 "autocfg",
1031 "num-integer",
1032 @@ -2509,10 +3213,21 @@ dependencies = [
1033 ]
1034
1035 [[package]]
1036+ name = "num-rational"
1037+ version = "0.4.2"
1038+ source = "registry+https://github.com/rust-lang/crates.io-index"
1039+ checksum = "f83d14da390562dca69fc84082e73e548e1ad308d24accdedd2720017cb37824"
1040+ dependencies = [
1041+ "num-bigint",
1042+ "num-integer",
1043+ "num-traits",
1044+ ]
1045+
1046+ [[package]]
1047 name = "num-traits"
1048- version = "0.2.18"
1049+ version = "0.2.19"
1050 source = "registry+https://github.com/rust-lang/crates.io-index"
1051- checksum = "da0df0e5185db44f69b44f26786fe401b6c293d1907744beaa7fa62b2e5a517a"
1052+ checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841"
1053 dependencies = [
1054 "autocfg",
1055 "libm",
1056 @@ -2695,6 +3410,12 @@ dependencies = [
1057 ]
1058
1059 [[package]]
1060+ name = "parking"
1061+ version = "2.2.0"
1062+ source = "registry+https://github.com/rust-lang/crates.io-index"
1063+ checksum = "bb813b8af86854136c6922af0598d719255ecb2179515e6e7730d468f05c9cae"
1064+
1065+ [[package]]
1066 name = "parking_lot"
1067 version = "0.11.2"
1068 source = "registry+https://github.com/rust-lang/crates.io-index"
1069 @@ -2965,6 +3686,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
1070 checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
1071
1072 [[package]]
1073+ name = "piper"
1074+ version = "0.2.3"
1075+ source = "registry+https://github.com/rust-lang/crates.io-index"
1076+ checksum = "ae1d5c74c9876f070d3e8fd503d748c7d974c3e48da8f41350fa5222ef9b4391"
1077+ dependencies = [
1078+ "atomic-waker",
1079+ "fastrand 2.0.1",
1080+ "futures-io",
1081+ ]
1082+
1083+ [[package]]
1084 name = "pkcs1"
1085 version = "0.7.5"
1086 source = "registry+https://github.com/rust-lang/crates.io-index"
1087 @@ -3065,6 +3797,37 @@ dependencies = [
1088 ]
1089
1090 [[package]]
1091+ name = "polling"
1092+ version = "2.8.0"
1093+ source = "registry+https://github.com/rust-lang/crates.io-index"
1094+ checksum = "4b2d323e8ca7996b3e23126511a523f7e62924d93ecd5ae73b333815b0eb3dce"
1095+ dependencies = [
1096+ "autocfg",
1097+ "bitflags 1.3.2",
1098+ "cfg-if",
1099+ "concurrent-queue",
1100+ "libc",
1101+ "log",
1102+ "pin-project-lite",
1103+ "windows-sys 0.48.0",
1104+ ]
1105+
1106+ [[package]]
1107+ name = "polling"
1108+ version = "3.7.1"
1109+ source = "registry+https://github.com/rust-lang/crates.io-index"
1110+ checksum = "5e6a007746f34ed64099e88783b0ae369eaa3da6392868ba262e2af9b8fbaea1"
1111+ dependencies = [
1112+ "cfg-if",
1113+ "concurrent-queue",
1114+ "hermit-abi 0.3.9",
1115+ "pin-project-lite",
1116+ "rustix 0.38.31",
1117+ "tracing",
1118+ "windows-sys 0.52.0",
1119+ ]
1120+
1121+ [[package]]
1122 name = "powerfmt"
1123 version = "0.2.0"
1124 source = "registry+https://github.com/rust-lang/crates.io-index"
1125 @@ -3455,6 +4218,22 @@ dependencies = [
1126 ]
1127
1128 [[package]]
1129+ name = "rusqlite"
1130+ version = "0.30.0"
1131+ source = "registry+https://github.com/rust-lang/crates.io-index"
1132+ checksum = "a78046161564f5e7cd9008aff3b2990b3850dc8e0349119b98e8f251e099f24d"
1133+ dependencies = [
1134+ "bitflags 2.4.2",
1135+ "chrono",
1136+ "fallible-iterator",
1137+ "fallible-streaming-iterator",
1138+ "hashlink",
1139+ "libsqlite3-sys",
1140+ "serde_json",
1141+ "smallvec",
1142+ ]
1143+
1144+ [[package]]
1145 name = "rustc-demangle"
1146 version = "0.1.23"
1147 source = "registry+https://github.com/rust-lang/crates.io-index"
1148 @@ -3471,6 +4250,20 @@ dependencies = [
1149
1150 [[package]]
1151 name = "rustix"
1152+ version = "0.37.27"
1153+ source = "registry+https://github.com/rust-lang/crates.io-index"
1154+ checksum = "fea8ca367a3a01fe35e6943c400addf443c0f57670e6ec51196f71a4b8762dd2"
1155+ dependencies = [
1156+ "bitflags 1.3.2",
1157+ "errno",
1158+ "io-lifetimes",
1159+ "libc",
1160+ "linux-raw-sys 0.3.8",
1161+ "windows-sys 0.48.0",
1162+ ]
1163+
1164+ [[package]]
1165+ name = "rustix"
1166 version = "0.38.31"
1167 source = "registry+https://github.com/rust-lang/crates.io-index"
1168 checksum = "6ea3e1a662af26cd7a3ba09c0297a31af215563ecf42817c98df621387f4e949"
1169 @@ -3478,7 +4271,7 @@ dependencies = [
1170 "bitflags 2.4.2",
1171 "errno",
1172 "libc",
1173- "linux-raw-sys",
1174+ "linux-raw-sys 0.4.13",
1175 "windows-sys 0.52.0",
1176 ]
1177
1178 @@ -3610,6 +4403,21 @@ dependencies = [
1179 ]
1180
1181 [[package]]
1182+ name = "self_cell"
1183+ version = "0.10.3"
1184+ source = "registry+https://github.com/rust-lang/crates.io-index"
1185+ checksum = "e14e4d63b804dc0c7ec4a1e52bcb63f02c7ac94476755aa579edac21e01f915d"
1186+ dependencies = [
1187+ "self_cell 1.0.4",
1188+ ]
1189+
1190+ [[package]]
1191+ name = "self_cell"
1192+ version = "1.0.4"
1193+ source = "registry+https://github.com/rust-lang/crates.io-index"
1194+ checksum = "d369a96f978623eb3dc28807c4852d6cc617fed53da5d3c400feff1ef34a714a"
1195+
1196+ [[package]]
1197 name = "semver"
1198 version = "1.0.22"
1199 source = "registry+https://github.com/rust-lang/crates.io-index"
1200 @@ -3658,9 +4466,9 @@ dependencies = [
1201
1202 [[package]]
1203 name = "serde_spanned"
1204- version = "0.6.5"
1205+ version = "0.6.6"
1206 source = "registry+https://github.com/rust-lang/crates.io-index"
1207- checksum = "eb3622f419d1296904700073ea6cc23ad690adbd66f13ea683df73298736f0c1"
1208+ checksum = "79e674e01f999af37c49f70a6ede167a8a60b2503e56c5599532a65baa5969a0"
1209 dependencies = [
1210 "serde",
1211 ]
1212 @@ -3689,6 +4497,12 @@ dependencies = [
1213 ]
1214
1215 [[package]]
1216+ name = "sha1_smol"
1217+ version = "1.0.0"
1218+ source = "registry+https://github.com/rust-lang/crates.io-index"
1219+ checksum = "ae1a47186c03a32177042e55dbc5fd5aee900b8e0069a8d70fba96a9375cd012"
1220+
1221+ [[package]]
1222 name = "sha2"
1223 version = "0.10.8"
1224 source = "registry+https://github.com/rust-lang/crates.io-index"
1225 @@ -3784,6 +4598,36 @@ name = "smallvec"
1226 version = "1.13.1"
1227 source = "registry+https://github.com/rust-lang/crates.io-index"
1228 checksum = "e6ecd384b10a64542d77071bd64bd7b231f4ed5940fba55e98c3de13824cf3d7"
1229+ dependencies = [
1230+ "serde",
1231+ ]
1232+
1233+ [[package]]
1234+ name = "smol"
1235+ version = "1.3.0"
1236+ source = "registry+https://github.com/rust-lang/crates.io-index"
1237+ checksum = "13f2b548cd8447f8de0fdf1c592929f70f4fc7039a05e47404b0d096ec6987a1"
1238+ dependencies = [
1239+ "async-channel 1.9.0",
1240+ "async-executor",
1241+ "async-fs",
1242+ "async-io 1.13.0",
1243+ "async-lock 2.8.0",
1244+ "async-net",
1245+ "async-process",
1246+ "blocking",
1247+ "futures-lite 1.13.0",
1248+ ]
1249+
1250+ [[package]]
1251+ name = "socket2"
1252+ version = "0.4.10"
1253+ source = "registry+https://github.com/rust-lang/crates.io-index"
1254+ checksum = "9f7916fc008ca5542385b89a3d3ce689953c143e9304a9bf8beec1de48994c0d"
1255+ dependencies = [
1256+ "libc",
1257+ "winapi",
1258+ ]
1259
1260 [[package]]
1261 name = "socket2"
1262 @@ -3857,7 +4701,7 @@ dependencies = [
1263 "crc",
1264 "crossbeam-queue",
1265 "either",
1266- "event-listener",
1267+ "event-listener 2.5.3",
1268 "futures-channel",
1269 "futures-core",
1270 "futures-intrusive",
1271 @@ -4202,8 +5046,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
1272 checksum = "85b77fafb263dd9d05cbeac119526425676db3784113aa9295c88498cbf8bff1"
1273 dependencies = [
1274 "cfg-if",
1275- "fastrand",
1276- "rustix",
1277+ "fastrand 2.0.1",
1278+ "rustix 0.38.31",
1279 "windows-sys 0.52.0",
1280 ]
1281
1282 @@ -4254,7 +5098,7 @@ version = "0.3.0"
1283 source = "registry+https://github.com/rust-lang/crates.io-index"
1284 checksum = "21bebf2b7c9e0a515f6e0f8c51dc0f8e4696391e6f1ff30379559f8365fb0df7"
1285 dependencies = [
1286- "rustix",
1287+ "rustix 0.38.31",
1288 "windows-sys 0.48.0",
1289 ]
1290
1291 @@ -4385,7 +5229,7 @@ dependencies = [
1292 "parking_lot 0.12.1",
1293 "pin-project-lite",
1294 "signal-hook-registry",
1295- "socket2",
1296+ "socket2 0.5.6",
1297 "tokio-macros",
1298 "windows-sys 0.48.0",
1299 ]
1300 @@ -4472,14 +5316,26 @@ dependencies = [
1301 "serde",
1302 "serde_spanned",
1303 "toml_datetime",
1304- "toml_edit",
1305+ "toml_edit 0.19.15",
1306+ ]
1307+
1308+ [[package]]
1309+ name = "toml"
1310+ version = "0.8.14"
1311+ source = "registry+https://github.com/rust-lang/crates.io-index"
1312+ checksum = "6f49eb2ab21d2f26bd6db7bf383edc527a7ebaee412d17af4d40fdccd442f335"
1313+ dependencies = [
1314+ "serde",
1315+ "serde_spanned",
1316+ "toml_datetime",
1317+ "toml_edit 0.22.14",
1318 ]
1319
1320 [[package]]
1321 name = "toml_datetime"
1322- version = "0.6.5"
1323+ version = "0.6.6"
1324 source = "registry+https://github.com/rust-lang/crates.io-index"
1325- checksum = "3550f4e9685620ac18a50ed434eb3aec30db8ba93b0287467bca5826ea25baf1"
1326+ checksum = "4badfd56924ae69bcc9039335b2e017639ce3f9b001c393c1b2d1ef846ce2cbf"
1327 dependencies = [
1328 "serde",
1329 ]
1330 @@ -4494,7 +5350,20 @@ dependencies = [
1331 "serde",
1332 "serde_spanned",
1333 "toml_datetime",
1334- "winnow",
1335+ "winnow 0.5.40",
1336+ ]
1337+
1338+ [[package]]
1339+ name = "toml_edit"
1340+ version = "0.22.14"
1341+ source = "registry+https://github.com/rust-lang/crates.io-index"
1342+ checksum = "f21c7aaf97f1bd9ca9d4f9e73b0a6c74bd5afef56f2bc931943a6e1c37e04e38"
1343+ dependencies = [
1344+ "indexmap 2.2.5",
1345+ "serde",
1346+ "serde_spanned",
1347+ "toml_datetime",
1348+ "winnow 0.6.13",
1349 ]
1350
1351 [[package]]
1352 @@ -4801,6 +5670,11 @@ name = "uuid"
1353 version = "1.7.0"
1354 source = "registry+https://github.com/rust-lang/crates.io-index"
1355 checksum = "f00cc9702ca12d3c81455259621e676d0f7251cec66a21e98fe2e9a37db93b2a"
1356+ dependencies = [
1357+ "getrandom",
1358+ "serde",
1359+ "sha1_smol",
1360+ ]
1361
1362 [[package]]
1363 name = "valuable"
1364 @@ -4833,6 +5707,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
1365 checksum = "65dd7eed29412da847b0f78bcec0ac98588165988a8cfe41d4ea1d429f8ccfff"
1366
1367 [[package]]
1368+ name = "waker-fn"
1369+ version = "1.2.0"
1370+ source = "registry+https://github.com/rust-lang/crates.io-index"
1371+ checksum = "317211a0dc0ceedd78fb2ca9a44aed3d7b9b26f81870d485c07122b4350673b7"
1372+
1373+ [[package]]
1374 name = "walkdir"
1375 version = "2.5.0"
1376 source = "registry+https://github.com/rust-lang/crates.io-index"
1377 @@ -5153,6 +6033,15 @@ dependencies = [
1378 ]
1379
1380 [[package]]
1381+ name = "winnow"
1382+ version = "0.6.13"
1383+ source = "registry+https://github.com/rust-lang/crates.io-index"
1384+ checksum = "59b5e5f6c299a3c7890b876a2a587f3115162487e704907d9b6cd29473052ba1"
1385+ dependencies = [
1386+ "memchr",
1387+ ]
1388+
1389+ [[package]]
1390 name = "winreg"
1391 version = "0.50.0"
1392 source = "registry+https://github.com/rust-lang/crates.io-index"
1393 diff --git a/Cargo.toml b/Cargo.toml
1394index d17ca56..cea9eaa 100644
1395--- a/Cargo.toml
1396+++ b/Cargo.toml
1397 @@ -9,7 +9,7 @@ members = [
1398 "crates/database",
1399 "ayllu",
1400 # "ayllu-build",
1401- # "ayllu-mail",
1402+ "ayllu-mail",
1403 # "ayllu-xmpp",
1404 "quipu"
1405 ]
1406 diff --git a/README.md b/README.md
1407index 826c8c4..81951d8 100644
1408--- a/README.md
1409+++ b/README.md
1410 @@ -86,7 +86,7 @@ installed on your system:
1411 - [libgit2](https://libgit2.org/)
1412
1413
1414- You can run the [check_build_dependencies.sh](https://ayllu-forge.org/projects/ayllu/blob/scripts/check_build_dependencies.sh)
1415+ You can run the [check_build_dependencies.sh](https://ayllu-forge.org/ayllu/ayllu/blob/scripts/check_build_dependencies.sh)
1416 script to verify you have all the necessary software on your system.
1417
1418 ```sh
1419 diff --git a/ayllu-mail/Cargo.toml b/ayllu-mail/Cargo.toml
1420index dc22d8c..fc76b0a 100644
1421--- a/ayllu-mail/Cargo.toml
1422+++ b/ayllu-mail/Cargo.toml
1423 @@ -18,6 +18,6 @@ tracing = "0.1.37"
1424 tokio = { version = "1.33.0", features = ["full"] }
1425 futures = "0.3.28"
1426 melib = "0.8.2"
1427- mailpot = { git = "https://ayllu-forge.org/forks/mailpot", branch = "ayllu-dev"}
1428+ mailpot = { git = "https://ayllu-forge.org/ayllu/mailpot", branch = "main"}
1429 clap_complete = "4.4.5"
1430 anyhow = "1.0.78"
1431 diff --git a/ayllu-mail/src/config.rs b/ayllu-mail/src/config.rs
1432index a324b05..901c169 100644
1433--- a/ayllu-mail/src/config.rs
1434+++ b/ayllu-mail/src/config.rs
1435 @@ -1,5 +1,5 @@
1436 use serde::{Deserialize, Serialize};
1437- use std::path::Path;
1438+ use std::path::{Path, PathBuf};
1439
1440 use ayllu_config::{data_dir, runtime_dir, Configurable, Error, Reader};
1441 use mailpot::{Configuration as MailPotConfig, SendMail};
1442 @@ -8,18 +8,27 @@ use mailpot::{Configuration as MailPotConfig, SendMail};
1443 pub struct Database {
1444 pub migrate: Option<bool>,
1445 #[serde(default = "Database::default_database_path")]
1446- pub path: String,
1447+ pub path: PathBuf,
1448 }
1449
1450 impl Database {
1451- pub fn default_database_path() -> String {
1452+ pub fn default_database_path() -> PathBuf {
1453 let mut data_path = data_dir();
1454 data_path.push("mail.db");
1455- let path = String::from(data_path.to_str().unwrap());
1456- path
1457+ data_path.clone()
1458 }
1459 }
1460
1461+ impl Default for Database {
1462+ fn default() -> Self {
1463+ Database {
1464+ path: Database::default_database_path(),
1465+ migrate: None,
1466+ }
1467+ }
1468+ }
1469+
1470+ /// PostPolicy
1471 #[derive(Deserialize, Serialize, Clone, Debug, Default)]
1472 pub enum PostPolicy {
1473 AnnounceOnly,
1474 @@ -39,6 +48,7 @@ pub enum SubscriptionPolicyKind {
1475 Custom,
1476 }
1477
1478+ /// SubscriptionPolicy
1479 #[derive(Deserialize, Serialize, Clone, Debug, Default)]
1480 pub struct SubscriptionPolicy {
1481 pub send_confirmation: bool,
1482 @@ -56,14 +66,23 @@ pub struct MailingList {
1483 pub subscription_policy: SubscriptionPolicy,
1484 }
1485
1486+ /// Postfix related configuration mostly used at container initialization
1487+ #[derive(Serialize, Deserialize, Clone)]
1488+ pub struct Postfix {}
1489+
1490 #[derive(Serialize, Deserialize, Clone)]
1491 pub struct Mail {
1492 #[serde(default = "Mail::default_socket_path")]
1493 pub socket_path: String,
1494+ #[serde(default = "Database::default")]
1495 pub database: Database,
1496 #[serde(default = "Mail::default_sendmail_command")]
1497 pub sendmail_command: String,
1498 pub lists: Vec<MailingList>,
1499+ #[serde(default = "Mail::default_postfix_user")]
1500+ pub postfix_user: String,
1501+ #[serde(default = "Mail::default_postfix_user")]
1502+ pub postfix_group: String,
1503 }
1504
1505 impl Mail {
1506 @@ -72,7 +91,24 @@ impl Mail {
1507 }
1508
1509 fn default_sendmail_command() -> String {
1510- String::from("/usr/bin/sendmail")
1511+ String::from("/usr/bin/false")
1512+ }
1513+
1514+ fn default_postfix_user() -> String {
1515+ String::from("ayllu")
1516+ }
1517+ }
1518+
1519+ impl Default for Mail {
1520+ fn default() -> Self {
1521+ Mail {
1522+ socket_path: Mail::default_socket_path(),
1523+ database: Database::default(),
1524+ sendmail_command: Mail::default_sendmail_command(),
1525+ lists: Vec::new(),
1526+ postfix_user: Mail::default_postfix_user(),
1527+ postfix_group: Mail::default_postfix_user(),
1528+ }
1529 }
1530 }
1531
1532 @@ -80,15 +116,22 @@ impl Mail {
1533 pub struct Config {
1534 pub sysadmin: Option<String>,
1535 pub log_level: String,
1536+ #[serde(default = "Mail::default")]
1537 pub mail: Mail,
1538 }
1539
1540 impl Config {
1541 pub fn mailpot_config(&self) -> MailPotConfig {
1542+ let data_path = self
1543+ .mail
1544+ .database
1545+ .path
1546+ .parent()
1547+ .expect("cannot resolve data path");
1548 MailPotConfig {
1549 send_mail: SendMail::ShellCommand(self.mail.sendmail_command.clone()),
1550- data_path: data_dir(),
1551- db_path: self.mail.database.path.clone().into(),
1552+ data_path: data_path.to_path_buf(),
1553+ db_path: self.mail.database.path.clone(),
1554 administrators: self
1555 .sysadmin
1556 .clone()
1557 diff --git a/ayllu-mail/src/main.rs b/ayllu-mail/src/main.rs
1558index c0f87be..1cf9bbf 100644
1559--- a/ayllu-mail/src/main.rs
1560+++ b/ayllu-mail/src/main.rs
1561 @@ -6,8 +6,10 @@ use std::str::FromStr;
1562 use anyhow::{format_err, Error, Result};
1563 use clap::{arg, Command, CommandFactory, Parser, Subcommand};
1564 use clap_complete::{generate, Generator, Shell};
1565+ use mailpot::models::{DbVal, MailingList, PostPolicy};
1566 use mailpot::{
1567 melib::Envelope,
1568+ postfix::PostfixConfiguration,
1569 queue::{Queue, QueueEntry},
1570 transaction::TransactionBehavior,
1571 Connection,
1572 @@ -32,11 +34,23 @@ struct Cli {
1573 #[arg(short, long, value_name = "LEVEL")]
1574 level: Option<Level>,
1575
1576+ /// Optional path to database
1577+ #[arg(short, long, value_name = "FILE")]
1578+ database: Option<PathBuf>,
1579+
1580 #[command(subcommand)]
1581 command: Commands,
1582 }
1583
1584 #[derive(Subcommand, Debug, PartialEq)]
1585+ enum Postfix {
1586+ /// Generate service line entry for Postfix master.cf
1587+ MasterCf {},
1588+ /// Generate transport maps for Postfix
1589+ Maps {},
1590+ }
1591+
1592+ #[derive(Subcommand, Debug, PartialEq)]
1593 enum Commands {
1594 /// generate autocomplete commands for common shells
1595 Complete {
1596 @@ -49,23 +63,43 @@ enum Commands {
1597 Post {},
1598 /// send all queued messages to their recipients
1599 Send {},
1600+ /// generate a postfix configuration
1601+ Postfix {
1602+ #[command(subcommand)]
1603+ command: Postfix,
1604+ },
1605 }
1606
1607 fn print_completions<G: Generator>(gen: G, cmd: &mut Command) {
1608 generate(gen, cmd, cmd.get_name().to_string(), &mut stdout());
1609 }
1610
1611- fn main() -> Result<(), Box<dyn std::error::Error>> {
1612- let cli = Cli::parse();
1613- let ayllu_config = config::load(cli.config.as_deref())?;
1614- let config_level = Level::from_str(&ayllu_config.log_level)?;
1615+ fn init_logger(level: Level) {
1616 tracing_subscriber::fmt()
1617 .compact()
1618 .with_line_number(true)
1619 .with_level(true)
1620- .with_max_level(cli.level.unwrap_or(config_level))
1621+ .with_max_level(level)
1622 .init();
1623 tracing::info!("logger initialized");
1624+ }
1625+
1626+ #[allow(clippy::type_complexity)]
1627+ fn main() -> Result<(), Box<dyn std::error::Error>> {
1628+ let cli = Cli::parse();
1629+ let mut ayllu_config = config::load(cli.config.as_deref())?;
1630+ if let Some(db_path) = cli.database {
1631+ ayllu_config.mail.database.path.clone_from(&db_path);
1632+ }
1633+ // ensure the database path exists since mailpot doesn't create it for us
1634+ std::fs::create_dir_all(
1635+ ayllu_config
1636+ .mail
1637+ .database
1638+ .path
1639+ .parent()
1640+ .expect("db path has no parent"),
1641+ )?;
1642 declarative::initialize(&ayllu_config)?;
1643 match cli.command {
1644 Commands::Complete { shell } => {
1645 @@ -73,6 +107,10 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
1646 print_completions(shell, &mut cmd);
1647 }
1648 Commands::Serve {} => {
1649+ init_logger(
1650+ cli.level
1651+ .unwrap_or(Level::from_str(&ayllu_config.log_level)?),
1652+ );
1653 TokioBuilder::new_current_thread()
1654 .enable_all()
1655 .build()
1656 @@ -84,6 +122,10 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
1657 })?;
1658 }
1659 Commands::Post {} => {
1660+ init_logger(
1661+ cli.level
1662+ .unwrap_or(Level::from_str(&ayllu_config.log_level)?),
1663+ );
1664 let mut input = String::new();
1665 stdin().read_to_string(&mut input)?;
1666 let envelope = Envelope::from_bytes(input.as_bytes(), None)?;
1667 @@ -93,6 +135,10 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
1668 tx.commit()?;
1669 }
1670 Commands::Send {} => {
1671+ init_logger(
1672+ cli.level
1673+ .unwrap_or(Level::from_str(&ayllu_config.log_level)?),
1674+ );
1675 let mut db = Connection::open_or_create_db(ayllu_config.mailpot_config())?.trusted();
1676 let tx = db.transaction(TransactionBehavior::Exclusive)?;
1677 let messages = tx.queue(Queue::Out)?;
1678 @@ -154,6 +200,45 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
1679 }
1680 tx.commit()?;
1681 }
1682+ Commands::Postfix { command } => {
1683+ let this_exe = std::env::current_exe().unwrap();
1684+ let cfg = PostfixConfiguration {
1685+ user: std::borrow::Cow::from(ayllu_config.mail.postfix_user.clone()),
1686+ binary_path: this_exe,
1687+ ..Default::default()
1688+ };
1689+ match command {
1690+ Postfix::MasterCf {} => {
1691+ print!(
1692+ "{}",
1693+ cfg.generate_master_cf_entry(
1694+ &ayllu_config.mailpot_config(),
1695+ cli.config
1696+ .expect("need to specify an absolute path to your config file")
1697+ .as_path()
1698+ )
1699+ );
1700+ }
1701+ Postfix::Maps {} => {
1702+ let mut db =
1703+ Connection::open_or_create_db(ayllu_config.mailpot_config())?.trusted();
1704+ let tx = db.transaction(TransactionBehavior::Exclusive)?;
1705+ let lists: Result<
1706+ Vec<(DbVal<MailingList>, Option<DbVal<PostPolicy>>)>,
1707+ mailpot::Error,
1708+ > = tx
1709+ .lists()?
1710+ .iter()
1711+ .map(|list| {
1712+ let policy = tx.list_post_policy(list.1)?;
1713+ Ok((list.clone(), policy))
1714+ })
1715+ .collect();
1716+ let maps = cfg.generate_maps(lists?.as_slice());
1717+ print!("{}", maps);
1718+ }
1719+ }
1720+ }
1721 }
1722
1723 Ok(())
1724 diff --git a/ayllu-mail/src/server.rs b/ayllu-mail/src/server.rs
1725index efc135d..d03cb9a 100644
1726--- a/ayllu-mail/src/server.rs
1727+++ b/ayllu-mail/src/server.rs
1728 @@ -45,6 +45,7 @@ fn to_message(post: &DbVal<Post>) -> Message {
1729 timestamp: post.timestamp,
1730 from: envelope.from[0].get_email(),
1731 address: post.address.clone(),
1732+ subject: envelope.subject.clone(),
1733 body,
1734 text,
1735 is_patch: envelope.subject.map_or(false, |subject| is_patch(&subject)),
1736 @@ -61,8 +62,9 @@ struct ServerImpl {
1737 impl MailServer for ServerImpl {
1738 #[doc = r" return all managed mailing lists"]
1739 async fn lists(self, _context: Context) -> Result<Vec<MailingList>, ApiError> {
1740- let db =
1741- Connection::open_or_create_db(self.mailpot_config.clone()).map_err(to_api_error)?.trusted();
1742+ let db = Connection::open_or_create_db(self.mailpot_config.clone())
1743+ .map_err(to_api_error)?
1744+ .trusted();
1745 Ok(db
1746 .lists()
1747 .map_err(|e| ApiError::Message(e.to_string()))?
1748 @@ -89,8 +91,9 @@ impl MailServer for ServerImpl {
1749 _offset: i64,
1750 _limit: i64,
1751 ) -> Result<Vec<Thread>, ApiError> {
1752- let db =
1753- Connection::open_or_create_db(self.mailpot_config.clone()).map_err(to_api_error)?.trusted();
1754+ let db = Connection::open_or_create_db(self.mailpot_config.clone())
1755+ .map_err(to_api_error)?
1756+ .trusted();
1757 let list = db
1758 .list_by_id(&id)
1759 .map_err(to_api_error)?
1760 @@ -133,8 +136,9 @@ impl MailServer for ServerImpl {
1761 _offset: i64,
1762 _limit: i64,
1763 ) -> Result<Vec<Message>, ApiError> {
1764- let db =
1765- Connection::open_or_create_db(self.mailpot_config.clone()).map_err(to_api_error)?.trusted();
1766+ let db = Connection::open_or_create_db(self.mailpot_config.clone())
1767+ .map_err(to_api_error)?
1768+ .trusted();
1769 let list = db
1770 .list_by_id(&id)
1771 .map_err(to_api_error)?
1772 @@ -154,7 +158,7 @@ impl MailServer for ServerImpl {
1773 posts.push(to_message(&first_post));
1774 // then all of it's replies
1775 let replies = db.list_thread(list.pk, &message_id).map_err(to_api_error)?;
1776- for (_, post, _, _) in replies.iter() {
1777+ for (_, post) in replies.iter() {
1778 posts.push(to_message(post));
1779 }
1780 Ok(posts)
1781 @@ -167,8 +171,9 @@ impl MailServer for ServerImpl {
1782 id: String,
1783 message_id: String,
1784 ) -> Result<Message, ApiError> {
1785- let db =
1786- Connection::open_or_create_db(self.mailpot_config.clone()).map_err(to_api_error)?.trusted();
1787+ let db = Connection::open_or_create_db(self.mailpot_config.clone())
1788+ .map_err(to_api_error)?
1789+ .trusted();
1790 let list = db
1791 .list_by_id(&id)
1792 .map_err(to_api_error)?
1793 @@ -194,8 +199,9 @@ impl MailServer for ServerImpl {
1794 message_id: Option<String>,
1795 as_thread: bool,
1796 ) -> Result<Vec<u8>, ApiError> {
1797- let db =
1798- Connection::open_or_create_db(self.mailpot_config.clone()).map_err(to_api_error)?.trusted();
1799+ let db = Connection::open_or_create_db(self.mailpot_config.clone())
1800+ .map_err(to_api_error)?
1801+ .trusted();
1802 let list = db
1803 .list_by_id(&id)
1804 .map_err(to_api_error)?
1805 diff --git a/ayllu/src/web2/routes/mail.rs b/ayllu/src/web2/routes/mail.rs
1806index 199f1de..89fb453 100644
1807--- a/ayllu/src/web2/routes/mail.rs
1808+++ b/ayllu/src/web2/routes/mail.rs
1809 @@ -7,6 +7,7 @@ use axum::{
1810 };
1811 use serde::{Deserialize, Serialize};
1812
1813+ use crate::highlight::Highlighter;
1814 use crate::web2::error::Error;
1815 use crate::web2::middleware::rpc_initiator::Initiator;
1816 use crate::web2::middleware::template::Template;
1817 @@ -103,7 +104,7 @@ pub async fn threads(
1818 pub async fn thread(
1819 Path(params): Path<Params>,
1820 Extension(initiator): Extension<Initiator>,
1821- // Extension(highlighter): Extension<Highlighter>,
1822+ Extension(highlighter): Extension<Highlighter>,
1823 Extension((templates, mut ctx)): Extension<Template>,
1824 ) -> Result<Html<String>, Error> {
1825 let client = initiator.mail.unwrap();
1826 @@ -113,21 +114,37 @@ pub async fn thread(
1827 let messages = client
1828 .read_thread(
1829 context::current(),
1830- params.thread_id.as_ref().unwrap().clone(),
1831+ params.list_id,
1832 params.message_id.unwrap().clone(),
1833 1000,
1834 0,
1835 )
1836 .await??;
1837- // FIXME
1838- // let subject = messages.first().map(|message| message.subject.clone());
1839+ let subject = messages.first().map(|message| message.subject.clone());
1840+ let messages: Vec<(ayllu_api::mail::Message, Option<String>)> = messages
1841+ .into_iter()
1842+ .map(|message| {
1843+ if message.is_patch {
1844+ let formatted_diff = highlighter.highlight(
1845+ message.text.as_str(),
1846+ None,
1847+ None,
1848+ Some("diff".into()),
1849+ true,
1850+ );
1851+ (message, Some(formatted_diff.1))
1852+ } else {
1853+ (message, None)
1854+ }
1855+ })
1856+ .collect();
1857 ctx.insert("title", &format!("list {}", mailing_list.address));
1858 ctx.insert("nav_elements", &navigation::global("mail", true));
1859 ctx.insert("list", &mailing_list);
1860 ctx.insert("list_id", &mailing_list.id);
1861 ctx.insert("thread_id", &params.thread_id.clone());
1862 ctx.insert("messages", &messages);
1863- // ctx.insert("subject", &subject);
1864+ ctx.insert("subject", &subject);
1865 let body = templates.render("thread.html", &ctx)?;
1866 Ok(Html(body))
1867 } else {
1868 @@ -140,6 +157,7 @@ pub async fn thread(
1869
1870 pub async fn message(
1871 Path(params): Path<Params>,
1872+ Extension(highlighter): Extension<Highlighter>,
1873 Extension(initiator): Extension<Initiator>,
1874 Extension((templates, mut ctx)): Extension<Template>,
1875 ) -> Result<Html<String>, Error> {
1876 @@ -160,6 +178,11 @@ pub async fn message(
1877 ctx.insert("list_id", &mailing_list.id.clone());
1878 ctx.insert("thread_id", &params.thread_id);
1879 ctx.insert("message", &message);
1880+ if message.is_patch {
1881+ let formatted_diff =
1882+ highlighter.highlight(message.text.as_str(), None, None, Some("diff".into()), true);
1883+ ctx.insert("diff", &formatted_diff);
1884+ }
1885 let body = templates.render("post.html", &ctx)?;
1886 Ok(Html(body))
1887 } else {
1888 diff --git a/ayllu/src/web2/server.rs b/ayllu/src/web2/server.rs
1889index 3ca2e13..99c31e8 100644
1890--- a/ayllu/src/web2/server.rs
1891+++ b/ayllu/src/web2/server.rs
1892 @@ -157,7 +157,7 @@ pub async fn serve(cfg: &Config) -> Result<(), Box<dyn Error>> {
1893 "/export/:list_id/:thread_id/:message_id",
1894 routing::get(mail::export),
1895 )
1896- .route("/thread/:list_id/:thread_id", routing::get(mail::thread))
1897+ .route("/thread/:list_id/:message_id", routing::get(mail::thread))
1898 .route("/message/:list_id/:message_id", routing::get(mail::message))
1899 .layer(from_fn_with_state(
1900 Arc::new((cfg.clone(), themes.clone(), mail_required_plugins)),
1901 diff --git a/ayllu/themes/default/templates/lists.html b/ayllu/themes/default/templates/lists.html
1902index 0f413de..a6fd237 100644
1903--- a/ayllu/themes/default/templates/lists.html
1904+++ b/ayllu/themes/default/templates/lists.html
1905 @@ -1,31 +1,17 @@
1906 {% import "macros.html" as macros %}
1907 {% extends "base.html" %}
1908 {% block content %}
1909- <section>
1910- <article>
1911- <header>
1912- <h1>Mailing Lists</h1>
1913- </header>
1914- <table>
1915- <thead>
1916- <th>id</th>
1917- <th>name</th>
1918- <th>description</th>
1919- <th>address</th>
1920- </thead>
1921- <tbody>
1922- {% for list in lists %}
1923- <tr>
1924- <td>
1925- <a href="/mail/{{ list.id }}">{{ list.id }}</a>
1926- </td>
1927- <td>{{ list.name }}</td>
1928- <td>{{ list.description }}</td>
1929- <td>{{ list.address }}</td>
1930- </tr>
1931- {% endfor %}
1932- </tbody>
1933- </table>
1934+ <h1>Mailing Lists</h1>
1935+ <div class="stretch">
1936+ {% for list in lists %}
1937+ <section class="card">
1938+ <article class="segmented-4">
1939+ <div><a href="/mail/{{list.id}}">{{list.name}} [{{list.id}}]</a></div>
1940+ <div>{{ list.description }}</div>
1941+ <div>{{ list.address }}</div>
1942+ <div>▃▅▇▇▁▅▃▅▅▃▅▁▁</div>
1943 </article>
1944 </section>
1945+ {% endfor %}
1946+ </div>
1947 {% endblock %}
1948 diff --git a/ayllu/themes/default/templates/post.html b/ayllu/themes/default/templates/post.html
1949index 5880bb1..0e2479e 100644
1950--- a/ayllu/themes/default/templates/post.html
1951+++ b/ayllu/themes/default/templates/post.html
1952 @@ -1,6 +1,6 @@
1953 {% extends "base.html" %}
1954 {% block content %}
1955- <section class="thread-view">
1956+ <section class="stretch">
1957 <article>
1958 <header>
1959 <h1>{{ message.message_id }}</h1>
1960 @@ -9,13 +9,17 @@
1961 <p>
1962 <a href="/mail/export/{{ list_id }}/{{ thread_id }}/{{ message.message_id }}">mbox</a>
1963 <p>
1964- <b>From: {{ message.from_address }}</b>
1965+ <b>From: {{ message.from }}</b>
1966 </br>
1967 <b>Subject: {{ message.subject }}</b>
1968 </br>
1969- <span class="right">{{ message.created_at | format_epoch }}</span>
1970+ <span class="right">{{ message.timestamp | format_epoch }}</span>
1971 </header>
1972+ {% if message.is_patch %}
1973+ {{ diff.1 | safe }}
1974+ {% else %}
1975 <pre>{{ message.text | safe }}</pre>
1976+ {% endif %}
1977 </article>
1978 </section>
1979 {% endblock %}
1980 diff --git a/ayllu/themes/default/templates/thread.html b/ayllu/themes/default/templates/thread.html
1981index af9d62d..a5717f3 100644
1982--- a/ayllu/themes/default/templates/thread.html
1983+++ b/ayllu/themes/default/templates/thread.html
1984 @@ -1,9 +1,9 @@
1985 {% extends "base.html" %}
1986 {% block content %}
1987- <section class="thread-view">
1988+ <section class="stretch">
1989 <article>
1990 <header>
1991- <h1>{{ subject }}</h1>
1992+ <h1>{{subject}}</h1>
1993 </br>
1994 <h4>Export Thread</h4>
1995 <p>
1996 @@ -13,14 +13,18 @@
1997 {% for reply in messages %}
1998 <article>
1999 <header>
2000- <b>From: {{ reply.from_address }}</b>
2001+ <b>From: {{ reply.0.from }}</b>
2002 </br>
2003- <b>Subject: {{ reply.subject }} </b>
2004+ <b>Subject: {{ reply.0.subject }} </b>
2005 </br>
2006- <b><a href="/mail/message/{{ list_id }}/{{ reply.message_id }}">{{ reply.message_id }}</a></b>
2007- <span class="right">{{ reply.created_at | format_epoch }}</span>
2008+ <b><a href="/mail/message/{{ list_id }}/{{ reply.0.message_id }}">{{ reply.0.message_id }}</a></b>
2009+ <span class="right">{{ reply.0.timestamp | format_epoch }}</span>
2010 </header>
2011- <pre>{{ reply.text | safe }}</pre>
2012+ {% if reply.0.is_patch %}
2013+ {{ reply.1 | safe }}
2014+ {% else %}
2015+ <pre>{{ reply.0.text | safe }}</pre>
2016+ {% endif %}
2017 </article>
2018 {% endfor %}
2019 </section>
2020 diff --git a/ayllu/themes/default/templates/threads.html b/ayllu/themes/default/templates/threads.html
2021index 7b74feb..c6b69ce 100644
2022--- a/ayllu/themes/default/templates/threads.html
2023+++ b/ayllu/themes/default/templates/threads.html
2024 @@ -6,7 +6,7 @@
2025 <header>
2026 <h1>{{ list.name }}</h1>
2027 <span class="right labels">
2028- {% for topic in list.topics %}<span class="feature">{{ topic }}</span>{% endfor %}
2029+ {% for topic in list.topics %}<span class="yellow-badge">{{ topic }}</span>{% endfor %}
2030 </span>
2031 </header>
2032 <div class="mailing-list-details">
2033 @@ -26,28 +26,19 @@
2034 </p>
2035 </div>
2036 <h4>Messages</h4>
2037- <table>
2038- <thead>
2039- <th>from</th>
2040- <th>date</th>
2041- <th>subject</th>
2042- <th>replies</th>
2043- </thead>
2044- <tbody>
2045+ <div class="stretch">
2046 {% for thread in threads %}
2047- <tr>
2048- <td>
2049- <a>{{ thread.from }}</a>
2050- </td>
2051- <td>{{ thread.timestamp | format_epoch }}</td>
2052- <td>
2053- <a href="/mail/thread/{{ list.id }}/{{ thread.message_id }}">{{ thread.subject }}</a>
2054- </td>
2055- <td>{{ thread.n_replies }}</td>
2056- </tr>
2057+ <section class="card">
2058+ {% set thread = thread.first %}
2059+ <article class="segmented-3">
2060+ <div> {{ thread.from }} </div>
2061+ <div> {{ thread.timestamp | format_epoch }} </div>
2062+ <div>{{ thread.n_replies }} </div>
2063+ </article>
2064+ <a href="/mail/thread/{{ list.id }}/{{ thread.message_id }}">{{ thread.subject }}</a>
2065+ </section>
2066 {% endfor %}
2067- </tbody>
2068- </table>
2069+ </div>
2070 </article>
2071 </section>
2072 {% endblock %}
2073 diff --git a/ayllu/themes/default/theme.css b/ayllu/themes/default/theme.css
2074index 08bcfa4..9ea20ef 100644
2075--- a/ayllu/themes/default/theme.css
2076+++ b/ayllu/themes/default/theme.css
2077 @@ -285,6 +285,8 @@ section.viewer svg {
2078
2079 .stretch {
2080 width: 100%;
2081+ overflow-x: auto;
2082+ white-space: nowrap;
2083 }
2084
2085 li.active {
2086 diff --git a/containers/base/Containerfile b/containers/base/Containerfile
2087index 3c21fdf..0e68f99 100644
2088--- a/containers/base/Containerfile
2089+++ b/containers/base/Containerfile
2090 @@ -46,6 +46,7 @@ COPY ayllu /src/ayllu/ayllu
2091 COPY contrib /src/ayllu/contrib
2092 COPY crates /src/ayllu/crates
2093 COPY quipu /src/ayllu/quipu
2094+ COPY ayllu-mail /src/ayllu/ayllu-mail
2095 COPY scripts /src/ayllu/scripts
2096 COPY *.toml /src/ayllu/
2097 COPY Cargo.lock /src/ayllu/
2098 @@ -66,16 +67,21 @@ RUN --mount=type=cache,target=/root/.cargo \
2099 # cache all Ayllu dependencies required for the build
2100 RUN --mount=type=cache,target=/root/.cargo cargo fetch --color=never --locked
2101
2102- # build the main Ayllu binary
2103+ # build the main ayllu binary
2104 RUN --mount=type=cache,target=/root/.cargo --network=none \
2105 cargo build \
2106 --color=never --locked --frozen --offline --release --package ayllu
2107
2108- # build the Quipu binary
2109+ # build the quipu binary
2110 RUN --mount=type=cache,target=/root/.cargo --network=none \
2111 cargo build \
2112 --color=never --locked --frozen --offline --release --package quipu
2113
2114+ # build the ayllu-mail binary
2115+ RUN --mount=type=cache,target=/root/.cargo --network=none \
2116+ cargo build \
2117+ --color=never --locked --frozen --offline --release --package ayllu-mail
2118+
2119 FROM alpine:3
2120
2121 RUN apk add \
2122 @@ -88,6 +94,7 @@ RUN rm -v /usr/lib/libtree-sitter-cpp.so
2123 COPY --from=build --chown=0:0 /usr/bin/rudolfs /usr/bin/
2124 COPY --from=build --chown=0:0 /src/ayllu/target/release/ayllu /usr/bin/
2125 COPY --from=build --chown=0:0 /src/ayllu/target/release/quipu /usr/bin/
2126+ COPY --from=build --chown=0:0 /src/ayllu/target/release/ayllu-mail /usr/bin/
2127 COPY --from=build --chown=0:0 /src/ayllu/ayllu/themes /usr/lib/ayllu/themes
2128 COPY --from=build --chown=0:0 /src/ayllu/ayllu/migrations /usr/lib/ayllu/migrations/ayllu
2129 COPY --from=build --chown=0:0 /src/ayllu/LICENSE /usr/share/licenses/ayllu/
2130 diff --git a/containers/multiuser/Containerfile b/containers/multiuser/Containerfile
2131index 34eaa20..4ad6237 100644
2132--- a/containers/multiuser/Containerfile
2133+++ b/containers/multiuser/Containerfile
2134 @@ -1,13 +1,30 @@
2135- ARG BASE_IMAGE=registry.ayllu-forge.org/projects/ayllu:main
2136+ ARG BASE_IMAGE=registry.ayllu-forge.org/ayllu/ayllu:main
2137 FROM $BASE_IMAGE
2138
2139 USER root
2140
2141- RUN apk add --no-cache acl gawk htop dropbear runit tmux vim
2142+ RUN apk add --no-cache \
2143+ acl \
2144+ cronie \
2145+ cronie-openrc \
2146+ dropbear \
2147+ gawk \
2148+ gettext-envsubst \
2149+ htop \
2150+ mutt \
2151+ postfix \
2152+ runit \
2153+ tmux \
2154+ vim
2155+
2156+ # template files used to configure postfix at runtime
2157+ COPY containers/multiuser/postfix /etc/postfix-templates
2158
2159 COPY containers/multiuser/motd.txt /etc/motd
2160 COPY containers/multiuser/service /etc/service
2161 COPY containers/multiuser/welcome.sh /etc/profile.d/
2162+ COPY containers/multiuser/crontabs /etc/crontabs/
2163+ COPY containers/multiuser/post-message.sh /usr/bin/post-message.sh
2164 COPY containers/multiuser/run_all.sh /
2165
2166 CMD ["/run_all.sh"]
2167 diff --git a/containers/multiuser/README.md b/containers/multiuser/README.md
2168index 8ad93ae..76ababd 100644
2169--- a/containers/multiuser/README.md
2170+++ b/containers/multiuser/README.md
2171 @@ -7,6 +7,11 @@ launches multiple processes which are managed by
2172
2173 ## Environment Variables
2174
2175+ ### AYLLU_LISTEN_ADDRESS
2176+
2177+ The interface that Ayllu's HTTP server should listen on
2178+ `default = 127.0.0.1:8080`.
2179+
2180 ### AYLLU_AUTHORIZED_KEYS
2181
2182 You can specify any number of authorized keys separated by a `;` which will
2183 @@ -21,3 +26,12 @@ container will automatically setup user accounts along with SSH access for each
2184 matching variable. This configuration is useful if you want to isolate users
2185 from one another. Note that you still need to configure collections in your
2186 Ayllu configuration file in order for them to be served.
2187+
2188+ ### AYLLU_POSTFIX_ROOT_USER
2189+
2190+ The user that can receive root's e-mail
2191+
2192+ ### AYLLU_VIRTUAL_DOMAINS
2193+
2194+ Virtual aliases Postfix should accept e-mail for,
2195+ see [virtual.5](https://www.postfix.org/virtual.5.html) for more details.
2196 diff --git a/containers/multiuser/crontabs/ayllu b/containers/multiuser/crontabs/ayllu
2197new file mode 100644
2198index 0000000..11009c7
2199--- /dev/null
2200+++ b/containers/multiuser/crontabs/ayllu
2201 @@ -0,0 +1,2 @@
2202+ # send messages from the queue every minute
2203+ */1 * * * * ayllu-mail --config /etc/ayllu/config.toml send
2204 diff --git a/containers/multiuser/crontabs/root b/containers/multiuser/crontabs/root
2205new file mode 100644
2206index 0000000..bcfb4cf
2207--- /dev/null
2208+++ b/containers/multiuser/crontabs/root
2209 @@ -0,0 +1,2 @@
2210+ # generate transport map every 5 minutes and reload postfix
2211+ */5 * * * * su ayllu -c 'ayllu-mail --config /etc/ayllu/config.toml postfix maps' > /etc/postfix/transport && postmap /etc/postfix/transport
2212 diff --git a/containers/multiuser/post-message.sh b/containers/multiuser/post-message.sh
2213new file mode 100755
2214index 0000000..75640a5
2215--- /dev/null
2216+++ b/containers/multiuser/post-message.sh
2217 @@ -0,0 +1,4 @@
2218+ #!/bin/sh
2219+
2220+ export HOME=/home/ayllu
2221+ ayllu-mail -c /etc/ayllu/config.toml post
2222 diff --git a/containers/multiuser/postfix/aliases b/containers/multiuser/postfix/aliases
2223new file mode 100644
2224index 0000000..bedd39e
2225--- /dev/null
2226+++ b/containers/multiuser/postfix/aliases
2227 @@ -0,0 +1,273 @@
2228+ #
2229+ # Sample aliases file. Install in the location as specified by the
2230+ # output from the command "postconf alias_maps". Typical path names
2231+ # are /etc/aliases or /etc/mail/aliases.
2232+ #
2233+ # >>>>>>>>>> The program "newaliases" must be run after
2234+ # >> NOTE >> this file is updated for any changes to
2235+ # >>>>>>>>>> show through to Postfix.
2236+ #
2237+
2238+ # Person who should get root's mail. Don't receive mail as root!
2239+ root: ${AYLLU_ROOT_MAIL_USER}
2240+
2241+ # Basic system aliases -- these MUST be present
2242+ MAILER-DAEMON: postmaster
2243+ postmaster: root
2244+
2245+ # General redirections for pseudo accounts
2246+ bin: root
2247+ daemon: root
2248+ named: root
2249+ nobody: root
2250+ uucp: root
2251+ www: root
2252+ ftp-bugs: root
2253+ postfix: root
2254+
2255+ # Put your local aliases here.
2256+
2257+ # Well-known aliases
2258+ manager: root
2259+ dumper: root
2260+ operator: root
2261+ abuse: postmaster
2262+
2263+ # trap decode to catch security attacks
2264+ decode: root
2265+
2266+ # ALIASES(5) ALIASES(5)
2267+ #
2268+ # NAME
2269+ # aliases - Postfix local alias database format
2270+ #
2271+ # SYNOPSIS
2272+ # newaliases
2273+ #
2274+ # DESCRIPTION
2275+ # The optional aliases(5) table (alias_maps) redirects mail
2276+ # for local recipients. The redirections are processed by
2277+ # the Postfix local(8) delivery agent.
2278+ #
2279+ # This is unlike virtual(5) aliasing (virtual_alias_maps)
2280+ # which applies to all recipients: local(8), virtual, and
2281+ # remote, and which is implemented by the cleanup(8) daemon.
2282+ #
2283+ # Normally, the aliases(5) table is specified as a text file
2284+ # that serves as input to the postalias(1) command. The
2285+ # result, an indexed file in dbm or db format, is used for
2286+ # fast lookup by the mail system. Execute the command
2287+ # newaliases in order to rebuild the indexed file after
2288+ # changing the Postfix alias database.
2289+ #
2290+ # When the table is provided via other means such as NIS,
2291+ # LDAP or SQL, the same lookups are done as for ordinary
2292+ # indexed files.
2293+ #
2294+ # Alternatively, the table can be provided as a regu-
2295+ # lar-expression map where patterns are given as regular
2296+ # expressions. In this case, the lookups are done in a
2297+ # slightly different way as described below under "REGULAR
2298+ # EXPRESSION TABLES".
2299+ #
2300+ # Users can control delivery of their own mail by setting up
2301+ # .forward files in their home directory. Lines in per-user
2302+ # .forward files have the same syntax as the right-hand side
2303+ # of aliases(5) entries.
2304+ #
2305+ # The format of the alias database input file is as follows:
2306+ #
2307+ # o An alias definition has the form
2308+ #
2309+ # name: value1, value2, ...
2310+ #
2311+ # o Empty lines and whitespace-only lines are ignored,
2312+ # as are lines whose first non-whitespace character
2313+ # is a `#'.
2314+ #
2315+ # o A logical line starts with non-whitespace text. A
2316+ # line that starts with whitespace continues a logi-
2317+ # cal line.
2318+ #
2319+ # The name is a local address (no domain part). Use double
2320+ # quotes when the name contains any special characters such
2321+ # as whitespace, `#', `:', or `@'. The name is folded to
2322+ # lowercase, in order to make database lookups case insensi-
2323+ # tive.
2324+ #
2325+ # In addition, when an alias exists for owner-name, this
2326+ # will override the envelope sender address, so that deliv-
2327+ # ery diagnostics are directed to owner-name, instead of the
2328+ # originator of the message (for details, see
2329+ # owner_request_special, expand_owner_alias and
2330+ # reset_owner_alias). This is typically used to direct
2331+ # delivery errors to the maintainer of a mailing list, who
2332+ # is in a better position to deal with mailing list delivery
2333+ # problems than the originator of the undelivered mail.
2334+ #
2335+ # The value contains one or more of the following:
2336+ #
2337+ # address
2338+ # Mail is forwarded to address, which is compatible
2339+ # with the RFC 822 standard.
2340+ #
2341+ # /file/name
2342+ # Mail is appended to /file/name. For details on how
2343+ # a file is written see the sections "EXTERNAL FILE
2344+ # DELIVERY" and "DELIVERY RIGHTS" in the local(8)
2345+ # documentation. Delivery is not limited to regular
2346+ # files. For example, to dispose of unwanted mail,
2347+ # deflect it to /dev/null.
2348+ #
2349+ # |command
2350+ # Mail is piped into command. Commands that contain
2351+ # special characters, such as whitespace, should be
2352+ # enclosed between double quotes. For details on how
2353+ # a command is executed see "EXTERNAL COMMAND DELIV-
2354+ # ERY" and "DELIVERY RIGHTS" in the local(8) documen-
2355+ # tation.
2356+ #
2357+ # When the command fails, a limited amount of command
2358+ # output is mailed back to the sender. The file
2359+ # /usr/include/sysexits.h defines the expected exit
2360+ # status codes. For example, use "|exit 67" to simu-
2361+ # late a "user unknown" error, and "|exit 0" to
2362+ # implement an expensive black hole.
2363+ #
2364+ # :include:/file/name
2365+ # Mail is sent to the destinations listed in the
2366+ # named file. Lines in :include: files have the same
2367+ # syntax as the right-hand side of alias entries.
2368+ #
2369+ # A destination can be any destination that is
2370+ # described in this manual page. However, delivery to
2371+ # "|command" and /file/name is disallowed by default.
2372+ # To enable, edit the allow_mail_to_commands and
2373+ # allow_mail_to_files configuration parameters.
2374+ #
2375+ # ADDRESS EXTENSION
2376+ # When alias database search fails, and the recipient local-
2377+ # part contains the optional recipient delimiter (e.g.,
2378+ # user+foo), the search is repeated for the unextended
2379+ # address (e.g., user).
2380+ #
2381+ # The propagate_unmatched_extensions parameter controls
2382+ # whether an unmatched address extension (+foo) is propa-
2383+ # gated to the result of table lookup.
2384+ #
2385+ # CASE FOLDING
2386+ # The local(8) delivery agent always folds the search string
2387+ # to lowercase before database lookup.
2388+ #
2389+ # REGULAR EXPRESSION TABLES
2390+ # This section describes how the table lookups change when
2391+ # the table is given in the form of regular expressions. For
2392+ # a description of regular expression lookup table syntax,
2393+ # see regexp_table(5) or pcre_table(5). NOTE: these formats
2394+ # do not use ":" at the end of a pattern.
2395+ #
2396+ # Each regular expression is applied to the entire search
2397+ # string. Thus, a search string user+foo is not broken up
2398+ # into user and foo.
2399+ #
2400+ # Regular expressions are applied in the order as specified
2401+ # in the table, until a regular expression is found that
2402+ # matches the search string.
2403+ #
2404+ # Lookup results are the same as with indexed file lookups.
2405+ # For security reasons there is no support for $1, $2 etc.
2406+ # substring interpolation.
2407+ #
2408+ # SECURITY
2409+ # The local(8) delivery agent disallows regular expression
2410+ # substitution of $1 etc. in alias_maps, because that would
2411+ # open a security hole.
2412+ #
2413+ # The local(8) delivery agent will silently ignore requests
2414+ # to use the proxymap(8) server within alias_maps. Instead
2415+ # it will open the table directly. Before Postfix version
2416+ # 2.2, the local(8) delivery agent will terminate with a
2417+ # fatal error.
2418+ #
2419+ # CONFIGURATION PARAMETERS
2420+ # The following main.cf parameters are especially relevant.
2421+ # The text below provides only a parameter summary. See
2422+ # postconf(5) for more details including examples.
2423+ #
2424+ # alias_database (see 'postconf -d' output)
2425+ # The alias databases for local(8) delivery that are
2426+ # updated with "newaliases" or with "sendmail -bi".
2427+ #
2428+ # alias_maps (see 'postconf -d' output)
2429+ # Optional lookup tables with aliases that apply only
2430+ # to local(8) recipients; this is unlike vir-
2431+ # tual_alias_maps that apply to all recipients:
2432+ # local(8), virtual, and remote.
2433+ #
2434+ # allow_mail_to_commands (alias, forward)
2435+ # Restrict local(8) mail delivery to external com-
2436+ # mands.
2437+ #
2438+ # allow_mail_to_files (alias, forward)
2439+ # Restrict local(8) mail delivery to external files.
2440+ #
2441+ # expand_owner_alias (no)
2442+ # When delivering to an alias "aliasname" that has an
2443+ # "owner-aliasname" companion alias, set the envelope
2444+ # sender address to the expansion of the
2445+ # "owner-aliasname" alias.
2446+ #
2447+ # propagate_unmatched_extensions (canonical, virtual)
2448+ # What address lookup tables copy an address exten-
2449+ # sion from the lookup key to the lookup result.
2450+ #
2451+ # owner_request_special (yes)
2452+ # Enable special treatment for owner-listname entries
2453+ # in the aliases(5) file, and don't split owner-list-
2454+ # name and listname-request address localparts when
2455+ # the recipient_delimiter is set to "-".
2456+ #
2457+ # recipient_delimiter (empty)
2458+ # The set of characters that can separate an email
2459+ # address localpart, user name, or a .forward file
2460+ # name from its extension.
2461+ #
2462+ # Available in Postfix version 2.3 and later:
2463+ #
2464+ # frozen_delivered_to (yes)
2465+ # Update the local(8) delivery agent's idea of the
2466+ # Delivered-To: address (see prepend_deliv-
2467+ # ered_header) only once, at the start of a delivery
2468+ # attempt; do not update the Delivered-To: address
2469+ # while expanding aliases or .forward files.
2470+ #
2471+ # STANDARDS
2472+ # RFC 822 (ARPA Internet Text Messages)
2473+ #
2474+ # SEE ALSO
2475+ # local(8), local delivery agent
2476+ # newaliases(1), create/update alias database
2477+ # postalias(1), create/update alias database
2478+ # postconf(5), configuration parameters
2479+ #
2480+ # README FILES
2481+ # Use "postconf readme_directory" or "postconf html_direc-
2482+ # tory" to locate this information.
2483+ # DATABASE_README, Postfix lookup table overview
2484+ #
2485+ # LICENSE
2486+ # The Secure Mailer license must be distributed with this
2487+ # software.
2488+ #
2489+ # AUTHOR(S)
2490+ # Wietse Venema
2491+ # IBM T.J. Watson Research
2492+ # P.O. Box 704
2493+ # Yorktown Heights, NY 10598, USA
2494+ #
2495+ # Wietse Venema
2496+ # Google, Inc.
2497+ # 111 8th Avenue
2498+ # New York, NY 10011, USA
2499+ #
2500+ #
2501 diff --git a/containers/multiuser/postfix/master.cf b/containers/multiuser/postfix/master.cf
2502new file mode 100644
2503index 0000000..fd282dd
2504--- /dev/null
2505+++ b/containers/multiuser/postfix/master.cf
2506 @@ -0,0 +1,147 @@
2507+ #
2508+ # Postfix master process configuration file. For details on the format
2509+ # of the file, see the master(5) manual page (command: "man 5 master" or
2510+ # on-line: http://www.postfix.org/master.5.html).
2511+ #
2512+ # Do not forget to execute "postfix reload" after editing this file.
2513+ #
2514+ # ==========================================================================
2515+ # service type private unpriv chroot wakeup maxproc command + args
2516+ # (yes) (yes) (no) (never) (100)
2517+ # ==========================================================================
2518+ smtp inet n - n - - smtpd
2519+ #smtp inet n - n - 1 postscreen
2520+ #smtpd pass - - n - - smtpd
2521+ #dnsblog unix - - n - 0 dnsblog
2522+ #tlsproxy unix - - n - 0 tlsproxy
2523+ # Choose one: enable submission for loopback clients only, or for any client.
2524+ #127.0.0.1:submission inet n - n - - smtpd
2525+ #submission inet n - n - - smtpd
2526+ # -o syslog_name=postfix/submission
2527+ # -o smtpd_tls_security_level=encrypt
2528+ # -o smtpd_sasl_auth_enable=yes
2529+ # -o smtpd_tls_auth_only=yes
2530+ # -o local_header_rewrite_clients=static:all
2531+ # -o smtpd_reject_unlisted_recipient=no
2532+ # Instead of specifying complex smtpd_<xxx>_restrictions here,
2533+ # specify "smtpd_<xxx>_restrictions=$mua_<xxx>_restrictions"
2534+ # here, and specify mua_<xxx>_restrictions in main.cf (where
2535+ # "<xxx>" is "client", "helo", "sender", "relay", or "recipient").
2536+ # -o smtpd_client_restrictions=
2537+ # -o smtpd_helo_restrictions=
2538+ # -o smtpd_sender_restrictions=
2539+ # -o smtpd_relay_restrictions=
2540+ # -o smtpd_recipient_restrictions=permit_sasl_authenticated,reject
2541+ # -o milter_macro_daemon_name=ORIGINATING
2542+ # Choose one: enable submissions for loopback clients only, or for any client.
2543+ #127.0.0.1:submissions inet n - n - - smtpd
2544+ #submissions inet n - n - - smtpd
2545+ # -o syslog_name=postfix/submissions
2546+ # -o smtpd_tls_wrappermode=yes
2547+ # -o smtpd_sasl_auth_enable=yes
2548+ # -o local_header_rewrite_clients=static:all
2549+ # -o smtpd_reject_unlisted_recipient=no
2550+ # Instead of specifying complex smtpd_<xxx>_restrictions here,
2551+ # specify "smtpd_<xxx>_restrictions=$mua_<xxx>_restrictions"
2552+ # here, and specify mua_<xxx>_restrictions in main.cf (where
2553+ # "<xxx>" is "client", "helo", "sender", "relay", or "recipient").
2554+ # -o smtpd_client_restrictions=
2555+ # -o smtpd_helo_restrictions=
2556+ # -o smtpd_sender_restrictions=
2557+ # -o smtpd_relay_restrictions=
2558+ # -o smtpd_recipient_restrictions=permit_sasl_authenticated,reject
2559+ # -o milter_macro_daemon_name=ORIGINATING
2560+ #628 inet n - n - - qmqpd
2561+ pickup unix n - n 60 1 pickup
2562+ cleanup unix n - n - 0 cleanup
2563+ qmgr unix n - n 300 1 qmgr
2564+ #qmgr unix n - n 300 1 oqmgr
2565+ tlsmgr unix - - n 1000? 1 tlsmgr
2566+ rewrite unix - - n - - trivial-rewrite
2567+ bounce unix - - n - 0 bounce
2568+ defer unix - - n - 0 bounce
2569+ trace unix - - n - 0 bounce
2570+ verify unix - - n - 1 verify
2571+ flush unix n - n 1000? 0 flush
2572+ proxymap unix - - n - - proxymap
2573+ proxywrite unix - - n - 1 proxymap
2574+ smtp unix - - n - - smtp
2575+ relay unix - - n - - smtp
2576+ -o syslog_name=postfix/$service_name
2577+ # -o smtp_helo_timeout=5 -o smtp_connect_timeout=5
2578+ showq unix n - n - - showq
2579+ error unix - - n - - error
2580+ retry unix - - n - - error
2581+ discard unix - - n - - discard
2582+ local unix - n n - - local
2583+ virtual unix - n n - - virtual
2584+ lmtp unix - - n - - lmtp
2585+ anvil unix - - n - 1 anvil
2586+ scache unix - - n - 1 scache
2587+ postlog unix-dgram n - n - 1 postlogd
2588+ #
2589+ # ====================================================================
2590+ # Interfaces to non-Postfix software. Be sure to examine the manual
2591+ # pages of the non-Postfix software to find out what options it wants.
2592+ #
2593+ # Many of the following services use the Postfix pipe(8) delivery
2594+ # agent. See the pipe(8) man page for information about ${recipient}
2595+ # and other message envelope options.
2596+ # ====================================================================
2597+ #
2598+ # maildrop. See the Postfix MAILDROP_README file for details.
2599+ # Also specify in main.cf: maildrop_destination_recipient_limit=1
2600+ #
2601+ #maildrop unix - n n - - pipe
2602+ # flags=DRXhu user=vmail argv=/usr/local/bin/maildrop -d ${recipient}
2603+ #
2604+ # ====================================================================
2605+ #
2606+ # Recent Cyrus versions can use the existing "lmtp" master.cf entry.
2607+ #
2608+ # Specify in cyrus.conf:
2609+ # lmtp cmd="lmtpd -a" listen="localhost:lmtp" proto=tcp4
2610+ #
2611+ # Specify in main.cf one or more of the following:
2612+ # mailbox_transport = lmtp:inet:localhost
2613+ # virtual_transport = lmtp:inet:localhost
2614+ #
2615+ # ====================================================================
2616+ #
2617+ # Cyrus 2.1.5 (Amos Gouaux)
2618+ # Also specify in main.cf: cyrus_destination_recipient_limit=1
2619+ #
2620+ #cyrus unix - n n - - pipe
2621+ # flags=DRX user=cyrus argv=/cyrus/bin/deliver -e -r ${sender} -m ${extension} ${user}
2622+ #
2623+ # ====================================================================
2624+ #
2625+ # Old example of delivery via Cyrus.
2626+ #
2627+ #old-cyrus unix - n n - - pipe
2628+ # flags=R user=cyrus argv=/cyrus/bin/deliver -e -m ${extension} ${user}
2629+ #
2630+ # ====================================================================
2631+ #
2632+ # See the Postfix UUCP_README file for configuration details.
2633+ #
2634+ #uucp unix - n n - - pipe
2635+ # flags=Fqhu user=uucp argv=uux -r -n -z -a$sender - $nexthop!rmail ($recipient)
2636+ #
2637+ # ====================================================================
2638+ #
2639+ # Other external delivery methods.
2640+ #
2641+ #ifmail unix - n n - - pipe
2642+ # flags=F user=ftn argv=/usr/lib/ifmail/ifmail -r $nexthop ($recipient)
2643+ #
2644+ #bsmtp unix - n n - - pipe
2645+ # flags=Fq. user=bsmtp argv=/usr/local/sbin/bsmtp -f $sender $nexthop $recipient
2646+ #
2647+ #scalemail-backend unix - n n - 2 pipe
2648+ # flags=R user=scalemail argv=/usr/lib/scalemail/bin/scalemail-store
2649+ # ${nexthop} ${user} ${extension}
2650+ #
2651+ #mailman unix - n n - - pipe
2652+ # flags=FRX user=list argv=/usr/lib/mailman/bin/postfix-to-mailman.py
2653+ # ${nexthop} ${user}
2654 diff --git a/containers/multiuser/postfix/smtp_header_checks b/containers/multiuser/postfix/smtp_header_checks
2655new file mode 100644
2656index 0000000..9e39150
2657--- /dev/null
2658+++ b/containers/multiuser/postfix/smtp_header_checks
2659 @@ -0,0 +1,2 @@
2660+ /^Received: .*/ IGNORE
2661+ /^User-Agent: .*/ IGNORE
2662 diff --git a/containers/multiuser/run_all.sh b/containers/multiuser/run_all.sh
2663index 8a37a44..15491b0 100755
2664--- a/containers/multiuser/run_all.sh
2665+++ b/containers/multiuser/run_all.sh
2666 @@ -4,6 +4,7 @@
2667 # to the ~/.ssh/authorized_keys file of the Ayllu user.
2668
2669 AYLLU_HOME="/home/ayllu"
2670+ AYLLU_CONFIG="/etc/ayllu/config.toml"
2671 AYLLU_SSH_AUTHORIZED_KEYS_FILE="$AYLLU_HOME/.ssh/authorized_keys"
2672
2673 mkdir -p /var/lib/ayllu
2674 @@ -38,7 +39,7 @@ do
2675
2676 addgroup "$username" ayllu
2677 mkdir -p "/home/$username/.ssh"
2678- echo /dev/null > "/home/$username/.ssh/authorized_keys"
2679+ cat /dev/null > "/home/$username/.ssh/authorized_keys"
2680
2681 env_key=$(echo "$env_entry" | awk -F= '{print $1}' | sed "r/")
2682 replacement=$(printf "s/%s=//" "$env_key")
2683 @@ -57,6 +58,15 @@ do
2684
2685 done
2686
2687+ # TODO: Ayllu should have a programmatic way to set configuration values
2688+ # at runtime e.g. ayllu config set http.address = ....
2689+ [ -n "${AYLLU_LISTEN_ADDRESS}" ] && {
2690+ sed -i "s/127.0.0.1:8080/$AYLLU_LISTEN_ADDRESS/" /etc/ayllu/config.toml
2691+ }
2692+
2693+ # ensure ayllu mail database exists even if it isn't configured yet
2694+ su ayllu -c "ayllu-mail --config $AYLLU_CONFIG send"
2695+
2696 /sbin/runsvdir /etc/service &
2697
2698 RUNIT_PID="$!"
2699 diff --git a/containers/multiuser/service/ayllu-mail/run b/containers/multiuser/service/ayllu-mail/run
2700new file mode 100755
2701index 0000000..8863caa
2702--- /dev/null
2703+++ b/containers/multiuser/service/ayllu-mail/run
2704 @@ -0,0 +1,3 @@
2705+ #!/bin/sh
2706+
2707+ exec su ayllu -c 'ayllu-mail --config /etc/ayllu/config.toml serve'
2708 diff --git a/containers/multiuser/service/crond/run b/containers/multiuser/service/crond/run
2709new file mode 100755
2710index 0000000..b0501c7
2711--- /dev/null
2712+++ b/containers/multiuser/service/crond/run
2713 @@ -0,0 +1,3 @@
2714+ #!/bin/sh
2715+
2716+ exec crond -f -s
2717 diff --git a/containers/multiuser/service/postfix/run b/containers/multiuser/service/postfix/run
2718new file mode 100755
2719index 0000000..51ead59
2720--- /dev/null
2721+++ b/containers/multiuser/service/postfix/run
2722 @@ -0,0 +1,62 @@
2723+ #!/bin/sh
2724+ set -e
2725+
2726+ AYLLU_MAIL="/usr/bin/ayllu-mail"
2727+ AYLLU_CONFIG="/etc/ayllu/config.toml"
2728+ AYLLU_DB_PATH="/home/ayllu/.local/share/ayllu/mail.db"
2729+
2730+ # FIXME: Mailpot's master-cf generation seems to be broken but it may also be
2731+ # due to my own ignorance so manually specifying it for now.
2732+
2733+ # "$AYLLU_MAIL" --config "$AYLLU_CONFIG" --database "$AYLLU_DB_PATH" postfix master-cf > /etc/postfix/ayllu.cf
2734+ gen_master_cf() {
2735+ printf "mailpot unix - n n - 1 pipe flags=RX user=ayllu directory=/home/ayllu argv=/usr/bin/ayllu-mail --database $AYLLU_DB_PATH --config $AYLLU_CONFIG post"
2736+ }
2737+
2738+ # FIXME
2739+ AYLLU_SMTP_TLS_SECURITY_LEVEL="${AYLLU_SMTP_TLS_SECURITY_LEVEL:-none}"
2740+
2741+ [ -n "${AYLLU_ROOT_MAIL_USER+x}" ] && {
2742+ echo "# AYLLU: DO NOT EDIT" > /etc/postfix/aliases
2743+ AYLLU_ROOT_MAIL_USER="$(echo "$AYLLU_ROOT_MAIL_USER" | tr '[:upper:]' '[:lower:]')"
2744+ AYLLU_ROOT_MAIL_USER="$AYLLU_ROOT_MAIL_USER" envsubst < /etc/postfix-templates/aliases >> /etc/postfix/aliases
2745+ newaliases
2746+ }
2747+
2748+ [ -n "${AYLLU_VIRTUAL_DOMAINS+x}" ] && {
2749+ echo "# AYLLU: DO NOT EDIT" > /etc/postfix/transport
2750+ echo "$AYLLU_VIRTUAL_DOMAINS" | sed 's/::/\n/g' | while IFS= read -r virtual_domain
2751+ do
2752+ echo "configuring virtual domain: ${virtual_domain}"
2753+ echo "$virtual_domain" >> /etc/postfix/transport
2754+ done
2755+
2756+ postmap /etc/postfix/transport
2757+ postconf virtual_alias_maps="lmdb:/etc/postfix/virtual"
2758+ }
2759+
2760+ # hide sender's IP address / User Agent
2761+ # See https://wiki.archlinux.org/title/Postfix#Hide_the_sender's_IP_and_user_agent_in_the_Received_header
2762+ cp /etc/postfix-templates/smtp_header_checks /etc/postfix/
2763+ postconf -e smtp_header_checks="regexp:/etc/postfix/smtp_header_checks"
2764+ postconf -e smtpd_helo_required=yes
2765+
2766+ postconf -e smtp_tls_security_level="$AYLLU_SMTP_TLS_SECURITY_LEVEL"
2767+ postconf -e maillog_file="/dev/stdout"
2768+
2769+ AYLLU_MAIL_HOSTNAME="${AYLLU_MAIL_HOSTNAME:-localhost}"
2770+ postconf -e myhostname="${AYLLU_MAIL_HOSTNAME}"
2771+
2772+ # disallow relay from anywhere but localhost
2773+ postconf -e inet_interfaces="loopback-only"
2774+ postconf -e mynetworks="127.0.0.0/8"
2775+ postconf -e local_transport="local"
2776+ postconf -e transport_maps="lmdb:/etc/postfix/transport"
2777+
2778+ cat /etc/postfix-templates/master.cf > /etc/postfix/master.cf
2779+ gen_master_cf >> /etc/postfix/master.cf
2780+
2781+ "$AYLLU_MAIL" --config "$AYLLU_CONFIG" --database "$AYLLU_DB_PATH" postfix maps > /etc/postfix/transport
2782+ postmap /etc/postfix/transport
2783+
2784+ exec postfix -c /etc/postfix start-fg
2785 diff --git a/contrib/nginx/config.d/general.conf b/contrib/nginx/config.d/general.conf
2786new file mode 100644
2787index 0000000..839ac4f
2788--- /dev/null
2789+++ b/contrib/nginx/config.d/general.conf
2790 @@ -0,0 +1,11 @@
2791+ # favicon.ico
2792+ location = /favicon.ico {
2793+ log_not_found off;
2794+ }
2795+
2796+ # gzip
2797+ gzip on;
2798+ gzip_vary on;
2799+ gzip_proxied any;
2800+ gzip_comp_level 6;
2801+ gzip_types text/plain text/css text/xml application/json application/javascript application/rss+xml application/atom+xml image/svg+xml;
2802 diff --git a/contrib/nginx/config.d/proxy.conf b/contrib/nginx/config.d/proxy.conf
2803new file mode 100644
2804index 0000000..34d559d
2805--- /dev/null
2806+++ b/contrib/nginx/config.d/proxy.conf
2807 @@ -0,0 +1,22 @@
2808+ proxy_http_version 1.1;
2809+ proxy_cache_bypass $http_upgrade;
2810+
2811+ # Proxy SSL
2812+ proxy_ssl_server_name on;
2813+
2814+ # Proxy headers
2815+ proxy_set_header Upgrade $http_upgrade;
2816+ # proxy_set_header Connection $connection_upgrade;
2817+ proxy_set_header X-Real-IP $remote_addr;
2818+ # proxy_set_header Forwarded $proxy_add_forwarded;
2819+ proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
2820+ proxy_set_header X-Forwarded-Proto $scheme;
2821+ proxy_set_header X-Forwarded-Host $host;
2822+ proxy_set_header X-Forwarded-Port $server_port;
2823+
2824+ proxy_max_temp_file_size 0;
2825+
2826+ # Proxy timeouts
2827+ proxy_connect_timeout 60s;
2828+ proxy_send_timeout 60s;
2829+ proxy_read_timeout 60s;
2830 diff --git a/contrib/nginx/config.d/security.conf b/contrib/nginx/config.d/security.conf
2831new file mode 100644
2832index 0000000..f134549
2833--- /dev/null
2834+++ b/contrib/nginx/config.d/security.conf
2835 @@ -0,0 +1,7 @@
2836+ # security headers
2837+ add_header X-XSS-Protection "1; mode=block" always;
2838+ add_header X-Content-Type-Options "nosniff" always;
2839+ add_header Referrer-Policy "no-referrer-when-downgrade" always;
2840+ add_header Content-Security-Policy "default-src 'self' http: https: ws: wss: data: blob: 'unsafe-inline'; frame-ancestors 'self';" always;
2841+ add_header Permissions-Policy "interest-cohort=()" always;
2842+ add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
2843 diff --git a/contrib/nginx/nginx.conf b/contrib/nginx/nginx.conf
2844index 09dc523..f38d5dd 100644
2845--- a/contrib/nginx/nginx.conf
2846+++ b/contrib/nginx/nginx.conf
2847 @@ -1,38 +1,102 @@
2848+ # This is an example NGINX config file that attempts to illistrate the
2849+ # relevant bits specific to Ayllu but also work locally. You'll need to adopt
2850+ # this specific to your individual needs.
2851+ #
2852 daemon off;
2853- worker_processes auto;
2854+ worker_processes auto;
2855 error_log stderr;
2856 pid /tmp/nginx.pid;
2857
2858-
2859- events {
2860- worker_connections 1024;
2861- }
2862-
2863+ events {}
2864
2865 http {
2866- include /etc/nginx/mime.types;
2867- default_type application/octet-stream;
2868
2869- log_format main '$remote_addr - $remote_user [$time_local] "$request" '
2870- '$status $body_bytes_sent "$http_referer" '
2871- '"$http_user_agent" "$http_x_forwarded_for"';
2872+ log_format main '$remote_addr - $remote_user [$time_local] "$request" '
2873+ '$status $body_bytes_sent "$http_referer" '
2874+ '"$http_user_agent" "$http_x_forwarded_for"';
2875
2876 access_log /dev/stdout main;
2877+ proxy_headers_hash_max_size 1024;
2878+ proxy_headers_hash_bucket_size 128;
2879+
2880+
2881+ server {
2882+ listen 8081;
2883+
2884+ include config.d/security.conf;
2885+ include config.d/general.conf;
2886+
2887+
2888+ location /projects/ayllu {
2889+ return 301 $scheme://$host/ayllu/ayllu;
2890+ }
2891+
2892+ location / {
2893+ include config.d/proxy.conf;
2894+ proxy_pass http://127.0.0.1:8080;
2895+ # proxy_set_header Host $host;
2896+ proxy_set_header X-Real-IP $remote_addr;
2897+ proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
2898+ }
2899+
2900+ location /lfs {
2901+
2902+ # it is important to prevent unauthenticated access to the
2903+ # rudolfs server except for GET.
2904+ limit_except GET {
2905+ # include config.d/basicauth.conf;
2906+ }
2907+
2908+ client_max_body_size 128M;
2909+ client_body_buffer_size 8M;
2910
2911- sendfile on;
2912- # tcp_nopush on;
2913- # keepalive_timeout 0;
2914- keepalive_timeout 65;
2915- # gzip on;
2916+ # the /api endpoint is hard coded into rudolfs so we rewrite this
2917+ # to a more reasonable /lfs as not to conflict with other Ayllu
2918+ # endpoints.
2919
2920+ rewrite ^/lfs/(.*)$ /api/$1 break;
2921+ proxy_pass http://127.0.0.1:7000/api/;
2922+ proxy_set_header Host $host;
2923+ proxy_set_header X-Real-IP $remote_addr;
2924+ proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
2925+ }
2926+
2927+ # private repositories can by set as hidden in your ayllu configuraiton
2928+ # and then optionally you can add a basic auth restriction such as
2929+ # below.
2930+
2931+ location ~ /private* {
2932+ proxy_pass http://127.0.0.1:8081;
2933+ proxy_set_header Host $host;
2934+ # include config.d/basicauth.conf;
2935+ }
2936+
2937+ }
2938+
2939+
2940+ # an additional virtual host which will pass the Host: my-custom-domain.local
2941+ # and serve static content when requested.
2942 server {
2943- listen 127.0.0.1:9000;
2944+
2945+ listen 8081;
2946+ server_name my-custom-domain.local;
2947+
2948+ include config.d/security.conf;
2949+ include config.d/general.conf;
2950+
2951+
2952+ location /projects/ayllu {
2953+ return 301 $scheme://$host/ayllu/ayllu;
2954+ }
2955+
2956 location / {
2957- rewrite ^(/.*)$ /_/_/sites$1 break;
2958- proxy_pass http://127.0.0.1:8080/_/_/sites;
2959+ include config.d/proxy.conf;
2960+ proxy_pass http://127.0.0.1:8080;
2961+ # Ayllu's static hosting can match any header to determine if it
2962+ # should serve static contents from within a repository or
2963 proxy_set_header Host $host;
2964- proxy_pass_request_headers on;
2965- proxy_http_version 1.1;
2966+ proxy_set_header X-Real-IP $remote_addr;
2967+ proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
2968 }
2969 }
2970 }
2971 diff --git a/contrib/systemd-podman/ayllu.container b/contrib/systemd-podman/ayllu.container
2972index f23e09c..df7e211 100644
2973--- a/contrib/systemd-podman/ayllu.container
2974+++ b/contrib/systemd-podman/ayllu.container
2975 @@ -7,7 +7,7 @@ After=network-online.target nss-lookup.target
2976
2977 [Container]
2978 ContainerName=ayllu
2979- Image=registry.ayllu-forge.org/projects/ayllu:main
2980+ Image=registry.ayllu-forge.org/ayllu/ayllu:main
2981
2982 # Enable auto-update container
2983 # AutoUpdate=registry
2984 diff --git a/crates/api/src/mail.rs b/crates/api/src/mail.rs
2985index 963822c..030d98a 100644
2986--- a/crates/api/src/mail.rs
2987+++ b/crates/api/src/mail.rs
2988 @@ -12,6 +12,7 @@ pub struct Message {
2989 pub timestamp: u64,
2990 pub from: String,
2991 pub address: String,
2992+ pub subject: Option<String>,
2993 /// entire email including headers
2994 pub body: String,
2995 /// text of just the email message
2996 diff --git a/package.json b/package.json
2997index b89e131..302bd32 100644
2998--- a/package.json
2999+++ b/package.json
3000 @@ -4,7 +4,7 @@
3001 "main": "index.js",
3002 "repository": {
3003 "type": "git",
3004- "url": "https://ayllu-forge.org/projects/ayllu"
3005+ "url": "https://ayllu-forge.org/ayllu/ayllu"
3006 },
3007 "author": "",
3008 "license": "AGPL-3.0",
3009 diff --git a/scripts/build_all_containers.sh b/scripts/build_all_containers.sh
3010new file mode 100755
3011index 0000000..296a506
3012--- /dev/null
3013+++ b/scripts/build_all_containers.sh
3014 @@ -0,0 +1,4 @@
3015+ #!/bin/sh
3016+
3017+ scripts/build_container.sh containers/base
3018+ scripts/build_container.sh containers/multiuser
3019 diff --git a/scripts/build_and_deploy.sh b/scripts/build_and_deploy.sh
3020index 23f0921..51b33ee 100755
3021--- a/scripts/build_and_deploy.sh
3022+++ b/scripts/build_and_deploy.sh
3023 @@ -1,10 +1,7 @@
3024 #!/bin/sh
3025 # Internal helper script to build and deploy Ayllu in one command
3026
3027- scripts/build_container.sh containers/base && \
3028- scripts/push_container.sh containers/base
3029- scripts/build_container.sh containers/multiuser && \
3030- scripts/push_container.sh containers/multiuser
3031+ scripts/build_all_containers.sh
3032
3033 /usr/lib/podman/quadlet --user "$HOME/.config/systemd/user"
3034 systemctl --user daemon-reload
3035 diff --git a/scripts/build_container.sh b/scripts/build_container.sh
3036index 5ba4ed9..f96dd13 100755
3037--- a/scripts/build_container.sh
3038+++ b/scripts/build_container.sh
3039 @@ -2,7 +2,7 @@
3040 set -e
3041
3042 REGISTRY="registry.ayllu-forge.org"
3043- IMAGE_NAME="projects/ayllu"
3044+ IMAGE_NAME="ayllu/ayllu"
3045 COMMIT_ID="$(git rev-parse HEAD)"
3046 BRANCH_NAME="$(git branch --show-current)"
3047
3048 diff --git a/scripts/check_build_dependencies.sh b/scripts/check_build_dependencies.sh
3049index e6c40da..3f3bc43 100755
3050--- a/scripts/check_build_dependencies.sh
3051+++ b/scripts/check_build_dependencies.sh
3052 @@ -1,16 +1,22 @@
3053 #!/bin/sh
3054 # quick script to verify the things needed to build ayllu from scratch
3055- # are present on your system.
3056+ # are present on your system. Some programs included here are optional and only
3057+ # used to enhance the development or run certain classes of tests.
3058
3059 FAILED="false"
3060
3061+
3062+ RED="$(tput setaf 1)"
3063+ GREEN="$(tput setaf 2)"
3064+ RESET="$(tput sgr0)"
3065+
3066 check_cmd() {
3067 command -v "$1" 1>/dev/null || {
3068- echo "$1 not installed"
3069+ echo "$1 ${RED}FAILED${RESET}"
3070 FAILED="true"
3071 return
3072 }
3073- echo "package $1 OK"
3074+ echo "package $1 ${GREEN}OK${RESET}"
3075 }
3076
3077 check_library() {
3078 @@ -25,18 +31,18 @@ check_library() {
3079 check_cmd "cargo"
3080 check_cmd "cargo-watch"
3081 check_cmd "cc"
3082+ check_cmd "djlint" # format / lint jinja html templates
3083 check_cmd "fc-list" # fontconfig
3084+ check_cmd "npm"
3085 check_cmd "pkg-config"
3086 check_cmd "sassc"
3087+ check_cmd "shellcheck"
3088 check_cmd "sqlx"
3089 check_cmd "sqlite3"
3090- check_cmd "npm"
3091- check_cmd "djlint" # format / lint jinja html templates
3092
3093 # check for openssl which annoyingly is required to build sqlx-cli if that is
3094 # being installed from a non-distribution package i.e. cargo install
3095 check_library "openssl"
3096-
3097 check_library "fontconfig"
3098
3099 [ "$FAILED" = "true" ] && exit 1
3100 diff --git a/scripts/push_container.sh b/scripts/push_container.sh
3101index 45a3b52..c8be56d 100755
3102--- a/scripts/push_container.sh
3103+++ b/scripts/push_container.sh
3104 @@ -3,7 +3,7 @@ set -e
3105
3106 REGISTRY="registry.ayllu-forge.org"
3107 REGISTRY_AUTH="registry-auth.ayllu-forge.org"
3108- IMAGE_NAME="projects/ayllu"
3109+ IMAGE_NAME="ayllu/ayllu"
3110 COMMIT_ID="$(git rev-parse HEAD)"
3111 BRANCH_NAME="$(git branch --show-current)"
3112
3113 diff --git a/scripts/shellcheck.sh b/scripts/shellcheck.sh
3114new file mode 100755
3115index 0000000..6a5a35f
3116--- /dev/null
3117+++ b/scripts/shellcheck.sh
3118 @@ -0,0 +1,5 @@
3119+ #!/bin/sh
3120+
3121+ find scripts -type f -name '*.sh' -exec shellcheck {} \;
3122+ find containers -type f \
3123+ \( -name '*.sh' -o -name 'run' -o -name 'finish' \) -exec shellcheck {} \;
3124 diff --git a/www/content/docs/configuration.md b/www/content/docs/configuration.md
3125index 8d52fe3..d6811fd 100644
3126--- a/www/content/docs/configuration.md
3127+++ b/www/content/docs/configuration.md
3128 @@ -99,8 +99,8 @@ By default Ayllu will attempt to load parsers and query files from those directo
3129 ### Example Custom Configuration
3130
3131 <div class="info">
3132- NOTE: Refer to <a href=https://ayllu-forge.org/projects/ayllu/blob/main/config.example.toml>config.sample.toml</a> for the latest
3133- example configuration or <a href="https://ayllu-forge.org/projects/ayllu/blob/main/src/config.rs">config.rs</a>.
3134+ NOTE: Refer to <a href=https://ayllu-forge.org/ayllu/ayllu/blob/main/config.example.toml>config.sample.toml</a> for the latest
3135+ example configuration or <a href="https://ayllu-forge.org/ayllu/ayllu/blob/main/src/config.rs">config.rs</a>.
3136 </div>
3137
3138
3139 diff --git a/www/content/docs/installation.md b/www/content/docs/installation.md
3140index 5d8824f..455803e 100644
3141--- a/www/content/docs/installation.md
3142+++ b/www/content/docs/installation.md
3143 @@ -33,8 +33,8 @@ First create configuration and data paths that we will map into the container.
3144 Next pull the latest version of the container and generate a new configuration
3145 file.
3146
3147- podman pull registry.ayllu-forge.org/projects/ayllu:main
3148- podman run --rm -ti registry.ayllu-forge.org/projects/ayllu:main ayllu config generate > ~/.config/ayllu-podman/config.toml
3149+ podman pull registry.ayllu-forge.org/ayllu/ayllu:main
3150+ podman run --rm -ti registry.ayllu-forge.org/ayllu/ayllu:main ayllu config generate > ~/.config/ayllu-podman/config.toml
3151
3152 The next step is to map "collections" (directories that contain git repositories)
3153 into the Ayllu container. On my system I keep active projects I'm working on
3154 @@ -78,7 +78,7 @@ Finally we can launch the actual container.
3155 -v ~/repos:/repos \
3156 -v ~/.config/ayllu-podman:/root/.config/ayllu \
3157 -v ~/.local/share/ayllu-podman:/root/.local/share/ayllu \
3158- registry.ayllu-forge.org/projects/ayllu:main \
3159+ registry.ayllu-forge.org/ayllu/ayllu:main \
3160 ayllu serve
3161
3162 You should now be able to browse to the user interface at
3163 @@ -99,7 +99,7 @@ Description=Ayllu Container
3164
3165 [Container]
3166 ContainerName=ayllu
3167- Image=registry.ayllu-forge.org/projects/ayllu:main
3168+ Image=registry.ayllu-forge.org/ayllu/ayllu:main
3169 Network=host
3170 User=root
3171 Volume=%h/repos:/repos:rw
3172 diff --git a/www/content/docs/proxy.md b/www/content/docs/proxy.md
3173new file mode 100644
3174index 0000000..5b9a566
3175--- /dev/null
3176+++ b/www/content/docs/proxy.md
3177 @@ -0,0 +1,17 @@
3178+ +++
3179+ title = "Reverse Proxy"
3180+ weight = 1
3181+ +++
3182+
3183+ # Proxy Configuration
3184+
3185+ Ayllu is designed to be run behind a reverse proxy server and it is not
3186+ recommended to deploy Ayllu directly to the internet. Things such as TLS
3187+ termination or basic authentication for private repositories are not directly
3188+ supported by Ayllu although some of these features may be added in the future.
3189+
3190+ ## Nginx
3191+
3192+ [nginx](https://nginx.org/) is the only supported software but others are
3193+ likely to work and we will happily accept documentation for operating other
3194+ free software proxy servers.
3195 diff --git a/www/public/docs/architecture/index.html b/www/public/docs/architecture/index.html
3196index 02dfd41..36a4f23 100644
3197--- a/www/public/docs/architecture/index.html
3198+++ b/www/public/docs/architecture/index.html
3199 @@ -1 +1 @@
3200- <!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><li><a href=https://ayllu-forge.org/docs/themes/>Themes</a></ul></div><div class=doc-content><h1 id=the-architecture-of-ayllu><a aria-label="Anchor link for: the-architecture-of-ayllu" class=zola-anchor href=#the-architecture-of-ayllu>The Architecture of Ayllu</a></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>
3201\ No newline at end of file
3202+ <!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><li><a href=https://ayllu-forge.org/docs/proxy/>Reverse Proxy</a><li><a href=https://ayllu-forge.org/docs/themes/>Themes</a></ul></div><div class=doc-content><h1 id=the-architecture-of-ayllu><a aria-label="Anchor link for: the-architecture-of-ayllu" class=zola-anchor href=#the-architecture-of-ayllu>The Architecture of Ayllu</a></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=/ayllu/ayllu/blob/main/ATTRIBUTIONS.md>attributions</a></footer></div>
3203\ No newline at end of file
3204 diff --git a/www/public/docs/builds/index.html b/www/public/docs/builds/index.html
3205index fe9cfe9..f3f264c 100644
3206--- a/www/public/docs/builds/index.html
3207+++ b/www/public/docs/builds/index.html
3208 @@ -1,4 +1,4 @@
3209- <!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><a href=https://ayllu-forge.org/docs/architecture/>Architecture</a><li class=active><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><li><a href=https://ayllu-forge.org/docs/themes/>Themes</a></ul></div><div class=doc-content><p>Ayllu contains a plugin called <code>ayllu-build</code> which is responsible for executing build manifests in a CI/CD environment. The design of the build system prioritizes the ability to run the entire workflow locally.<h3 id=an-example-local-workflow><a aria-label="Anchor link for: an-example-local-workflow" class=zola-anchor href=#an-example-local-workflow>An Example Local Workflow</a></h3><p>Build manifests are written in <a href=https://nickel-lang.org/>Nickel</a>.<pre class=language-sh data-lang=sh><code class=language-sh data-lang=sh>mkdir -p my-test-repo/.ayllu/build
3210+ <!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><a href=https://ayllu-forge.org/docs/architecture/>Architecture</a><li class=active><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><li><a href=https://ayllu-forge.org/docs/proxy/>Reverse Proxy</a><li><a href=https://ayllu-forge.org/docs/themes/>Themes</a></ul></div><div class=doc-content><p>Ayllu contains a plugin called <code>ayllu-build</code> which is responsible for executing build manifests in a CI/CD environment. The design of the build system prioritizes the ability to run the entire workflow locally.<h3 id=an-example-local-workflow><a aria-label="Anchor link for: an-example-local-workflow" class=zola-anchor href=#an-example-local-workflow>An Example Local Workflow</a></h3><p>Build manifests are written in <a href=https://nickel-lang.org/>Nickel</a>.<pre class=language-sh data-lang=sh><code class=language-sh data-lang=sh>mkdir -p my-test-repo/.ayllu/build
3211 cd my-test-repo && git init
3212 # create a simple workflow that prints "Hello" and "World"
3213 cat <&LTEOF > .ayllu/build/main.ncl
3214 @@ -34,4 +34,4 @@ ayllu_build plan
3215 ayllu_build plan | dot -Tx11
3216 # finally you can execute the workflow locally
3217 ayllu_build evaluate
3218- </code></pre></div></div></main><footer class=page-footer>2024, <a href=/projects/ayllu/blob/main/ATTRIBUTIONS.md>attributions</a></footer></div>
3219\ No newline at end of file
3220+ </code></pre></div></div></main><footer class=page-footer>2024, <a href=/ayllu/ayllu/blob/main/ATTRIBUTIONS.md>attributions</a></footer></div>
3221\ No newline at end of file
3222 diff --git a/www/public/docs/configuration/index.html b/www/public/docs/configuration/index.html
3223index d54754b..1f99c9a 100644
3224--- a/www/public/docs/configuration/index.html
3225+++ b/www/public/docs/configuration/index.html
3226 @@ -1,4 +1,4 @@
3227- <!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><a href=https://ayllu-forge.org/docs/architecture/>Architecture</a><li><a href=https://ayllu-forge.org/docs/builds/>Builds</a><li class=active><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><li><a href=https://ayllu-forge.org/docs/themes/>Themes</a></ul></div><div class=doc-content><h1 id=initial-configuration><a aria-label="Anchor link for: initial-configuration" class=zola-anchor href=#initial-configuration>Initial Configuration</a></h1><p>After following the <a href=https://ayllu-forge.org/docs/installation>installation</a> you need to setup your configuration file and initialize the database.<h2 id=user-based-installation><a aria-label="Anchor link for: user-based-installation" class=zola-anchor href=#user-based-installation>User Based Installation</a></h2><pre class=language-sh data-lang=sh><code class=language-sh data-lang=sh>mkdir ~/.config/ayllu
3228+ <!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><a href=https://ayllu-forge.org/docs/architecture/>Architecture</a><li><a href=https://ayllu-forge.org/docs/builds/>Builds</a><li class=active><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><li><a href=https://ayllu-forge.org/docs/proxy/>Reverse Proxy</a><li><a href=https://ayllu-forge.org/docs/themes/>Themes</a></ul></div><div class=doc-content><h1 id=initial-configuration><a aria-label="Anchor link for: initial-configuration" class=zola-anchor href=#initial-configuration>Initial Configuration</a></h1><p>After following the <a href=https://ayllu-forge.org/docs/installation>installation</a> you need to setup your configuration file and initialize the database.<h2 id=user-based-installation><a aria-label="Anchor link for: user-based-installation" class=zola-anchor href=#user-based-installation>User Based Installation</a></h2><pre class=language-sh data-lang=sh><code class=language-sh data-lang=sh>mkdir ~/.config/ayllu
3229 ayllu config generate > ~/.config/ayllu/config.yaml
3230 # open up config.yaml in your favorite editor and configure at least one
3231 # collection
3232 @@ -40,7 +40,7 @@ ayllu jobs run .
3233 /usr/lib/libtree-sitter-$language.so
3234 # supplemental query files
3235 /usr/share/tree-sitter/queries/$language/[highlights|injections|locals].scm
3236- </code></pre><p>By default Ayllu will attempt to load parsers and query files from those directories.<h3 id=example-custom-configuration><a aria-label="Anchor link for: example-custom-configuration" class=zola-anchor href=#example-custom-configuration>Example Custom Configuration</a></h3><div class=info>NOTE: Refer to <a href=https://ayllu-forge.org/projects/ayllu/blob/main/config.example.toml>config.sample.toml</a> for the latest example configuration or <a href=https://ayllu-forge.org/projects/ayllu/blob/main/src/config.rs>config.rs</a>.</div><pre class=language-toml data-lang=toml><code class=language-toml data-lang=toml>[tree-sitter]
3237+ </code></pre><p>By default Ayllu will attempt to load parsers and query files from those directories.<h3 id=example-custom-configuration><a aria-label="Anchor link for: example-custom-configuration" class=zola-anchor href=#example-custom-configuration>Example Custom Configuration</a></h3><div class=info>NOTE: Refer to <a href=https://ayllu-forge.org/ayllu/ayllu/blob/main/config.example.toml>config.sample.toml</a> for the latest example configuration or <a href=https://ayllu-forge.org/ayllu/ayllu/blob/main/src/config.rs>config.rs</a>.</div><pre class=language-toml data-lang=toml><code class=language-toml data-lang=toml>[tree-sitter]
3238 # specify the base path to search for libtree-sitter-$language.so
3239 base_path = "/usr/lib"
3240 # directory of syntax queries
3241 @@ -79,4 +79,4 @@ diff = """
3242 (location) @attribute
3243 (command) @variable.builtin
3244 """
3245- </code></pre></div></div></main><footer class=page-footer>2024, <a href=/projects/ayllu/blob/main/ATTRIBUTIONS.md>attributions</a></footer></div>
3246\ No newline at end of file
3247+ </code></pre></div></div></main><footer class=page-footer>2024, <a href=/ayllu/ayllu/blob/main/ATTRIBUTIONS.md>attributions</a></footer></div>
3248\ No newline at end of file
3249 diff --git a/www/public/docs/developers/index.html b/www/public/docs/developers/index.html
3250index 9c8d943..67b8045 100644
3251--- a/www/public/docs/developers/index.html
3252+++ b/www/public/docs/developers/index.html
3253 @@ -1,5 +1,5 @@
3254- <!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><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 class=active><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><li><a href=https://ayllu-forge.org/docs/themes/>Themes</a></ul></div><div class=doc-content><h1 id=developing-components><a aria-label="Anchor link for: developing-components" class=zola-anchor href=#developing-components>Developing Components</a></h1><p>Ayllu is split up into several distinct components, to work on one of them you typically want to start one of two feedback cycles: <code>lint->test->compile</code> or <code>compile->serve</code>. To start each one you can can run <code>scripts/test.sh $COMPONENT</code> or <code>scripts/watch.sh $COMPONENT</code> and the appropriate <code>cargo watch</code> command will be started for you.<h1 id=testing-the-entire-ci-workflow><a aria-label="Anchor link for: testing-the-entire-ci-workflow" class=zola-anchor href=#testing-the-entire-ci-workflow>Testing the Entire CI Workflow</a></h1><p>You can run all of the CI tests locally by invoking <code>ayllu-build</code> directly, assuming it is compiled it can be launched like this:<pre class=language-sh data-lang=sh><code class=language-sh data-lang=sh># run all tests in .ayllu
3255+ <!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><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 class=active><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><li><a href=https://ayllu-forge.org/docs/proxy/>Reverse Proxy</a><li><a href=https://ayllu-forge.org/docs/themes/>Themes</a></ul></div><div class=doc-content><h1 id=developing-components><a aria-label="Anchor link for: developing-components" class=zola-anchor href=#developing-components>Developing Components</a></h1><p>Ayllu is split up into several distinct components, to work on one of them you typically want to start one of two feedback cycles: <code>lint->test->compile</code> or <code>compile->serve</code>. To start each one you can can run <code>scripts/test.sh $COMPONENT</code> or <code>scripts/watch.sh $COMPONENT</code> and the appropriate <code>cargo watch</code> command will be started for you.<h1 id=testing-the-entire-ci-workflow><a aria-label="Anchor link for: testing-the-entire-ci-workflow" class=zola-anchor href=#testing-the-entire-ci-workflow>Testing the Entire CI Workflow</a></h1><p>You can run all of the CI tests locally by invoking <code>ayllu-build</code> directly, assuming it is compiled it can be launched like this:<pre class=language-sh data-lang=sh><code class=language-sh data-lang=sh># run all tests in .ayllu
3256 ayllu-build evaluate
3257 </code></pre><h1 id=ayllu-forge-org><a aria-label="Anchor link for: ayllu-forge-org" class=zola-anchor href=#ayllu-forge-org>ayllu-forge.org</a></h1><p>The <a href=https://ayllu-forge.org>Ayllu website</a> and project documentation are built together with <a href=https://www.getzola.org/>Zola</a> which you must install. After which you can startup a local preview of the site with the following commands:<pre class=language-sh data-lang=sh><code class=language-sh data-lang=sh>cd www && zola serve
3258 </code></pre><p>The generated content are hosted directly from Ayllu and you simply need to build and commit the generated code directly into the repository.<pre class=language-sh data-lang=sh><code class=language-sh data-lang=sh>cd www && zola build
3259- </code></pre><h2 id=diagrams><a aria-label="Anchor link for: diagrams" class=zola-anchor href=#diagrams>Diagrams</a></h2><p>Diagrams are built with <a href=https://www.nomnoml.com/>nomnoml</a> and their SVG content contain the source for generating the diagrams. Simply open one of the source SVGs up and copy-paste it to the editor of the nomnoml website.</div></div></main><footer class=page-footer>2024, <a href=/projects/ayllu/blob/main/ATTRIBUTIONS.md>attributions</a></footer></div>
3260\ No newline at end of file
3261+ </code></pre><h2 id=diagrams><a aria-label="Anchor link for: diagrams" class=zola-anchor href=#diagrams>Diagrams</a></h2><p>Diagrams are built with <a href=https://www.nomnoml.com/>nomnoml</a> and their SVG content contain the source for generating the diagrams. Simply open one of the source SVGs up and copy-paste it to the editor of the nomnoml website.</div></div></main><footer class=page-footer>2024, <a href=/ayllu/ayllu/blob/main/ATTRIBUTIONS.md>attributions</a></footer></div>
3262\ No newline at end of file
3263 diff --git a/www/public/docs/faq/index.html b/www/public/docs/faq/index.html
3264index 03d2494..4cef575 100644
3265--- a/www/public/docs/faq/index.html
3266+++ b/www/public/docs/faq/index.html
3267 @@ -1 +1 @@
3268- <!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><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 class=active><a href=https://ayllu-forge.org/docs/faq/>FAQ</a><li><a href=https://ayllu-forge.org/docs/mail/>Mail</a><li><a href=https://ayllu-forge.org/docs/themes/>Themes</a></ul></div><div class=doc-content><h5 id=what-does-ayllu-mean><a aria-label="Anchor link for: what-does-ayllu-mean" class=zola-anchor href=#what-does-ayllu-mean>What Does "Ayllu" Mean?</a></h5><p>The name <a href=https://en.wikipedia.org/wiki/Ayllu>Ayllu</a> <em>/ˈajʎu/</em>, <em>eye-joo</em> is the Quechua word for the traditional form of a community in the Andes region of South America, particularly in Bolivia and Peru.<h5 id=do-you-provide-hosting><a aria-label="Anchor link for: do-you-provide-hosting" class=zola-anchor href=#do-you-provide-hosting>Do You Provide Hosting?</a></h5><p>Not currently. If there was enough interest the project might offer hosted environments as a way to sustain it's development.</div></div></main><footer class=page-footer>2024, <a href=/projects/ayllu/blob/main/ATTRIBUTIONS.md>attributions</a></footer></div>
3269\ No newline at end of file
3270+ <!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><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 class=active><a href=https://ayllu-forge.org/docs/faq/>FAQ</a><li><a href=https://ayllu-forge.org/docs/mail/>Mail</a><li><a href=https://ayllu-forge.org/docs/proxy/>Reverse Proxy</a><li><a href=https://ayllu-forge.org/docs/themes/>Themes</a></ul></div><div class=doc-content><h5 id=what-does-ayllu-mean><a aria-label="Anchor link for: what-does-ayllu-mean" class=zola-anchor href=#what-does-ayllu-mean>What Does "Ayllu" Mean?</a></h5><p>The name <a href=https://en.wikipedia.org/wiki/Ayllu>Ayllu</a> <em>/ˈajʎu/</em>, <em>eye-joo</em> is the Quechua word for the traditional form of a community in the Andes region of South America, particularly in Bolivia and Peru.<h5 id=do-you-provide-hosting><a aria-label="Anchor link for: do-you-provide-hosting" class=zola-anchor href=#do-you-provide-hosting>Do You Provide Hosting?</a></h5><p>Not currently. If there was enough interest the project might offer hosted environments as a way to sustain it's development.</div></div></main><footer class=page-footer>2024, <a href=/ayllu/ayllu/blob/main/ATTRIBUTIONS.md>attributions</a></footer></div>
3271\ No newline at end of file
3272 diff --git a/www/public/docs/index.html b/www/public/docs/index.html
3273index 9b4ac14..e55604c 100644
3274--- a/www/public/docs/index.html
3275+++ b/www/public/docs/index.html
3276 @@ -1 +1 @@
3277- <!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><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><li><a href=https://ayllu-forge.org/docs/themes/>Themes</a></ul></div><div class=doc-content><h1 id=documentation><a aria-label="Anchor link for: documentation" class=zola-anchor href=#documentation>Documentation</a></h1><p>Welcome to the Ayllu documentation 📖, this is section serves as an authoritative reference on all things related to the Ayllu code forge!</div></div></main><footer class=page-footer>2024, <a href=/projects/ayllu/blob/main/ATTRIBUTIONS.md>attributions</a></footer></div>
3278\ No newline at end of file
3279+ <!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><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><li><a href=https://ayllu-forge.org/docs/proxy/>Reverse Proxy</a><li><a href=https://ayllu-forge.org/docs/themes/>Themes</a></ul></div><div class=doc-content><h1 id=documentation><a aria-label="Anchor link for: documentation" class=zola-anchor href=#documentation>Documentation</a></h1><p>Welcome to the Ayllu documentation 📖, this is section serves as an authoritative reference on all things related to the Ayllu code forge!</div></div></main><footer class=page-footer>2024, <a href=/ayllu/ayllu/blob/main/ATTRIBUTIONS.md>attributions</a></footer></div>
3280\ No newline at end of file
3281 diff --git a/www/public/docs/installation/index.html b/www/public/docs/installation/index.html
3282index 7b1f4a7..f7df5ce 100644
3283--- a/www/public/docs/installation/index.html
3284+++ b/www/public/docs/installation/index.html
3285 @@ -1,7 +1,7 @@
3286- <!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><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 class=active><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><li><a href=https://ayllu-forge.org/docs/themes/>Themes</a></ul></div><div class=doc-content><h1 id=running-as-a-container><a aria-label="Anchor link for: running-as-a-container" class=zola-anchor href=#running-as-a-container>Running as a Container</a></h1><p>The quickest way to get started running Ayllu is by using a container. <a href=https://podman.io/>Podman</a> is the only supported container platform although Docker may work as well.<p>There are several different configurations that can be used to run Ayllu as a container.<ul><li><a href=https://ayllu-forge.org/docs/installation/#rootless-container-with-ayllu-running-as-root>Rootless with Ayllu running as root within the container</a><li><a href=https://ayllu-forge.org/docs/installation/#rootless-with-a-non-root-user-within-a-container>Rootless with a non-root user within the container</a><li><a href=https://ayllu-forge.org/docs/installation/#as-root-with-ayllu-running-as-a-non-root-user>As root with Ayllu running as a non-root user</a></ul><h2 id=rootless-container-with-ayllu-running-as-root><a aria-label="Anchor link for: rootless-container-with-ayllu-running-as-root" class=zola-anchor href=#rootless-container-with-ayllu-running-as-root>Rootless Container with Ayllu Running as Root</a></h2><p>This configuration works best if you want Ayllu to have access to your repositories for browsing your code locally. The tutorial assumes that you have permissions to access all of your code repositories.Note that the commands below use a separate configuration and data paths to avoid conflicting with the default paths if the Ayllu binary is installed directly on your system.<p>First create configuration and data paths that we will map into the container.<pre><code>mkdir ~/.config/ayllu-podman
3287+ <!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><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 class=active><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><li><a href=https://ayllu-forge.org/docs/proxy/>Reverse Proxy</a><li><a href=https://ayllu-forge.org/docs/themes/>Themes</a></ul></div><div class=doc-content><h1 id=running-as-a-container><a aria-label="Anchor link for: running-as-a-container" class=zola-anchor href=#running-as-a-container>Running as a Container</a></h1><p>The quickest way to get started running Ayllu is by using a container. <a href=https://podman.io/>Podman</a> is the only supported container platform although Docker may work as well.<p>There are several different configurations that can be used to run Ayllu as a container.<ul><li><a href=https://ayllu-forge.org/docs/installation/#rootless-container-with-ayllu-running-as-root>Rootless with Ayllu running as root within the container</a><li><a href=https://ayllu-forge.org/docs/installation/#rootless-with-a-non-root-user-within-a-container>Rootless with a non-root user within the container</a><li><a href=https://ayllu-forge.org/docs/installation/#as-root-with-ayllu-running-as-a-non-root-user>As root with Ayllu running as a non-root user</a></ul><h2 id=rootless-container-with-ayllu-running-as-root><a aria-label="Anchor link for: rootless-container-with-ayllu-running-as-root" class=zola-anchor href=#rootless-container-with-ayllu-running-as-root>Rootless Container with Ayllu Running as Root</a></h2><p>This configuration works best if you want Ayllu to have access to your repositories for browsing your code locally. The tutorial assumes that you have permissions to access all of your code repositories.Note that the commands below use a separate configuration and data paths to avoid conflicting with the default paths if the Ayllu binary is installed directly on your system.<p>First create configuration and data paths that we will map into the container.<pre><code>mkdir ~/.config/ayllu-podman
3288 mkdir ~/.local/share/ayllu-podman
3289- </code></pre><p>Next pull the latest version of the container and generate a new configuration file.<pre><code>podman pull registry.ayllu-forge.org/projects/ayllu:main
3290- podman run --rm -ti registry.ayllu-forge.org/projects/ayllu:main ayllu config generate > ~/.config/ayllu-podman/config.toml
3291+ </code></pre><p>Next pull the latest version of the container and generate a new configuration file.<pre><code>podman pull registry.ayllu-forge.org/ayllu/ayllu:main
3292+ podman run --rm -ti registry.ayllu-forge.org/ayllu/ayllu:main ayllu config generate > ~/.config/ayllu-podman/config.toml
3293 </code></pre><p>The next step is to map "collections" (directories that contain git repositories) into the Ayllu container. On my system I keep active projects I'm working on in a directory called <code>~/repos/projects</code> and I keep past projects for reference in a directory called <code>~/repos/attic</code>.<pre><code>...
3294 [[collections]]
3295 name = "projects"
3296 @@ -25,14 +25,14 @@ path = "/repos/attic"
3297 -v ~/repos:/repos \
3298 -v ~/.config/ayllu-podman:/root/.config/ayllu \
3299 -v ~/.local/share/ayllu-podman:/root/.local/share/ayllu \
3300- registry.ayllu-forge.org/projects/ayllu:main \
3301+ registry.ayllu-forge.org/ayllu/ayllu:main \
3302 ayllu serve
3303 </code></pre><p>You should now be able to browse to the user interface at <a href=http://localhost:8080>localhost:8080</a>.<h3 id=configure-with-systemd><a aria-label="Anchor link for: configure-with-systemd" class=zola-anchor href=#configure-with-systemd>Configure with Systemd</a></h3><p>You can also configure the container to run as a systemd-user service. See the documentation for <a href=https://docs.podman.io/en/latest/markdown/podman-systemd.unit.5.html>podman systemd units</a>. You can also checkout the <a href=https://wiki.archlinux.org/title/Podman#Quadlet>archlinux wiki</a> for a tutorial.<p>Here is an example file at <code>~/.config/containers/systemd/ayllu.container</code>.<pre><code>[Unit]
3304 Description=Ayllu Container
3305
3306 [Container]
3307 ContainerName=ayllu
3308- Image=registry.ayllu-forge.org/projects/ayllu:main
3309+ Image=registry.ayllu-forge.org/ayllu/ayllu:main
3310 Network=host
3311 User=root
3312 Volume=%h/repos:/repos:rw
3313 @@ -57,4 +57,4 @@ WantedBy=default.target
3314 systemctl --user daemon-reload
3315 # start the ayllu container
3316 systemctl --user start ayllu
3317- </code></pre><p>If all went well Ayllu should now be running as a container managed by systemd!<h1 id=rootless-with-a-non-root-user-within-a-container><a aria-label="Anchor link for: rootless-with-a-non-root-user-within-a-container" class=zola-anchor href=#rootless-with-a-non-root-user-within-a-container>Rootless with a non-root User within a Container</a></h1><p>This method is the most restricted technique for running Ayllu in a production deployment. Not fully supported yet, TODO.<h1 id=as-root-with-ayllu-running-as-a-non-root-user><a aria-label="Anchor link for: as-root-with-ayllu-running-as-a-non-root-user" class=zola-anchor href=#as-root-with-ayllu-running-as-a-non-root-user>As Root with Ayllu Running as a Non-root User</a></h1><p>This method is a hybrid approach that is useful for serving components of Ayllu from a container but also retaining a system level installation. Not fully supported yet, TODO.<h1 id=distribution-packages><a aria-label="Anchor link for: distribution-packages" class=zola-anchor href=#distribution-packages>Distribution Packages</a></h1><div class=warning>NOTE: Few distribution packages currently exist for Ayllu, any contributions in this regard would be gladly accepted!</div><h2 id=arch-linux><a aria-label="Anchor link for: arch-linux" class=zola-anchor href=#arch-linux>Arch Linux</a></h2><h6 id=release-package-unfinished><a aria-label="Anchor link for: release-package-unfinished" class=zola-anchor href=#release-package-unfinished>Release Package (Unfinished)</a></h6><p><a href=https://aur.archlinux.org/packages/ayllu>ayllu</a><h6 id=source-package-unfinished><a aria-label="Anchor link for: source-package-unfinished" class=zola-anchor href=#source-package-unfinished>Source Package (Unfinished)</a></h6><p><a href=https://aur.archlinux.org/packages/ayllu-git>ayllu-git</a><h1 id=from-source><a aria-label="Anchor link for: from-source" class=zola-anchor href=#from-source>From Source</a></h1><p>TODO</div></div></main><footer class=page-footer>2024, <a href=/projects/ayllu/blob/main/ATTRIBUTIONS.md>attributions</a></footer></div>
3318\ No newline at end of file
3319+ </code></pre><p>If all went well Ayllu should now be running as a container managed by systemd!<h1 id=rootless-with-a-non-root-user-within-a-container><a aria-label="Anchor link for: rootless-with-a-non-root-user-within-a-container" class=zola-anchor href=#rootless-with-a-non-root-user-within-a-container>Rootless with a non-root User within a Container</a></h1><p>This method is the most restricted technique for running Ayllu in a production deployment. Not fully supported yet, TODO.<h1 id=as-root-with-ayllu-running-as-a-non-root-user><a aria-label="Anchor link for: as-root-with-ayllu-running-as-a-non-root-user" class=zola-anchor href=#as-root-with-ayllu-running-as-a-non-root-user>As Root with Ayllu Running as a Non-root User</a></h1><p>This method is a hybrid approach that is useful for serving components of Ayllu from a container but also retaining a system level installation. Not fully supported yet, TODO.<h1 id=distribution-packages><a aria-label="Anchor link for: distribution-packages" class=zola-anchor href=#distribution-packages>Distribution Packages</a></h1><div class=warning>NOTE: Few distribution packages currently exist for Ayllu, any contributions in this regard would be gladly accepted!</div><h2 id=arch-linux><a aria-label="Anchor link for: arch-linux" class=zola-anchor href=#arch-linux>Arch Linux</a></h2><h6 id=release-package-unfinished><a aria-label="Anchor link for: release-package-unfinished" class=zola-anchor href=#release-package-unfinished>Release Package (Unfinished)</a></h6><p><a href=https://aur.archlinux.org/packages/ayllu>ayllu</a><h6 id=source-package-unfinished><a aria-label="Anchor link for: source-package-unfinished" class=zola-anchor href=#source-package-unfinished>Source Package (Unfinished)</a></h6><p><a href=https://aur.archlinux.org/packages/ayllu-git>ayllu-git</a><h1 id=from-source><a aria-label="Anchor link for: from-source" class=zola-anchor href=#from-source>From Source</a></h1><p>TODO</div></div></main><footer class=page-footer>2024, <a href=/ayllu/ayllu/blob/main/ATTRIBUTIONS.md>attributions</a></footer></div>
3320\ No newline at end of file
3321 diff --git a/www/public/docs/mail/index.html b/www/public/docs/mail/index.html
3322index 7eac75c..1bdbdcc 100644
3323--- a/www/public/docs/mail/index.html
3324+++ b/www/public/docs/mail/index.html
3325 @@ -1 +1 @@
3326- <!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><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 class=active><a href=https://ayllu-forge.org/docs/mail/>Mail</a><li><a href=https://ayllu-forge.org/docs/themes/>Themes</a></ul></div><div class=doc-content><h1 id=mailing-list-support><a aria-label="Anchor link for: mailing-list-support" class=zola-anchor href=#mailing-list-support>Mailing List Support</a></h1><p>Ayllu has full featured support for email based development workflows. It is based on the excellent <a href=https://git.meli-email.org/meli/mailpot>mailpot</a> mailing list manager.</div></div></main><footer class=page-footer>2024, <a href=/projects/ayllu/blob/main/ATTRIBUTIONS.md>attributions</a></footer></div>
3327\ No newline at end of file
3328+ <!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><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 class=active><a href=https://ayllu-forge.org/docs/mail/>Mail</a><li><a href=https://ayllu-forge.org/docs/proxy/>Reverse Proxy</a><li><a href=https://ayllu-forge.org/docs/themes/>Themes</a></ul></div><div class=doc-content><h1 id=mailing-list-support><a aria-label="Anchor link for: mailing-list-support" class=zola-anchor href=#mailing-list-support>Mailing List Support</a></h1><p>Ayllu has full featured support for email based development workflows. It is based on the excellent <a href=https://git.meli-email.org/meli/mailpot>mailpot</a> mailing list manager.</div></div></main><footer class=page-footer>2024, <a href=/ayllu/ayllu/blob/main/ATTRIBUTIONS.md>attributions</a></footer></div>
3329\ No newline at end of file
3330 diff --git a/www/public/docs/proxy/index.html b/www/public/docs/proxy/index.html
3331new file mode 100644
3332index 0000000..210443a
3333--- /dev/null
3334+++ b/www/public/docs/proxy/index.html
3335 @@ -0,0 +1 @@
3336+ <!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><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><li class=active><a href=https://ayllu-forge.org/docs/proxy/>Reverse Proxy</a><li><a href=https://ayllu-forge.org/docs/themes/>Themes</a></ul></div><div class=doc-content><h1 id=proxy-configuration><a aria-label="Anchor link for: proxy-configuration" class=zola-anchor href=#proxy-configuration>Proxy Configuration</a></h1><p>Ayllu is designed to be run behind a reverse proxy server and it is not recommended to deploy Ayllu directly to the internet. Things such as TLS termination or basic authentication for private repositories are not directly supported by Ayllu although some of these features may be added in the future.<h2 id=nginx><a aria-label="Anchor link for: nginx" class=zola-anchor href=#nginx>Nginx</a></h2><p><a href=https://nginx.org/>nginx</a> is the only supported software but others are likely to work and we will happily accept documentation for operating other free software proxy servers.</div></div></main><footer class=page-footer>2024, <a href=/ayllu/ayllu/blob/main/ATTRIBUTIONS.md>attributions</a></footer></div>
3337\ No newline at end of file
3338 diff --git a/www/public/docs/themes/index.html b/www/public/docs/themes/index.html
3339index 344aae8..18c6e08 100644
3340--- a/www/public/docs/themes/index.html
3341+++ b/www/public/docs/themes/index.html
3342 @@ -1,4 +1,4 @@
3343- <!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><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><li class=active><a href=https://ayllu-forge.org/docs/themes/>Themes</a></ul></div><div class=doc-content><h1 id=themeing-ayllu><a aria-label="Anchor link for: themeing-ayllu" class=zola-anchor href=#themeing-ayllu>Themeing Ayllu</a></h1><p>Ayllu can be customized in a variety of ways including custom CSS, modifying Jinja based HTML templates, or by adding new static assets such as images. Available themes can be viewed and enabled from the <code>/config</code> page.<h2 id=the-default-theme><a aria-label="Anchor link for: the-default-theme" class=zola-anchor href=#the-default-theme>The Default Theme</a></h2><p>The default theme is located in <code>ayllu/themes/default</code> and is served as a base and all themes serve content from this theme if they do not override it's assets. It is possible to change the default theme by setting it in your Ayllu configuration file.<pre><code>[web]
3344+ <!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><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><li><a href=https://ayllu-forge.org/docs/proxy/>Reverse Proxy</a><li class=active><a href=https://ayllu-forge.org/docs/themes/>Themes</a></ul></div><div class=doc-content><h1 id=themeing-ayllu><a aria-label="Anchor link for: themeing-ayllu" class=zola-anchor href=#themeing-ayllu>Themeing Ayllu</a></h1><p>Ayllu can be customized in a variety of ways including custom CSS, modifying Jinja based HTML templates, or by adding new static assets such as images. Available themes can be viewed and enabled from the <code>/config</code> page.<h2 id=the-default-theme><a aria-label="Anchor link for: the-default-theme" class=zola-anchor href=#the-default-theme>The Default Theme</a></h2><p>The default theme is located in <code>ayllu/themes/default</code> and is served as a base and all themes serve content from this theme if they do not override it's assets. It is possible to change the default theme by setting it in your Ayllu configuration file.<pre><code>[web]
3345 default_theme = "my-custom-theme"
3346 </code></pre><p>By changing the default theme all additionally loaded themes will inherit the templates and assets of the default theme.<h2 id=built-in-vs-external-themes><a aria-label="Anchor link for: built-in-vs-external-themes" class=zola-anchor href=#built-in-vs-external-themes>Built-in vs External Themes</a></h2><h3 id=built-in-themes><a aria-label="Anchor link for: built-in-themes" class=zola-anchor href=#built-in-themes>Built-in Themes</a></h3><p>Built-in themes are compiled into the Ayllu binary and available for selection from the config section of the web interface. These themes are available at <code>ayllu/themes/</code>. These themes use <a href=https://lightningcss.dev/>lightning css</a> at build time to build the theme's <code>main.min.css</code> file.<h3 id=external-themes><a aria-label="Anchor link for: external-themes" class=zola-anchor href=#external-themes>External Themes</a></h3><p>Additional themes can be loaded directly from the file system and allow the addition of new themes without recompiling Ayllu.<pre><code>[web]
3347
3348 @@ -15,4 +15,4 @@ assets/feed.xsl # XSL file used to style RSS feeds
3349 assets/*.svg # additional assets for various parts of the UI
3350 templates/ # directory of Jinja templates
3351 theme.css # additional stylesheet to extend the default
3352- </code></pre><p>See <code>contrib/example-theme</code> for a functional custom theme to extend as a base.</div></div></main><footer class=page-footer>2024, <a href=/projects/ayllu/blob/main/ATTRIBUTIONS.md>attributions</a></footer></div>
3353\ No newline at end of file
3354+ </code></pre><p>See <code>contrib/example-theme</code> for a functional custom theme to extend as a base.</div></div></main><footer class=page-footer>2024, <a href=/ayllu/ayllu/blob/main/ATTRIBUTIONS.md>attributions</a></footer></div>
3355\ No newline at end of file
3356 diff --git a/www/public/index.html b/www/public/index.html
3357index 1c7e3f4..d1d7f39 100644
3358--- a/www/public/index.html
3359+++ b/www/public/index.html
3360 @@ -1 +1 @@
3361- <!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><h3 id=hyper-performant-hackable-code-forge-built-on-open-standards>Hyper Performant & Hackable Code Forge Built on Open Standards</h3><p>Ayllu is a lightweight <a href=https://en.wikipedia.org/wiki/Forge_(software)>code forge</a> designed to help individuals and grassroots community projects create and develop software in collaboration across open internet standards.<hr><p>Check out the <a href=/docs>documentation</a> or <a href=/browse>browse</a> some of the projects hosted on this server.</main><footer class=page-footer>2024, <a href=/projects/ayllu/blob/main/ATTRIBUTIONS.md>attributions</a></footer></div>
3362\ No newline at end of file
3363+ <!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><h3 id=hyper-performant-hackable-code-forge-built-on-open-standards>Hyper Performant & Hackable Code Forge Built on Open Standards</h3><p>Ayllu is a lightweight <a href=https://en.wikipedia.org/wiki/Forge_(software)>code forge</a> designed to help individuals and grassroots community projects create and develop software in collaboration across open internet standards.<hr><p>Check out the <a href=/docs>documentation</a> or <a href=/browse>browse</a> some of the projects hosted on this server.</main><footer class=page-footer>2024, <a href=/ayllu/ayllu/blob/main/ATTRIBUTIONS.md>attributions</a></footer></div>
3364\ No newline at end of file
3365 diff --git a/www/public/sitemap.xml b/www/public/sitemap.xml
3366index 7d45076..e3565d5 100644
3367--- a/www/public/sitemap.xml
3368+++ b/www/public/sitemap.xml
3369 @@ -28,6 +28,9 @@
3370 <loc>https://ayllu-forge.org/docs/mail/</loc>
3371 </url>
3372 <url>
3373+ <loc>https://ayllu-forge.org/docs/proxy/</loc>
3374+ </url>
3375+ <url>
3376 <loc>https://ayllu-forge.org/docs/themes/</loc>
3377 </url>
3378 </urlset>
3379 diff --git a/www/templates/base.html b/www/templates/base.html
3380index 9e0b317..78e1b0c 100644
3381--- a/www/templates/base.html
3382+++ b/www/templates/base.html
3383 @@ -34,7 +34,7 @@
3384 content %}
3385 </main>
3386 <footer class="page-footer">
3387- {{ now() | date(format="%Y") }}, <a href="/projects/ayllu/blob/main/ATTRIBUTIONS.md">attributions</a>
3388+ {{ now() | date(format="%Y") }}, <a href="/ayllu/ayllu/blob/main/ATTRIBUTIONS.md">attributions</a>
3389 </footer>
3390 </div>
3391 </body>