Compare commits
1789 Commits
Author | SHA1 | Date | |
---|---|---|---|
![]() |
ccd6ad9936 | ||
![]() |
979fcb0570 | ||
![]() |
58afec98f1 | ||
![]() |
91434a5c6f | ||
![]() |
37c4545444 | ||
![]() |
7b7b257725 | ||
![]() |
1eff4b306f | ||
![]() |
700af8caa2 | ||
![]() |
90b43e154a | ||
![]() |
a3892302b0 | ||
![]() |
d2ee319684 | ||
![]() |
5a6923a9aa | ||
![]() |
0a634883a7 | ||
![]() |
428f9cd761 | ||
![]() |
d828c1a2ff | ||
![]() |
25b49db7c0 | ||
![]() |
55a40708a6 | ||
![]() |
dae5bca883 | ||
![]() |
fc74da9b82 | ||
![]() |
2b006907d5 | ||
![]() |
1ba1afdce5 | ||
![]() |
a98317c52a | ||
![]() |
ffddd0f323 | ||
![]() |
4cb2f0acef | ||
![]() |
98663e902f | ||
![]() |
0f5e935214 | ||
![]() |
b9636a3def | ||
![]() |
35574f3b86 | ||
![]() |
00a8f0cd6e | ||
![]() |
6779042242 | ||
![]() |
6379e7b54f | ||
![]() |
2fa742c94b | ||
![]() |
aa0da2f516 | ||
![]() |
f07441a408 | ||
![]() |
f6084acfc8 | ||
![]() |
23ceb2a5ec | ||
![]() |
a698791059 | ||
![]() |
41c1f38ab2 | ||
![]() |
83c85dc10e | ||
![]() |
a020d807d4 | ||
![]() |
464ee51de8 | ||
![]() |
c57c1d5389 | ||
![]() |
af16bb3934 | ||
![]() |
fba416e8e1 | ||
![]() |
3d8de50b5a | ||
![]() |
86263a52ea | ||
![]() |
754627681c | ||
![]() |
bf11dc8d1b | ||
![]() |
84721b001f | ||
![]() |
f48a20c75f | ||
![]() |
16f4552e0e | ||
![]() |
86811d0733 | ||
![]() |
d2f9b5d5e5 | ||
![]() |
f5e1675107 | ||
![]() |
ae016cae4b | ||
![]() |
c7f6d03508 | ||
![]() |
955f2d0db9 | ||
![]() |
ba32684df6 | ||
![]() |
2f7adf40ac | ||
![]() |
a52031161b | ||
![]() |
3e2c541b7b | ||
![]() |
967fc98090 | ||
![]() |
22e95f45bd | ||
![]() |
38c777ec0f | ||
![]() |
ccbc97399a | ||
![]() |
f43013a746 | ||
![]() |
1335ab5f1b | ||
![]() |
90b4691f16 | ||
![]() |
f053ee3191 | ||
![]() |
86748c1e96 | ||
![]() |
22ded7d4c3 | ||
![]() |
ff5063849a | ||
![]() |
ec49284274 | ||
![]() |
6bd5c34b54 | ||
![]() |
db0a2eb1a3 | ||
![]() |
4948438378 | ||
![]() |
76064178f5 | ||
![]() |
c772bd94b0 | ||
![]() |
7f8f7fbb15 | ||
![]() |
388d821f45 | ||
![]() |
9c15623a89 | ||
![]() |
966eb00de0 | ||
![]() |
1c699278a3 | ||
![]() |
4c6c976f63 | ||
![]() |
ebc9ce17b5 | ||
![]() |
8039ce3c2b | ||
![]() |
385d48f644 | ||
![]() |
f682fe25fc | ||
![]() |
7924bf8611 | ||
![]() |
d75b909d28 | ||
![]() |
47dfe85a7c | ||
![]() |
4d0e8a338f | ||
![]() |
cfc64d37bb | ||
![]() |
2db66280cc | ||
![]() |
9f045f4494 | ||
![]() |
4fdb28c8d6 | ||
![]() |
f1049cf889 | ||
![]() |
8d664fad56 | ||
![]() |
f6ddcfa839 | ||
![]() |
ce59f2ad5e | ||
![]() |
0de00a4ac1 | ||
![]() |
bec72dffeb | ||
![]() |
463e95367c | ||
![]() |
1739de2694 | ||
![]() |
fd8db27a88 | ||
![]() |
9ddf14bebe | ||
![]() |
1cf8ea3aba | ||
![]() |
134993fce6 | ||
![]() |
ff1955e014 | ||
![]() |
d83bbdc50b | ||
![]() |
907b6d1294 | ||
![]() |
4e7bb1c8da | ||
![]() |
09ab694d05 | ||
![]() |
03ced65d5f | ||
![]() |
01d919cf31 | ||
![]() |
8d5f331e63 | ||
![]() |
ed556ead6f | ||
![]() |
e10a904f33 | ||
![]() |
f2a05b61da | ||
![]() |
21f96f0679 | ||
![]() |
2ffabd54e5 | ||
![]() |
1197437750 | ||
![]() |
d1339374d0 | ||
![]() |
5ba4b9d6b2 | ||
![]() |
b386ea9426 | ||
![]() |
197174f400 | ||
![]() |
97dceba783 | ||
![]() |
2ed4400827 | ||
![]() |
58cbcbd6ef | ||
![]() |
4aeb2e1a74 | ||
![]() |
45c5f81b34 | ||
![]() |
13201dbfff | ||
![]() |
0b1523f4e5 | ||
![]() |
cd3b1a221e | ||
![]() |
4855f4b8b1 | ||
![]() |
6587470033 | ||
![]() |
6487dab132 | ||
![]() |
b643a68fa3 | ||
![]() |
b60e16fe33 | ||
![]() |
c508be6ecd | ||
![]() |
3b2d4fe876 | ||
![]() |
b47f301831 | ||
![]() |
a79b9de1a2 | ||
![]() |
e98da2e72c | ||
![]() |
718171a125 | ||
![]() |
aaa130e20d | ||
![]() |
4606caeaa8 | ||
![]() |
4813a7bc70 | ||
![]() |
fb82aa0ee1 | ||
![]() |
c7e0c32226 | ||
![]() |
607adf44f3 | ||
![]() |
625780899d | ||
![]() |
25542c56b9 | ||
![]() |
45e2b7f814 | ||
![]() |
6b34f592df | ||
![]() |
6cf732e6ec | ||
![]() |
dfd959839f | ||
![]() |
d165e89ac3 | ||
![]() |
421a87c94b | ||
![]() |
b55529b913 | ||
![]() |
c62d892969 | ||
![]() |
9e6aa55230 | ||
![]() |
6090305b77 | ||
![]() |
d1b516a089 | ||
![]() |
89aff63e52 | ||
![]() |
11e9c4d8cc | ||
![]() |
9d84e95771 | ||
![]() |
2aced1c305 | ||
![]() |
454098630b | ||
![]() |
8f3ab2791b | ||
![]() |
61209b1057 | ||
![]() |
38a817e887 | ||
![]() |
5e3d1b26e7 | ||
![]() |
4996b7e5f7 | ||
![]() |
2c8fddb554 | ||
![]() |
ae05011062 | ||
![]() |
50a6b7e154 | ||
![]() |
d0ce4113e0 | ||
![]() |
d55900b877 | ||
![]() |
2a73ab4693 | ||
![]() |
2aea220c6d | ||
![]() |
b0c305e852 | ||
![]() |
73a77d2a45 | ||
![]() |
3a011e7c04 | ||
![]() |
d48b75d862 | ||
![]() |
f6e26d5953 | ||
![]() |
7863780883 | ||
![]() |
c2ac9a26a2 | ||
![]() |
58e8f796d1 | ||
![]() |
ba0f4718e5 | ||
![]() |
6fb4daf03e | ||
![]() |
f6c34494a7 | ||
![]() |
1e4d284b30 | ||
![]() |
1f3406fd77 | ||
![]() |
398faf36fc | ||
![]() |
ce841d4196 | ||
![]() |
c77f8acf41 | ||
![]() |
212674f9df | ||
![]() |
283ced56d1 | ||
![]() |
530e57151d | ||
![]() |
1141c3f361 | ||
![]() |
49416d3372 | ||
![]() |
88ae60a4a0 | ||
![]() |
6651c80fb9 | ||
![]() |
6d6650d5f6 | ||
![]() |
6df252c99b | ||
![]() |
5881f05dbc | ||
![]() |
00eba3b223 | ||
![]() |
f7ab8d23a7 | ||
![]() |
85b596d20d | ||
![]() |
4d43f6b63d | ||
![]() |
ea1eb551a7 | ||
![]() |
5842944d1e | ||
![]() |
5781a0d51f | ||
![]() |
e5f48739a0 | ||
![]() |
d378c861f6 | ||
![]() |
9466bfdb00 | ||
![]() |
f02e8e0dc3 | ||
![]() |
c1ed87a44f | ||
![]() |
16169ca331 | ||
![]() |
26900e0766 | ||
![]() |
aa798604b3 | ||
![]() |
bb98fc5f65 | ||
![]() |
dc1918ad10 | ||
![]() |
ea632d0417 | ||
![]() |
648dc709fd | ||
![]() |
1a84f6a20e | ||
![]() |
96af953e6f | ||
![]() |
6db9e292ba | ||
![]() |
2e2362e2df | ||
![]() |
51dd95be3d | ||
![]() |
e16645b146 | ||
![]() |
0068f091bb | ||
![]() |
ad6efd2898 | ||
![]() |
86e380bb1c | ||
![]() |
58aacd4814 | ||
![]() |
ad07791bac | ||
![]() |
783090c2cd | ||
![]() |
41a3c7c89b | ||
![]() |
16cc7415c1 | ||
![]() |
98c5cf89ef | ||
![]() |
53e04e66cf | ||
![]() |
2a6e79acc8 | ||
![]() |
2da5e46386 | ||
![]() |
4dbf8d7969 | ||
![]() |
4a52fc27d4 | ||
![]() |
05cd34c8af | ||
![]() |
8a622181fc | ||
![]() |
13f38bf3a1 | ||
![]() |
16acc2d6ad | ||
![]() |
c2c9a953d3 | ||
![]() |
530f4a8b28 | ||
![]() |
8eb1dc4f62 | ||
![]() |
a2b87fe012 | ||
![]() |
3dcb973adb | ||
![]() |
8e8810cbaa | ||
![]() |
b0aeec4c43 | ||
![]() |
1ac298f6ff | ||
![]() |
6d5f4e92cc | ||
![]() |
416ad13aaf | ||
![]() |
a7e1299194 | ||
![]() |
a12e1fae72 | ||
![]() |
f525ac0af6 | ||
![]() |
58bf9c552b | ||
![]() |
22d257cd1f | ||
![]() |
f1bf1ddc54 | ||
![]() |
6015cc0e4a | ||
![]() |
f9926d77d5 | ||
![]() |
4f85dcecfc | ||
![]() |
30c31a3d4c | ||
![]() |
c64667d396 | ||
![]() |
9f6613fe05 | ||
![]() |
ea47af7034 | ||
![]() |
d46abeff01 | ||
![]() |
2b39697ffb | ||
![]() |
4b00a72ff5 | ||
![]() |
e590b2482e | ||
![]() |
eb7dd80410 | ||
![]() |
86338465fb | ||
![]() |
a41dbdd12c | ||
![]() |
1e10a438cd | ||
![]() |
ab34ea724d | ||
![]() |
fd8bfe1a80 | ||
![]() |
9043f45350 | ||
![]() |
5921e6d13e | ||
![]() |
ee2bfe2350 | ||
![]() |
0957a7ca8e | ||
![]() |
f4e75c7fb7 | ||
![]() |
fae0e3b405 | ||
![]() |
ef335517ce | ||
![]() |
e2be166e67 | ||
![]() |
37e34d92de | ||
![]() |
bd35030c59 | ||
![]() |
a82e3771ae | ||
![]() |
3115106dc1 | ||
![]() |
d623af9c41 | ||
![]() |
355a434a07 | ||
![]() |
8da2535a65 | ||
![]() |
5963dfe41b | ||
![]() |
c6dcaa0472 | ||
![]() |
21063a5c22 | ||
![]() |
ba2f51bed1 | ||
![]() |
bbf64b7e93 | ||
![]() |
3b6ce16f1c | ||
![]() |
46e6be319f | ||
![]() |
e6d6f21d33 | ||
![]() |
77b9b79a9e | ||
![]() |
f0016ad70c | ||
![]() |
054468ffc2 | ||
![]() |
607c1282e3 | ||
![]() |
3f4f4444f7 | ||
![]() |
54372b5618 | ||
![]() |
670a3f6c7f | ||
![]() |
35a4d3fb54 | ||
![]() |
fb81612ed1 | ||
![]() |
c5d622279c | ||
![]() |
b93f655039 | ||
![]() |
428ffb4729 | ||
![]() |
061f33fb05 | ||
![]() |
da058b915b | ||
![]() |
cf869b1356 | ||
![]() |
05e294fc81 | ||
![]() |
bd904d9e6b | ||
![]() |
a7ac719711 | ||
![]() |
370f6ddb3b | ||
![]() |
3861e84f89 | ||
![]() |
76001105b8 | ||
![]() |
d47cc9460b | ||
![]() |
fbeb03c377 | ||
![]() |
2b13fa4712 | ||
![]() |
98255f8e14 | ||
![]() |
eaeeb6447f | ||
![]() |
e6a9868e86 | ||
![]() |
f03592d17d | ||
![]() |
e5db44bc2b | ||
![]() |
ffad42615f | ||
![]() |
5576a073a5 | ||
![]() |
151d337f6c | ||
![]() |
74e89b0ee3 | ||
![]() |
945fb675e9 | ||
![]() |
7570945cf0 | ||
![]() |
d94d80a8c8 | ||
![]() |
8a0a49dd57 | ||
![]() |
66b2d90c50 | ||
![]() |
5723bd8dd8 | ||
![]() |
9b08ce1761 | ||
![]() |
a6248bec2d | ||
![]() |
b1f6f52486 | ||
![]() |
638d9970fd | ||
![]() |
5e8de4c1da | ||
![]() |
088bad9030 | ||
![]() |
cfa908243b | ||
![]() |
cabeb00632 | ||
![]() |
2426c01978 | ||
![]() |
80f2bee6e8 | ||
![]() |
7ec51758eb | ||
![]() |
9e93ae952a | ||
![]() |
829836ddf6 | ||
![]() |
1197a048bc | ||
![]() |
7289c4ea56 | ||
![]() |
55dadf0b00 | ||
![]() |
341815cc03 | ||
![]() |
d22b27afe7 | ||
![]() |
6ee2d023d5 | ||
![]() |
5010cc6f15 | ||
![]() |
383cced158 | ||
![]() |
a3d6967192 | ||
![]() |
c5881f75c9 | ||
![]() |
c4b7429e99 | ||
![]() |
b1eced3612 | ||
![]() |
9d5b07537d | ||
![]() |
122e4141b0 | ||
![]() |
be2de4f15d | ||
![]() |
92a920021d | ||
![]() |
72000cac36 | ||
![]() |
4510902677 | ||
![]() |
c2b9d2fa7b | ||
![]() |
cd38c39908 | ||
![]() |
9016a1e6df | ||
![]() |
627254d5a7 | ||
![]() |
ff31558252 | ||
![]() |
9454978264 | ||
![]() |
e2d25a7a09 | ||
![]() |
85f824f032 | ||
![]() |
1a48910e6b | ||
![]() |
bffd5829d0 | ||
![]() |
7e12bd1bef | ||
![]() |
af0817ab74 | ||
![]() |
fbf1a051a2 | ||
![]() |
7ecf7f704a | ||
![]() |
7b7a74d821 | ||
![]() |
e4acc33519 | ||
![]() |
2fd141d914 | ||
![]() |
aad814d342 | ||
![]() |
7e21aaec17 | ||
![]() |
6d5fdfe2e2 | ||
![]() |
5942cd6cd2 | ||
![]() |
5cd17e71e2 | ||
![]() |
2f2ecaa61e | ||
![]() |
aa858a35e2 | ||
![]() |
d658150d42 | ||
![]() |
c312149b35 | ||
![]() |
18a9a3df12 | ||
![]() |
0cdd3581c9 | ||
![]() |
cae79b811f | ||
![]() |
6d953babcb | ||
![]() |
975e5f3fd0 | ||
![]() |
6cfe92bed1 | ||
![]() |
a51c0850c8 | ||
![]() |
03415456bf | ||
![]() |
0309a0fae1 | ||
![]() |
b48910bb94 | ||
![]() |
9a89786dd3 | ||
![]() |
15a5261189 | ||
![]() |
f616da3b85 | ||
![]() |
0e9cf016ec | ||
![]() |
4481f12e32 | ||
![]() |
66efaedcbb | ||
![]() |
771c1fab92 | ||
![]() |
8d6e7ed477 | ||
![]() |
5d80511b9b | ||
![]() |
90f90dc9b4 | ||
![]() |
a58e8498aa | ||
![]() |
ca355d5855 | ||
![]() |
826322b610 | ||
![]() |
80ff5677ea | ||
![]() |
62c417cd51 | ||
![]() |
f27f25aa03 | ||
![]() |
285a4b5aef | ||
![]() |
47a2ded30d | ||
![]() |
5b502b1e1a | ||
![]() |
aff56077a8 | ||
![]() |
6e371ac5ac | ||
![]() |
1b69b89d2d | ||
![]() |
5a20c8e512 | ||
![]() |
4ca1503beb | ||
![]() |
567a7eb7f3 | ||
![]() |
20f27fe32f | ||
![]() |
1a50d6bb86 | ||
![]() |
0b16c2db03 | ||
![]() |
76ac888386 | ||
![]() |
7cfa05d7f2 | ||
![]() |
e3496d0485 | ||
![]() |
d2c33c0074 | ||
![]() |
9c5caecafa | ||
![]() |
64651d5a84 | ||
![]() |
4493236879 | ||
![]() |
ce643942ea | ||
![]() |
46d216b02f | ||
![]() |
133d43ae30 | ||
![]() |
27155cb7e3 | ||
![]() |
b55c413774 | ||
![]() |
69be86e16c | ||
![]() |
65f6b0881e | ||
![]() |
c2bede40c7 | ||
![]() |
33c2398de9 | ||
![]() |
64cfc43891 | ||
![]() |
6575c69409 | ||
![]() |
e3f4e0b775 | ||
![]() |
5be89bfda5 | ||
![]() |
e1b573adeb | ||
![]() |
0913c7aa9e | ||
![]() |
5297626816 | ||
![]() |
c075642d78 | ||
![]() |
253f1a44c1 | ||
![]() |
26837c8871 | ||
![]() |
c732d31edd | ||
![]() |
c2595f28fb | ||
![]() |
58656a63cf | ||
![]() |
f4ce178cfa | ||
![]() |
2c1cfc64d5 | ||
![]() |
4b84d93cee | ||
![]() |
0bfa347595 | ||
![]() |
c7a07b59fd | ||
![]() |
c7c737d8c9 | ||
![]() |
1126e668d1 | ||
![]() |
b71a94ebca | ||
![]() |
815137af19 | ||
![]() |
3c4fbfec54 | ||
![]() |
b3a8eb7e06 | ||
![]() |
1e5c64ff73 | ||
![]() |
a320bfa425 | ||
![]() |
1c4dfc3c6e | ||
![]() |
92ee4d33c3 | ||
![]() |
01c2f01d3f | ||
![]() |
b83e6a5d5b | ||
![]() |
16281b38e0 | ||
![]() |
ca11e116b6 | ||
![]() |
1f32e4a642 | ||
![]() |
4c01089de0 | ||
![]() |
47a416dd9b | ||
![]() |
1ab1bbdd70 | ||
![]() |
1e05cb168c | ||
![]() |
d0383c1edf | ||
![]() |
c8ee35692c | ||
![]() |
3e062a8021 | ||
![]() |
0eb17e7102 | ||
![]() |
a14796cf90 | ||
![]() |
84b1c1ce6c | ||
![]() |
27e0b65c2d | ||
![]() |
ef35576174 | ||
![]() |
804faf726b | ||
![]() |
a94e5d2e47 | ||
![]() |
54d817d656 | ||
![]() |
00aa6c1f6a | ||
![]() |
ed34815393 | ||
![]() |
ceb15716c3 | ||
![]() |
bc97f540de | ||
![]() |
4ae67c79e0 | ||
![]() |
be857e989b | ||
![]() |
232402197c | ||
![]() |
12cbe84166 | ||
![]() |
b7b0be141c | ||
![]() |
8f9f4d9b71 | ||
![]() |
b97b9b7a28 | ||
![]() |
cf23732da3 | ||
![]() |
4e6b1d54e8 | ||
![]() |
fec8f90b78 | ||
![]() |
bedd4dafc4 | ||
![]() |
0d92648d62 | ||
![]() |
07989bc2fa | ||
![]() |
09ff2cb9a3 | ||
![]() |
6aa2e60037 | ||
![]() |
9f3ef532b8 | ||
![]() |
654621b438 | ||
![]() |
f8cfa9e02d | ||
![]() |
7476aa5897 | ||
![]() |
1d00a59706 | ||
![]() |
82d4e5e456 | ||
![]() |
07b6a36aab | ||
![]() |
0ceb6cc143 | ||
![]() |
864a155ee8 | ||
![]() |
15e2e575ee | ||
![]() |
253c524131 | ||
![]() |
48c02b8554 | ||
![]() |
d8fef22c9a | ||
![]() |
d82103e02d | ||
![]() |
9e5b22931e | ||
![]() |
30cd361edb | ||
![]() |
48da0071bd | ||
![]() |
32037e3645 | ||
![]() |
4279038a62 | ||
![]() |
c8afe7503b | ||
![]() |
4dfae98250 | ||
![]() |
b44dca49f5 | ||
![]() |
3d5892a841 | ||
![]() |
365e06891a | ||
![]() |
0f436481b8 | ||
![]() |
a5510be0bf | ||
![]() |
45a3c95a66 | ||
![]() |
94cb13ce9d | ||
![]() |
fecc431b10 | ||
![]() |
c88a975bf0 | ||
![]() |
4ed26ab934 | ||
![]() |
76a8da20bf | ||
![]() |
9f05747681 | ||
![]() |
f0bb9bb953 | ||
![]() |
72c6ac09ac | ||
![]() |
07c38d6353 | ||
![]() |
dd1cafbc77 | ||
![]() |
77e30d7844 | ||
![]() |
1c4f772a14 | ||
![]() |
bfc3148e4f | ||
![]() |
85db76c86e | ||
![]() |
f6c0d97172 | ||
![]() |
67f846513b | ||
![]() |
ad4cf843dd | ||
![]() |
af79b1504f | ||
![]() |
caff72967e | ||
![]() |
4abd21bbca | ||
![]() |
a2770a1bff | ||
![]() |
30e2d15321 | ||
![]() |
ba7e8b8627 | ||
![]() |
58850e97ff | ||
![]() |
b35acabc56 | ||
![]() |
ef907f1bc4 | ||
![]() |
c25128917b | ||
![]() |
9db6e64666 | ||
![]() |
f51a29a2e1 | ||
![]() |
b90025a2d0 | ||
![]() |
03b5ae02e1 | ||
![]() |
70627b992a | ||
![]() |
e830cd9baa | ||
![]() |
05fb10390e | ||
![]() |
d2c802c9da | ||
![]() |
741c165a5b | ||
![]() |
53d3ede184 | ||
![]() |
a41d2f831d | ||
![]() |
e30b7d0fa4 | ||
![]() |
7222741863 | ||
![]() |
49cea717af | ||
![]() |
49a7b62155 | ||
![]() |
3abc88985a | ||
![]() |
764b007d2c | ||
![]() |
a5c0d3dfae | ||
![]() |
386070923f | ||
![]() |
e79015d877 | ||
![]() |
b574396751 | ||
![]() |
91400070a7 | ||
![]() |
23d02085e8 | ||
![]() |
a9459dca89 | ||
![]() |
bd08cd1983 | ||
![]() |
3ae91495c1 | ||
![]() |
7f9fa46271 | ||
![]() |
f3b02d9922 | ||
![]() |
f2be582299 | ||
![]() |
b2eb403f9b | ||
![]() |
7e6a5927d7 | ||
![]() |
2c4a3650c4 | ||
![]() |
5b125d4513 | ||
![]() |
66167aeb55 | ||
![]() |
9804269cd1 | ||
![]() |
3239c478a5 | ||
![]() |
8f2ca5761b | ||
![]() |
ef9669cdb6 | ||
![]() |
caddcaf807 | ||
![]() |
90707d661b | ||
![]() |
facb7226fe | ||
![]() |
b671f54cb7 | ||
![]() |
55f26c9c4c | ||
![]() |
18995b3561 | ||
![]() |
8e3fbdddc7 | ||
![]() |
c19df84bef | ||
![]() |
e8527ba723 | ||
![]() |
577b49df9d | ||
![]() |
5b83bd03f5 | ||
![]() |
73cbf6c33d | ||
![]() |
95ab3b99f8 | ||
![]() |
24dbf669a9 | ||
![]() |
8716590cef | ||
![]() |
10729f0362 | ||
![]() |
800f54f263 | ||
![]() |
e63a543c29 | ||
![]() |
0f08796e1b | ||
![]() |
d10c32bad6 | ||
![]() |
813ad6551c | ||
![]() |
5582b33d40 | ||
![]() |
79afc236c9 | ||
![]() |
1f83a75d5f | ||
![]() |
2a3baf5aec | ||
![]() |
f92ae3b232 | ||
![]() |
03523244ef | ||
![]() |
55e799b833 | ||
![]() |
10929e9ac8 | ||
![]() |
89757609c2 | ||
![]() |
0f1dea67b7 | ||
![]() |
38e035b95c | ||
![]() |
f695d4b9da | ||
![]() |
4fbe7f16f3 | ||
![]() |
dcc2cc5001 | ||
![]() |
cb271deb8f | ||
![]() |
d480e91196 | ||
![]() |
9880f9ebc7 | ||
![]() |
999ae678c2 | ||
![]() |
5f0eba694c | ||
![]() |
f893ba929a | ||
![]() |
d35b0423b6 | ||
![]() |
651a22d056 | ||
![]() |
8dee246854 | ||
![]() |
90db397ec6 | ||
![]() |
cf973ff41e | ||
![]() |
fd8de5b1ea | ||
![]() |
27772257a8 | ||
![]() |
5665db844e | ||
![]() |
21a9963a2b | ||
![]() |
99f260225a | ||
![]() |
e4054d684c | ||
![]() |
01af725d79 | ||
![]() |
db0f31adea | ||
![]() |
226c771735 | ||
![]() |
b18b070622 | ||
![]() |
e52ae28426 | ||
![]() |
6acd30eda1 | ||
![]() |
f170cc0354 | ||
![]() |
d9abae51b5 | ||
![]() |
ada67bd54e | ||
![]() |
f7f5d0efa6 | ||
![]() |
a2bdd64ad0 | ||
![]() |
48f4f21d28 | ||
![]() |
57b8ee37ec | ||
![]() |
71bf8eb332 | ||
![]() |
fb1e288580 | ||
![]() |
720c6dd3b0 | ||
![]() |
be6506da08 | ||
![]() |
4f6698e39f | ||
![]() |
cdcd22e6a6 | ||
![]() |
b1b3eb4406 | ||
![]() |
b0c6f5e56b | ||
![]() |
322d8a61c2 | ||
![]() |
d41c4730cd | ||
![]() |
b38bb47491 | ||
![]() |
6973691cce | ||
![]() |
96176589ca | ||
![]() |
a74740877a | ||
![]() |
1de4072a48 | ||
![]() |
cf2796b2af | ||
![]() |
85315b768c | ||
![]() |
76a147e58b | ||
![]() |
de88c6de8c | ||
![]() |
613b429540 | ||
![]() |
d9abb745a9 | ||
![]() |
702225f535 | ||
![]() |
612e6341a3 | ||
![]() |
70b566f746 | ||
![]() |
6dd69126ae | ||
![]() |
f3d6756fba | ||
![]() |
8d60506884 | ||
![]() |
9712ac109d | ||
![]() |
379b4f8cd3 | ||
![]() |
86d223fd93 | ||
![]() |
205106b566 | ||
![]() |
54099d8441 | ||
![]() |
7c8b501c40 | ||
![]() |
02bf0349ca | ||
![]() |
9d72d1fc81 | ||
![]() |
085e6da1f2 | ||
![]() |
1ce0eae931 | ||
![]() |
ff5c25fc8c | ||
![]() |
ef863bec7c | ||
![]() |
89117da57d | ||
![]() |
6170f4f56a | ||
![]() |
0b00f8f4f0 | ||
![]() |
a45c128c38 | ||
![]() |
fe26655653 | ||
![]() |
2277fb5a58 | ||
![]() |
b6ab3095ab | ||
![]() |
591466d16d | ||
![]() |
666c12f2e8 | ||
![]() |
8180226eb7 | ||
![]() |
0d5be4a730 | ||
![]() |
49a2e2f3fa | ||
![]() |
219563146d | ||
![]() |
0d01295e79 | ||
![]() |
b65e195c27 | ||
![]() |
f5717cca1c | ||
![]() |
a2c9f6792e | ||
![]() |
95c12c1840 | ||
![]() |
4de1cb0a09 | ||
![]() |
ec9ebd3026 | ||
![]() |
5ee9ad3e4f | ||
![]() |
9e2135e2c7 | ||
![]() |
116da276c7 | ||
![]() |
7c9ab8c0b6 | ||
![]() |
3a36d9b1ae | ||
![]() |
5fce21e269 | ||
![]() |
a46bfc8b5f | ||
![]() |
a860c29304 | ||
![]() |
cc90b45022 | ||
![]() |
78ae4c42f7 | ||
![]() |
d1292c59ea | ||
![]() |
1a87c730bc | ||
![]() |
7aa72f768f | ||
![]() |
81b9f2d4e0 | ||
![]() |
a03a745295 | ||
![]() |
ce8bf90663 | ||
![]() |
e2ae919a84 | ||
![]() |
9a0e44a731 | ||
![]() |
650c816a7b | ||
![]() |
c8bfbb9315 | ||
![]() |
80b32dd392 | ||
![]() |
e3352ea426 | ||
![]() |
faed7683be | ||
![]() |
46a39190a4 | ||
![]() |
a2f738772c | ||
![]() |
a1697ff21c | ||
![]() |
b9fdf68be3 | ||
![]() |
ec971d0473 | ||
![]() |
d462464af9 | ||
![]() |
423e0768f9 | ||
![]() |
0ef3a141a8 | ||
![]() |
d532913d56 | ||
![]() |
3205bb3bdf | ||
![]() |
b238ba054d | ||
![]() |
a8e13df249 | ||
![]() |
9dc77d94ed | ||
![]() |
714995877a | ||
![]() |
729d7a11cd | ||
![]() |
95cd86a541 | ||
![]() |
ca1b8344fa | ||
![]() |
e797295b82 | ||
![]() |
f216b322c2 | ||
![]() |
b97c19ca3c | ||
![]() |
d54c7ca27c | ||
![]() |
077de8dcaa | ||
![]() |
6f50e5671a | ||
![]() |
bab9bdc832 | ||
![]() |
8e0adbb0fb | ||
![]() |
6b175ae7e3 | ||
![]() |
9494633da3 | ||
![]() |
c754a5f391 | ||
![]() |
2b692c6fc8 | ||
![]() |
35b6fb1a6d | ||
![]() |
61566a34d1 | ||
![]() |
e14f4c94c2 | ||
![]() |
52bdb1a80c | ||
![]() |
88ee3bdb6d | ||
![]() |
8f8a99a645 | ||
![]() |
8d35f22daf | ||
![]() |
6f8ef9bbdc | ||
![]() |
e7ddf6ba8f | ||
![]() |
407a119b9a | ||
![]() |
96aa2451bf | ||
![]() |
7680aab04d | ||
![]() |
3aef26b229 | ||
![]() |
935141adc0 | ||
![]() |
4300733d0c | ||
![]() |
c284a091c0 | ||
![]() |
7e768bfe23 | ||
![]() |
938f388a0b | ||
![]() |
d22a6fb8ea | ||
![]() |
11142e2f05 | ||
![]() |
fe7fb488c0 | ||
![]() |
0929c5086a | ||
![]() |
eb3ed7719b | ||
![]() |
6c19a0f8c7 | ||
![]() |
a0ece589b0 | ||
![]() |
8165071edf | ||
![]() |
a667974378 | ||
![]() |
fe1f88ce5d | ||
![]() |
8f2715e437 | ||
![]() |
57a3223c77 | ||
![]() |
df82ac8ac4 | ||
![]() |
16adddc803 | ||
![]() |
d1ae82c5c2 | ||
![]() |
0098936347 | ||
![]() |
17b85f6400 | ||
![]() |
48be9c0fd1 | ||
![]() |
ce36c2d0ea | ||
![]() |
06c63ef4a4 | ||
![]() |
03d93a7d6e | ||
![]() |
a0005c8b3e | ||
![]() |
2220343c50 | ||
![]() |
68fbed996f | ||
![]() |
20a4d8949d | ||
![]() |
1029ecfd49 | ||
![]() |
4de2a3b09e | ||
![]() |
651407c88e | ||
![]() |
3e2e485f66 | ||
![]() |
10efaad224 | ||
![]() |
3dda02660c | ||
![]() |
0b4e8141b0 | ||
![]() |
16ab0efa59 | ||
![]() |
20ca1ec547 | ||
![]() |
a1c9ab237f | ||
![]() |
a65239f7f1 | ||
![]() |
a0622675fd | ||
![]() |
1e0b778097 | ||
![]() |
3b666fef77 | ||
![]() |
9291c98189 | ||
![]() |
f6dadd8c82 | ||
![]() |
022bb272e6 | ||
![]() |
edad1a41e8 | ||
![]() |
421e78a748 | ||
![]() |
b961df90a7 | ||
![]() |
0b26b7098a | ||
![]() |
b09566a9a9 | ||
![]() |
b869deab66 | ||
![]() |
3d552c3112 | ||
![]() |
2a2bf3bf55 | ||
![]() |
26863b8cdc | ||
![]() |
b0f7d07214 | ||
![]() |
30c6557a32 | ||
![]() |
e18e173089 | ||
![]() |
6a8bdbd4f6 | ||
![]() |
b5dec87a62 | ||
![]() |
e50d30876a | ||
![]() |
cbcd9ed67d | ||
![]() |
6bcc26b487 | ||
![]() |
56fcb3fee1 | ||
![]() |
557e1790dd | ||
![]() |
2e67697d36 | ||
![]() |
ca4500692f | ||
![]() |
5aba6bff09 | ||
![]() |
9ed74068b9 | ||
![]() |
be4685742c | ||
![]() |
86b0a38811 | ||
![]() |
3e528f0a9a | ||
![]() |
170d7b6922 | ||
![]() |
f05249a9ad | ||
![]() |
5957ed7af3 | ||
![]() |
8ed63893eb | ||
![]() |
ea9dd926bc | ||
![]() |
f31d3b531f | ||
![]() |
d851448c32 | ||
![]() |
65327d52a6 | ||
![]() |
2ea5ae59b2 | ||
![]() |
a04d09028b | ||
![]() |
dd9255cb81 | ||
![]() |
536d7ecd3e | ||
![]() |
f50aac08df | ||
![]() |
db0f1d2159 | ||
![]() |
d97b565d6c | ||
![]() |
d3071f13d8 | ||
![]() |
768407c1d7 | ||
![]() |
c5d18b03cd | ||
![]() |
5f05b44cde | ||
![]() |
beaa09e9b3 | ||
![]() |
d6960f537b | ||
![]() |
0918eab004 | ||
![]() |
9b16789a17 | ||
![]() |
157240351f | ||
![]() |
6ad3d45d60 | ||
![]() |
b715e4d426 | ||
![]() |
aad5e9e99f | ||
![]() |
2a104ad33f | ||
![]() |
851290ee89 | ||
![]() |
a8c6c55e3b | ||
![]() |
992a647424 | ||
![]() |
c22461a1b6 | ||
![]() |
23fefc3ab7 | ||
![]() |
0beb9f0b5f | ||
![]() |
d376f9e7a3 | ||
![]() |
07e7bcd30b | ||
![]() |
95e86cb649 | ||
![]() |
802e5591ce | ||
![]() |
26d5730ad2 | ||
![]() |
8c7554e081 | ||
![]() |
9f5d47c320 | ||
![]() |
4aa452ce63 | ||
![]() |
7ef81ae10f | ||
![]() |
3628292afa | ||
![]() |
8aa5ecde62 | ||
![]() |
2f149eac9d | ||
![]() |
fcfc705b87 | ||
![]() |
7bd5c010a1 | ||
![]() |
52168d8e61 | ||
![]() |
a3842d9228 | ||
![]() |
452c51bd16 | ||
![]() |
22bedd9957 | ||
![]() |
cb318c723d | ||
![]() |
9a81d3c28e | ||
![]() |
3ca59e3b7a | ||
![]() |
07a12bdf15 | ||
![]() |
62e81d8bf0 | ||
![]() |
e295a41caa | ||
![]() |
c545a80aa3 | ||
![]() |
56f1a0cb51 | ||
![]() |
7218b6da97 | ||
![]() |
83f9f2d387 | ||
![]() |
13a2e38385 | ||
![]() |
996d942387 | ||
![]() |
cc42eb9fab | ||
![]() |
44125be979 | ||
![]() |
c2e9cc9a51 | ||
![]() |
fcd10f2adc | ||
![]() |
93009c1eed | ||
![]() |
d875be60d4 | ||
![]() |
db48d4c576 | ||
![]() |
3293231ad2 | ||
![]() |
aa1f2d3b59 | ||
![]() |
f492b679e3 | ||
![]() |
7ca84322bd | ||
![]() |
e3257b8fa3 | ||
![]() |
0bcda5ded8 | ||
![]() |
7ec82c0891 | ||
![]() |
3241ac7dc2 | ||
![]() |
e974605fc8 | ||
![]() |
ce13380533 | ||
![]() |
e23e3acda3 | ||
![]() |
63ab9972da | ||
![]() |
feb4901620 | ||
![]() |
67788a1b1b | ||
![]() |
001faf9ed7 | ||
![]() |
7a464d8a6e | ||
![]() |
5acd1c7c1b | ||
![]() |
de14540374 | ||
![]() |
37e0c2667b | ||
![]() |
252abb41c3 | ||
![]() |
fb2af341d8 | ||
![]() |
931f5f9c27 | ||
![]() |
b5d04e575e | ||
![]() |
3d395601fe | ||
![]() |
6630ce646c | ||
![]() |
f5508eea1c | ||
![]() |
c8c460432f | ||
![]() |
18299dafd2 | ||
![]() |
817d09026e | ||
![]() |
d76b009390 | ||
![]() |
9effed3ce1 | ||
![]() |
c1bbfc5dcf | ||
![]() |
b6c9cfb76f | ||
![]() |
59ca7bbcf2 | ||
![]() |
52c8d5e999 | ||
![]() |
5851e7f1b7 | ||
![]() |
e05b3441de | ||
![]() |
0d6e79cb93 | ||
![]() |
76a102d901 | ||
![]() |
bbd4659fbf | ||
![]() |
0880420ef6 | ||
![]() |
2351c79282 | ||
![]() |
d6016fc798 | ||
![]() |
bc17291006 | ||
![]() |
ed6cb14c4d | ||
![]() |
08de8a04b8 | ||
![]() |
5c67de8b47 | ||
![]() |
38b0408b1a | ||
![]() |
9ccad7ea86 | ||
![]() |
4a4e810a14 | ||
![]() |
76d2df3bde | ||
![]() |
c02563d894 | ||
![]() |
574ec6780b | ||
![]() |
9e0f56982b | ||
![]() |
1c66daf12b | ||
![]() |
59d683849e | ||
![]() |
9946acb1a0 | ||
![]() |
83a760644d | ||
![]() |
25ccff8640 | ||
![]() |
5c4c5a7794 | ||
![]() |
cb6af97595 | ||
![]() |
c4407dccf6 | ||
![]() |
ecdea4c3c8 | ||
![]() |
26d6f302cf | ||
![]() |
ecf10622ef | ||
![]() |
0fb553675b | ||
![]() |
2080fde4f9 | ||
![]() |
d10e67ce09 | ||
![]() |
74fe7c586b | ||
![]() |
05188aed6d | ||
![]() |
865efb7752 | ||
![]() |
4782b4da07 | ||
![]() |
4693632c7d | ||
![]() |
4c4b571a88 | ||
![]() |
328c87995b | ||
![]() |
a1d10e7d4a | ||
![]() |
77d9a7e9d3 | ||
![]() |
981b090088 | ||
![]() |
6d1c788ee0 | ||
![]() |
02de773d5b | ||
![]() |
2a240d83fd | ||
![]() |
25cdf7916d | ||
![]() |
11b5983a0d | ||
![]() |
4964987245 | ||
![]() |
ed129d6074 | ||
![]() |
37e928d869 | ||
![]() |
06def8c11e | ||
![]() |
10571676a4 | ||
![]() |
af5160237d | ||
![]() |
b86842ba73 | ||
![]() |
bfc271e743 | ||
![]() |
ee88140fdd | ||
![]() |
51249a1dce | ||
![]() |
57ec9e6b13 | ||
![]() |
1324d17d87 | ||
![]() |
26b438a888 | ||
![]() |
70f3f98363 | ||
![]() |
71e4be2d5e | ||
![]() |
5740806a28 | ||
![]() |
9b50a1b7a6 | ||
![]() |
19caad832e | ||
![]() |
dd6ae13281 | ||
![]() |
077abbe961 | ||
![]() |
3d85dc1127 | ||
![]() |
e3ea5dd13c | ||
![]() |
714b2ecd9c | ||
![]() |
883937bfd7 | ||
![]() |
0ebe08d796 | ||
![]() |
36b4fff5c7 | ||
![]() |
0684c8c388 | ||
![]() |
67744c877d | ||
![]() |
45d8c945e2 | ||
![]() |
ee19307ea2 | ||
![]() |
2c1cd25be4 | ||
![]() |
6e65558ea4 | ||
![]() |
304324ebd0 | ||
![]() |
97cd06d2ba | ||
![]() |
df948065a3 | ||
![]() |
f92126b44f | ||
![]() |
e329f6cdf1 | ||
![]() |
2c96438d61 | ||
![]() |
41a9aac75d | ||
![]() |
8768168536 | ||
![]() |
325809fbbf | ||
![]() |
3dd47a9f5b | ||
![]() |
00f16ef8f0 | ||
![]() |
5e67aae83b | ||
![]() |
ae5c603c98 | ||
![]() |
c62aa3cb55 | ||
![]() |
7073cb6d5c | ||
![]() |
a495ad58d0 | ||
![]() |
569165371c | ||
![]() |
ea14fa5251 | ||
![]() |
4a02865697 | ||
![]() |
3a2a20cefd | ||
![]() |
f2f42de701 | ||
![]() |
6d60d4897c | ||
![]() |
9f71ce8083 | ||
![]() |
50f8f7da93 | ||
![]() |
d475344b51 | ||
![]() |
2630863409 | ||
![]() |
e120f4a3f7 | ||
![]() |
6aff4c986c | ||
![]() |
2e891b1634 | ||
![]() |
35a0c5d36f | ||
![]() |
82d786b94c | ||
![]() |
de49d602a1 | ||
![]() |
3cb6511b66 | ||
![]() |
1d3ae777d5 | ||
![]() |
dbb2ea39d2 | ||
![]() |
fb607332b9 | ||
![]() |
b956f627b0 | ||
![]() |
2ac64ab573 | ||
![]() |
482e00970c | ||
![]() |
d62340efb5 | ||
![]() |
eb0f35219c | ||
![]() |
243598ae50 | ||
![]() |
74c965d21d | ||
![]() |
30316179a0 | ||
![]() |
f16a1101e6 | ||
![]() |
0466f7a18a | ||
![]() |
97cf3b2079 | ||
![]() |
6542d75a6a | ||
![]() |
c6900c5d51 | ||
![]() |
0e9642ea3e | ||
![]() |
3ab2892066 | ||
![]() |
69b69aca6a | ||
![]() |
a05dbd2e5a | ||
![]() |
c1641f6fb8 | ||
![]() |
452c79f9a1 | ||
![]() |
37959fe31c | ||
![]() |
30f73f39a0 | ||
![]() |
fa613cd5fb | ||
![]() |
b8afb22902 | ||
![]() |
07e07fc7e8 | ||
![]() |
58f95c1891 | ||
![]() |
7ad8e3b3da | ||
![]() |
b9a548758a | ||
![]() |
a436caf2ad | ||
![]() |
5461f8a225 | ||
![]() |
78b747571c | ||
![]() |
0c6a9a189b | ||
![]() |
45d666ff2d | ||
![]() |
4143925322 | ||
![]() |
9be3d2ccaf | ||
![]() |
8be8a310d7 | ||
![]() |
b81c339922 | ||
![]() |
d3d103f141 | ||
![]() |
dd575ccb88 | ||
![]() |
a83c7c64b5 | ||
![]() |
c04ded6fd8 | ||
![]() |
f24c779737 | ||
![]() |
3cdd358fc8 | ||
![]() |
e11939b149 | ||
![]() |
a4ef0702c9 | ||
![]() |
548b6352ca | ||
![]() |
2658c16073 | ||
![]() |
49d0b6aa00 | ||
![]() |
e65f584197 | ||
![]() |
ce1bbda188 | ||
![]() |
846897fb4c | ||
![]() |
457e134261 | ||
![]() |
3e129763c7 | ||
![]() |
c49d086965 | ||
![]() |
df7bfc4efd | ||
![]() |
7fba1f9ed2 | ||
![]() |
3205d52331 | ||
![]() |
111960c530 | ||
![]() |
e1bc1a0129 | ||
![]() |
8b543a5fa9 | ||
![]() |
dc7a67a1d7 | ||
![]() |
350c20d6ab | ||
![]() |
b5f0cd7c70 | ||
![]() |
90488cd77a | ||
![]() |
5bbc59e87c | ||
![]() |
c02758213b | ||
![]() |
09c62d67c1 | ||
![]() |
3f3fa3044c | ||
![]() |
62673145fb | ||
![]() |
0baf73de5e | ||
![]() |
66a0783e7b | ||
![]() |
17144c45e5 | ||
![]() |
311c0ba4f1 | ||
![]() |
e293d23ae3 | ||
![]() |
93769d2608 | ||
![]() |
e7540563d0 | ||
![]() |
fc1047550e | ||
![]() |
dadc618719 | ||
![]() |
36f3bd2869 | ||
![]() |
fdcea983a4 | ||
![]() |
081534457c | ||
![]() |
94a6272a1d | ||
![]() |
d389e0ecf8 | ||
![]() |
17eb1c604f | ||
![]() |
99474aab06 | ||
![]() |
f3d3bf20de | ||
![]() |
3c999e9847 | ||
![]() |
692fa5f606 | ||
![]() |
752b8e79ff | ||
![]() |
3f82cf4ab3 | ||
![]() |
1549b9df74 | ||
![]() |
78ef87a952 | ||
![]() |
29ede48e0f | ||
![]() |
6349d25219 | ||
![]() |
830a450f00 | ||
![]() |
18f9ce9c0b | ||
![]() |
2471be0c78 | ||
![]() |
60cfd687dc | ||
![]() |
e06c61b95d | ||
![]() |
471eee0872 | ||
![]() |
20abd8a9f8 | ||
![]() |
88e5c471de | ||
![]() |
09086e574d | ||
![]() |
8d95c13e31 | ||
![]() |
c922cc4351 | ||
![]() |
a42f28c502 | ||
![]() |
b802f3a71f | ||
![]() |
f78f212a77 | ||
![]() |
22cbfd473b | ||
![]() |
e5973ef713 | ||
![]() |
5364a29b5f | ||
![]() |
49754d33fa | ||
![]() |
d7d95037be | ||
![]() |
515146d4a2 | ||
![]() |
b7540fab58 | ||
![]() |
88e6f8abf6 | ||
![]() |
3c4dadd905 | ||
![]() |
f18f997796 | ||
![]() |
3a1daf46ae | ||
![]() |
8dffea4a42 | ||
![]() |
3852a6c5cf | ||
![]() |
6493f51a29 | ||
![]() |
028f42e775 | ||
![]() |
eb1cc55f94 | ||
![]() |
fb864f1132 | ||
![]() |
8b8d988c07 | ||
![]() |
c2b5451fe4 | ||
![]() |
487d3a6262 | ||
![]() |
fe990b4cd2 | ||
![]() |
019c7e2f78 | ||
![]() |
1c64a4f145 | ||
![]() |
fc869aa203 | ||
![]() |
3a0ada9f46 | ||
![]() |
cc9980fc19 | ||
![]() |
7515d8af64 | ||
![]() |
5e7579c1fd | ||
![]() |
38af53f281 | ||
![]() |
a26bec5b00 | ||
![]() |
feb943b6df | ||
![]() |
059e37a41f | ||
![]() |
7ce67fd465 | ||
![]() |
6b8b8209f3 | ||
![]() |
fd1d12859d | ||
![]() |
efb00b2387 | ||
![]() |
9b2ca57038 | ||
![]() |
9694face16 | ||
![]() |
7ef14832d0 | ||
![]() |
33f7b58e6e | ||
![]() |
9e992da863 | ||
![]() |
c986a218c7 | ||
![]() |
8ee6312402 | ||
![]() |
3c86b12ef9 | ||
![]() |
02d09edd49 | ||
![]() |
55af3c3dd1 | ||
![]() |
f8f5a77744 | ||
![]() |
5b6956ff24 | ||
![]() |
f1c138eaed | ||
![]() |
caf43638de | ||
![]() |
b783d2e210 | ||
![]() |
9a40a5f019 | ||
![]() |
81a7b34101 | ||
![]() |
f124e2a889 | ||
![]() |
02b2bcafc5 | ||
![]() |
81a5fd377e | ||
![]() |
f875ae4abf | ||
![]() |
01fd400ec7 | ||
![]() |
c59420581c | ||
![]() |
0aa9462cea | ||
![]() |
bf2f6f84e5 | ||
![]() |
5126f01b57 | ||
![]() |
ec4814a76e | ||
![]() |
69b53d70c5 | ||
![]() |
02875f5a34 | ||
![]() |
29d8c4e08d | ||
![]() |
df203311fe | ||
![]() |
10f9b91c44 | ||
![]() |
cd861364a2 | ||
![]() |
90b52abc04 | ||
![]() |
093b726c52 | ||
![]() |
fd84fc9dbe | ||
![]() |
4353646b3a | ||
![]() |
7545e5312c | ||
![]() |
bd494ce9ec | ||
![]() |
ee3cf8e6d1 | ||
![]() |
df524fdc1f | ||
![]() |
f0c0cfee1d | ||
![]() |
cf1bf3c163 | ||
![]() |
b9d703fe25 | ||
![]() |
46f7e685b6 | ||
![]() |
8023331fca | ||
![]() |
597db7d4bd | ||
![]() |
773bd32cd0 | ||
![]() |
c7e3756de1 | ||
![]() |
64bf122c95 | ||
![]() |
a92b0411fd | ||
![]() |
728d61762a | ||
![]() |
d9783e2a4d | ||
![]() |
b6303d2c16 | ||
![]() |
dd673a62b5 | ||
![]() |
b7577038a0 | ||
![]() |
613b71d23b | ||
![]() |
0284100c2d | ||
![]() |
26cd470d31 | ||
![]() |
ebaf509a42 | ||
![]() |
646db73061 | ||
![]() |
e6df581909 | ||
![]() |
a8e12409b5 | ||
![]() |
b7c7e293f7 | ||
![]() |
fe85aff052 | ||
![]() |
12d8bcad6e | ||
![]() |
bbfc244f16 | ||
![]() |
e275a2736a | ||
![]() |
d2a8076596 | ||
![]() |
83344f748f | ||
![]() |
1d5dbc454d | ||
![]() |
16e2dc60aa | ||
![]() |
a6fd4a8472 | ||
![]() |
c25698dfa7 | ||
![]() |
2ab2064a72 | ||
![]() |
356c26ce84 | ||
![]() |
bc56dfbcb5 | ||
![]() |
daaeb36363 | ||
![]() |
a46a9cf0bf | ||
![]() |
cbf435169a | ||
![]() |
7d05f6c54a | ||
![]() |
8a8667d1f4 | ||
![]() |
b9b8b764db | ||
![]() |
4978af351d | ||
![]() |
b5d639652d | ||
![]() |
c4ebfaf7f6 | ||
![]() |
256266280d | ||
![]() |
f886b58529 | ||
![]() |
14fe93b9ab | ||
![]() |
a2fb0ceb7d | ||
![]() |
bc284ecf6d | ||
![]() |
6113f586c9 | ||
![]() |
6c0862248c | ||
![]() |
0e8f2a7c6c | ||
![]() |
a808f8bbd5 | ||
![]() |
9428d5638e | ||
![]() |
e00cd5e304 | ||
![]() |
3c04bf2742 | ||
![]() |
2ef6d450bc | ||
![]() |
628b0bffeb | ||
![]() |
27eaa566a5 | ||
![]() |
fb36646bd3 | ||
![]() |
304cc37618 | ||
![]() |
8239e8a581 | ||
![]() |
69e117d898 | ||
![]() |
c773ec8a30 | ||
![]() |
b4b49ee096 | ||
![]() |
cf5ab87db9 | ||
![]() |
deaff293d2 | ||
![]() |
dccdebd2c0 | ||
![]() |
2674d4f034 | ||
![]() |
cb529561e1 | ||
![]() |
1a1cf49c67 | ||
![]() |
757b61a010 | ||
![]() |
448dcbab46 | ||
![]() |
30fc5bbb09 | ||
![]() |
d3e14818df | ||
![]() |
864e242ed9 | ||
![]() |
8f18baea8f | ||
![]() |
130489a1a9 | ||
![]() |
88a5a2049b | ||
![]() |
15fb3e5328 | ||
![]() |
90b800b030 | ||
![]() |
be88ad2676 | ||
![]() |
dfadfc0f13 | ||
![]() |
5ae48c8012 | ||
![]() |
6f163111ce | ||
![]() |
3bcbd05252 | ||
![]() |
e0d2697618 | ||
![]() |
7340535b9a | ||
![]() |
c385355c2b | ||
![]() |
a119790697 | ||
![]() |
e392098e35 | ||
![]() |
a2d4d16867 | ||
![]() |
7f74a85400 | ||
![]() |
1fc9eaf360 | ||
![]() |
1898f9b183 | ||
![]() |
1fb03a755f | ||
![]() |
8a505e3b66 | ||
![]() |
45ecec5623 | ||
![]() |
b34dfcd72f | ||
![]() |
319aa39925 | ||
![]() |
08ac40dd48 | ||
![]() |
0a0dc25e15 | ||
![]() |
0557a15fa8 | ||
![]() |
c5fafdda11 | ||
![]() |
434d1fe225 | ||
![]() |
405769dc97 | ||
![]() |
ffa116bf44 | ||
![]() |
6b1d8cabf4 | ||
![]() |
20c21e9e65 | ||
![]() |
088743a155 | ||
![]() |
d1fba28936 | ||
![]() |
65064a6934 | ||
![]() |
c7f5b7ae82 | ||
![]() |
2b244165e2 | ||
![]() |
47682bc143 | ||
![]() |
0dcfb97824 | ||
![]() |
50af671e02 | ||
![]() |
1ef273c35d | ||
![]() |
91b9831548 | ||
![]() |
3241968626 | ||
![]() |
5dee65afcb | ||
![]() |
4108eabd0d | ||
![]() |
829a693128 | ||
![]() |
bf1e49fc4c | ||
![]() |
d1984c0dda | ||
![]() |
22bb28db62 | ||
![]() |
721b52a45b | ||
![]() |
edf4f98d41 | ||
![]() |
7321ea1603 | ||
![]() |
b80c2126a3 | ||
![]() |
70afed9122 | ||
![]() |
87479c32de | ||
![]() |
35f75563a7 | ||
![]() |
f5d6a9f428 | ||
![]() |
2d314efb98 | ||
![]() |
930bac3c8b | ||
![]() |
d457f66e8b | ||
![]() |
98ef1ba579 | ||
![]() |
dd8514a84d | ||
![]() |
7bcfeab85c | ||
![]() |
d1cd03302c | ||
![]() |
36669652bf | ||
![]() |
48234896a4 | ||
![]() |
d4c310433e | ||
![]() |
a9d82a64a8 | ||
![]() |
74b6b8cb62 | ||
![]() |
52e8a1aba3 | ||
![]() |
0f92523d28 | ||
![]() |
6934fc6510 | ||
![]() |
ad746be010 | ||
![]() |
97119f729a | ||
![]() |
e576f1b0c4 | ||
![]() |
1771293fcf | ||
![]() |
d872423a76 | ||
![]() |
6c12f65b2d | ||
![]() |
566f50ec66 | ||
![]() |
e702f9c317 | ||
![]() |
680f8086e7 | ||
![]() |
9216f000ad | ||
![]() |
cea6ef7a66 | ||
![]() |
3287daf4e4 | ||
![]() |
376b40b25f | ||
![]() |
b00ca90d15 | ||
![]() |
06ec83701c | ||
![]() |
2b61ec32d0 | ||
![]() |
08299dd85d | ||
![]() |
a2e7199ff5 | ||
![]() |
28d68c8a77 | ||
![]() |
999abb7016 | ||
![]() |
5af2658d88 | ||
![]() |
46fd55d100 | ||
![]() |
fd4d747927 | ||
![]() |
ec3ea965d0 | ||
![]() |
41e4438a05 | ||
![]() |
254e0ea132 | ||
![]() |
190e24b25d | ||
![]() |
3f22aa9638 | ||
![]() |
0e679841a4 | ||
![]() |
e512e658e6 | ||
![]() |
f7cea2f92e | ||
![]() |
de5689f5b2 | ||
![]() |
e6cf5a5984 | ||
![]() |
23e7ccb543 | ||
![]() |
b4d97d4a2b | ||
![]() |
8f90fe79c8 | ||
![]() |
eb0df5d5e9 | ||
![]() |
e75510309d | ||
![]() |
3425d01853 | ||
![]() |
606737f3b2 | ||
![]() |
bf3b5fbf8e | ||
![]() |
f2fb06e6f3 | ||
![]() |
e58ba44e3d | ||
![]() |
1e19ec6b9a | ||
![]() |
6ed637cfdd | ||
![]() |
1d17e24c6e | ||
![]() |
de155a753d | ||
![]() |
1b4020b3d7 | ||
![]() |
b948750d55 | ||
![]() |
ce41ac9158 | ||
![]() |
5869467db3 | ||
![]() |
80472af53c | ||
![]() |
eaa6039082 | ||
![]() |
03b84e7c43 | ||
![]() |
0ee8f5c498 | ||
![]() |
41d15e8731 | ||
![]() |
bef081d353 | ||
![]() |
a79045c064 | ||
![]() |
24ae8249f9 | ||
![]() |
81341df635 | ||
![]() |
3c2bbf244d | ||
![]() |
fa60251c18 | ||
![]() |
5bd06494d5 | ||
![]() |
2fd217ef1f | ||
![]() |
d7068ca42b | ||
![]() |
f7b19cddbf | ||
![]() |
62e756a11e | ||
![]() |
c992661a17 | ||
![]() |
dde3205425 | ||
![]() |
15a264de3d | ||
![]() |
66929a9088 | ||
![]() |
45c9518a6d | ||
![]() |
95d32dc0da | ||
![]() |
d5ab1119d3 | ||
![]() |
d9110f4ef7 | ||
![]() |
5468394ef2 | ||
![]() |
7ad21e0e45 | ||
![]() |
5012c0c97c | ||
![]() |
698208fcd5 | ||
![]() |
e0d5fd9290 | ||
![]() |
4bd1974911 | ||
![]() |
d246e4090a | ||
![]() |
ff172f5ea1 | ||
![]() |
09b1413748 | ||
![]() |
14b997fe2c | ||
![]() |
908b412a9a | ||
![]() |
cbd80615be | ||
![]() |
a9707f0ab0 | ||
![]() |
4637e33326 | ||
![]() |
4a5f21dd87 | ||
![]() |
0778c2808b | ||
![]() |
567a1bb770 | ||
![]() |
743ee886be | ||
![]() |
20c6abae63 | ||
![]() |
ae0c585918 | ||
![]() |
4cfc416cdc | ||
![]() |
9902c4745d | ||
![]() |
e373ca7bdc | ||
![]() |
6a34a35585 | ||
![]() |
ee935a2988 | ||
![]() |
0a977a9d0a | ||
![]() |
4d26a3d2c6 | ||
![]() |
276d11e4e8 | ||
![]() |
e89c0f15dd | ||
![]() |
2bdf0aae14 | ||
![]() |
2bc7f0b8e0 | ||
![]() |
bf8ae22f3f | ||
![]() |
a5c6dab7c3 | ||
![]() |
f3eedec402 | ||
![]() |
741152dd50 | ||
![]() |
89c639f850 | ||
![]() |
727fb38baf | ||
![]() |
e19dd2d527 | ||
![]() |
9aa41b3524 | ||
![]() |
3911740360 | ||
![]() |
f161722b34 | ||
![]() |
adb956467b | ||
![]() |
00e17f4d69 | ||
![]() |
dbe49b24df | ||
![]() |
7e75193f4a | ||
![]() |
96e8cfb765 | ||
![]() |
d47ca6109a | ||
![]() |
8ac7d56fc5 | ||
![]() |
bfaede26c4 | ||
![]() |
477fd360f8 | ||
![]() |
5b50870f21 | ||
![]() |
8161316c01 | ||
![]() |
3cb26722f1 | ||
![]() |
7912f4a22a | ||
![]() |
851b23fb09 | ||
![]() |
849a108520 | ||
![]() |
97ff2e126c | ||
![]() |
ef6c4e6789 | ||
![]() |
3e467c517d | ||
![]() |
70ac696f17 | ||
![]() |
39c80ded58 | ||
![]() |
98782ca69d | ||
![]() |
c332eea354 | ||
![]() |
41a3f039b5 | ||
![]() |
2d3cf43bc5 | ||
![]() |
7c90702ff7 | ||
![]() |
952cf11b9e | ||
![]() |
4383550d98 | ||
![]() |
fcba2cca77 | ||
![]() |
2042b85056 | ||
![]() |
283a2ab648 | ||
![]() |
5e7b93d153 | ||
![]() |
c4ac35164b | ||
![]() |
22a13981f3 | ||
![]() |
29251b6e38 | ||
![]() |
b382f1412a | ||
![]() |
320537a054 | ||
![]() |
2fe7f8be46 | ||
![]() |
f100198a8a | ||
![]() |
b470fc0140 | ||
![]() |
db02d5eff0 | ||
![]() |
9564a9c28d | ||
![]() |
55295922c8 | ||
![]() |
c5b701f99d | ||
![]() |
3c606efc46 | ||
![]() |
cbab1a51f1 | ||
![]() |
41bcfcaffe | ||
![]() |
7cb14374cf | ||
![]() |
19b9fd0578 | ||
![]() |
d668c475de | ||
![]() |
248f3f2181 | ||
![]() |
bcd10f63ea | ||
![]() |
db9733f0d5 | ||
![]() |
e6aa213aa1 | ||
![]() |
f0fa726e71 | ||
![]() |
ae46ef7add | ||
![]() |
c87ca25f22 | ||
![]() |
ef627d53e5 | ||
![]() |
d0fcf3607d | ||
![]() |
3dbb7e5781 | ||
![]() |
9597358cb0 | ||
![]() |
c5a21a3b0e | ||
![]() |
f56ccec77f | ||
![]() |
489340a338 | ||
![]() |
e9e3d75383 | ||
![]() |
74a1e5ed86 | ||
![]() |
3d961a3dbb | ||
![]() |
604d56d0b8 | ||
![]() |
a82e259c1d | ||
![]() |
34ef99a8ab | ||
![]() |
fa389d19d0 | ||
![]() |
ebe70d996c | ||
![]() |
e83e13fd57 | ||
![]() |
02362ae5e1 | ||
![]() |
e0f324f61c | ||
![]() |
c422a081bf | ||
![]() |
72efb24b73 | ||
![]() |
ee7653a8cd | ||
![]() |
020abaf7d6 | ||
![]() |
6e2f6350e6 | ||
![]() |
6b939f7567 | ||
![]() |
c958a7c593 | ||
![]() |
8709ea4df0 | ||
![]() |
16d3041d7a | ||
![]() |
64b2037eda | ||
![]() |
4133001c73 | ||
![]() |
d5f46eedab | ||
![]() |
7a2a3e048e | ||
![]() |
20891a370b | ||
![]() |
ca412e0184 | ||
![]() |
8a89f5ae27 | ||
![]() |
dbea2acc8f | ||
![]() |
65167625c4 | ||
![]() |
77b23d3acb | ||
![]() |
5d8aa27831 | ||
![]() |
b15eda9466 | ||
![]() |
5eb4b975ae | ||
![]() |
3ce1e01d96 | ||
![]() |
afcefe3d04 | ||
![]() |
4726fe8b6f | ||
![]() |
201a4a7ef9 | ||
![]() |
93a6391f96 | ||
![]() |
782db3f324 | ||
![]() |
7610a0459e | ||
![]() |
927616decb | ||
![]() |
8b2b7bbe6d | ||
![]() |
ca30dbc832 | ||
![]() |
3c3c847db5 | ||
![]() |
b7a2601724 | ||
![]() |
1189df1fe6 | ||
![]() |
a37177703c | ||
![]() |
8df1324afd | ||
![]() |
a6e2708605 | ||
![]() |
0df91c31f1 | ||
![]() |
1718cf6504 | ||
![]() |
bec8d00232 | ||
![]() |
1471dd72a6 | ||
![]() |
588a786d73 | ||
![]() |
7e1c1da424 | ||
![]() |
94ebe3b61c | ||
![]() |
75c5ccccec | ||
![]() |
eb4c8e1b1e | ||
![]() |
73b1b942a9 | ||
![]() |
8c5ef111d8 | ||
![]() |
b6266ad18f | ||
![]() |
2635c3a1a0 | ||
![]() |
13ece25de0 | ||
![]() |
c69ece1d0e | ||
![]() |
07ec6ff7ab | ||
![]() |
66e23bd356 | ||
![]() |
abc58000b4 | ||
![]() |
5e3ef94697 | ||
![]() |
2daee375d0 | ||
![]() |
857944aabe | ||
![]() |
72f58d54a3 | ||
![]() |
668b068bb5 | ||
![]() |
9893ae9880 | ||
![]() |
9cdf2f046f | ||
![]() |
f7f841ce6d | ||
![]() |
e8a52d48cf | ||
![]() |
21eb253c57 | ||
![]() |
754286cb9a | ||
![]() |
08f5d9a92f | ||
![]() |
a1a61000ab | ||
![]() |
dd91d4264a | ||
![]() |
9d87ac5244 | ||
![]() |
3559e27cdd | ||
![]() |
4b9c79fa07 | ||
![]() |
ee197bf89f | ||
![]() |
31dac60d04 | ||
![]() |
75a7dd38a1 | ||
![]() |
d7cb7c78af | ||
![]() |
148f394679 | ||
![]() |
f57873fb1b | ||
![]() |
32754defef | ||
![]() |
0e83c94832 | ||
![]() |
d2a6f79612 | ||
![]() |
94db39e055 | ||
![]() |
d15773f282 | ||
![]() |
118e3703dc | ||
![]() |
9b1b620a9c | ||
![]() |
6d3feaebfd | ||
![]() |
781929e9a8 | ||
![]() |
5fc7c15039 | ||
![]() |
44f860d9b0 | ||
![]() |
0cfa5211e9 | ||
![]() |
d689a707a4 | ||
![]() |
55e1745889 | ||
![]() |
43ec058593 | ||
![]() |
a4d96061de | ||
![]() |
f1eecd146d | ||
![]() |
1663450c1f | ||
![]() |
d840308392 | ||
![]() |
a08467342c | ||
![]() |
d71d388c08 | ||
![]() |
e2093436ac | ||
![]() |
b7e2013589 | ||
![]() |
f31cee75f3 | ||
![]() |
ec27f3c053 | ||
![]() |
737f00df3a | ||
![]() |
e6804dad2f | ||
![]() |
1875e9852e | ||
![]() |
f021e7fcc3 | ||
![]() |
4cf9ed9d26 | ||
![]() |
f8b77d7ef7 | ||
![]() |
31850c3351 | ||
![]() |
446842ecfc | ||
![]() |
8159b7574c | ||
![]() |
7050f29cff | ||
![]() |
2f32565476 | ||
![]() |
ceeb2da3fe | ||
![]() |
6dc5c1de32 | ||
![]() |
a5ab6f2558 | ||
![]() |
f846c2934c | ||
![]() |
1adb5e724d | ||
![]() |
8fad13b500 | ||
![]() |
6b2ee0e301 | ||
![]() |
7a241950d4 | ||
![]() |
c1a1f6d74e | ||
![]() |
b99422da12 | ||
![]() |
2ec695fba7 | ||
![]() |
109c07b23b | ||
![]() |
bf34c955ff | ||
![]() |
6ece5240a5 | ||
![]() |
211fbf0cf6 | ||
![]() |
f2d635671d | ||
![]() |
8b204cac99 | ||
![]() |
d15c701510 | ||
![]() |
c73688d167 | ||
![]() |
32da039d5f | ||
![]() |
79da613cb6 | ||
![]() |
2973e4672a | ||
![]() |
18e0012a59 | ||
![]() |
2554ced198 | ||
![]() |
fad13c148e | ||
![]() |
dbaa606a9f | ||
![]() |
c0bccc6a95 | ||
![]() |
b1a4eec7be | ||
![]() |
bb8a0d26e2 | ||
![]() |
629a5dd61e | ||
![]() |
b21970fd53 | ||
![]() |
4279ba13e9 | ||
![]() |
28d70438ec | ||
![]() |
ca6454f9fd | ||
![]() |
0ffc9955b2 | ||
![]() |
cf53d0866a | ||
![]() |
3f19c0ed03 | ||
![]() |
355efadf87 | ||
![]() |
927a9781ad | ||
![]() |
70eb22df42 | ||
![]() |
f461485aa0 | ||
![]() |
b6f1ced455 | ||
![]() |
10f36870e6 | ||
![]() |
fdaf9e9b46 | ||
![]() |
57b709824f | ||
![]() |
c7b46ac861 | ||
![]() |
bf28a512c6 | ||
![]() |
4333bd58cf | ||
![]() |
96a29883cd | ||
![]() |
59e359ff98 | ||
![]() |
4603813896 |
@@ -1,9 +0,0 @@
|
||||
{
|
||||
"qpdf": {
|
||||
"version": "11.2.0"
|
||||
},
|
||||
"jbig2enc": {
|
||||
"version": "0.29",
|
||||
"git_tag": "0.29"
|
||||
}
|
||||
}
|
15
.codecov.yml
@@ -1,3 +1,15 @@
|
||||
codecov:
|
||||
require_ci_to_pass: true
|
||||
# https://docs.codecov.com/docs/flags#recommended-automatic-flag-management
|
||||
# Require each flag to have 1 upload before notification
|
||||
flag_management:
|
||||
individual_flags:
|
||||
- name: backend
|
||||
paths:
|
||||
- src/
|
||||
- name: frontend
|
||||
paths:
|
||||
- src-ui/
|
||||
# https://docs.codecov.com/docs/pull-request-comments
|
||||
# codecov will only comment if coverage changes
|
||||
comment:
|
||||
@@ -8,12 +20,9 @@ coverage:
|
||||
default:
|
||||
# https://docs.codecov.com/docs/commit-status#threshold
|
||||
threshold: 1%
|
||||
# https://docs.codecov.com/docs/commit-status#only_pulls
|
||||
only_pulls: true
|
||||
patch:
|
||||
default:
|
||||
# For the changed lines only, target 75% covered, but
|
||||
# allow as low as 50%
|
||||
target: 75%
|
||||
threshold: 25%
|
||||
only_pulls: true
|
||||
|
3
.codespellrc
Normal file
@@ -0,0 +1,3 @@
|
||||
[codespell]
|
||||
write-changes = True
|
||||
ignore-words-list = criterias,afterall,valeu,ureue,equest,ure
|
@@ -1,21 +1,28 @@
|
||||
# Tool caches
|
||||
**/__pycache__
|
||||
/src-ui/.vscode
|
||||
/src-ui/node_modules
|
||||
/src-ui/dist
|
||||
**/.ruff_cache/
|
||||
**/.mypy_cache/
|
||||
# Virtual environment & similar
|
||||
.venv/
|
||||
./src-ui/node_modules
|
||||
./src-ui/dist
|
||||
# IDE folders
|
||||
.idea/
|
||||
.vscode/
|
||||
./src-ui/.vscode
|
||||
# VCS
|
||||
.git
|
||||
/export
|
||||
/consume
|
||||
/media
|
||||
/data
|
||||
/docs
|
||||
.pytest_cache
|
||||
/dist
|
||||
/scripts
|
||||
/resources
|
||||
# Test related
|
||||
**/.pytest_cache
|
||||
**/tests
|
||||
**/*.spec.ts
|
||||
**/htmlcov
|
||||
/src/.pytest_cache
|
||||
.idea
|
||||
.venv/
|
||||
.vscode/
|
||||
# Local folders
|
||||
./export
|
||||
./consume
|
||||
./media
|
||||
./data
|
||||
./docs
|
||||
./dist
|
||||
./scripts
|
||||
./resources
|
||||
|
1
.env
@@ -1,2 +1 @@
|
||||
COMPOSE_PROJECT_NAME=paperless
|
||||
export PROMPT="(pipenv-projectname)$P$G"
|
||||
|
14
.github/DISCUSSION_TEMPLATE/feature-requests.yml
vendored
Normal file
@@ -0,0 +1,14 @@
|
||||
title: "[Feature Request] "
|
||||
body:
|
||||
- type: textarea
|
||||
id: description
|
||||
attributes:
|
||||
label: Description
|
||||
description: A clear and concise description of what you would like to see.
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: other
|
||||
attributes:
|
||||
label: Other
|
||||
description: Add any other context or information about the feature request here.
|
24
.github/ISSUE_TEMPLATE/bug-report.yml
vendored
@@ -6,14 +6,21 @@ body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
Have a question? 👉 [Start a new discussion](https://github.com/paperless-ngx/paperless-ngx/discussions/new) or [ask in chat](https://matrix.to/#/#paperlessngx:matrix.org).
|
||||
### ⚠️ Please remember: issues are for *bugs*
|
||||
That is, something you believe affects every single user of Paperless-ngx, not just you. If you're not sure, start with one of the other options below.
|
||||
|
||||
Before opening an issue, please double check:
|
||||
Also, note that **Paperless-ngx does not perform OCR itself**, that is handled by other tools. Problems with OCR of specific files should likely be raised 'upstream', see https://github.com/ocrmypdf/OCRmyPDF/issues or https://github.com/tesseract-ocr/tesseract/issues
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
#### Have a question? 👉 [Start a new discussion](https://github.com/paperless-ngx/paperless-ngx/discussions/new) or [ask in chat](https://matrix.to/#/#paperlessngx:matrix.org).
|
||||
|
||||
#### Before opening an issue, please double check:
|
||||
|
||||
- [The troubleshooting documentation](https://docs.paperless-ngx.com/troubleshooting/).
|
||||
- [The installation instructions](https://docs.paperless-ngx.com/setup/#installation).
|
||||
- [Existing issues and discussions](https://github.com/paperless-ngx/paperless-ngx/search?q=&type=issues).
|
||||
- Disable any customer container initialization scripts, if using any
|
||||
- Disable any custom container initialization scripts, if using
|
||||
|
||||
If you encounter issues while installing or configuring Paperless-ngx, please post in the ["Support" section of the discussions](https://github.com/paperless-ngx/paperless-ngx/discussions/new?category=support).
|
||||
- type: textarea
|
||||
@@ -95,3 +102,14 @@ body:
|
||||
attributes:
|
||||
label: Other
|
||||
description: Any other relevant details.
|
||||
- type: checkboxes
|
||||
id: required-checks
|
||||
attributes:
|
||||
label: Please confirm the following
|
||||
options:
|
||||
- label: I believe this issue is a bug that affects all users of Paperless-ngx, not something specific to my installation.
|
||||
required: true
|
||||
- label: I have already searched for relevant existing issues and discussions before opening this report.
|
||||
required: true
|
||||
- label: I have updated the title field above with a concise description.
|
||||
required: true
|
||||
|
20
.github/PULL_REQUEST_TEMPLATE.md
vendored
@@ -8,7 +8,11 @@ Note: All PRs with code changes should be targeted to the `dev` branch, pure doc
|
||||
Please include a summary of the change and which issue is fixed (if any) and any relevant motivation / context. List any dependencies that are required for this change. If appropriate, please include an explanation of how your proposed change can be tested. Screenshots and / or videos can also be helpful if appropriate.
|
||||
-->
|
||||
|
||||
Fixes # (issue)
|
||||
<!--
|
||||
⚠️ Important: Pull requests that implement a new feature or enhancement *should almost always target an existing feature request* with evidence of community interest and discussion. This is in order to balance the work of implementing and maintaining new features / enhancements. If that is not currently the case, please open a feature request instead of this PR to gather feedback from both users and the project maintainers.
|
||||
-->
|
||||
|
||||
Closes #(issue or discussion)
|
||||
|
||||
## Type of change
|
||||
|
||||
@@ -17,14 +21,20 @@ What type of change does your PR introduce to Paperless-ngx?
|
||||
NOTE: Please check only one box!
|
||||
-->
|
||||
|
||||
- [ ] Bug fix (non-breaking change which fixes an issue)
|
||||
- [ ] New feature (non-breaking change which adds functionality)
|
||||
- [ ] Breaking change (fix or feature that would cause existing functionality to not work as expected)
|
||||
- [ ] Other (please explain)
|
||||
- [ ] Bug fix: non-breaking change which fixes an issue.
|
||||
- [ ] New feature / Enhancement: non-breaking change which adds functionality. _Please read the important note above._
|
||||
- [ ] Breaking change: fix or feature that would cause existing functionality to not work as expected.
|
||||
- [ ] Documentation only.
|
||||
- [ ] Other. Please explain:
|
||||
|
||||
## Checklist:
|
||||
|
||||
<!--
|
||||
NOTE: PRs that do not address the following will not be merged, please do not skip any relevant items.
|
||||
-->
|
||||
|
||||
- [ ] I have read & agree with the [contributing guidelines](https://github.com/paperless-ngx/paperless-ngx/blob/main/CONTRIBUTING.md).
|
||||
- [ ] If applicable, I have included testing coverage for new code in this PR, for [backend](https://docs.paperless-ngx.com/development/#testing) and / or [front-end](https://docs.paperless-ngx.com/development/#testing-and-code-style) changes.
|
||||
- [ ] If applicable, I have tested my code for new features & regressions on both mobile & desktop devices, using the latest version of major browsers.
|
||||
- [ ] If applicable, I have checked that all tests pass, see [documentation](https://docs.paperless-ngx.com/development/#back-end-development).
|
||||
- [ ] I have run all `pre-commit` hooks, see [documentation](https://docs.paperless-ngx.com/development/#code-formatting-with-pre-commit-hooks).
|
||||
|
44
.github/dependabot.yml
vendored
@@ -8,7 +8,7 @@ updates:
|
||||
target-branch: "dev"
|
||||
# Look for `package.json` and `lock` files in the `/src-ui` directory
|
||||
directory: "/src-ui"
|
||||
# Check the npm registry for updates every month
|
||||
open-pull-requests-limit: 10
|
||||
schedule:
|
||||
interval: "monthly"
|
||||
labels:
|
||||
@@ -17,6 +17,21 @@ updates:
|
||||
# Add reviewers
|
||||
reviewers:
|
||||
- "paperless-ngx/frontend"
|
||||
groups:
|
||||
frontend-angular-dependencies:
|
||||
patterns:
|
||||
- "@angular*"
|
||||
- "@ng-*"
|
||||
- "ngx-*"
|
||||
- "ng2-pdf-viewer"
|
||||
frontend-jest-dependencies:
|
||||
patterns:
|
||||
- "@types/jest"
|
||||
- "jest*"
|
||||
frontend-eslint-dependencies:
|
||||
patterns:
|
||||
- "@typescript-eslint*"
|
||||
- "eslint"
|
||||
|
||||
# Enable version updates for Python
|
||||
- package-ecosystem: "pip"
|
||||
@@ -32,8 +47,27 @@ updates:
|
||||
# Add reviewers
|
||||
reviewers:
|
||||
- "paperless-ngx/backend"
|
||||
ignore:
|
||||
- dependency-name: "uvicorn"
|
||||
groups:
|
||||
development:
|
||||
patterns:
|
||||
- "*pytest*"
|
||||
- "black"
|
||||
- "ruff"
|
||||
- "mkdocs-material"
|
||||
django:
|
||||
patterns:
|
||||
- "*django*"
|
||||
major-versions:
|
||||
update-types:
|
||||
- "major"
|
||||
small-changes:
|
||||
update-types:
|
||||
- "minor"
|
||||
- "patch"
|
||||
|
||||
# Enable updates for Github Actions
|
||||
# Enable updates for GitHub Actions
|
||||
- package-ecosystem: "github-actions"
|
||||
target-branch: "dev"
|
||||
directory: "/"
|
||||
@@ -46,3 +80,9 @@ updates:
|
||||
# Add reviewers
|
||||
reviewers:
|
||||
- "paperless-ngx/ci-cd"
|
||||
groups:
|
||||
actions:
|
||||
update-types:
|
||||
- "major"
|
||||
- "minor"
|
||||
- "patch"
|
||||
|
4
.github/release-drafter.yml
vendored
@@ -40,7 +40,7 @@ categories:
|
||||
labels:
|
||||
- 'frontend'
|
||||
- 'backend'
|
||||
collapse-after: 0
|
||||
collapse-after: 1
|
||||
include-labels:
|
||||
- 'enhancement'
|
||||
- 'bug'
|
||||
@@ -54,6 +54,8 @@ include-labels:
|
||||
- 'ci-cd'
|
||||
- 'breaking-change'
|
||||
- 'notable'
|
||||
exclude-labels:
|
||||
- 'skip-changelog'
|
||||
category-template: '### $TITLE'
|
||||
change-template: '- $TITLE @$AUTHOR ([#$NUMBER]($URL))'
|
||||
change-title-escapes: '\<*_&#@'
|
||||
|
491
.github/scripts/cleanup-tags.py
vendored
@@ -1,491 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
import json
|
||||
import logging
|
||||
import os
|
||||
import shutil
|
||||
import subprocess
|
||||
from argparse import ArgumentParser
|
||||
from typing import Dict
|
||||
from typing import Final
|
||||
from typing import Iterator
|
||||
from typing import List
|
||||
from typing import Optional
|
||||
|
||||
from common import get_log_level
|
||||
from github import ContainerPackage
|
||||
from github import GithubBranchApi
|
||||
from github import GithubContainerRegistryApi
|
||||
|
||||
logger = logging.getLogger("cleanup-tags")
|
||||
|
||||
|
||||
class ImageProperties:
|
||||
"""
|
||||
Data class wrapping the properties of an entry in the image index
|
||||
manifests list. It is NOT an actual image with layers, etc
|
||||
|
||||
https://docs.docker.com/registry/spec/manifest-v2-2/
|
||||
https://github.com/opencontainers/image-spec/blob/main/manifest.md
|
||||
https://github.com/opencontainers/image-spec/blob/main/descriptor.md
|
||||
"""
|
||||
|
||||
def __init__(self, data: Dict) -> None:
|
||||
self._data = data
|
||||
# This is the sha256: digest string. Corresponds to GitHub API name
|
||||
# if the package is an untagged package
|
||||
self.digest = self._data["digest"]
|
||||
platform_data_os = self._data["platform"]["os"]
|
||||
platform_arch = self._data["platform"]["architecture"]
|
||||
platform_variant = self._data["platform"].get(
|
||||
"variant",
|
||||
"",
|
||||
)
|
||||
self.platform = f"{platform_data_os}/{platform_arch}{platform_variant}"
|
||||
|
||||
|
||||
class ImageIndex:
|
||||
"""
|
||||
Data class wrapping up logic for an OCI Image Index
|
||||
JSON data. Primary use is to access the manifests listing
|
||||
|
||||
See https://github.com/opencontainers/image-spec/blob/main/image-index.md
|
||||
"""
|
||||
|
||||
def __init__(self, package_url: str, tag: str) -> None:
|
||||
self.qualified_name = f"{package_url}:{tag}"
|
||||
logger.info(f"Getting image index for {self.qualified_name}")
|
||||
try:
|
||||
proc = subprocess.run(
|
||||
[
|
||||
shutil.which("docker"),
|
||||
"buildx",
|
||||
"imagetools",
|
||||
"inspect",
|
||||
"--raw",
|
||||
self.qualified_name,
|
||||
],
|
||||
capture_output=True,
|
||||
check=True,
|
||||
)
|
||||
|
||||
self._data = json.loads(proc.stdout)
|
||||
|
||||
except subprocess.CalledProcessError as e:
|
||||
logger.error(
|
||||
f"Failed to get image index for {self.qualified_name}: {e.stderr}",
|
||||
)
|
||||
raise e
|
||||
|
||||
@property
|
||||
def image_pointers(self) -> Iterator[ImageProperties]:
|
||||
for manifest_data in self._data["manifests"]:
|
||||
yield ImageProperties(manifest_data)
|
||||
|
||||
|
||||
class RegistryTagsCleaner:
|
||||
"""
|
||||
This is the base class for the image registry cleaning. Given a package
|
||||
name, it will keep all images which are tagged and all untagged images
|
||||
referred to by a manifest. This results in only images which have been untagged
|
||||
and cannot be referenced except by their SHA in being removed. None of these
|
||||
images should be referenced, so it is fine to delete them.
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
package_name: str,
|
||||
repo_owner: str,
|
||||
repo_name: str,
|
||||
package_api: GithubContainerRegistryApi,
|
||||
branch_api: Optional[GithubBranchApi],
|
||||
):
|
||||
self.actually_delete = False
|
||||
self.package_api = package_api
|
||||
self.branch_api = branch_api
|
||||
self.package_name = package_name
|
||||
self.repo_owner = repo_owner
|
||||
self.repo_name = repo_name
|
||||
self.tags_to_delete: List[str] = []
|
||||
self.tags_to_keep: List[str] = []
|
||||
|
||||
# Get the information about all versions of the given package
|
||||
# These are active, not deleted, the default returned from the API
|
||||
self.all_package_versions = self.package_api.get_active_package_versions(
|
||||
self.package_name,
|
||||
)
|
||||
|
||||
# Get a mapping from a tag like "1.7.0" or "feature-xyz" to the ContainerPackage
|
||||
# tagged with it. It makes certain lookups easy
|
||||
self.all_pkgs_tags_to_version: Dict[str, ContainerPackage] = {}
|
||||
for pkg in self.all_package_versions:
|
||||
for tag in pkg.tags:
|
||||
self.all_pkgs_tags_to_version[tag] = pkg
|
||||
logger.info(
|
||||
f"Located {len(self.all_package_versions)} versions of package {self.package_name}",
|
||||
)
|
||||
|
||||
self.decide_what_tags_to_keep()
|
||||
|
||||
def clean(self):
|
||||
"""
|
||||
This method will delete image versions, based on the selected tags to delete.
|
||||
It behaves more like an unlinking than actual deletion. Removing the tag
|
||||
simply removes a pointer to an image, but the actual image data remains accessible
|
||||
if one has the sha256 digest of it.
|
||||
"""
|
||||
for tag_to_delete in self.tags_to_delete:
|
||||
package_version_info = self.all_pkgs_tags_to_version[tag_to_delete]
|
||||
|
||||
if self.actually_delete:
|
||||
logger.info(
|
||||
f"Deleting {tag_to_delete} (id {package_version_info.id})",
|
||||
)
|
||||
self.package_api.delete_package_version(
|
||||
package_version_info,
|
||||
)
|
||||
|
||||
else:
|
||||
logger.info(
|
||||
f"Would delete {tag_to_delete} (id {package_version_info.id})",
|
||||
)
|
||||
else:
|
||||
logger.info("No tags to delete")
|
||||
|
||||
def clean_untagged(self, is_manifest_image: bool):
|
||||
"""
|
||||
This method will delete untagged images, that is those which are not named. It
|
||||
handles if the image tag is actually a manifest, which points to images that look otherwise
|
||||
untagged.
|
||||
"""
|
||||
|
||||
def _clean_untagged_manifest():
|
||||
"""
|
||||
|
||||
Handles the deletion of untagged images, but where the package is a manifest, ie a multi
|
||||
arch image, which means some "untagged" images need to exist still.
|
||||
|
||||
Ok, bear with me, these are annoying.
|
||||
|
||||
Our images are multi-arch, so the manifest is more like a pointer to a sha256 digest.
|
||||
These images are untagged, but pointed to, and so should not be removed (or every pull fails).
|
||||
|
||||
So for each image getting kept, parse the manifest to find the digest(s) it points to. Then
|
||||
remove those from the list of untagged images. The final result is the untagged, not pointed to
|
||||
version which should be safe to remove.
|
||||
|
||||
Example:
|
||||
Tag: ghcr.io/paperless-ngx/paperless-ngx:1.7.1 refers to
|
||||
amd64: sha256:b9ed4f8753bbf5146547671052d7e91f68cdfc9ef049d06690b2bc866fec2690
|
||||
armv7: sha256:81605222df4ba4605a2ba4893276e5d08c511231ead1d5da061410e1bbec05c3
|
||||
arm64: sha256:374cd68db40734b844705bfc38faae84cc4182371de4bebd533a9a365d5e8f3b
|
||||
each of which appears as untagged image, but isn't really.
|
||||
|
||||
So from the list of untagged packages, remove those digests. Once all tags which
|
||||
are being kept are checked, the remaining untagged packages are actually untagged
|
||||
with no referrals in a manifest to them.
|
||||
"""
|
||||
# Simplify the untagged data, mapping name (which is a digest) to the version
|
||||
# At the moment, these are the images which APPEAR untagged.
|
||||
untagged_versions = {}
|
||||
for x in self.all_package_versions:
|
||||
if x.untagged:
|
||||
untagged_versions[x.name] = x
|
||||
|
||||
skips = 0
|
||||
|
||||
# Parse manifests to locate digests pointed to
|
||||
for tag in sorted(self.tags_to_keep):
|
||||
try:
|
||||
image_index = ImageIndex(
|
||||
f"ghcr.io/{self.repo_owner}/{self.package_name}",
|
||||
tag,
|
||||
)
|
||||
for manifest in image_index.image_pointers:
|
||||
|
||||
if manifest.digest in untagged_versions:
|
||||
logger.info(
|
||||
f"Skipping deletion of {manifest.digest},"
|
||||
f" referred to by {image_index.qualified_name}"
|
||||
f" for {manifest.platform}",
|
||||
)
|
||||
del untagged_versions[manifest.digest]
|
||||
skips += 1
|
||||
|
||||
except Exception as err:
|
||||
self.actually_delete = False
|
||||
logger.exception(err)
|
||||
return
|
||||
|
||||
logger.info(
|
||||
f"Skipping deletion of {skips} packages referred to by a manifest",
|
||||
)
|
||||
|
||||
# Delete the untagged and not pointed at packages
|
||||
logger.info(f"Deleting untagged packages of {self.package_name}")
|
||||
for to_delete_name in untagged_versions:
|
||||
to_delete_version = untagged_versions[to_delete_name]
|
||||
|
||||
if self.actually_delete:
|
||||
logger.info(
|
||||
f"Deleting id {to_delete_version.id} named {to_delete_version.name}",
|
||||
)
|
||||
self.package_api.delete_package_version(
|
||||
to_delete_version,
|
||||
)
|
||||
else:
|
||||
logger.info(
|
||||
f"Would delete {to_delete_name} (id {to_delete_version.id})",
|
||||
)
|
||||
|
||||
def _clean_untagged_non_manifest():
|
||||
"""
|
||||
If the package is not a multi-arch manifest, images without tags are safe to delete.
|
||||
"""
|
||||
|
||||
for package in self.all_package_versions:
|
||||
if package.untagged:
|
||||
if self.actually_delete:
|
||||
logger.info(
|
||||
f"Deleting id {package.id} named {package.name}",
|
||||
)
|
||||
self.package_api.delete_package_version(
|
||||
package,
|
||||
)
|
||||
else:
|
||||
logger.info(
|
||||
f"Would delete {package.name} (id {package.id})",
|
||||
)
|
||||
else:
|
||||
logger.info(
|
||||
f"Not deleting tag {package.tags[0]} of package {self.package_name}",
|
||||
)
|
||||
|
||||
logger.info("Beginning untagged image cleaning")
|
||||
|
||||
if is_manifest_image:
|
||||
_clean_untagged_manifest()
|
||||
else:
|
||||
_clean_untagged_non_manifest()
|
||||
|
||||
def decide_what_tags_to_keep(self):
|
||||
"""
|
||||
This method holds the logic to delete what tags to keep and there fore
|
||||
what tags to delete.
|
||||
|
||||
By default, any image with at least 1 tag will be kept
|
||||
"""
|
||||
# By default, keep anything which is tagged
|
||||
self.tags_to_keep = list(set(self.all_pkgs_tags_to_version.keys()))
|
||||
|
||||
def check_remaining_tags_valid(self):
|
||||
"""
|
||||
Checks the non-deleted tags are still valid. The assumption is if the
|
||||
manifest is can be inspected and each image manifest if points to can be
|
||||
inspected, the image will still pull.
|
||||
|
||||
https://github.com/opencontainers/image-spec/blob/main/image-index.md
|
||||
"""
|
||||
logger.info("Beginning confirmation step")
|
||||
a_tag_failed = False
|
||||
for tag in sorted(self.tags_to_keep):
|
||||
|
||||
try:
|
||||
image_index = ImageIndex(
|
||||
f"ghcr.io/{self.repo_owner}/{self.package_name}",
|
||||
tag,
|
||||
)
|
||||
for manifest in image_index.image_pointers:
|
||||
logger.info(f"Checking {manifest.digest} for {manifest.platform}")
|
||||
|
||||
# This follows the pointer from the index to an actual image, layers and all
|
||||
# Note the format is @
|
||||
digest_name = f"ghcr.io/{self.repo_owner}/{self.package_name}@{manifest.digest}"
|
||||
|
||||
try:
|
||||
|
||||
subprocess.run(
|
||||
[
|
||||
shutil.which("docker"),
|
||||
"buildx",
|
||||
"imagetools",
|
||||
"inspect",
|
||||
"--raw",
|
||||
digest_name,
|
||||
],
|
||||
capture_output=True,
|
||||
check=True,
|
||||
)
|
||||
except subprocess.CalledProcessError as e:
|
||||
logger.error(f"Failed to inspect digest: {e.stderr}")
|
||||
a_tag_failed = True
|
||||
except subprocess.CalledProcessError as e:
|
||||
a_tag_failed = True
|
||||
logger.error(f"Failed to inspect: {e.stderr}")
|
||||
continue
|
||||
|
||||
if a_tag_failed:
|
||||
raise Exception("At least one image tag failed to inspect")
|
||||
|
||||
|
||||
class MainImageTagsCleaner(RegistryTagsCleaner):
|
||||
def decide_what_tags_to_keep(self):
|
||||
"""
|
||||
Overrides the default logic for deciding what images to keep. Images tagged as "feature-"
|
||||
will be removed, if the corresponding branch no longer exists.
|
||||
"""
|
||||
|
||||
# Default to everything gets kept still
|
||||
super().decide_what_tags_to_keep()
|
||||
|
||||
# Locate the feature branches
|
||||
feature_branches = {}
|
||||
for branch in self.branch_api.get_branches(
|
||||
repo=self.repo_name,
|
||||
):
|
||||
if branch.name.startswith("feature-"):
|
||||
logger.debug(f"Found feature branch {branch.name}")
|
||||
feature_branches[branch.name] = branch
|
||||
|
||||
logger.info(f"Located {len(feature_branches)} feature branches")
|
||||
|
||||
if not len(feature_branches):
|
||||
# Our work here is done, delete nothing
|
||||
return
|
||||
|
||||
# Filter to packages which are tagged with feature-*
|
||||
packages_tagged_feature: List[ContainerPackage] = []
|
||||
for package in self.all_package_versions:
|
||||
if package.tag_matches("feature-"):
|
||||
packages_tagged_feature.append(package)
|
||||
|
||||
# Map tags like "feature-xyz" to a ContainerPackage
|
||||
feature_pkgs_tags_to_versions: Dict[str, ContainerPackage] = {}
|
||||
for pkg in packages_tagged_feature:
|
||||
for tag in pkg.tags:
|
||||
feature_pkgs_tags_to_versions[tag] = pkg
|
||||
|
||||
logger.info(
|
||||
f'Located {len(feature_pkgs_tags_to_versions)} versions of package {self.package_name} tagged "feature-"',
|
||||
)
|
||||
|
||||
# All the feature tags minus all the feature branches leaves us feature tags
|
||||
# with no corresponding branch
|
||||
self.tags_to_delete = list(
|
||||
set(feature_pkgs_tags_to_versions.keys()) - set(feature_branches.keys()),
|
||||
)
|
||||
|
||||
# All the tags minus the set of going to be deleted tags leaves us the
|
||||
# tags which will be kept around
|
||||
self.tags_to_keep = list(
|
||||
set(self.all_pkgs_tags_to_version.keys()) - set(self.tags_to_delete),
|
||||
)
|
||||
logger.info(
|
||||
f"Located {len(self.tags_to_delete)} versions of package {self.package_name} to delete",
|
||||
)
|
||||
|
||||
|
||||
class LibraryTagsCleaner(RegistryTagsCleaner):
|
||||
"""
|
||||
Exists for the off chance that someday, the installer library images
|
||||
will need their own logic
|
||||
"""
|
||||
|
||||
pass
|
||||
|
||||
|
||||
def _main():
|
||||
parser = ArgumentParser(
|
||||
description="Using the GitHub API locate and optionally delete container"
|
||||
" tags which no longer have an associated feature branch",
|
||||
)
|
||||
|
||||
# Requires an affirmative command to actually do a delete
|
||||
parser.add_argument(
|
||||
"--delete",
|
||||
action="store_true",
|
||||
default=False,
|
||||
help="If provided, actually delete the container tags",
|
||||
)
|
||||
|
||||
# When a tagged image is updated, the previous version remains, but it no longer tagged
|
||||
# Add this option to remove them as well
|
||||
parser.add_argument(
|
||||
"--untagged",
|
||||
action="store_true",
|
||||
default=False,
|
||||
help="If provided, delete untagged containers as well",
|
||||
)
|
||||
|
||||
# If given, the package is assumed to be a multi-arch manifest. Cache packages are
|
||||
# not multi-arch, all other types are
|
||||
parser.add_argument(
|
||||
"--is-manifest",
|
||||
action="store_true",
|
||||
default=False,
|
||||
help="If provided, the package is assumed to be a multi-arch manifest following schema v2",
|
||||
)
|
||||
|
||||
# Allows configuration of log level for debugging
|
||||
parser.add_argument(
|
||||
"--loglevel",
|
||||
default="info",
|
||||
help="Configures the logging level",
|
||||
)
|
||||
|
||||
# Get the name of the package being processed this round
|
||||
parser.add_argument(
|
||||
"package",
|
||||
help="The package to process",
|
||||
)
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
logging.basicConfig(
|
||||
level=get_log_level(args),
|
||||
datefmt="%Y-%m-%d %H:%M:%S",
|
||||
format="%(asctime)s %(levelname)-8s %(message)s",
|
||||
)
|
||||
|
||||
# Must be provided in the environment
|
||||
repo_owner: Final[str] = os.environ["GITHUB_REPOSITORY_OWNER"]
|
||||
repo: Final[str] = os.environ["GITHUB_REPOSITORY"]
|
||||
gh_token: Final[str] = os.environ["TOKEN"]
|
||||
|
||||
# Find all branches named feature-*
|
||||
# Note: Only relevant to the main application, but simpler to
|
||||
# leave in for all packages
|
||||
with GithubBranchApi(gh_token) as branch_api:
|
||||
with GithubContainerRegistryApi(gh_token, repo_owner) as container_api:
|
||||
if args.package in {"paperless-ngx", "paperless-ngx/builder/cache/app"}:
|
||||
cleaner = MainImageTagsCleaner(
|
||||
args.package,
|
||||
repo_owner,
|
||||
repo,
|
||||
container_api,
|
||||
branch_api,
|
||||
)
|
||||
else:
|
||||
cleaner = LibraryTagsCleaner(
|
||||
args.package,
|
||||
repo_owner,
|
||||
repo,
|
||||
container_api,
|
||||
None,
|
||||
)
|
||||
|
||||
# Set if actually doing a delete vs dry run
|
||||
cleaner.actually_delete = args.delete
|
||||
|
||||
# Clean images with tags
|
||||
cleaner.clean()
|
||||
|
||||
# Clean images which are untagged
|
||||
cleaner.clean_untagged(args.is_manifest)
|
||||
|
||||
# Verify remaining tags still pull
|
||||
if args.is_manifest:
|
||||
cleaner.check_remaining_tags_valid()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
_main()
|
48
.github/scripts/common.py
vendored
@@ -1,48 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
import logging
|
||||
|
||||
|
||||
def get_image_tag(
|
||||
repo_name: str,
|
||||
pkg_name: str,
|
||||
pkg_version: str,
|
||||
) -> str:
|
||||
"""
|
||||
Returns a string representing the normal image for a given package
|
||||
"""
|
||||
return f"ghcr.io/{repo_name.lower()}/builder/{pkg_name}:{pkg_version}"
|
||||
|
||||
|
||||
def get_cache_image_tag(
|
||||
repo_name: str,
|
||||
pkg_name: str,
|
||||
pkg_version: str,
|
||||
branch_name: str,
|
||||
) -> str:
|
||||
"""
|
||||
Returns a string representing the expected image cache tag for a given package
|
||||
|
||||
Registry type caching is utilized for the builder images, to allow fast
|
||||
rebuilds, generally almost instant for the same version
|
||||
"""
|
||||
return f"ghcr.io/{repo_name.lower()}/builder/cache/{pkg_name}:{pkg_version}"
|
||||
|
||||
|
||||
def get_log_level(args) -> int:
|
||||
"""
|
||||
Returns a logging level, based
|
||||
:param args:
|
||||
:return:
|
||||
"""
|
||||
levels = {
|
||||
"critical": logging.CRITICAL,
|
||||
"error": logging.ERROR,
|
||||
"warn": logging.WARNING,
|
||||
"warning": logging.WARNING,
|
||||
"info": logging.INFO,
|
||||
"debug": logging.DEBUG,
|
||||
}
|
||||
level = levels.get(args.loglevel.lower())
|
||||
if level is None:
|
||||
level = logging.INFO
|
||||
return level
|
92
.github/scripts/get-build-json.py
vendored
@@ -1,92 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
This is a helper script for the mutli-stage Docker image builder.
|
||||
It provides a single point of configuration for package version control.
|
||||
The output JSON object is used by the CI workflow to determine what versions
|
||||
to build and pull into the final Docker image.
|
||||
|
||||
Python package information is obtained from the Pipfile.lock. As this is
|
||||
kept updated by dependabot, it usually will need no further configuration.
|
||||
The sole exception currently is pikepdf, which has a dependency on qpdf,
|
||||
and is configured here to use the latest version of qpdf built by the workflow.
|
||||
|
||||
Other package version information is configured directly below, generally by
|
||||
setting the version and Git information, if any.
|
||||
|
||||
"""
|
||||
import argparse
|
||||
import json
|
||||
import os
|
||||
from pathlib import Path
|
||||
from typing import Final
|
||||
|
||||
from common import get_cache_image_tag
|
||||
from common import get_image_tag
|
||||
|
||||
|
||||
def _main():
|
||||
parser = argparse.ArgumentParser(
|
||||
description="Generate a JSON object of information required to build the given package, based on the Pipfile.lock",
|
||||
)
|
||||
parser.add_argument(
|
||||
"package",
|
||||
help="The name of the package to generate JSON for",
|
||||
)
|
||||
|
||||
PIPFILE_LOCK_PATH: Final[Path] = Path("Pipfile.lock")
|
||||
BUILD_CONFIG_PATH: Final[Path] = Path(".build-config.json")
|
||||
|
||||
# Read the main config file
|
||||
build_json: Final = json.loads(BUILD_CONFIG_PATH.read_text())
|
||||
|
||||
# Read Pipfile.lock file
|
||||
pipfile_data: Final = json.loads(PIPFILE_LOCK_PATH.read_text())
|
||||
|
||||
args: Final = parser.parse_args()
|
||||
|
||||
# Read from environment variables set by GitHub Actions
|
||||
repo_name: Final[str] = os.environ["GITHUB_REPOSITORY"]
|
||||
branch_name: Final[str] = os.environ["GITHUB_REF_NAME"]
|
||||
|
||||
# Default output values
|
||||
version = None
|
||||
extra_config = {}
|
||||
|
||||
if args.package in pipfile_data["default"]:
|
||||
# Read the version from Pipfile.lock
|
||||
pkg_data = pipfile_data["default"][args.package]
|
||||
pkg_version = pkg_data["version"].split("==")[-1]
|
||||
version = pkg_version
|
||||
|
||||
# Any extra/special values needed
|
||||
if args.package == "pikepdf":
|
||||
extra_config["qpdf_version"] = build_json["qpdf"]["version"]
|
||||
|
||||
elif args.package in build_json:
|
||||
version = build_json[args.package]["version"]
|
||||
|
||||
else:
|
||||
raise NotImplementedError(args.package)
|
||||
|
||||
# The JSON object we'll output
|
||||
output = {
|
||||
"name": args.package,
|
||||
"version": version,
|
||||
"image_tag": get_image_tag(repo_name, args.package, version),
|
||||
"cache_tag": get_cache_image_tag(
|
||||
repo_name,
|
||||
args.package,
|
||||
version,
|
||||
branch_name,
|
||||
),
|
||||
}
|
||||
|
||||
# Add anything special a package may need
|
||||
output.update(extra_config)
|
||||
|
||||
# Output the JSON info to stdout
|
||||
print(json.dumps(output))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
_main()
|
274
.github/scripts/github.py
vendored
@@ -1,274 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
This module contains some useful classes for interacting with the Github API.
|
||||
The full documentation for the API can be found here: https://docs.github.com/en/rest
|
||||
|
||||
Mostly, this focusses on two areas, repo branches and repo packages, as the use case
|
||||
is cleaning up container images which are no longer referred to.
|
||||
|
||||
"""
|
||||
import functools
|
||||
import logging
|
||||
import re
|
||||
import urllib.parse
|
||||
from typing import Dict
|
||||
from typing import List
|
||||
from typing import Optional
|
||||
|
||||
import httpx
|
||||
|
||||
logger = logging.getLogger("github-api")
|
||||
|
||||
|
||||
class _GithubApiBase:
|
||||
"""
|
||||
A base class for interacting with the Github API. It
|
||||
will handle the session and setting authorization headers.
|
||||
"""
|
||||
|
||||
def __init__(self, token: str) -> None:
|
||||
self._token = token
|
||||
self._client: Optional[httpx.Client] = None
|
||||
|
||||
def __enter__(self) -> "_GithubApiBase":
|
||||
"""
|
||||
Sets up the required headers for auth and response
|
||||
type from the API
|
||||
"""
|
||||
self._client = httpx.Client()
|
||||
self._client.headers.update(
|
||||
{
|
||||
"Accept": "application/vnd.github.v3+json",
|
||||
"Authorization": f"token {self._token}",
|
||||
},
|
||||
)
|
||||
return self
|
||||
|
||||
def __exit__(self, exc_type, exc_val, exc_tb):
|
||||
"""
|
||||
Ensures the authorization token is cleaned up no matter
|
||||
the reason for the exit
|
||||
"""
|
||||
if "Accept" in self._client.headers:
|
||||
del self._client.headers["Accept"]
|
||||
if "Authorization" in self._client.headers:
|
||||
del self._client.headers["Authorization"]
|
||||
|
||||
# Close the session as well
|
||||
self._client.close()
|
||||
self._client = None
|
||||
|
||||
def _read_all_pages(self, endpoint):
|
||||
"""
|
||||
Helper function to read all pages of an endpoint, utilizing the
|
||||
next.url until exhausted. Assumes the endpoint returns a list
|
||||
"""
|
||||
internal_data = []
|
||||
|
||||
while True:
|
||||
resp = self._client.get(endpoint)
|
||||
if resp.status_code == 200:
|
||||
internal_data += resp.json()
|
||||
if "next" in resp.links:
|
||||
endpoint = resp.links["next"]["url"]
|
||||
else:
|
||||
logger.debug("Exiting pagination loop")
|
||||
break
|
||||
else:
|
||||
logger.warning(f"Request to {endpoint} return HTTP {resp.status_code}")
|
||||
resp.raise_for_status()
|
||||
|
||||
return internal_data
|
||||
|
||||
|
||||
class _EndpointResponse:
|
||||
"""
|
||||
For all endpoint JSON responses, store the full
|
||||
response data, for ease of extending later, if need be.
|
||||
"""
|
||||
|
||||
def __init__(self, data: Dict) -> None:
|
||||
self._data = data
|
||||
|
||||
|
||||
class GithubBranch(_EndpointResponse):
|
||||
"""
|
||||
Simple wrapper for a repository branch, only extracts name information
|
||||
for now.
|
||||
"""
|
||||
|
||||
def __init__(self, data: Dict) -> None:
|
||||
super().__init__(data)
|
||||
self.name = self._data["name"]
|
||||
|
||||
|
||||
class GithubBranchApi(_GithubApiBase):
|
||||
"""
|
||||
Wrapper around branch API.
|
||||
|
||||
See https://docs.github.com/en/rest/branches/branches
|
||||
|
||||
"""
|
||||
|
||||
def __init__(self, token: str) -> None:
|
||||
super().__init__(token)
|
||||
|
||||
self._ENDPOINT = "https://api.github.com/repos/{REPO}/branches"
|
||||
|
||||
def get_branches(self, repo: str) -> List[GithubBranch]:
|
||||
"""
|
||||
Returns all current branches of the given repository owned by the given
|
||||
owner or organization.
|
||||
"""
|
||||
# The environment GITHUB_REPOSITORY already contains the owner in the correct location
|
||||
endpoint = self._ENDPOINT.format(REPO=repo)
|
||||
internal_data = self._read_all_pages(endpoint)
|
||||
return [GithubBranch(branch) for branch in internal_data]
|
||||
|
||||
|
||||
class ContainerPackage(_EndpointResponse):
|
||||
"""
|
||||
Data class wrapping the JSON response from the package related
|
||||
endpoints
|
||||
"""
|
||||
|
||||
def __init__(self, data: Dict):
|
||||
super().__init__(data)
|
||||
# This is a numerical ID, required for interactions with this
|
||||
# specific package, including deletion of it or restoration
|
||||
self.id: int = self._data["id"]
|
||||
|
||||
# A string name. This might be an actual name or it could be a
|
||||
# digest string like "sha256:"
|
||||
self.name: str = self._data["name"]
|
||||
|
||||
# URL to the package, including its ID, can be used for deletion
|
||||
# or restoration without needing to build up a URL ourselves
|
||||
self.url: str = self._data["url"]
|
||||
|
||||
# The list of tags applied to this image. Maybe an empty list
|
||||
self.tags: List[str] = self._data["metadata"]["container"]["tags"]
|
||||
|
||||
@functools.cached_property
|
||||
def untagged(self) -> bool:
|
||||
"""
|
||||
Returns True if the image has no tags applied to it, False otherwise
|
||||
"""
|
||||
return len(self.tags) == 0
|
||||
|
||||
@functools.cache
|
||||
def tag_matches(self, pattern: str) -> bool:
|
||||
"""
|
||||
Returns True if the image has at least one tag which matches the given regex,
|
||||
False otherwise
|
||||
"""
|
||||
for tag in self.tags:
|
||||
if re.match(pattern, tag) is not None:
|
||||
return True
|
||||
return False
|
||||
|
||||
def __repr__(self):
|
||||
return f"Package {self.name}"
|
||||
|
||||
|
||||
class GithubContainerRegistryApi(_GithubApiBase):
|
||||
"""
|
||||
Class wrapper to deal with the Github packages API. This class only deals with
|
||||
container type packages, the only type published by paperless-ngx.
|
||||
"""
|
||||
|
||||
def __init__(self, token: str, owner_or_org: str) -> None:
|
||||
super().__init__(token)
|
||||
self._owner_or_org = owner_or_org
|
||||
if self._owner_or_org == "paperless-ngx":
|
||||
# https://docs.github.com/en/rest/packages#get-all-package-versions-for-a-package-owned-by-an-organization
|
||||
self._PACKAGES_VERSIONS_ENDPOINT = "https://api.github.com/orgs/{ORG}/packages/{PACKAGE_TYPE}/{PACKAGE_NAME}/versions"
|
||||
# https://docs.github.com/en/rest/packages#delete-package-version-for-an-organization
|
||||
self._PACKAGE_VERSION_DELETE_ENDPOINT = "https://api.github.com/orgs/{ORG}/packages/{PACKAGE_TYPE}/{PACKAGE_NAME}/versions/{PACKAGE_VERSION_ID}"
|
||||
else:
|
||||
# https://docs.github.com/en/rest/packages#get-all-package-versions-for-a-package-owned-by-the-authenticated-user
|
||||
self._PACKAGES_VERSIONS_ENDPOINT = "https://api.github.com/user/packages/{PACKAGE_TYPE}/{PACKAGE_NAME}/versions"
|
||||
# https://docs.github.com/en/rest/packages#delete-a-package-version-for-the-authenticated-user
|
||||
self._PACKAGE_VERSION_DELETE_ENDPOINT = "https://api.github.com/user/packages/{PACKAGE_TYPE}/{PACKAGE_NAME}/versions/{PACKAGE_VERSION_ID}"
|
||||
self._PACKAGE_VERSION_RESTORE_ENDPOINT = (
|
||||
f"{self._PACKAGE_VERSION_DELETE_ENDPOINT}/restore"
|
||||
)
|
||||
|
||||
def get_active_package_versions(
|
||||
self,
|
||||
package_name: str,
|
||||
) -> List[ContainerPackage]:
|
||||
"""
|
||||
Returns all the versions of a given package (container images) from
|
||||
the API
|
||||
"""
|
||||
|
||||
package_type: str = "container"
|
||||
# Need to quote this for slashes in the name
|
||||
package_name = urllib.parse.quote(package_name, safe="")
|
||||
|
||||
endpoint = self._PACKAGES_VERSIONS_ENDPOINT.format(
|
||||
ORG=self._owner_or_org,
|
||||
PACKAGE_TYPE=package_type,
|
||||
PACKAGE_NAME=package_name,
|
||||
)
|
||||
|
||||
pkgs = []
|
||||
|
||||
for data in self._read_all_pages(endpoint):
|
||||
pkgs.append(ContainerPackage(data))
|
||||
|
||||
return pkgs
|
||||
|
||||
def get_deleted_package_versions(
|
||||
self,
|
||||
package_name: str,
|
||||
) -> List[ContainerPackage]:
|
||||
package_type: str = "container"
|
||||
# Need to quote this for slashes in the name
|
||||
package_name = urllib.parse.quote(package_name, safe="")
|
||||
|
||||
endpoint = (
|
||||
self._PACKAGES_VERSIONS_ENDPOINT.format(
|
||||
ORG=self._owner_or_org,
|
||||
PACKAGE_TYPE=package_type,
|
||||
PACKAGE_NAME=package_name,
|
||||
)
|
||||
+ "?state=deleted"
|
||||
)
|
||||
|
||||
pkgs = []
|
||||
|
||||
for data in self._read_all_pages(endpoint):
|
||||
pkgs.append(ContainerPackage(data))
|
||||
|
||||
return pkgs
|
||||
|
||||
def delete_package_version(self, package_data: ContainerPackage):
|
||||
"""
|
||||
Deletes the given package version from the GHCR
|
||||
"""
|
||||
resp = self._client.delete(package_data.url)
|
||||
if resp.status_code != 204:
|
||||
logger.warning(
|
||||
f"Request to delete {package_data.url} returned HTTP {resp.status_code}",
|
||||
)
|
||||
|
||||
def restore_package_version(
|
||||
self,
|
||||
package_name: str,
|
||||
package_data: ContainerPackage,
|
||||
):
|
||||
package_type: str = "container"
|
||||
endpoint = self._PACKAGE_VERSION_RESTORE_ENDPOINT.format(
|
||||
ORG=self._owner_or_org,
|
||||
PACKAGE_TYPE=package_type,
|
||||
PACKAGE_NAME=package_name,
|
||||
PACKAGE_VERSION_ID=package_data.id,
|
||||
)
|
||||
|
||||
resp = self._client.post(endpoint)
|
||||
if resp.status_code != 204:
|
||||
logger.warning(
|
||||
f"Request to delete {endpoint} returned HTTP {resp.status_code}",
|
||||
)
|
23
.github/stale.yml
vendored
@@ -1,23 +0,0 @@
|
||||
# Number of days of inactivity before an issue becomes stale
|
||||
daysUntilStale: 30
|
||||
|
||||
# Number of days of inactivity before a stale issue is closed
|
||||
daysUntilClose: 7
|
||||
|
||||
# Only issues or pull requests with all of these labels are check if stale. Defaults to `[]` (disabled)
|
||||
onlyLabels: [cant-reproduce]
|
||||
|
||||
# Label to use when marking an issue as stale
|
||||
staleLabel: stale
|
||||
|
||||
# Comment to post when marking an issue as stale. Set to `false` to disable
|
||||
markComment: >
|
||||
This issue has been automatically marked as stale because it has not had
|
||||
recent activity. It will be closed if no further activity occurs. Thank you
|
||||
for your contributions.
|
||||
|
||||
# Comment to post when closing a stale issue. Set to `false` to disable
|
||||
closeComment: false
|
||||
|
||||
# See https://github.com/marketplace/stale for more info on the app
|
||||
# and https://github.com/probot/stale for the configuration docs
|
448
.github/workflows/ci.yml
vendored
@@ -16,41 +16,47 @@ on:
|
||||
env:
|
||||
# This is the version of pipenv all the steps will use
|
||||
# If changing this, change Dockerfile
|
||||
DEFAULT_PIP_ENV_VERSION: "2022.11.30"
|
||||
# This is the default version of Python to use in most steps
|
||||
# If changing this, change Dockerfile
|
||||
DEFAULT_PYTHON_VERSION: "3.9"
|
||||
DEFAULT_PIP_ENV_VERSION: "2023.12.1"
|
||||
# This is the default version of Python to use in most steps which aren't specific
|
||||
DEFAULT_PYTHON_VERSION: "3.10"
|
||||
|
||||
jobs:
|
||||
pre-commit:
|
||||
# We want to run on external PRs, but not on our own internal PRs as they'll be run
|
||||
# by the push to the branch. Without this if check, checks are duplicated since
|
||||
# internal PRs match both the push and pull_request events.
|
||||
if:
|
||||
github.event_name == 'push' || github.event.pull_request.head.repo.full_name !=
|
||||
github.repository
|
||||
|
||||
name: Linting Checks
|
||||
runs-on: ubuntu-22.04
|
||||
steps:
|
||||
-
|
||||
name: Checkout repository
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v4
|
||||
-
|
||||
name: Install python
|
||||
uses: actions/setup-python@v4
|
||||
uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: ${{ env.DEFAULT_PYTHON_VERSION }}
|
||||
-
|
||||
name: Check files
|
||||
uses: pre-commit/action@v3.0.0
|
||||
uses: pre-commit/action@v3.0.1
|
||||
|
||||
documentation:
|
||||
name: "Build Documentation"
|
||||
name: "Build & Deploy Documentation"
|
||||
runs-on: ubuntu-22.04
|
||||
needs:
|
||||
- pre-commit
|
||||
steps:
|
||||
-
|
||||
name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v4
|
||||
-
|
||||
name: Set up Python
|
||||
id: setup-python
|
||||
uses: actions/setup-python@v4
|
||||
uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: ${{ env.DEFAULT_PYTHON_VERSION }}
|
||||
cache: "pipenv"
|
||||
@@ -58,7 +64,7 @@ jobs:
|
||||
-
|
||||
name: Install pipenv
|
||||
run: |
|
||||
pip install --user pipenv==${DEFAULT_PIP_ENV_VERSION}
|
||||
pip install --user pipenv==${{ env.DEFAULT_PIP_ENV_VERSION }}
|
||||
-
|
||||
name: Install dependencies
|
||||
run: |
|
||||
@@ -71,63 +77,44 @@ jobs:
|
||||
name: Make documentation
|
||||
run: |
|
||||
pipenv --python ${{ steps.setup-python.outputs.python-version }} run mkdocs build --config-file ./mkdocs.yml
|
||||
-
|
||||
name: Deploy documentation
|
||||
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
|
||||
run: |
|
||||
echo "docs.paperless-ngx.com" > "${{ github.workspace }}/docs/CNAME"
|
||||
git config --global user.name "${{ github.actor }}"
|
||||
git config --global user.email "${{ github.actor }}@users.noreply.github.com"
|
||||
pipenv --python ${{ steps.setup-python.outputs.python-version }} run mkdocs gh-deploy --force --no-history
|
||||
-
|
||||
name: Upload artifact
|
||||
uses: actions/upload-artifact@v3
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: documentation
|
||||
path: site/
|
||||
|
||||
documentation-deploy:
|
||||
name: "Deploy Documentation"
|
||||
runs-on: ubuntu-22.04
|
||||
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
|
||||
needs:
|
||||
- documentation
|
||||
steps:
|
||||
-
|
||||
name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
-
|
||||
name: Deploy docs
|
||||
uses: mhausenblas/mkdocs-deploy-gh-pages@master
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
CUSTOM_DOMAIN: docs.paperless-ngx.com
|
||||
CONFIG_FILE: mkdocs.yml
|
||||
EXTRA_PACKAGES: build-base
|
||||
retention-days: 7
|
||||
|
||||
tests-backend:
|
||||
name: "Tests (${{ matrix.python-version }})"
|
||||
name: "Backend Tests (Python ${{ matrix.python-version }})"
|
||||
runs-on: ubuntu-22.04
|
||||
needs:
|
||||
- pre-commit
|
||||
strategy:
|
||||
matrix:
|
||||
python-version: ['3.8', '3.9', '3.10']
|
||||
python-version: ['3.9', '3.10', '3.11']
|
||||
fail-fast: false
|
||||
env:
|
||||
# Enable Tika end to end testing
|
||||
TIKA_LIVE: 1
|
||||
# Enable paperless_mail testing against real server
|
||||
PAPERLESS_MAIL_TEST_HOST: ${{ secrets.TEST_MAIL_HOST }}
|
||||
PAPERLESS_MAIL_TEST_USER: ${{ secrets.TEST_MAIL_USER }}
|
||||
PAPERLESS_MAIL_TEST_PASSWD: ${{ secrets.TEST_MAIL_PASSWD }}
|
||||
# Enable Gotenberg end to end testing
|
||||
GOTENBERG_LIVE: 1
|
||||
steps:
|
||||
-
|
||||
name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v4
|
||||
-
|
||||
name: Start containers
|
||||
run: |
|
||||
docker compose --file ${GITHUB_WORKSPACE}/docker/compose/docker-compose.ci-test.yml pull --quiet
|
||||
docker compose --file ${GITHUB_WORKSPACE}/docker/compose/docker-compose.ci-test.yml up --detach
|
||||
docker compose --file ${{ github.workspace }}/docker/compose/docker-compose.ci-test.yml pull --quiet
|
||||
docker compose --file ${{ github.workspace }}/docker/compose/docker-compose.ci-test.yml up --detach
|
||||
-
|
||||
name: Set up Python
|
||||
id: setup-python
|
||||
uses: actions/setup-python@v4
|
||||
uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: "${{ matrix.python-version }}"
|
||||
cache: "pipenv"
|
||||
@@ -135,7 +122,7 @@ jobs:
|
||||
-
|
||||
name: Install pipenv
|
||||
run: |
|
||||
pip install --user pipenv==${DEFAULT_PIP_ENV_VERSION}
|
||||
pip install --user pipenv==${{ env.DEFAULT_PIP_ENV_VERSION }}
|
||||
-
|
||||
name: Install system dependencies
|
||||
run: |
|
||||
@@ -156,129 +143,184 @@ jobs:
|
||||
pipenv --python ${{ steps.setup-python.outputs.python-version }} run pip list
|
||||
-
|
||||
name: Tests
|
||||
env:
|
||||
PAPERLESS_CI_TEST: 1
|
||||
# Enable paperless_mail testing against real server
|
||||
PAPERLESS_MAIL_TEST_HOST: ${{ secrets.TEST_MAIL_HOST }}
|
||||
PAPERLESS_MAIL_TEST_USER: ${{ secrets.TEST_MAIL_USER }}
|
||||
PAPERLESS_MAIL_TEST_PASSWD: ${{ secrets.TEST_MAIL_PASSWD }}
|
||||
run: |
|
||||
cd src/
|
||||
pipenv --python ${{ steps.setup-python.outputs.python-version }} run pytest -ra
|
||||
-
|
||||
name: Upload coverage to Codecov
|
||||
name: Upload coverage
|
||||
if: ${{ matrix.python-version == env.DEFAULT_PYTHON_VERSION }}
|
||||
uses: codecov/codecov-action@v3
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: backend-coverage-report
|
||||
path: src/coverage.xml
|
||||
retention-days: 7
|
||||
if-no-files-found: warn
|
||||
-
|
||||
name: Stop containers
|
||||
if: always()
|
||||
run: |
|
||||
docker compose --file ${{ github.workspace }}/docker/compose/docker-compose.ci-test.yml logs
|
||||
docker compose --file ${{ github.workspace }}/docker/compose/docker-compose.ci-test.yml down
|
||||
|
||||
install-frontend-depedendencies:
|
||||
name: "Install Frontend Dependencies"
|
||||
runs-on: ubuntu-22.04
|
||||
needs:
|
||||
- pre-commit
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
-
|
||||
name: Use Node.js 20
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 20.x
|
||||
cache: 'npm'
|
||||
cache-dependency-path: 'src-ui/package-lock.json'
|
||||
- name: Cache frontend dependencies
|
||||
id: cache-frontend-deps
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: |
|
||||
~/.npm
|
||||
~/.cache
|
||||
key: ${{ runner.os }}-frontenddeps-${{ hashFiles('src-ui/package-lock.json') }}
|
||||
-
|
||||
name: Install dependencies
|
||||
if: steps.cache-frontend-deps.outputs.cache-hit != 'true'
|
||||
run: cd src-ui && npm ci
|
||||
-
|
||||
name: Install Playwright
|
||||
if: steps.cache-frontend-deps.outputs.cache-hit != 'true'
|
||||
run: cd src-ui && npx playwright install --with-deps
|
||||
|
||||
tests-frontend:
|
||||
name: "Frontend Tests (Node ${{ matrix.node-version }} - ${{ matrix.shard-index }}/${{ matrix.shard-count }})"
|
||||
runs-on: ubuntu-22.04
|
||||
needs:
|
||||
- install-frontend-depedendencies
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
node-version: [20.x]
|
||||
shard-index: [1, 2, 3, 4]
|
||||
shard-count: [4]
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
-
|
||||
name: Use Node.js 20
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 20.x
|
||||
cache: 'npm'
|
||||
cache-dependency-path: 'src-ui/package-lock.json'
|
||||
- name: Cache frontend dependencies
|
||||
id: cache-frontend-deps
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: |
|
||||
~/.npm
|
||||
~/.cache
|
||||
key: ${{ runner.os }}-frontenddeps-${{ hashFiles('src-ui/package-lock.json') }}
|
||||
- name: Re-link Angular cli
|
||||
run: cd src-ui && npm link @angular/cli
|
||||
-
|
||||
name: Linting checks
|
||||
run: cd src-ui && npm run lint
|
||||
-
|
||||
name: Run Jest unit tests
|
||||
run: cd src-ui && npm run test -- --max-workers=2 --shard=${{ matrix.shard-index }}/${{ matrix.shard-count }}
|
||||
-
|
||||
name: Upload Jest coverage
|
||||
if: always()
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: jest-coverage-report-${{ matrix.shard-index }}
|
||||
path: |
|
||||
src-ui/coverage/coverage-final.json
|
||||
src-ui/coverage/lcov.info
|
||||
src-ui/coverage/clover.xml
|
||||
retention-days: 7
|
||||
if-no-files-found: warn
|
||||
-
|
||||
name: Run Playwright e2e tests
|
||||
run: cd src-ui && npx playwright test --shard ${{ matrix.shard-index }}/${{ matrix.shard-count }}
|
||||
-
|
||||
name: Upload Playwright test results
|
||||
if: always()
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: playwright-report-${{ matrix.shard-index }}
|
||||
path: src-ui/playwright-report
|
||||
retention-days: 7
|
||||
|
||||
tests-coverage-upload:
|
||||
name: "Upload Coverage"
|
||||
runs-on: ubuntu-22.04
|
||||
needs:
|
||||
- tests-backend
|
||||
- tests-frontend
|
||||
steps:
|
||||
-
|
||||
uses: actions/checkout@v4
|
||||
-
|
||||
name: Download frontend jest coverage
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
path: src-ui/coverage/
|
||||
pattern: jest-coverage-report-*
|
||||
-
|
||||
name: Download frontend playwright coverage
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
path: src-ui/coverage/
|
||||
pattern: playwright-report-*
|
||||
merge-multiple: true
|
||||
-
|
||||
name: Upload frontend coverage to Codecov
|
||||
uses: codecov/codecov-action@v4
|
||||
with:
|
||||
# not required for public repos, but intermittently fails otherwise
|
||||
token: ${{ secrets.CODECOV_TOKEN }}
|
||||
flags: frontend
|
||||
directory: src-ui/coverage/
|
||||
# dont include backend coverage files here
|
||||
files: '!coverage.xml'
|
||||
-
|
||||
name: Download backend coverage
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: backend-coverage-report
|
||||
path: src/
|
||||
-
|
||||
name: Upload coverage to Codecov
|
||||
uses: codecov/codecov-action@v4
|
||||
with:
|
||||
# not required for public repos, but intermittently fails otherwise
|
||||
token: ${{ secrets.CODECOV_TOKEN }}
|
||||
# future expansion
|
||||
flags: backend
|
||||
-
|
||||
name: Stop containers
|
||||
if: always()
|
||||
run: |
|
||||
docker compose --file ${GITHUB_WORKSPACE}/docker/compose/docker-compose.ci-test.yml logs
|
||||
docker compose --file ${GITHUB_WORKSPACE}/docker/compose/docker-compose.ci-test.yml down
|
||||
directory: src/
|
||||
|
||||
tests-frontend:
|
||||
name: "Tests Frontend"
|
||||
runs-on: ubuntu-22.04
|
||||
needs:
|
||||
- pre-commit
|
||||
strategy:
|
||||
matrix:
|
||||
node-version: [16.x]
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
-
|
||||
name: Use Node.js ${{ matrix.node-version }}
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: ${{ matrix.node-version }}
|
||||
- run: cd src-ui && npm ci
|
||||
- run: cd src-ui && npm run lint
|
||||
- run: cd src-ui && npm run test
|
||||
- run: cd src-ui && npm run e2e:ci
|
||||
|
||||
prepare-docker-build:
|
||||
name: Prepare Docker Pipeline Data
|
||||
if: github.event_name == 'push' && (startsWith(github.ref, 'refs/heads/feature-') || github.ref == 'refs/heads/dev' || github.ref == 'refs/heads/beta' || contains(github.ref, 'beta.rc') || startsWith(github.ref, 'refs/tags/v'))
|
||||
runs-on: ubuntu-22.04
|
||||
needs:
|
||||
- documentation
|
||||
- tests-backend
|
||||
- tests-frontend
|
||||
steps:
|
||||
-
|
||||
name: Set ghcr repository name
|
||||
id: set-ghcr-repository
|
||||
run: |
|
||||
ghcr_name=$(echo "${GITHUB_REPOSITORY}" | awk '{ print tolower($0) }')
|
||||
echo "repository=${ghcr_name}" >> $GITHUB_OUTPUT
|
||||
-
|
||||
name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
-
|
||||
name: Set up Python
|
||||
uses: actions/setup-python@v4
|
||||
with:
|
||||
python-version: ${{ env.DEFAULT_PYTHON_VERSION }}
|
||||
-
|
||||
name: Setup qpdf image
|
||||
id: qpdf-setup
|
||||
run: |
|
||||
build_json=$(python ${GITHUB_WORKSPACE}/.github/scripts/get-build-json.py qpdf)
|
||||
|
||||
echo ${build_json}
|
||||
|
||||
echo "qpdf-json=${build_json}" >> $GITHUB_OUTPUT
|
||||
-
|
||||
name: Setup psycopg2 image
|
||||
id: psycopg2-setup
|
||||
run: |
|
||||
build_json=$(python ${GITHUB_WORKSPACE}/.github/scripts/get-build-json.py psycopg2)
|
||||
|
||||
echo ${build_json}
|
||||
|
||||
echo "psycopg2-json=${build_json}" >> $GITHUB_OUTPUT
|
||||
-
|
||||
name: Setup pikepdf image
|
||||
id: pikepdf-setup
|
||||
run: |
|
||||
build_json=$(python ${GITHUB_WORKSPACE}/.github/scripts/get-build-json.py pikepdf)
|
||||
|
||||
echo ${build_json}
|
||||
|
||||
echo "pikepdf-json=${build_json}" >> $GITHUB_OUTPUT
|
||||
-
|
||||
name: Setup jbig2enc image
|
||||
id: jbig2enc-setup
|
||||
run: |
|
||||
build_json=$(python ${GITHUB_WORKSPACE}/.github/scripts/get-build-json.py jbig2enc)
|
||||
|
||||
echo ${build_json}
|
||||
|
||||
echo "jbig2enc-json=${build_json}" >> $GITHUB_OUTPUT
|
||||
|
||||
outputs:
|
||||
|
||||
ghcr-repository: ${{ steps.set-ghcr-repository.outputs.repository }}
|
||||
|
||||
qpdf-json: ${{ steps.qpdf-setup.outputs.qpdf-json }}
|
||||
|
||||
pikepdf-json: ${{ steps.pikepdf-setup.outputs.pikepdf-json }}
|
||||
|
||||
psycopg2-json: ${{ steps.psycopg2-setup.outputs.psycopg2-json }}
|
||||
|
||||
jbig2enc-json: ${{ steps.jbig2enc-setup.outputs.jbig2enc-json}}
|
||||
|
||||
# build and push image to docker hub.
|
||||
build-docker-image:
|
||||
name: Build Docker image for ${{ github.ref_name }}
|
||||
runs-on: ubuntu-22.04
|
||||
if: github.event_name == 'push' && (startsWith(github.ref, 'refs/heads/feature-') || startsWith(github.ref, 'refs/heads/fix-') || github.ref == 'refs/heads/dev' || github.ref == 'refs/heads/beta' || contains(github.ref, 'beta.rc') || startsWith(github.ref, 'refs/tags/v'))
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-build-docker-image-${{ github.ref_name }}
|
||||
cancel-in-progress: true
|
||||
needs:
|
||||
- prepare-docker-build
|
||||
- tests-backend
|
||||
- tests-frontend
|
||||
steps:
|
||||
-
|
||||
name: Check pushing to Docker Hub
|
||||
id: docker-hub
|
||||
id: push-other-places
|
||||
# Only push to Dockerhub from the main repo AND the ref is either:
|
||||
# main
|
||||
# dev
|
||||
@@ -286,21 +328,29 @@ jobs:
|
||||
# a tag
|
||||
# Otherwise forks would require a Docker Hub account and secrets setup
|
||||
run: |
|
||||
if [[ ${{ needs.prepare-docker-build.outputs.ghcr-repository }} == "paperless-ngx/paperless-ngx" && ( ${{ github.ref_name }} == "main" || ${{ github.ref_name }} == "dev" || ${{ github.ref_name }} == "beta" || ${{ startsWith(github.ref, 'refs/tags/v') }} == "true" ) ]] ; then
|
||||
if [[ ${{ github.repository_owner }} == "paperless-ngx" && ( ${{ github.ref_name }} == "dev" || ${{ github.ref_name }} == "beta" || ${{ startsWith(github.ref, 'refs/tags/v') }} == "true" ) ]] ; then
|
||||
echo "Enabling DockerHub image push"
|
||||
echo "enable=true" >> $GITHUB_OUTPUT
|
||||
else
|
||||
echo "Not pushing to DockerHub"
|
||||
echo "enable=false" >> $GITHUB_OUTPUT
|
||||
fi
|
||||
-
|
||||
name: Set ghcr repository name
|
||||
id: set-ghcr-repository
|
||||
run: |
|
||||
ghcr_name=$(echo "${{ github.repository }}" | awk '{ print tolower($0) }')
|
||||
echo "Name is ${ghcr_name}"
|
||||
echo "ghcr-repository=${ghcr_name}" >> $GITHUB_OUTPUT
|
||||
-
|
||||
name: Gather Docker metadata
|
||||
id: docker-meta
|
||||
uses: docker/metadata-action@v4
|
||||
uses: docker/metadata-action@v5
|
||||
with:
|
||||
images: |
|
||||
ghcr.io/${{ needs.prepare-docker-build.outputs.ghcr-repository }}
|
||||
name=paperlessngx/paperless-ngx,enable=${{ steps.docker-hub.outputs.enable }}
|
||||
ghcr.io/${{ steps.set-ghcr-repository.outputs.ghcr-repository }}
|
||||
name=paperlessngx/paperless-ngx,enable=${{ steps.push-other-places.outputs.enable }}
|
||||
name=quay.io/paperlessngx/paperless-ngx,enable=${{ steps.push-other-places.outputs.enable }}
|
||||
tags: |
|
||||
# Tag branches with branch name
|
||||
type=ref,event=branch
|
||||
@@ -310,51 +360,59 @@ jobs:
|
||||
type=semver,pattern={{major}}.{{minor}}
|
||||
-
|
||||
name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v4
|
||||
# If https://github.com/docker/buildx/issues/1044 is resolved,
|
||||
# the append input with a native arm64 arch could be used to
|
||||
# significantly speed up building
|
||||
-
|
||||
name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v2
|
||||
uses: docker/setup-buildx-action@v3
|
||||
-
|
||||
name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v2
|
||||
uses: docker/setup-qemu-action@v3
|
||||
with:
|
||||
platforms: arm64
|
||||
-
|
||||
name: Login to Github Container Registry
|
||||
uses: docker/login-action@v2
|
||||
name: Login to GitHub Container Registry
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
-
|
||||
name: Login to Docker Hub
|
||||
uses: docker/login-action@v2
|
||||
uses: docker/login-action@v3
|
||||
# Don't attempt to login is not pushing to Docker Hub
|
||||
if: steps.docker-hub.outputs.enable == 'true'
|
||||
if: steps.push-other-places.outputs.enable == 'true'
|
||||
with:
|
||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
-
|
||||
name: Login to Quay.io
|
||||
uses: docker/login-action@v3
|
||||
# Don't attempt to login is not pushing to Quay.io
|
||||
if: steps.push-other-places.outputs.enable == 'true'
|
||||
with:
|
||||
registry: quay.io
|
||||
username: ${{ secrets.QUAY_USERNAME }}
|
||||
password: ${{ secrets.QUAY_ROBOT_TOKEN }}
|
||||
-
|
||||
name: Build and push
|
||||
uses: docker/build-push-action@v4
|
||||
uses: docker/build-push-action@v5
|
||||
with:
|
||||
context: .
|
||||
file: ./Dockerfile
|
||||
platforms: linux/amd64,linux/arm/v7,linux/arm64
|
||||
platforms: linux/amd64,linux/arm64
|
||||
push: ${{ github.event_name != 'pull_request' }}
|
||||
tags: ${{ steps.docker-meta.outputs.tags }}
|
||||
labels: ${{ steps.docker-meta.outputs.labels }}
|
||||
build-args: |
|
||||
JBIG2ENC_VERSION=${{ fromJSON(needs.prepare-docker-build.outputs.jbig2enc-json).version }}
|
||||
QPDF_VERSION=${{ fromJSON(needs.prepare-docker-build.outputs.qpdf-json).version }}
|
||||
PIKEPDF_VERSION=${{ fromJSON(needs.prepare-docker-build.outputs.pikepdf-json).version }}
|
||||
PSYCOPG2_VERSION=${{ fromJSON(needs.prepare-docker-build.outputs.psycopg2-json).version }}
|
||||
# Get cache layers from this branch, then dev, then main
|
||||
# Get cache layers from this branch, then dev
|
||||
# This allows new branches to get at least some cache benefits, generally from dev
|
||||
cache-from: |
|
||||
type=registry,ref=ghcr.io/${{ needs.prepare-docker-build.outputs.ghcr-repository }}/builder/cache/app:${{ github.ref_name }}
|
||||
type=registry,ref=ghcr.io/${{ needs.prepare-docker-build.outputs.ghcr-repository }}/builder/cache/app:dev
|
||||
type=registry,ref=ghcr.io/${{ needs.prepare-docker-build.outputs.ghcr-repository }}/builder/cache/app:main
|
||||
type=registry,ref=ghcr.io/${{ steps.set-ghcr-repository.outputs.ghcr-repository }}/builder/cache/app:${{ github.ref_name }}
|
||||
type=registry,ref=ghcr.io/${{ steps.set-ghcr-repository.outputs.ghcr-repository }}/builder/cache/app:dev
|
||||
cache-to: |
|
||||
type=registry,mode=max,ref=ghcr.io/${{ needs.prepare-docker-build.outputs.ghcr-repository }}/builder/cache/app:${{ github.ref_name }}
|
||||
type=registry,mode=max,ref=ghcr.io/${{ steps.set-ghcr-repository.outputs.ghcr-repository }}/builder/cache/app:${{ github.ref_name }}
|
||||
-
|
||||
name: Inspect image
|
||||
run: |
|
||||
@@ -366,23 +424,26 @@ jobs:
|
||||
docker cp frontend-extract:/usr/src/paperless/src/documents/static/frontend src/documents/static/frontend/
|
||||
-
|
||||
name: Upload frontend artifact
|
||||
uses: actions/upload-artifact@v3
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: frontend-compiled
|
||||
path: src/documents/static/frontend/
|
||||
retention-days: 7
|
||||
|
||||
build-release:
|
||||
name: "Build Release"
|
||||
needs:
|
||||
- build-docker-image
|
||||
- documentation
|
||||
runs-on: ubuntu-22.04
|
||||
steps:
|
||||
-
|
||||
name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v4
|
||||
-
|
||||
name: Set up Python
|
||||
id: setup-python
|
||||
uses: actions/setup-python@v4
|
||||
uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: ${{ env.DEFAULT_PYTHON_VERSION }}
|
||||
cache: "pipenv"
|
||||
@@ -390,11 +451,17 @@ jobs:
|
||||
-
|
||||
name: Install pipenv + tools
|
||||
run: |
|
||||
pip install --upgrade --user pipenv==${DEFAULT_PIP_ENV_VERSION} setuptools wheel
|
||||
pip install --upgrade --user pipenv==${{ env.DEFAULT_PIP_ENV_VERSION }} setuptools wheel
|
||||
-
|
||||
name: Install Python dependencies
|
||||
run: |
|
||||
pipenv --python ${{ steps.setup-python.outputs.python-version }} sync --dev
|
||||
-
|
||||
name: Patch whitenoise
|
||||
run: |
|
||||
curl --fail --silent --show-error --location --output 484.patch https://github.com/evansd/whitenoise/pull/484.patch
|
||||
patch -d $(pipenv --venv)/lib/python3.10/site-packages --verbose -p2 < 484.patch
|
||||
rm 484.patch
|
||||
-
|
||||
name: Install system dependencies
|
||||
run: |
|
||||
@@ -402,13 +469,13 @@ jobs:
|
||||
sudo apt-get install -qq --no-install-recommends gettext liblept5
|
||||
-
|
||||
name: Download frontend artifact
|
||||
uses: actions/download-artifact@v3
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: frontend-compiled
|
||||
path: src/documents/static/frontend/
|
||||
-
|
||||
name: Download documentation artifact
|
||||
uses: actions/download-artifact@v3
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: documentation
|
||||
path: docs/_build/html/
|
||||
@@ -451,7 +518,7 @@ jobs:
|
||||
do
|
||||
cp --verbose ${file_name} dist/paperless-ngx/
|
||||
done
|
||||
mv --verbose dist/paperless-ngx/paperless.conf.example paperless.conf
|
||||
mv --verbose dist/paperless-ngx/paperless.conf.example dist/paperless-ngx/paperless.conf
|
||||
|
||||
echo "Copying Docker related files"
|
||||
cp --recursive docker/ dist/paperless-ngx/docker
|
||||
@@ -474,12 +541,14 @@ jobs:
|
||||
tar -cJf paperless-ngx.tar.xz paperless-ngx/
|
||||
-
|
||||
name: Upload release artifact
|
||||
uses: actions/upload-artifact@v3
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: release
|
||||
path: dist/paperless-ngx.tar.xz
|
||||
retention-days: 7
|
||||
|
||||
publish-release:
|
||||
name: "Publish Release"
|
||||
runs-on: ubuntu-22.04
|
||||
outputs:
|
||||
prerelease: ${{ steps.get_version.outputs.prerelease }}
|
||||
@@ -491,7 +560,7 @@ jobs:
|
||||
steps:
|
||||
-
|
||||
name: Download release artifact
|
||||
uses: actions/download-artifact@v3
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: release
|
||||
path: ./
|
||||
@@ -508,7 +577,7 @@ jobs:
|
||||
-
|
||||
name: Create Release and Changelog
|
||||
id: create-release
|
||||
uses: release-drafter/release-drafter@v5
|
||||
uses: release-drafter/release-drafter@v6
|
||||
with:
|
||||
name: Paperless-ngx ${{ steps.get_version.outputs.version }}
|
||||
tag: ${{ steps.get_version.outputs.version }}
|
||||
@@ -529,6 +598,7 @@ jobs:
|
||||
asset_content_type: application/x-xz
|
||||
|
||||
append-changelog:
|
||||
name: "Append Changelog"
|
||||
runs-on: ubuntu-22.04
|
||||
needs:
|
||||
- publish-release
|
||||
@@ -536,12 +606,12 @@ jobs:
|
||||
steps:
|
||||
-
|
||||
name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
ref: main
|
||||
-
|
||||
name: Set up Python
|
||||
uses: actions/setup-python@v4
|
||||
uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: ${{ env.DEFAULT_PYTHON_VERSION }}
|
||||
cache: "pipenv"
|
||||
@@ -549,7 +619,7 @@ jobs:
|
||||
-
|
||||
name: Install pipenv + tools
|
||||
run: |
|
||||
pip install --upgrade --user pipenv==${DEFAULT_PIP_ENV_VERSION} setuptools wheel
|
||||
pip install --upgrade --user pipenv==${{ env.DEFAULT_PIP_ENV_VERSION }} setuptools wheel
|
||||
-
|
||||
name: Append Changelog to docs
|
||||
id: append-Changelog
|
||||
@@ -570,12 +640,12 @@ jobs:
|
||||
git push origin ${{ needs.publish-release.outputs.version }}-changelog
|
||||
-
|
||||
name: Create Pull Request
|
||||
uses: actions/github-script@v6
|
||||
uses: actions/github-script@v7
|
||||
with:
|
||||
script: |
|
||||
const { repo, owner } = context.repo;
|
||||
const result = await github.rest.pulls.create({
|
||||
title: '[Documentation] Add ${{ needs.publish-release.outputs.version }} changelog',
|
||||
title: 'Documentation: Add ${{ needs.publish-release.outputs.version }} changelog',
|
||||
owner,
|
||||
repo,
|
||||
head: '${{ needs.publish-release.outputs.version }}-changelog',
|
||||
@@ -586,5 +656,5 @@ jobs:
|
||||
owner,
|
||||
repo,
|
||||
issue_number: result.data.number,
|
||||
labels: ['documentation']
|
||||
labels: ['documentation', 'skip-changelog']
|
||||
});
|
||||
|
85
.github/workflows/cleanup-tags.yml
vendored
@@ -12,9 +12,6 @@ on:
|
||||
push:
|
||||
paths:
|
||||
- ".github/workflows/cleanup-tags.yml"
|
||||
- ".github/scripts/cleanup-tags.py"
|
||||
- ".github/scripts/github.py"
|
||||
- ".github/scripts/common.py"
|
||||
|
||||
concurrency:
|
||||
group: registry-tags-cleanup
|
||||
@@ -26,58 +23,48 @@ jobs:
|
||||
if: github.repository_owner == 'paperless-ngx'
|
||||
runs-on: ubuntu-22.04
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
- primary-name: "paperless-ngx"
|
||||
cache-name: "paperless-ngx/builder/cache/app"
|
||||
|
||||
- primary-name: "paperless-ngx/builder/qpdf"
|
||||
cache-name: "paperless-ngx/builder/cache/qpdf"
|
||||
|
||||
- primary-name: "paperless-ngx/builder/pikepdf"
|
||||
cache-name: "paperless-ngx/builder/cache/pikepdf"
|
||||
|
||||
- primary-name: "paperless-ngx/builder/jbig2enc"
|
||||
cache-name: "paperless-ngx/builder/cache/jbig2enc"
|
||||
|
||||
- primary-name: "paperless-ngx/builder/psycopg2"
|
||||
cache-name: "paperless-ngx/builder/cache/psycopg2"
|
||||
primary-name: ["paperless-ngx", "paperless-ngx/builder/cache/app"]
|
||||
env:
|
||||
# Requires a personal access token with the OAuth scope delete:packages
|
||||
TOKEN: ${{ secrets.GHA_CONTAINER_DELETE_TOKEN }}
|
||||
steps:
|
||||
-
|
||||
name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
-
|
||||
name: Login to Github Container Registry
|
||||
uses: docker/login-action@v2
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
-
|
||||
name: Set up Python
|
||||
uses: actions/setup-python@v4
|
||||
with:
|
||||
python-version: "3.10"
|
||||
-
|
||||
name: Install Python libraries
|
||||
run: |
|
||||
python -m pip install httpx docker
|
||||
#
|
||||
# Clean up primary package
|
||||
#
|
||||
-
|
||||
name: Cleanup for package "${{ matrix.primary-name }}"
|
||||
name: Clean temporary images
|
||||
if: "${{ env.TOKEN != '' }}"
|
||||
run: |
|
||||
python ${GITHUB_WORKSPACE}/.github/scripts/cleanup-tags.py --untagged --is-manifest --delete "${{ matrix.primary-name }}"
|
||||
#
|
||||
# Clean up registry cache package
|
||||
#
|
||||
uses: stumpylog/image-cleaner-action/ephemeral@v0.5.0
|
||||
with:
|
||||
token: "${{ env.TOKEN }}"
|
||||
owner: "${{ github.repository_owner }}"
|
||||
is_org: "true"
|
||||
package_name: "${{ matrix.primary-name }}"
|
||||
scheme: "branch"
|
||||
repo_name: "paperless-ngx"
|
||||
match_regex: "(feature|fix)"
|
||||
do_delete: "true"
|
||||
|
||||
cleanup-untagged-images:
|
||||
name: Cleanup Untagged Images Tags for ${{ matrix.primary-name }}
|
||||
if: github.repository_owner == 'paperless-ngx'
|
||||
runs-on: ubuntu-22.04
|
||||
needs:
|
||||
- cleanup-images
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
primary-name: ["paperless-ngx", "paperless-ngx/builder/cache/app"]
|
||||
env:
|
||||
# Requires a personal access token with the OAuth scope delete:packages
|
||||
TOKEN: ${{ secrets.GHA_CONTAINER_DELETE_TOKEN }}
|
||||
steps:
|
||||
-
|
||||
name: Cleanup for package "${{ matrix.cache-name }}"
|
||||
name: Clean untagged images
|
||||
if: "${{ env.TOKEN != '' }}"
|
||||
run: |
|
||||
python ${GITHUB_WORKSPACE}/.github/scripts/cleanup-tags.py --untagged --delete "${{ matrix.cache-name }}"
|
||||
uses: stumpylog/image-cleaner-action/untagged@v0.5.0
|
||||
with:
|
||||
token: "${{ env.TOKEN }}"
|
||||
owner: "${{ github.repository_owner }}"
|
||||
is_org: "true"
|
||||
package_name: "${{ matrix.primary-name }}"
|
||||
do_delete: "true"
|
||||
|
6
.github/workflows/codeql-analysis.yml
vendored
@@ -38,11 +38,11 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v4
|
||||
|
||||
# Initializes the CodeQL tools for scanning.
|
||||
- name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@v2
|
||||
uses: github/codeql-action/init@v3
|
||||
with:
|
||||
languages: ${{ matrix.language }}
|
||||
# If you wish to specify custom queries, you can do so here or in a config file.
|
||||
@@ -51,4 +51,4 @@ jobs:
|
||||
# queries: ./path/to/local/query, your-org/your-repo/queries@main
|
||||
|
||||
- name: Perform CodeQL Analysis
|
||||
uses: github/codeql-action/analyze@v2
|
||||
uses: github/codeql-action/analyze@v3
|
||||
|
34
.github/workflows/crowdin.yml
vendored
Normal file
@@ -0,0 +1,34 @@
|
||||
name: Crowdin Action
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
schedule:
|
||||
- cron: '2 */12 * * *'
|
||||
push:
|
||||
paths: [
|
||||
'src/locale/**',
|
||||
'src-ui/messages.xlf',
|
||||
'src-ui/src/locale/**'
|
||||
]
|
||||
branches: [ dev ]
|
||||
|
||||
jobs:
|
||||
synchronize-with-crowdin:
|
||||
name: Crowdin Sync
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
- name: crowdin action
|
||||
uses: crowdin/github-action@v1
|
||||
with:
|
||||
upload_translations: false
|
||||
download_translations: true
|
||||
crowdin_branch_name: 'dev'
|
||||
localization_branch_name: l10n_dev
|
||||
pull_request_labels: 'skip-changelog, translation'
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
CROWDIN_PROJECT_ID: ${{ secrets.CROWDIN_PROJECT_ID }}
|
||||
CROWDIN_PERSONAL_TOKEN: ${{ secrets.CROWDIN_PERSONAL_TOKEN }}
|
310
.github/workflows/installer-library.yml
vendored
@@ -1,310 +0,0 @@
|
||||
# This workflow will run to update the installer library of
|
||||
# Docker images. These are the images which provide updated wheels
|
||||
# .deb installation packages or maybe just some compiled library
|
||||
|
||||
name: Build Image Library
|
||||
|
||||
on:
|
||||
push:
|
||||
# Must match one of these branches AND one of the paths
|
||||
# to be triggered
|
||||
branches:
|
||||
- "main"
|
||||
- "dev"
|
||||
- "library-*"
|
||||
- "feature-*"
|
||||
paths:
|
||||
# Trigger the workflow if a Dockerfile changed
|
||||
- "docker-builders/**"
|
||||
# Trigger if a package was updated
|
||||
- ".build-config.json"
|
||||
- "Pipfile.lock"
|
||||
# Also trigger on workflow changes related to the library
|
||||
- ".github/workflows/installer-library.yml"
|
||||
- ".github/workflows/reusable-workflow-builder.yml"
|
||||
- ".github/scripts/**"
|
||||
|
||||
# Set a workflow level concurrency group so primary workflow
|
||||
# can wait for this to complete if needed
|
||||
# DO NOT CHANGE without updating main workflow group
|
||||
concurrency:
|
||||
group: build-installer-library
|
||||
cancel-in-progress: false
|
||||
|
||||
jobs:
|
||||
prepare-docker-build:
|
||||
name: Prepare Docker Image Version Data
|
||||
runs-on: ubuntu-22.04
|
||||
steps:
|
||||
-
|
||||
name: Set ghcr repository name
|
||||
id: set-ghcr-repository
|
||||
run: |
|
||||
ghcr_name=$(echo "${GITHUB_REPOSITORY}" | awk '{ print tolower($0) }')
|
||||
echo "repository=${ghcr_name}" >> $GITHUB_OUTPUT
|
||||
-
|
||||
name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
-
|
||||
name: Set up Python
|
||||
uses: actions/setup-python@v4
|
||||
with:
|
||||
python-version: "3.9"
|
||||
-
|
||||
name: Install jq
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install jq
|
||||
-
|
||||
name: Setup qpdf image
|
||||
id: qpdf-setup
|
||||
run: |
|
||||
build_json=$(python ${GITHUB_WORKSPACE}/.github/scripts/get-build-json.py qpdf)
|
||||
|
||||
echo ${build_json}
|
||||
|
||||
echo "qpdf-json=${build_json}" >> $GITHUB_OUTPUT
|
||||
-
|
||||
name: Setup psycopg2 image
|
||||
id: psycopg2-setup
|
||||
run: |
|
||||
build_json=$(python ${GITHUB_WORKSPACE}/.github/scripts/get-build-json.py psycopg2)
|
||||
|
||||
echo ${build_json}
|
||||
|
||||
echo "psycopg2-json=${build_json}" >> $GITHUB_OUTPUT
|
||||
-
|
||||
name: Setup pikepdf image
|
||||
id: pikepdf-setup
|
||||
run: |
|
||||
build_json=$(python ${GITHUB_WORKSPACE}/.github/scripts/get-build-json.py pikepdf)
|
||||
|
||||
echo ${build_json}
|
||||
|
||||
echo "pikepdf-json=${build_json}" >> $GITHUB_OUTPUT
|
||||
-
|
||||
name: Setup jbig2enc image
|
||||
id: jbig2enc-setup
|
||||
run: |
|
||||
build_json=$(python ${GITHUB_WORKSPACE}/.github/scripts/get-build-json.py jbig2enc)
|
||||
|
||||
echo ${build_json}
|
||||
|
||||
echo "jbig2enc-json=${build_json}" >> $GITHUB_OUTPUT
|
||||
-
|
||||
name: Setup other versions
|
||||
id: cache-bust-setup
|
||||
run: |
|
||||
pillow_version=$(jq -r '.default.pillow.version | gsub("=";"")' Pipfile.lock)
|
||||
lxml_version=$(jq -r '.default.lxml.version | gsub("=";"")' Pipfile.lock)
|
||||
|
||||
echo "Pillow is ${pillow_version}"
|
||||
echo "lxml is ${lxml_version}"
|
||||
|
||||
echo "pillow-version=${pillow_version}" >> $GITHUB_OUTPUT
|
||||
echo "lxml-version=${lxml_version}" >> $GITHUB_OUTPUT
|
||||
|
||||
outputs:
|
||||
|
||||
ghcr-repository: ${{ steps.set-ghcr-repository.outputs.repository }}
|
||||
|
||||
qpdf-json: ${{ steps.qpdf-setup.outputs.qpdf-json }}
|
||||
|
||||
pikepdf-json: ${{ steps.pikepdf-setup.outputs.pikepdf-json }}
|
||||
|
||||
psycopg2-json: ${{ steps.psycopg2-setup.outputs.psycopg2-json }}
|
||||
|
||||
jbig2enc-json: ${{ steps.jbig2enc-setup.outputs.jbig2enc-json }}
|
||||
|
||||
pillow-version: ${{ steps.cache-bust-setup.outputs.pillow-version }}
|
||||
|
||||
lxml-version: ${{ steps.cache-bust-setup.outputs.lxml-version }}
|
||||
|
||||
build-qpdf-debs:
|
||||
name: qpdf
|
||||
needs:
|
||||
- prepare-docker-build
|
||||
uses: ./.github/workflows/reusable-workflow-builder.yml
|
||||
with:
|
||||
dockerfile: ./docker-builders/Dockerfile.qpdf
|
||||
build-platforms: linux/amd64
|
||||
build-json: ${{ needs.prepare-docker-build.outputs.qpdf-json }}
|
||||
build-args: |
|
||||
QPDF_VERSION=${{ fromJSON(needs.prepare-docker-build.outputs.qpdf-json).version }}
|
||||
|
||||
build-jbig2enc:
|
||||
name: jbig2enc
|
||||
needs:
|
||||
- prepare-docker-build
|
||||
uses: ./.github/workflows/reusable-workflow-builder.yml
|
||||
with:
|
||||
dockerfile: ./docker-builders/Dockerfile.jbig2enc
|
||||
build-json: ${{ needs.prepare-docker-build.outputs.jbig2enc-json }}
|
||||
build-args: |
|
||||
JBIG2ENC_VERSION=${{ fromJSON(needs.prepare-docker-build.outputs.jbig2enc-json).version }}
|
||||
|
||||
build-psycopg2-wheel:
|
||||
name: psycopg2
|
||||
needs:
|
||||
- prepare-docker-build
|
||||
uses: ./.github/workflows/reusable-workflow-builder.yml
|
||||
with:
|
||||
dockerfile: ./docker-builders/Dockerfile.psycopg2
|
||||
build-json: ${{ needs.prepare-docker-build.outputs.psycopg2-json }}
|
||||
build-args: |
|
||||
PSYCOPG2_VERSION=${{ fromJSON(needs.prepare-docker-build.outputs.psycopg2-json).version }}
|
||||
|
||||
build-pikepdf-wheel:
|
||||
name: pikepdf
|
||||
needs:
|
||||
- prepare-docker-build
|
||||
- build-qpdf-debs
|
||||
uses: ./.github/workflows/reusable-workflow-builder.yml
|
||||
with:
|
||||
dockerfile: ./docker-builders/Dockerfile.pikepdf
|
||||
build-json: ${{ needs.prepare-docker-build.outputs.pikepdf-json }}
|
||||
build-args: |
|
||||
REPO=${{ needs.prepare-docker-build.outputs.ghcr-repository }}
|
||||
QPDF_VERSION=${{ fromJSON(needs.prepare-docker-build.outputs.qpdf-json).version }}
|
||||
PIKEPDF_VERSION=${{ fromJSON(needs.prepare-docker-build.outputs.pikepdf-json).version }}
|
||||
PILLOW_VERSION=${{ needs.prepare-docker-build.outputs.pillow-version }}
|
||||
LXML_VERSION=${{ needs.prepare-docker-build.outputs.lxml-version }}
|
||||
|
||||
commit-binary-files:
|
||||
name: Store installers
|
||||
needs:
|
||||
- prepare-docker-build
|
||||
- build-qpdf-debs
|
||||
- build-jbig2enc
|
||||
- build-psycopg2-wheel
|
||||
- build-pikepdf-wheel
|
||||
runs-on: ubuntu-22.04
|
||||
steps:
|
||||
-
|
||||
name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
ref: binary-library
|
||||
-
|
||||
name: Set up Python
|
||||
uses: actions/setup-python@v4
|
||||
with:
|
||||
python-version: "3.9"
|
||||
-
|
||||
name: Install system dependencies
|
||||
run: |
|
||||
sudo apt-get update -qq
|
||||
sudo apt-get install -qq --no-install-recommends tree
|
||||
-
|
||||
name: Extract qpdf files
|
||||
run: |
|
||||
version=${{ fromJSON(needs.prepare-docker-build.outputs.qpdf-json).version }}
|
||||
tag=${{ fromJSON(needs.prepare-docker-build.outputs.qpdf-json).image_tag }}
|
||||
|
||||
docker pull --quiet ${tag}
|
||||
docker create --name qpdf-extract ${tag}
|
||||
|
||||
mkdir --parents qpdf/${version}/amd64
|
||||
docker cp qpdf-extract:/usr/src/qpdf/${version}/amd64 qpdf/${version}
|
||||
|
||||
mkdir --parents qpdf/${version}/arm64
|
||||
docker cp qpdf-extract:/usr/src/qpdf/${version}/arm64 qpdf/${version}
|
||||
|
||||
mkdir --parents qpdf/${version}/armv7
|
||||
docker cp qpdf-extract:/usr/src/qpdf/${version}/armv7 qpdf/${version}
|
||||
-
|
||||
name: Extract psycopg2 files
|
||||
run: |
|
||||
version=${{ fromJSON(needs.prepare-docker-build.outputs.psycopg2-json).version }}
|
||||
tag=${{ fromJSON(needs.prepare-docker-build.outputs.psycopg2-json).image_tag }}
|
||||
|
||||
docker pull --quiet --platform linux/amd64 ${tag}
|
||||
docker create --platform linux/amd64 --name psycopg2-extract ${tag}
|
||||
mkdir --parents psycopg2/${version}/amd64
|
||||
docker cp psycopg2-extract:/usr/src/wheels/ psycopg2/${version}/amd64
|
||||
mv psycopg2/${version}/amd64/wheels/* psycopg2/${version}/amd64
|
||||
rm -r psycopg2/${version}/amd64/wheels/
|
||||
docker rm psycopg2-extract
|
||||
|
||||
docker pull --quiet --platform linux/arm64 ${tag}
|
||||
docker create --platform linux/arm64 --name psycopg2-extract ${tag}
|
||||
mkdir --parents psycopg2/${version}/arm64
|
||||
docker cp psycopg2-extract:/usr/src/wheels/ psycopg2/${version}/arm64
|
||||
mv psycopg2/${version}/arm64/wheels/* psycopg2/${version}/arm64
|
||||
rm -r psycopg2/${version}/arm64/wheels/
|
||||
docker rm psycopg2-extract
|
||||
|
||||
docker pull --quiet --platform linux/arm/v7 ${tag}
|
||||
docker create --platform linux/arm/v7 --name psycopg2-extract ${tag}
|
||||
mkdir --parents psycopg2/${version}/armv7
|
||||
docker cp psycopg2-extract:/usr/src/wheels/ psycopg2/${version}/armv7
|
||||
mv psycopg2/${version}/armv7/wheels/* psycopg2/${version}/armv7
|
||||
rm -r psycopg2/${version}/armv7/wheels/
|
||||
docker rm psycopg2-extract
|
||||
-
|
||||
name: Extract pikepdf files
|
||||
run: |
|
||||
version=${{ fromJSON(needs.prepare-docker-build.outputs.pikepdf-json).version }}
|
||||
tag=${{ fromJSON(needs.prepare-docker-build.outputs.pikepdf-json).image_tag }}
|
||||
|
||||
docker pull --quiet --platform linux/amd64 ${tag}
|
||||
docker create --platform linux/amd64 --name pikepdf-extract ${tag}
|
||||
mkdir --parents pikepdf/${version}/amd64
|
||||
docker cp pikepdf-extract:/usr/src/wheels/ pikepdf/${version}/amd64
|
||||
mv pikepdf/${version}/amd64/wheels/* pikepdf/${version}/amd64
|
||||
rm -r pikepdf/${version}/amd64/wheels/
|
||||
docker rm pikepdf-extract
|
||||
|
||||
docker pull --quiet --platform linux/arm64 ${tag}
|
||||
docker create --platform linux/arm64 --name pikepdf-extract ${tag}
|
||||
mkdir --parents pikepdf/${version}/arm64
|
||||
docker cp pikepdf-extract:/usr/src/wheels/ pikepdf/${version}/arm64
|
||||
mv pikepdf/${version}/arm64/wheels/* pikepdf/${version}/arm64
|
||||
rm -r pikepdf/${version}/arm64/wheels/
|
||||
docker rm pikepdf-extract
|
||||
|
||||
docker pull --quiet --platform linux/arm/v7 ${tag}
|
||||
docker create --platform linux/arm/v7 --name pikepdf-extract ${tag}
|
||||
mkdir --parents pikepdf/${version}/armv7
|
||||
docker cp pikepdf-extract:/usr/src/wheels/ pikepdf/${version}/armv7
|
||||
mv pikepdf/${version}/armv7/wheels/* pikepdf/${version}/armv7
|
||||
rm -r pikepdf/${version}/armv7/wheels/
|
||||
docker rm pikepdf-extract
|
||||
-
|
||||
name: Extract jbig2enc files
|
||||
run: |
|
||||
version=${{ fromJSON(needs.prepare-docker-build.outputs.jbig2enc-json).version }}
|
||||
tag=${{ fromJSON(needs.prepare-docker-build.outputs.jbig2enc-json).image_tag }}
|
||||
|
||||
docker pull --quiet --platform linux/amd64 ${tag}
|
||||
docker create --platform linux/amd64 --name jbig2enc-extract ${tag}
|
||||
mkdir --parents jbig2enc/${version}/amd64
|
||||
docker cp jbig2enc-extract:/usr/src/jbig2enc/build jbig2enc/${version}/amd64/
|
||||
mv jbig2enc/${version}/amd64/build/* jbig2enc/${version}/amd64/
|
||||
docker rm jbig2enc-extract
|
||||
|
||||
docker pull --quiet --platform linux/arm64 ${tag}
|
||||
docker create --platform linux/arm64 --name jbig2enc-extract ${tag}
|
||||
mkdir --parents jbig2enc/${version}/arm64
|
||||
docker cp jbig2enc-extract:/usr/src/jbig2enc/build jbig2enc/${version}/arm64
|
||||
mv jbig2enc/${version}/arm64/build/* jbig2enc/${version}/arm64/
|
||||
docker rm jbig2enc-extract
|
||||
|
||||
docker pull --quiet --platform linux/arm/v7 ${tag}
|
||||
docker create --platform linux/arm/v7 --name jbig2enc-extract ${tag}
|
||||
mkdir --parents jbig2enc/${version}/armv7
|
||||
docker cp jbig2enc-extract:/usr/src/jbig2enc/build jbig2enc/${version}/armv7
|
||||
mv jbig2enc/${version}/armv7/build/* jbig2enc/${version}/armv7/
|
||||
docker rm jbig2enc-extract
|
||||
-
|
||||
name: Show file structure
|
||||
run: |
|
||||
tree .
|
||||
-
|
||||
name: Commit files
|
||||
run: |
|
||||
git config --global user.name "github-actions"
|
||||
git config --global user.email "41898282+github-actions[bot]@users.noreply.github.com"
|
||||
git add pikepdf/ qpdf/ psycopg2/ jbig2enc/
|
||||
git commit -m "Updating installer packages" || true
|
||||
git push origin || true
|
32
.github/workflows/project-actions.yml
vendored
@@ -1,10 +1,6 @@
|
||||
name: Project Automations
|
||||
|
||||
on:
|
||||
issues:
|
||||
types:
|
||||
- opened
|
||||
- reopened
|
||||
pull_request_target: #_target allows access to secrets
|
||||
types:
|
||||
- opened
|
||||
@@ -16,25 +12,7 @@ on:
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
env:
|
||||
todo: Todo
|
||||
done: Done
|
||||
in_progress: In Progress
|
||||
|
||||
jobs:
|
||||
issue_opened_or_reopened:
|
||||
name: issue_opened_or_reopened
|
||||
runs-on: ubuntu-22.04
|
||||
if: github.event_name == 'issues' && (github.event.action == 'opened' || github.event.action == 'reopened')
|
||||
steps:
|
||||
- name: Add issue to project and set status to ${{ env.todo }}
|
||||
uses: leonsteinhaeuser/project-beta-automations@v2.0.1
|
||||
with:
|
||||
gh_token: ${{ secrets.GH_TOKEN }}
|
||||
organization: paperless-ngx
|
||||
project_id: 2
|
||||
resource_node_id: ${{ github.event.issue.node_id }}
|
||||
status_value: ${{ env.todo }} # Target status
|
||||
pr_opened_or_reopened:
|
||||
name: pr_opened_or_reopened
|
||||
runs-on: ubuntu-22.04
|
||||
@@ -43,15 +21,7 @@ jobs:
|
||||
pull-requests: write
|
||||
if: github.event_name == 'pull_request_target' && (github.event.action == 'opened' || github.event.action == 'reopened') && github.event.pull_request.user.login != 'dependabot'
|
||||
steps:
|
||||
- name: Add PR to project and set status to "Needs Review"
|
||||
uses: leonsteinhaeuser/project-beta-automations@v2.0.1
|
||||
with:
|
||||
gh_token: ${{ secrets.GH_TOKEN }}
|
||||
organization: paperless-ngx
|
||||
project_id: 2
|
||||
resource_node_id: ${{ github.event.pull_request.node_id }}
|
||||
status_value: "Needs Review" # Target status
|
||||
- name: Label PR with release-drafter
|
||||
uses: release-drafter/release-drafter@v5
|
||||
uses: release-drafter/release-drafter@v6
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
281
.github/workflows/repo-maintenance.yml
vendored
Normal file
@@ -0,0 +1,281 @@
|
||||
name: 'Repository Maintenance'
|
||||
|
||||
on:
|
||||
schedule:
|
||||
- cron: '0 3 * * *'
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
issues: write
|
||||
pull-requests: write
|
||||
discussions: write
|
||||
|
||||
concurrency:
|
||||
group: lock
|
||||
|
||||
jobs:
|
||||
stale:
|
||||
name: 'Stale'
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/stale@v9
|
||||
with:
|
||||
days-before-stale: 7
|
||||
days-before-close: 14
|
||||
any-of-labels: 'cant-reproduce,not a bug'
|
||||
stale-issue-label: stale
|
||||
stale-pr-label: stale
|
||||
stale-issue-message: >
|
||||
This issue has been automatically marked as stale because it has not had
|
||||
recent activity. It will be closed if no further activity occurs. Thank you
|
||||
for your contributions. See our [contributing guidelines](https://github.com/paperless-ngx/paperless-ngx/blob/dev/CONTRIBUTING.md#automatic-repository-maintenance) for more details.
|
||||
lock-threads:
|
||||
name: 'Lock Old Threads'
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: dessant/lock-threads@v5
|
||||
with:
|
||||
issue-inactive-days: '30'
|
||||
pr-inactive-days: '30'
|
||||
discussion-inactive-days: '30'
|
||||
log-output: true
|
||||
issue-comment: >
|
||||
This issue has been automatically locked since there
|
||||
has not been any recent activity after it was closed.
|
||||
Please open a new discussion or issue for related concerns.
|
||||
See our [contributing guidelines](https://github.com/paperless-ngx/paperless-ngx/blob/dev/CONTRIBUTING.md#automatic-repository-maintenance) for more details.
|
||||
pr-comment: >
|
||||
This pull request has been automatically locked since there
|
||||
has not been any recent activity after it was closed.
|
||||
Please open a new discussion or issue for related concerns.
|
||||
See our [contributing guidelines](https://github.com/paperless-ngx/paperless-ngx/blob/dev/CONTRIBUTING.md#automatic-repository-maintenance) for more details.
|
||||
discussion-comment: >
|
||||
This discussion has been automatically locked since there
|
||||
has not been any recent activity after it was closed.
|
||||
Please open a new discussion for related concerns.
|
||||
See our [contributing guidelines](https://github.com/paperless-ngx/paperless-ngx/blob/dev/CONTRIBUTING.md#automatic-repository-maintenance) for more details.
|
||||
close-answered-discussions:
|
||||
name: 'Close Answered Discussions'
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/github-script@v7
|
||||
with:
|
||||
script: |
|
||||
function sleep(ms) {
|
||||
return new Promise(resolve => setTimeout(resolve, ms));
|
||||
}
|
||||
|
||||
const query = `query($owner:String!, $name:String!) {
|
||||
repository(owner:$owner, name:$name){
|
||||
discussions(first:100, answered:true, states:[OPEN]) {
|
||||
nodes {
|
||||
id,
|
||||
number
|
||||
}
|
||||
}
|
||||
}
|
||||
}`;
|
||||
const variables = {
|
||||
owner: context.repo.owner,
|
||||
name: context.repo.repo,
|
||||
}
|
||||
const result = await github.graphql(query, variables)
|
||||
|
||||
console.log(`Found ${result.repository.discussions.nodes.length} open answered discussions`)
|
||||
|
||||
for (const discussion of result.repository.discussions.nodes) {
|
||||
console.log(`Closing discussion #${discussion.number} (${discussion.id})`)
|
||||
|
||||
const addCommentMutation = `mutation($discussion:ID!, $body:String!) {
|
||||
addDiscussionComment(input:{discussionId:$discussion, body:$body}) {
|
||||
clientMutationId
|
||||
}
|
||||
}`;
|
||||
const commentVariables = {
|
||||
discussion: discussion.id,
|
||||
body: 'This discussion has been automatically closed because it was marked as answered. Please see our [contributing guidelines](https://github.com/paperless-ngx/paperless-ngx/blob/dev/CONTRIBUTING.md#automatic-repository-maintenance) for more details.',
|
||||
}
|
||||
await github.graphql(addCommentMutation, commentVariables)
|
||||
|
||||
const closeDiscussionMutation = `mutation($discussion:ID!, $reason:DiscussionCloseReason!) {
|
||||
closeDiscussion(input:{discussionId:$discussion, reason:$reason}) {
|
||||
clientMutationId
|
||||
}
|
||||
}`;
|
||||
const closeVariables = {
|
||||
discussion: discussion.id,
|
||||
reason: "RESOLVED",
|
||||
}
|
||||
await github.graphql(closeDiscussionMutation, closeVariables)
|
||||
|
||||
await sleep(1000)
|
||||
}
|
||||
close-outdated-discussions:
|
||||
name: 'Close Outdated Discussions'
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/github-script@v7
|
||||
with:
|
||||
script: |
|
||||
function sleep(ms) {
|
||||
return new Promise(resolve => setTimeout(resolve, ms));
|
||||
}
|
||||
|
||||
const CUTOFF_DAYS = 180;
|
||||
const cutoff = new Date();
|
||||
cutoff.setDate(cutoff.getDate() - CUTOFF_DAYS);
|
||||
|
||||
const query = `query(
|
||||
$owner:String!,
|
||||
$name:String!,
|
||||
$supportCategory:ID!,
|
||||
$generalCategory:ID!,
|
||||
) {
|
||||
supportDiscussions: repository(owner:$owner, name:$name){
|
||||
discussions(
|
||||
categoryId:$supportCategory,
|
||||
last:50,
|
||||
answered:false,
|
||||
states:[OPEN],
|
||||
) {
|
||||
nodes {
|
||||
id,
|
||||
number,
|
||||
updatedAt
|
||||
}
|
||||
},
|
||||
},
|
||||
generalDiscussions: repository(owner:$owner, name:$name){
|
||||
discussions(
|
||||
categoryId:$generalCategory,
|
||||
last:50,
|
||||
states:[OPEN],
|
||||
) {
|
||||
nodes {
|
||||
id,
|
||||
number,
|
||||
updatedAt
|
||||
}
|
||||
}
|
||||
}
|
||||
}`;
|
||||
const variables = {
|
||||
owner: context.repo.owner,
|
||||
name: context.repo.repo,
|
||||
supportCategory: "DIC_kwDOG1Zs184CBKWK",
|
||||
generalCategory: "DIC_kwDOG1Zs184CBKWJ"
|
||||
}
|
||||
const result = await github.graphql(query, variables);
|
||||
const combinedDiscussions = [
|
||||
...result.supportDiscussions.discussions.nodes,
|
||||
...result.generalDiscussions.discussions.nodes,
|
||||
]
|
||||
|
||||
console.log(`Checking ${combinedDiscussions.length} open discussions`);
|
||||
|
||||
for (const discussion of combinedDiscussions) {
|
||||
if (new Date(discussion.updatedAt) < cutoff) {
|
||||
console.log(`Closing outdated discussion #${discussion.number} (${discussion.id}), last updated at ${discussion.updatedAt}`);
|
||||
const addCommentMutation = `mutation($discussion:ID!, $body:String!) {
|
||||
addDiscussionComment(input:{discussionId:$discussion, body:$body}) {
|
||||
clientMutationId
|
||||
}
|
||||
}`;
|
||||
const commentVariables = {
|
||||
discussion: discussion.id,
|
||||
body: 'This discussion has been automatically closed due to inactivity. Please see our [contributing guidelines](https://github.com/paperless-ngx/paperless-ngx/blob/dev/CONTRIBUTING.md#automatic-repository-maintenance) for more details.',
|
||||
}
|
||||
await github.graphql(addCommentMutation, commentVariables);
|
||||
|
||||
const closeDiscussionMutation = `mutation($discussion:ID!, $reason:DiscussionCloseReason!) {
|
||||
closeDiscussion(input:{discussionId:$discussion, reason:$reason}) {
|
||||
clientMutationId
|
||||
}
|
||||
}`;
|
||||
const closeVariables = {
|
||||
discussion: discussion.id,
|
||||
reason: "OUTDATED",
|
||||
}
|
||||
await github.graphql(closeDiscussionMutation, closeVariables);
|
||||
|
||||
await sleep(1000);
|
||||
}
|
||||
}
|
||||
close-unsupported-feature-requests:
|
||||
name: 'Close Unsupported Feature Requests'
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/github-script@v7
|
||||
with:
|
||||
script: |
|
||||
function sleep(ms) {
|
||||
return new Promise(resolve => setTimeout(resolve, ms));
|
||||
}
|
||||
|
||||
const CUTOFF_1_DAYS = 180;
|
||||
const CUTOFF_1_COUNT = 5;
|
||||
const CUTOFF_2_DAYS = 365;
|
||||
const CUTOFF_2_COUNT = 10;
|
||||
|
||||
const cutoff1Date = new Date();
|
||||
cutoff1Date.setDate(cutoff1Date.getDate() - CUTOFF_1_DAYS);
|
||||
const cutoff2Date = new Date();
|
||||
cutoff2Date.setDate(cutoff2Date.getDate() - CUTOFF_2_DAYS);
|
||||
|
||||
const query = `query(
|
||||
$owner:String!,
|
||||
$name:String!,
|
||||
$featureRequestsCategory:ID!,
|
||||
) {
|
||||
repository(owner:$owner, name:$name){
|
||||
discussions(
|
||||
categoryId:$featureRequestsCategory,
|
||||
last:100,
|
||||
states:[OPEN],
|
||||
) {
|
||||
nodes {
|
||||
id,
|
||||
number,
|
||||
updatedAt,
|
||||
upvoteCount,
|
||||
}
|
||||
},
|
||||
}
|
||||
}`;
|
||||
const variables = {
|
||||
owner: context.repo.owner,
|
||||
name: context.repo.repo,
|
||||
featureRequestsCategory: "DIC_kwDOG1Zs184CBNr4"
|
||||
}
|
||||
const result = await github.graphql(query, variables);
|
||||
|
||||
for (const discussion of result.repository.discussions.nodes) {
|
||||
const discussionDate = new Date(discussion.updatedAt);
|
||||
if ((discussionDate < cutoff1Date && discussion.upvoteCount < CUTOFF_1_COUNT) ||
|
||||
(discussionDate < cutoff2Date && discussion.upvoteCount < CUTOFF_2_COUNT)) {
|
||||
console.log(`Closing discussion #${discussion.number} (${discussion.id}), last updated at ${discussion.updatedAt} with votes ${discussion.upvoteCount}`);
|
||||
const addCommentMutation = `mutation($discussion:ID!, $body:String!) {
|
||||
addDiscussionComment(input:{discussionId:$discussion, body:$body}) {
|
||||
clientMutationId
|
||||
}
|
||||
}`;
|
||||
const commentVariables = {
|
||||
discussion: discussion.id,
|
||||
body: 'This discussion has been automatically closed due to lack of community support. Please see our [contributing guidelines](https://github.com/paperless-ngx/paperless-ngx/blob/dev/CONTRIBUTING.md#automatic-repository-maintenance) for more details.',
|
||||
}
|
||||
await github.graphql(addCommentMutation, commentVariables);
|
||||
|
||||
const closeDiscussionMutation = `mutation($discussion:ID!, $reason:DiscussionCloseReason!) {
|
||||
closeDiscussion(input:{discussionId:$discussion, reason:$reason}) {
|
||||
clientMutationId
|
||||
}
|
||||
}`;
|
||||
const closeVariables = {
|
||||
discussion: discussion.id,
|
||||
reason: "OUTDATED",
|
||||
}
|
||||
await github.graphql(closeDiscussionMutation, closeVariables);
|
||||
|
||||
await sleep(1000);
|
||||
}
|
||||
}
|
57
.github/workflows/reusable-workflow-builder.yml
vendored
@@ -1,57 +0,0 @@
|
||||
name: Reusable Image Builder
|
||||
|
||||
on:
|
||||
workflow_call:
|
||||
inputs:
|
||||
dockerfile:
|
||||
required: true
|
||||
type: string
|
||||
build-json:
|
||||
required: true
|
||||
type: string
|
||||
build-args:
|
||||
required: false
|
||||
default: ""
|
||||
type: string
|
||||
build-platforms:
|
||||
required: false
|
||||
default: linux/amd64,linux/arm64,linux/arm/v7
|
||||
type: string
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ fromJSON(inputs.build-json).name }}-${{ fromJSON(inputs.build-json).version }}
|
||||
cancel-in-progress: false
|
||||
|
||||
jobs:
|
||||
build-image:
|
||||
name: Build ${{ fromJSON(inputs.build-json).name }} @ ${{ fromJSON(inputs.build-json).version }}
|
||||
runs-on: ubuntu-22.04
|
||||
steps:
|
||||
-
|
||||
name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
-
|
||||
name: Login to Github Container Registry
|
||||
uses: docker/login-action@v2
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
-
|
||||
name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v2
|
||||
-
|
||||
name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v2
|
||||
-
|
||||
name: Build ${{ fromJSON(inputs.build-json).name }}
|
||||
uses: docker/build-push-action@v4
|
||||
with:
|
||||
context: .
|
||||
file: ${{ inputs.dockerfile }}
|
||||
tags: ${{ fromJSON(inputs.build-json).image_tag }}
|
||||
platforms: ${{ inputs.build-platforms }}
|
||||
build-args: ${{ inputs.build-args }}
|
||||
push: true
|
||||
cache-from: type=registry,ref=${{ fromJSON(inputs.build-json).cache_tag }}
|
||||
cache-to: type=registry,mode=max,ref=${{ fromJSON(inputs.build-json).cache_tag }}
|
1
.gitignore
vendored
@@ -73,6 +73,7 @@ virtualenv
|
||||
.venv/
|
||||
/docker-compose.env
|
||||
/docker-compose.yml
|
||||
.ruff_cache/
|
||||
|
||||
# Used for development
|
||||
scripts/import-for-development
|
||||
|
@@ -5,12 +5,14 @@
|
||||
repos:
|
||||
# General hooks
|
||||
- repo: https://github.com/pre-commit/pre-commit-hooks
|
||||
rev: v4.4.0
|
||||
rev: v4.5.0
|
||||
hooks:
|
||||
- id: check-docstring-first
|
||||
- id: check-json
|
||||
exclude: "tsconfig.*json"
|
||||
- id: check-yaml
|
||||
args:
|
||||
- "--unsafe"
|
||||
- id: check-toml
|
||||
- id: check-executables-have-shebangs
|
||||
- id: end-of-file-fixer
|
||||
@@ -26,8 +28,16 @@ repos:
|
||||
- svg
|
||||
- id: check-case-conflict
|
||||
- id: detect-private-key
|
||||
- repo: https://github.com/codespell-project/codespell
|
||||
rev: v2.2.6
|
||||
hooks:
|
||||
- id: codespell
|
||||
exclude: "(^src-ui/src/locale/)|(^src-ui/e2e/)|(^src/paperless_mail/tests/samples/)"
|
||||
exclude_types:
|
||||
- pofile
|
||||
- json
|
||||
- repo: https://github.com/pre-commit/mirrors-prettier
|
||||
rev: "v2.7.1"
|
||||
rev: 'v3.1.0'
|
||||
hooks:
|
||||
- id: prettier
|
||||
types_or:
|
||||
@@ -36,42 +46,17 @@ repos:
|
||||
- markdown
|
||||
exclude: "(^Pipfile\\.lock$)"
|
||||
# Python hooks
|
||||
- repo: https://github.com/asottile/reorder_python_imports
|
||||
rev: v3.9.0
|
||||
- repo: https://github.com/astral-sh/ruff-pre-commit
|
||||
rev: 'v0.3.0'
|
||||
hooks:
|
||||
- id: reorder-python-imports
|
||||
exclude: "(migrations)"
|
||||
- repo: https://github.com/asottile/yesqa
|
||||
rev: "v1.4.0"
|
||||
hooks:
|
||||
- id: yesqa
|
||||
exclude: "(migrations)"
|
||||
- repo: https://github.com/asottile/add-trailing-comma
|
||||
rev: "v2.4.0"
|
||||
hooks:
|
||||
- id: add-trailing-comma
|
||||
exclude: "(migrations)"
|
||||
- repo: https://github.com/PyCQA/flake8
|
||||
rev: 6.0.0
|
||||
hooks:
|
||||
- id: flake8
|
||||
files: ^src/
|
||||
args:
|
||||
- "--config=./src/setup.cfg"
|
||||
- repo: https://github.com/psf/black
|
||||
rev: 22.12.0
|
||||
- id: ruff
|
||||
- repo: https://github.com/psf/black-pre-commit-mirror
|
||||
rev: 24.2.0
|
||||
hooks:
|
||||
- id: black
|
||||
- repo: https://github.com/asottile/pyupgrade
|
||||
rev: v3.3.1
|
||||
hooks:
|
||||
- id: pyupgrade
|
||||
exclude: "(migrations)"
|
||||
args:
|
||||
- "--py38-plus"
|
||||
# Dockerfile hooks
|
||||
- repo: https://github.com/AleksaC/hadolint-py
|
||||
rev: v2.10.0
|
||||
rev: v2.12.0.3
|
||||
hooks:
|
||||
- id: hadolint
|
||||
# Shell script hooks
|
||||
@@ -82,6 +67,6 @@ repos:
|
||||
args:
|
||||
- "--tab"
|
||||
- repo: https://github.com/shellcheck-py/shellcheck-py
|
||||
rev: "v0.9.0.2"
|
||||
rev: "v0.9.0.6"
|
||||
hooks:
|
||||
- id: shellcheck
|
||||
|
20
.prettierrc
@@ -1,4 +1,16 @@
|
||||
# https://prettier.io/docs/en/options.html#semicolons
|
||||
semi: false
|
||||
# https://prettier.io/docs/en/options.html#quotes
|
||||
singleQuote: true
|
||||
{
|
||||
# https://prettier.io/docs/en/options.html#semicolons
|
||||
"semi": false,
|
||||
# https://prettier.io/docs/en/options.html#quotes
|
||||
"singleQuote": true,
|
||||
# https://prettier.io/docs/en/options.html#trailing-commas
|
||||
"trailingComma": "es5",
|
||||
"overrides": [
|
||||
{
|
||||
"files": ["index.md", "administration.md"],
|
||||
"options": {
|
||||
"tabWidth": 4
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
@@ -1 +1 @@
|
||||
3.8.16
|
||||
3.9.18
|
||||
|
47
.ruff.toml
Normal file
@@ -0,0 +1,47 @@
|
||||
fix = true
|
||||
line-length = 88
|
||||
respect-gitignore = true
|
||||
src = ["src"]
|
||||
target-version = "py39"
|
||||
output-format = "grouped"
|
||||
show-fixes = true
|
||||
|
||||
# https://docs.astral.sh/ruff/settings/
|
||||
# https://docs.astral.sh/ruff/rules/
|
||||
[lint]
|
||||
extend-select = [
|
||||
"W", # https://docs.astral.sh/ruff/rules/#pycodestyle-e-w
|
||||
"I", # https://docs.astral.sh/ruff/rules/#isort-i
|
||||
"UP", # https://docs.astral.sh/ruff/rules/#pyupgrade-up
|
||||
"COM", # https://docs.astral.sh/ruff/rules/#flake8-commas-com
|
||||
"DJ", # https://docs.astral.sh/ruff/rules/#flake8-django-dj
|
||||
"EXE", # https://docs.astral.sh/ruff/rules/#flake8-executable-exe
|
||||
"ISC", # https://docs.astral.sh/ruff/rules/#flake8-implicit-str-concat-isc
|
||||
"ICN", # https://docs.astral.sh/ruff/rules/#flake8-import-conventions-icn
|
||||
"G201", # https://docs.astral.sh/ruff/rules/#flake8-logging-format-g
|
||||
"INP", # https://docs.astral.sh/ruff/rules/#flake8-no-pep420-inp
|
||||
"PIE", # https://docs.astral.sh/ruff/rules/#flake8-pie-pie
|
||||
"Q", # https://docs.astral.sh/ruff/rules/#flake8-quotes-q
|
||||
"RSE", # https://docs.astral.sh/ruff/rules/#flake8-raise-rse
|
||||
"T20", # https://docs.astral.sh/ruff/rules/#flake8-print-t20
|
||||
"SIM", # https://docs.astral.sh/ruff/rules/#flake8-simplify-sim
|
||||
"TID", # https://docs.astral.sh/ruff/rules/#flake8-tidy-imports-tid
|
||||
"TCH", # https://docs.astral.sh/ruff/rules/#flake8-type-checking-tch
|
||||
"PLC", # https://docs.astral.sh/ruff/rules/#pylint-pl
|
||||
"PLE", # https://docs.astral.sh/ruff/rules/#pylint-pl
|
||||
"RUF", # https://docs.astral.sh/ruff/rules/#ruff-specific-rules-ruf
|
||||
"FLY", # https://docs.astral.sh/ruff/rules/#flynt-fly
|
||||
]
|
||||
# TODO PTH https://docs.astral.sh/ruff/rules/#flake8-use-pathlib-pth
|
||||
ignore = ["DJ001", "SIM105", "RUF012"]
|
||||
|
||||
[lint.per-file-ignores]
|
||||
".github/scripts/*.py" = ["E501", "INP001", "SIM117"]
|
||||
"docker/wait-for-redis.py" = ["INP001", "T201"]
|
||||
"*/tests/*.py" = ["E501", "SIM117"]
|
||||
"*/migrations/*.py" = ["E501", "SIM", "T201"]
|
||||
"src/paperless_tesseract/tests/test_parser.py" = ["RUF001"]
|
||||
"src/documents/models.py" = ["SIM115"]
|
||||
|
||||
[lint.isort]
|
||||
force-single-line = true
|
@@ -11,7 +11,7 @@ If you want to implement something big:
|
||||
|
||||
## Python
|
||||
|
||||
Paperless supports python 3.8 and 3.9. We format Python code with [Black](https://github.com/psf/black).
|
||||
Paperless supports python 3.9 - 3.11. We format Python code with [Black](https://github.com/psf/black).
|
||||
|
||||
## Branches
|
||||
|
||||
@@ -45,7 +45,7 @@ Examples of `non-trivial` PRs might include:
|
||||
|
||||
- Additional features
|
||||
- Large changes to many distinct files
|
||||
- Breaking or depreciation of existing features
|
||||
- Breaking or deprecation of existing features
|
||||
|
||||
Our community review process for `non-trivial` PRs is the following:
|
||||
|
||||
@@ -58,6 +58,13 @@ Our community review process for `non-trivial` PRs is the following:
|
||||
|
||||
This process might be slow as community members have different schedules and time to dedicate to the Paperless project. However it ensures community code reviews are as brilliantly thorough as they once were with @jonaswinkler.
|
||||
|
||||
# AI-Generated Code
|
||||
|
||||
This project does not specifically prohibit the use of AI-generated code _during the process_ of creating a PR, however:
|
||||
|
||||
1. Any code present in the final PR that was generated using AI sources should be clearly attributed as such and must not violate copyright protections.
|
||||
2. We will not accept PRs that are entirely or mostly AI-derived.
|
||||
|
||||
# Translating Paperless-ngx
|
||||
|
||||
Some notes about translation:
|
||||
@@ -87,7 +94,7 @@ The following files need to be changed:
|
||||
|
||||
- src-ui/angular.json (under the _projects/paperless-ui/i18n/locales_ JSON key)
|
||||
- src/paperless/settings.py (in the _LANGUAGES_ array)
|
||||
- src-ui/src/app/services/settings.service.ts (inside the _getLanguageOptions_ method)
|
||||
- src-ui/src/app/services/settings.service.ts (inside the _LANGUAGE_OPTIONS_ array)
|
||||
- src-ui/src/app/app.module.ts (import locale from _angular/common/locales_ and call _registerLocaleData_)
|
||||
|
||||
Please add the language in the correct order, alphabetically by locale.
|
||||
@@ -130,3 +137,19 @@ All team members are notified when mentioned or assigned to a relevant issue or
|
||||
We are not overly strict with inviting people to the organization. If you have read the [team permissions](#permissions) and think having additional access would enhance your contributions, please reach out to an [admin](#structure) of the team.
|
||||
|
||||
The admins occasionally invite contributors directly if we believe having them on a team will accelerate their work.
|
||||
|
||||
# Automatic Repository Maintenance
|
||||
|
||||
The Paperless-ngx team appreciates all effort and interest from the community in filing bug reports, creating feature requests, sharing ideas and helping other
|
||||
community members. That said, in an effort to keep the repository organized and managebale the project uses automatic handling of certain areas:
|
||||
|
||||
- Issues that cannot be reproduced will be marked 'stale' after 7 days of inactivity and closed after 14 further days of inactivity.
|
||||
- Issues, pull requests and discussions that are closed will be locked after 30 days of inactivity.
|
||||
- Discussions with a marked answer will be automatically closed.
|
||||
- Discussions in the 'General' or 'Support' categories will be closed after 180 days of inactivity.
|
||||
- Feature requests that do not meet the following thresholds will be closed: 5 "up-votes" after 180 days of inactivity or 10 "up-votes" after 365 days.
|
||||
|
||||
In all cases, threads can be re-opened by project maintainers and, of course, users can always create a new discussion for related concerns.
|
||||
Finally, remember that all information remains searchable and 'closed' feature requests can still serve as inspiration for new features.
|
||||
|
||||
Thank you all for your contributions.
|
||||
|
165
Dockerfile
@@ -1,18 +1,18 @@
|
||||
# syntax=docker/dockerfile:1.4
|
||||
# syntax=docker/dockerfile:1
|
||||
# https://github.com/moby/buildkit/blob/master/frontend/dockerfile/docs/reference.md
|
||||
|
||||
# Stage: compile-frontend
|
||||
# Purpose: Compiles the frontend
|
||||
# Notes:
|
||||
# - Does NPM stuff with Typescript and such
|
||||
FROM --platform=$BUILDPLATFORM node:16-bullseye-slim AS compile-frontend
|
||||
FROM --platform=$BUILDPLATFORM docker.io/node:20-bookworm-slim AS compile-frontend
|
||||
|
||||
COPY ./src-ui /src/src-ui
|
||||
|
||||
WORKDIR /src/src-ui
|
||||
RUN set -eux \
|
||||
&& npm update npm -g \
|
||||
&& npm ci --omit=optional
|
||||
&& npm ci
|
||||
RUN set -eux \
|
||||
&& ./node_modules/.bin/ng build --configuration production
|
||||
|
||||
@@ -21,7 +21,7 @@ RUN set -eux \
|
||||
# Comments:
|
||||
# - pipenv dependencies are not left in the final image
|
||||
# - pipenv can't touch the final image somehow
|
||||
FROM --platform=$BUILDPLATFORM python:3.9-slim-bullseye as pipenv-base
|
||||
FROM --platform=$BUILDPLATFORM docker.io/python:3.11-alpine as pipenv-base
|
||||
|
||||
WORKDIR /usr/src/pipenv
|
||||
|
||||
@@ -29,7 +29,7 @@ COPY Pipfile* ./
|
||||
|
||||
RUN set -eux \
|
||||
&& echo "Installing pipenv" \
|
||||
&& python3 -m pip install --no-cache-dir --upgrade pipenv==2022.11.30 \
|
||||
&& python3 -m pip install --no-cache-dir --upgrade pipenv==2023.12.1 \
|
||||
&& echo "Generating requirement.txt" \
|
||||
&& pipenv requirements > requirements.txt
|
||||
|
||||
@@ -37,7 +37,7 @@ RUN set -eux \
|
||||
# Purpose: The final image
|
||||
# Comments:
|
||||
# - Don't leave anything extra in here
|
||||
FROM python:3.9-slim-bullseye as main-app
|
||||
FROM docker.io/python:3.11-slim-bookworm as main-app
|
||||
|
||||
LABEL org.opencontainers.image.authors="paperless-ngx team <hello@paperless-ngx.com>"
|
||||
LABEL org.opencontainers.image.documentation="https://docs.paperless-ngx.com/"
|
||||
@@ -46,15 +46,21 @@ LABEL org.opencontainers.image.url="https://github.com/paperless-ngx/paperless-n
|
||||
LABEL org.opencontainers.image.licenses="GPL-3.0-only"
|
||||
|
||||
ARG DEBIAN_FRONTEND=noninteractive
|
||||
|
||||
# Buildx provided, must be defined to use though
|
||||
ARG TARGETARCH
|
||||
ARG TARGETVARIANT
|
||||
|
||||
# Workflow provided
|
||||
ARG JBIG2ENC_VERSION
|
||||
ARG QPDF_VERSION
|
||||
ARG PIKEPDF_VERSION
|
||||
ARG PSYCOPG2_VERSION
|
||||
# Can be workflow provided, defaults set for manual building
|
||||
ARG JBIG2ENC_VERSION=0.29
|
||||
ARG QPDF_VERSION=11.6.4
|
||||
ARG GS_VERSION=10.02.1
|
||||
|
||||
# Set Python environment variables
|
||||
ENV PYTHONDONTWRITEBYTECODE=1 \
|
||||
PYTHONUNBUFFERED=1 \
|
||||
# Ignore warning from Whitenoise
|
||||
PYTHONWARNINGS="ignore:::django.http.response:517" \
|
||||
PNGX_CONTAINERIZED=1
|
||||
|
||||
#
|
||||
# Begin installation and configuration
|
||||
@@ -76,23 +82,11 @@ ARG RUNTIME_PACKAGES="\
|
||||
gnupg \
|
||||
icc-profiles-free \
|
||||
imagemagick \
|
||||
# Image processing
|
||||
liblept5 \
|
||||
liblcms2-2 \
|
||||
libtiff5 \
|
||||
libfreetype6 \
|
||||
libwebp6 \
|
||||
libopenjp2-7 \
|
||||
libimagequant0 \
|
||||
libraqm0 \
|
||||
libjpeg62-turbo \
|
||||
# PostgreSQL
|
||||
libpq5 \
|
||||
postgresql-client \
|
||||
# MySQL / MariaDB
|
||||
mariadb-client \
|
||||
# For Numpy
|
||||
libatlas3-base \
|
||||
# OCRmyPDF dependencies
|
||||
tesseract-ocr \
|
||||
tesseract-ocr-eng \
|
||||
@@ -102,11 +96,12 @@ ARG RUNTIME_PACKAGES="\
|
||||
tesseract-ocr-spa \
|
||||
unpaper \
|
||||
pngquant \
|
||||
# pikepdf / qpdf
|
||||
jbig2dec \
|
||||
# lxml
|
||||
libxml2 \
|
||||
libxslt1.1 \
|
||||
libgnutls30 \
|
||||
# itself
|
||||
qpdf \
|
||||
# Mime type detection
|
||||
file \
|
||||
libmagic1 \
|
||||
@@ -114,9 +109,7 @@ ARG RUNTIME_PACKAGES="\
|
||||
zlib1g \
|
||||
# Barcode splitter
|
||||
libzbar0 \
|
||||
poppler-utils \
|
||||
# RapidFuzz on armv7
|
||||
libatomic1"
|
||||
poppler-utils"
|
||||
|
||||
# Install basic runtime packages.
|
||||
# These change very infrequently
|
||||
@@ -124,7 +117,37 @@ RUN set -eux \
|
||||
echo "Installing system packages" \
|
||||
&& apt-get update \
|
||||
&& apt-get install --yes --quiet --no-install-recommends ${RUNTIME_PACKAGES} \
|
||||
&& rm -rf /var/lib/apt/lists/* \
|
||||
&& echo "Installing pre-built updates" \
|
||||
&& echo "Installing qpdf ${QPDF_VERSION}" \
|
||||
&& curl --fail --silent --show-error --location \
|
||||
--output libqpdf29_${QPDF_VERSION}-1_${TARGETARCH}.deb \
|
||||
https://github.com/paperless-ngx/builder/releases/download/qpdf-${QPDF_VERSION}/libqpdf29_${QPDF_VERSION}-1_${TARGETARCH}.deb \
|
||||
&& curl --fail --silent --show-error --location \
|
||||
--output qpdf_${QPDF_VERSION}-1_${TARGETARCH}.deb \
|
||||
https://github.com/paperless-ngx/builder/releases/download/qpdf-${QPDF_VERSION}/qpdf_${QPDF_VERSION}-1_${TARGETARCH}.deb \
|
||||
&& dpkg --install ./libqpdf29_${QPDF_VERSION}-1_${TARGETARCH}.deb \
|
||||
&& dpkg --install ./qpdf_${QPDF_VERSION}-1_${TARGETARCH}.deb \
|
||||
&& echo "Installing Ghostscript ${GS_VERSION}" \
|
||||
&& curl --fail --silent --show-error --location \
|
||||
--output libgs10_${GS_VERSION}.dfsg-2_${TARGETARCH}.deb \
|
||||
https://github.com/paperless-ngx/builder/releases/download/ghostscript-${GS_VERSION}/libgs10_${GS_VERSION}.dfsg-1_${TARGETARCH}.deb \
|
||||
&& curl --fail --silent --show-error --location \
|
||||
--output ghostscript_${GS_VERSION}.dfsg-2_${TARGETARCH}.deb \
|
||||
https://github.com/paperless-ngx/builder/releases/download/ghostscript-${GS_VERSION}/ghostscript_${GS_VERSION}.dfsg-1_${TARGETARCH}.deb \
|
||||
&& curl --fail --silent --show-error --location \
|
||||
--output libgs10-common_${GS_VERSION}.dfsg-2_all.deb \
|
||||
https://github.com/paperless-ngx/builder/releases/download/ghostscript-${GS_VERSION}/libgs10-common_${GS_VERSION}.dfsg-1_all.deb \
|
||||
&& dpkg --install ./libgs10-common_${GS_VERSION}.dfsg-2_all.deb \
|
||||
&& dpkg --install ./libgs10_${GS_VERSION}.dfsg-2_${TARGETARCH}.deb \
|
||||
&& dpkg --install ./ghostscript_${GS_VERSION}.dfsg-2_${TARGETARCH}.deb \
|
||||
&& echo "Installing jbig2enc" \
|
||||
&& curl --fail --silent --show-error --location \
|
||||
--output jbig2enc_${JBIG2ENC_VERSION}-1_${TARGETARCH}.deb \
|
||||
https://github.com/paperless-ngx/builder/releases/download/jbig2enc-${JBIG2ENC_VERSION}/jbig2enc_${JBIG2ENC_VERSION}-1_${TARGETARCH}.deb \
|
||||
&& dpkg --install ./jbig2enc_${JBIG2ENC_VERSION}-1_${TARGETARCH}.deb \
|
||||
&& echo "Cleaning up image layer" \
|
||||
&& rm --force --verbose *.deb \
|
||||
&& rm --recursive --force --verbose /var/lib/apt/lists/* \
|
||||
&& echo "Installing supervisor" \
|
||||
&& python3 -m pip install --default-timeout=1000 --upgrade --no-cache-dir supervisor==4.2.5
|
||||
|
||||
@@ -171,37 +194,10 @@ RUN set -eux \
|
||||
&& chmod 755 /usr/local/bin/paperless_cmd.sh \
|
||||
&& mv flower-conditional.sh /usr/local/bin/flower-conditional.sh \
|
||||
&& chmod 755 /usr/local/bin/flower-conditional.sh \
|
||||
&& echo "Installing managment commands" \
|
||||
&& echo "Installing management commands" \
|
||||
&& chmod +x install_management_commands.sh \
|
||||
&& ./install_management_commands.sh
|
||||
|
||||
# Install the built packages from the installer library images
|
||||
# These change sometimes
|
||||
RUN set -eux \
|
||||
&& echo "Getting binaries" \
|
||||
&& mkdir paperless-ngx \
|
||||
&& curl --fail --silent --show-error --output paperless-ngx.tar.gz --location https://github.com/paperless-ngx/paperless-ngx/archive/41d6e7e407af09a0882736d50c89b6e015997bff.tar.gz \
|
||||
&& tar -xf paperless-ngx.tar.gz --directory paperless-ngx --strip-components=1 \
|
||||
&& cd paperless-ngx \
|
||||
# Setting a specific revision ensures we know what this installed
|
||||
# and ensures cache breaking on changes
|
||||
&& echo "Installing jbig2enc" \
|
||||
&& cp ./jbig2enc/${JBIG2ENC_VERSION}/${TARGETARCH}${TARGETVARIANT}/jbig2 /usr/local/bin/ \
|
||||
&& cp ./jbig2enc/${JBIG2ENC_VERSION}/${TARGETARCH}${TARGETVARIANT}/libjbig2enc* /usr/local/lib/ \
|
||||
&& echo "Installing qpdf" \
|
||||
&& apt-get install --yes --no-install-recommends ./qpdf/${QPDF_VERSION}/${TARGETARCH}${TARGETVARIANT}/libqpdf29_*.deb \
|
||||
&& apt-get install --yes --no-install-recommends ./qpdf/${QPDF_VERSION}/${TARGETARCH}${TARGETVARIANT}/qpdf_*.deb \
|
||||
&& echo "Installing pikepdf and dependencies" \
|
||||
&& python3 -m pip install --no-cache-dir ./pikepdf/${PIKEPDF_VERSION}/${TARGETARCH}${TARGETVARIANT}/*.whl \
|
||||
&& python3 -m pip list \
|
||||
&& echo "Installing psycopg2" \
|
||||
&& python3 -m pip install --no-cache-dir ./psycopg2/${PSYCOPG2_VERSION}/${TARGETARCH}${TARGETVARIANT}/psycopg2*.whl \
|
||||
&& python3 -m pip list \
|
||||
&& echo "Cleaning up image layer" \
|
||||
&& cd ../ \
|
||||
&& rm -rf paperless-ngx \
|
||||
&& rm paperless-ngx.tar.gz
|
||||
|
||||
WORKDIR /usr/src/paperless/src/
|
||||
|
||||
# Python dependencies
|
||||
@@ -213,44 +209,61 @@ COPY --from=pipenv-base /usr/src/pipenv/requirements.txt ./
|
||||
ARG BUILD_PACKAGES="\
|
||||
build-essential \
|
||||
git \
|
||||
# https://www.psycopg.org/docs/install.html#prerequisites
|
||||
libpq-dev \
|
||||
# https://github.com/PyMySQL/mysqlclient#linux
|
||||
default-libmysqlclient-dev \
|
||||
python3-dev"
|
||||
pkg-config"
|
||||
|
||||
RUN set -eux \
|
||||
# hadolint ignore=DL3042
|
||||
RUN --mount=type=cache,target=/root/.cache/pip/,id=pip-cache \
|
||||
set -eux \
|
||||
&& echo "Installing build system packages" \
|
||||
&& apt-get update \
|
||||
&& apt-get install --yes --quiet --no-install-recommends ${BUILD_PACKAGES} \
|
||||
&& python3 -m pip install --no-cache-dir --upgrade wheel \
|
||||
&& echo "Installing Python requirements" \
|
||||
&& python3 -m pip install --default-timeout=1000 --no-cache-dir --requirement requirements.txt \
|
||||
&& python3 -m pip install --default-timeout=1000 --requirement requirements.txt \
|
||||
&& echo "Patching whitenoise for compression speedup" \
|
||||
&& curl --fail --silent --show-error --location --output 484.patch https://github.com/evansd/whitenoise/pull/484.patch \
|
||||
&& patch -d /usr/local/lib/python3.11/site-packages --verbose -p2 < 484.patch \
|
||||
&& rm 484.patch \
|
||||
&& echo "Installing NLTK data" \
|
||||
&& python3 -W ignore::RuntimeWarning -m nltk.downloader -d "/usr/share/nltk_data" snowball_data \
|
||||
&& python3 -W ignore::RuntimeWarning -m nltk.downloader -d "/usr/share/nltk_data" stopwords \
|
||||
&& python3 -W ignore::RuntimeWarning -m nltk.downloader -d "/usr/share/nltk_data" punkt \
|
||||
&& echo "Cleaning up image" \
|
||||
&& apt-get -y purge ${BUILD_PACKAGES} \
|
||||
&& apt-get -y autoremove --purge \
|
||||
&& apt-get --yes purge ${BUILD_PACKAGES} \
|
||||
&& apt-get --yes autoremove --purge \
|
||||
&& apt-get clean --yes \
|
||||
&& rm -rf /var/lib/apt/lists/* \
|
||||
&& rm -rf /tmp/* \
|
||||
&& rm -rf /var/tmp/* \
|
||||
&& rm -rf /var/cache/apt/archives/* \
|
||||
&& truncate -s 0 /var/log/*log
|
||||
&& rm --recursive --force --verbose /var/lib/apt/lists/* \
|
||||
&& rm --recursive --force --verbose /tmp/* \
|
||||
&& rm --recursive --force --verbose /var/tmp/* \
|
||||
&& rm --recursive --force --verbose /var/cache/apt/archives/* \
|
||||
&& truncate --size 0 /var/log/*log
|
||||
|
||||
# copy backend
|
||||
COPY ./src ./
|
||||
COPY --chown=1000:1000 ./src ./
|
||||
|
||||
# copy frontend
|
||||
COPY --from=compile-frontend /src/src/documents/static/frontend/ ./documents/static/frontend/
|
||||
COPY --from=compile-frontend --chown=1000:1000 /src/src/documents/static/frontend/ ./documents/static/frontend/
|
||||
|
||||
# add users, setup scripts
|
||||
# Mount the compiled frontend to expected location
|
||||
RUN set -eux \
|
||||
&& addgroup --gid 1000 paperless \
|
||||
&& useradd --uid 1000 --gid paperless --home-dir /usr/src/paperless paperless \
|
||||
&& chown -R paperless:paperless /usr/src/paperless \
|
||||
&& gosu paperless python3 manage.py collectstatic --clear --no-input --link \
|
||||
&& gosu paperless python3 manage.py compilemessages
|
||||
&& echo "Setting up user/group" \
|
||||
&& addgroup --gid 1000 paperless \
|
||||
&& useradd --uid 1000 --gid paperless --home-dir /usr/src/paperless paperless \
|
||||
&& echo "Creating volume directories" \
|
||||
&& mkdir --parents --verbose /usr/src/paperless/data \
|
||||
&& mkdir --parents --verbose /usr/src/paperless/media \
|
||||
&& mkdir --parents --verbose /usr/src/paperless/consume \
|
||||
&& mkdir --parents --verbose /usr/src/paperless/export \
|
||||
&& echo "Adjusting all permissions" \
|
||||
&& chown --from root:root --changes --recursive paperless:paperless /usr/src/paperless \
|
||||
&& echo "Collecting static files" \
|
||||
&& gosu paperless python3 manage.py collectstatic --clear --no-input --link \
|
||||
&& gosu paperless python3 manage.py compilemessages
|
||||
|
||||
VOLUME ["/usr/src/paperless/data", \
|
||||
"/usr/src/paperless/media", \
|
||||
@@ -262,3 +275,5 @@ ENTRYPOINT ["/sbin/docker-entrypoint.sh"]
|
||||
EXPOSE 8000
|
||||
|
||||
CMD ["/usr/local/bin/paperless_cmd.sh"]
|
||||
|
||||
HEALTHCHECK --interval=30s --timeout=10s --retries=5 CMD [ "curl", "-fs", "-S", "--max-time", "2", "http://localhost:8000" ]
|
||||
|
119
Pipfile
@@ -3,79 +3,94 @@ url = "https://pypi.python.org/simple"
|
||||
verify_ssl = true
|
||||
name = "pypi"
|
||||
|
||||
[[source]]
|
||||
url = "https://www.piwheels.org/simple"
|
||||
verify_ssl = true
|
||||
name = "piwheels"
|
||||
|
||||
[packages]
|
||||
dateparser = "~=1.1"
|
||||
django = "~=4.1"
|
||||
django-cors-headers = "*"
|
||||
dateparser = "~=1.2"
|
||||
# WARNING: django does not use semver.
|
||||
# Only patch versions are guaranteed to not introduce breaking changes.
|
||||
django = "~=4.2.11"
|
||||
django-allauth = "*"
|
||||
django-auditlog = "*"
|
||||
django-celery-results = "*"
|
||||
django-compression-middleware = "*"
|
||||
django-cors-headers = "*"
|
||||
django-extensions = "*"
|
||||
django-filter = "~=22.1"
|
||||
django-filter = "~=23.5"
|
||||
django-guardian = "*"
|
||||
django-multiselectfield = "*"
|
||||
djangorestframework = "~=3.14"
|
||||
djangorestframework-guardian = "*"
|
||||
drf-writable-nested = "*"
|
||||
bleach = "*"
|
||||
celery = {extras = ["redis"], version = "*"}
|
||||
channels = "~=4.0"
|
||||
channels-redis = "*"
|
||||
concurrent-log-handler = "*"
|
||||
filelock = "*"
|
||||
flower = "*"
|
||||
gotenberg-client = "*"
|
||||
gunicorn = "*"
|
||||
imap-tools = "*"
|
||||
inotifyrecursive = "~=0.3"
|
||||
langdetect = "*"
|
||||
mysqlclient = "*"
|
||||
nltk = "*"
|
||||
ocrmypdf = "~=15.4"
|
||||
pathvalidate = "*"
|
||||
pillow = "~=9.3"
|
||||
pikepdf = "*"
|
||||
python-gnupg = "*"
|
||||
python-dotenv = "*"
|
||||
python-dateutil = "*"
|
||||
python-magic = "*"
|
||||
pdf2image = "*"
|
||||
psycopg2 = "*"
|
||||
python-dateutil = "*"
|
||||
python-dotenv = "*"
|
||||
python-gnupg = "*"
|
||||
python-ipware = "*"
|
||||
python-magic = "*"
|
||||
pyzbar = "*"
|
||||
rapidfuzz = "*"
|
||||
redis = {extras = ["hiredis"], version = "*"}
|
||||
scikit-learn = "~=1.1"
|
||||
numpy = "*"
|
||||
whitenoise = "~=6.2"
|
||||
watchdog = "~=2.1"
|
||||
whoosh="~=2.7"
|
||||
inotifyrecursive = "~=0.3"
|
||||
ocrmypdf = "~=14.0"
|
||||
tqdm = "*"
|
||||
tika = "*"
|
||||
# TODO: This will sadly also install daphne+dependencies,
|
||||
# which an ASGI server we don't need. Adds about 15MB image size.
|
||||
channels = "~=3.0"
|
||||
uvicorn = {extras = ["standard"], version = "*"}
|
||||
concurrent-log-handler = "*"
|
||||
"pdfminer.six" = "*"
|
||||
pyzbar = "*"
|
||||
mysqlclient = "*"
|
||||
celery = {extras = ["redis"], version = "*"}
|
||||
django-celery-results = "*"
|
||||
scikit-learn = "~=1.4"
|
||||
setproctitle = "*"
|
||||
nltk = "*"
|
||||
pdf2image = "*"
|
||||
flower = "*"
|
||||
bleach = "*"
|
||||
#
|
||||
# Packages locked due to issues (try to check if these are fixed in a release every so often)
|
||||
#
|
||||
# Pin this until piwheels is building 1.9 (see https://www.piwheels.org/project/scipy/)
|
||||
scipy = "==1.8.1"
|
||||
# Newer versions aren't builting yet (see https://www.piwheels.org/project/cryptography/)
|
||||
cryptography = "==38.0.1"
|
||||
# Locked version until https://github.com/django/channels_redis/issues/332
|
||||
# is resolved
|
||||
channels-redis = "==3.4.1"
|
||||
|
||||
tika-client = "*"
|
||||
tqdm = "*"
|
||||
uvicorn = {extras = ["standard"], version = "==0.25.0"}
|
||||
watchdog = "~=4.0"
|
||||
whitenoise = "~=6.6"
|
||||
whoosh="~=2.7"
|
||||
zxing-cpp = {version = "*", platform_machine = "== 'x86_64'"}
|
||||
|
||||
[dev-packages]
|
||||
coveralls = "*"
|
||||
# Linting
|
||||
black = "*"
|
||||
pre-commit = "*"
|
||||
ruff = "*"
|
||||
# Testing
|
||||
factory-boy = "*"
|
||||
pytest = "*"
|
||||
pytest-cov = "*"
|
||||
pytest-django = "*"
|
||||
pytest-httpx = "*"
|
||||
pytest-env = "*"
|
||||
pytest-sugar = "*"
|
||||
pytest-xdist = "*"
|
||||
black = "*"
|
||||
pre-commit = "*"
|
||||
pytest-rerunfailures = "*"
|
||||
imagehash = "*"
|
||||
daphne = "*"
|
||||
# Documentation
|
||||
mkdocs-material = "*"
|
||||
mkdocs-glightbox = "*"
|
||||
|
||||
[typing-dev]
|
||||
mypy = "*"
|
||||
types-Pillow = "*"
|
||||
django-filter-stubs = "*"
|
||||
types-python-dateutil = "*"
|
||||
djangorestframework-stubs = {extras= ["compatible-mypy"], version="*"}
|
||||
celery-types = "*"
|
||||
django-stubs = {extras= ["compatible-mypy"], version="*"}
|
||||
types-dateparser = "*"
|
||||
types-bleach = "*"
|
||||
types-redis = "*"
|
||||
types-tqdm = "*"
|
||||
types-Markdown = "*"
|
||||
types-Pygments = "*"
|
||||
types-colorama = "*"
|
||||
types-psycopg2 = "*"
|
||||
types-setuptools = "*"
|
||||
|
5704
Pipfile.lock
generated
68
README.md
@@ -6,8 +6,11 @@
|
||||
[](https://demo.paperless-ngx.com)
|
||||
|
||||
<p align="center">
|
||||
<img src="https://github.com/paperless-ngx/paperless-ngx/raw/main/resources/logo/web/png/Black%20logo%20-%20no%20background.png#gh-light-mode-only" width="50%" />
|
||||
<img src="https://github.com/paperless-ngx/paperless-ngx/raw/main/resources/logo/web/png/White%20logo%20-%20no%20background.png#gh-dark-mode-only" width="50%" />
|
||||
<picture>
|
||||
<source media="(prefers-color-scheme: dark)" srcset="https://github.com/paperless-ngx/paperless-ngx/blob/main/resources/logo/web/png/White%20logo%20-%20no%20background.png" width="50%">
|
||||
<source media="(prefers-color-scheme: light)" srcset="https://github.com/paperless-ngx/paperless-ngx/raw/main/resources/logo/web/png/Black%20logo%20-%20no%20background.png" width="50%">
|
||||
<img src="https://github.com/paperless-ngx/paperless-ngx/raw/main/resources/logo/web/png/Black%20logo%20-%20no%20background.png" width="50%">
|
||||
</picture>
|
||||
</p>
|
||||
|
||||
<!-- omit in toc -->
|
||||
@@ -16,10 +19,9 @@
|
||||
|
||||
Paperless-ngx is a document management system that transforms your physical documents into a searchable online archive so you can keep, well, _less paper_.
|
||||
|
||||
Paperless-ngx forked from [paperless-ng](https://github.com/jonaswinkler/paperless-ng) to continue the great work and distribute responsibility of supporting and advancing the project among a team of people. [Consider joining us!](#community-support) Discussion of this transition can be found in issues
|
||||
[#1599](https://github.com/jonaswinkler/paperless-ng/issues/1599) and [#1632](https://github.com/jonaswinkler/paperless-ng/issues/1632).
|
||||
Paperless-ngx is the official successor to the original [Paperless](https://github.com/the-paperless-project/paperless) & [Paperless-ng](https://github.com/jonaswinkler/paperless-ng) projects and is designed to distribute the responsibility of advancing and supporting the project among a team of people. [Consider joining us!](#community-support)
|
||||
|
||||
A demo is available at [demo.paperless-ngx.com](https://demo.paperless-ngx.com) using login `demo` / `demo`. _Note: demo content is reset frequently and confidential information should not be uploaded._
|
||||
Thanks to the generous folks at [DigitalOcean](https://m.do.co/c/8d70b916d462), a demo is available at [demo.paperless-ngx.com](https://demo.paperless-ngx.com) using login `demo` / `demo`. _Note: demo content is reset frequently and confidential information should not be uploaded._
|
||||
|
||||
- [Features](#features)
|
||||
- [Getting started](#getting-started)
|
||||
@@ -31,39 +33,31 @@ A demo is available at [demo.paperless-ngx.com](https://demo.paperless-ngx.com)
|
||||
- [Affiliated Projects](#affiliated-projects)
|
||||
- [Important Note](#important-note)
|
||||
|
||||
<p align="right">This project is supported by:<br/>
|
||||
<a href="https://m.do.co/c/8d70b916d462" style="padding-top: 4px; display: block;">
|
||||
<picture>
|
||||
<source media="(prefers-color-scheme: dark)" srcset="https://opensource.nyc3.cdn.digitaloceanspaces.com/attribution/assets/SVG/DO_Logo_horizontal_white.svg" width="140px">
|
||||
<source media="(prefers-color-scheme: light)" srcset="https://opensource.nyc3.cdn.digitaloceanspaces.com/attribution/assets/SVG/DO_Logo_horizontal_black_.svg" width="140px">
|
||||
<img src="https://opensource.nyc3.cdn.digitaloceanspaces.com/attribution/assets/SVG/DO_Logo_horizontal_black_.svg" width="140px">
|
||||
</picture>
|
||||
</a>
|
||||
</p>
|
||||
|
||||
# Features
|
||||
|
||||

|
||||

|
||||
<picture>
|
||||
<source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/paperless-ngx/paperless-ngx/main/docs/assets/screenshots/documents-smallcards-dark.png">
|
||||
<source media="(prefers-color-scheme: light)" srcset="https://raw.githubusercontent.com/paperless-ngx/paperless-ngx/main/docs/assets/screenshots/documents-smallcards.png">
|
||||
<img src="https://raw.githubusercontent.com/paperless-ngx/paperless-ngx/main/docs/assets/screenshots/documents-smallcards.png">
|
||||
</picture>
|
||||
|
||||
- Organize and index your scanned documents with tags, correspondents, types, and more.
|
||||
- Performs OCR on your documents, adds selectable text to image only documents and adds tags, correspondents and document types to your documents.
|
||||
- Supports PDF documents, images, plain text files, and Office documents (Word, Excel, Powerpoint, and LibreOffice equivalents).
|
||||
- Office document support is optional and provided by Apache Tika (see [configuration](https://docs.paperless-ngx.com/configuration/#tika))
|
||||
- Paperless stores your documents plain on disk. Filenames and folders are managed by paperless and their format can be configured freely.
|
||||
- Single page application front end.
|
||||
- Includes a dashboard that shows basic statistics and has document upload.
|
||||
- Filtering by tags, correspondents, types, and more.
|
||||
- Customizable views can be saved and displayed on the dashboard.
|
||||
- Full text search helps you find what you need.
|
||||
- Auto completion suggests relevant words from your documents.
|
||||
- Results are sorted by relevance to your search query.
|
||||
- Highlighting shows you which parts of the document matched the query.
|
||||
- Searching for similar documents ("More like this")
|
||||
- Email processing: Paperless adds documents from your email accounts.
|
||||
- Configure multiple accounts and filters for each account.
|
||||
- When adding documents from mail, paperless can move these mail to a new folder, mark them as read, flag them as important or delete them.
|
||||
- Machine learning powered document matching.
|
||||
- Paperless-ngx learns from your documents and will be able to automatically assign tags, correspondents and types to documents once you've stored a few documents in paperless.
|
||||
- Optimized for multi core systems: Paperless-ngx consumes multiple documents in parallel.
|
||||
- The integrated sanity checker makes sure that your document archive is in good health.
|
||||
- [More screenshots are available in the documentation](https://docs.paperless-ngx.com/#screenshots).
|
||||
A full list of [features](https://docs.paperless-ngx.com/#features) and [screenshots](https://docs.paperless-ngx.com/#screenshots) are available in the [documentation](https://docs.paperless-ngx.com/).
|
||||
|
||||
# Getting started
|
||||
|
||||
The easiest way to deploy paperless is docker-compose. The files in the [`/docker/compose` directory](https://github.com/paperless-ngx/paperless-ngx/tree/main/docker/compose) are configured to pull the image from Github Packages.
|
||||
The easiest way to deploy paperless is `docker compose`. The files in the [`/docker/compose` directory](https://github.com/paperless-ngx/paperless-ngx/tree/main/docker/compose) are configured to pull the image from GitHub Packages.
|
||||
|
||||
If you'd like to jump right in, you can configure a docker-compose environment with our install script:
|
||||
If you'd like to jump right in, you can configure a `docker compose` environment with our install script:
|
||||
|
||||
```bash
|
||||
bash -c "$(curl -L https://raw.githubusercontent.com/paperless-ngx/paperless-ngx/main/install-paperless-ngx.sh)"
|
||||
@@ -85,7 +79,7 @@ If you feel like contributing to the project, please do! Bug fixes, enhancements
|
||||
|
||||
## Community Support
|
||||
|
||||
People interested in continuing the work on paperless-ngx are encouraged to reach out here on github and in the [Matrix Room](https://matrix.to/#/#paperless:adnidor.de). If you would like to contribute to the project on an ongoing basis there are multiple [teams](https://github.com/orgs/paperless-ngx/people) (frontend, ci/cd, etc) that could use your help so please reach out!
|
||||
People interested in continuing the work on paperless-ngx are encouraged to reach out here on github and in the [Matrix Room](https://matrix.to/#/#paperless:matrix.org). If you would like to contribute to the project on an ongoing basis there are multiple [teams](https://github.com/orgs/paperless-ngx/people) (frontend, ci/cd, etc) that could use your help so please reach out!
|
||||
|
||||
## Translation
|
||||
|
||||
@@ -101,13 +95,9 @@ For bugs please [open an issue](https://github.com/paperless-ngx/paperless-ngx/i
|
||||
|
||||
# Affiliated Projects
|
||||
|
||||
Paperless has been around a while now, and people are starting to build stuff on top of it. If you're one of those people, we can add your project to this list:
|
||||
|
||||
- [Paperless App](https://github.com/bauerj/paperless_app): An Android/iOS app for Paperless-ngx. Also works with the original Paperless and Paperless-ng.
|
||||
- [Paperless Share](https://github.com/qcasey/paperless_share). Share any files from your Android application with paperless. Very simple, but works with all of the mobile scanning apps out there that allow you to share scanned documents.
|
||||
- [Scan to Paperless](https://github.com/sbrunner/scan-to-paperless): Scan and prepare (crop, deskew, OCR, ...) your documents for Paperless.
|
||||
- [Paperless Mobile](https://github.com/astubenbord/paperless-mobile): A modern, feature rich mobile application for Paperless.
|
||||
Please see [the wiki](https://github.com/paperless-ngx/paperless-ngx/wiki/Affiliated-Projects) for a user-maintained list of affiliated projects and software that is compatible with Paperless-ngx.
|
||||
|
||||
# Important Note
|
||||
|
||||
Document scanners are typically used to scan sensitive documents. Things like your social insurance number, tax records, invoices, etc. Everything is stored in the clear without encryption. This means that Paperless should never be run on an untrusted host. Instead, I recommend that if you do want to use it, run it locally on a server in your own home.
|
||||
> Document scanners are typically used to scan sensitive documents like your social insurance number, tax records, invoices, etc. **Paperless-ngx should never be run on an untrusted host** because information is stored in clear text without encryption. No guarantees are made regarding security (but we do try!) and you use the app at your own risk.
|
||||
> **The safest way to run Paperless-ngx is on a local server in your own home with backups in place**.
|
||||
|
9
SECURITY.md
Normal file
@@ -0,0 +1,9 @@
|
||||
# Security Policy
|
||||
|
||||
## Reporting a Vulnerability
|
||||
|
||||
The Paperless-ngx team and community take security bugs seriously. We appreciate your efforts to responsibly disclose your findings, and will make every effort to acknowledge your contributions.
|
||||
|
||||
To report a security issue, please use the GitHub Security Advisory ["Report a Vulnerability"](https://github.com/paperless-ngx/paperless-ngx/security/advisories/new) tab.
|
||||
|
||||
The team will send a response indicating the next steps in handling your report. After the initial reply to your report, the security team will keep you informed of the progress towards a fix and full announcement, and may ask for additional information or guidance.
|
@@ -1,81 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
# Helper script for building the Docker image locally.
|
||||
# Parses and provides the nessecary versions of other images to Docker
|
||||
# before passing in the rest of script args.
|
||||
|
||||
# First Argument: The Dockerfile to build
|
||||
# Other Arguments: Additional arguments to docker build
|
||||
|
||||
# Example Usage:
|
||||
# ./build-docker-image.sh Dockerfile -t paperless-ngx:my-awesome-feature
|
||||
|
||||
set -eu
|
||||
|
||||
if ! command -v jq &> /dev/null ; then
|
||||
echo "jq required"
|
||||
exit 1
|
||||
elif [ ! -f "$1" ]; then
|
||||
echo "$1 is not a file, please provide the Dockerfile"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Get the branch name (used for caching)
|
||||
branch_name=$(git rev-parse --abbrev-ref HEAD)
|
||||
|
||||
# Parse eithe Pipfile.lock or the .build-config.json
|
||||
jbig2enc_version=$(jq -r '.jbig2enc.version' .build-config.json)
|
||||
qpdf_version=$(jq -r '.qpdf.version' .build-config.json)
|
||||
psycopg2_version=$(jq -r '.default.psycopg2.version | gsub("=";"")' Pipfile.lock)
|
||||
pikepdf_version=$(jq -r '.default.pikepdf.version | gsub("=";"")' Pipfile.lock)
|
||||
pillow_version=$(jq -r '.default.pillow.version | gsub("=";"")' Pipfile.lock)
|
||||
lxml_version=$(jq -r '.default.lxml.version | gsub("=";"")' Pipfile.lock)
|
||||
|
||||
base_filename="$(basename -- "${1}")"
|
||||
build_args_str=""
|
||||
cache_from_str=""
|
||||
|
||||
case "${base_filename}" in
|
||||
|
||||
*.jbig2enc)
|
||||
build_args_str="--build-arg JBIG2ENC_VERSION=${jbig2enc_version}"
|
||||
cache_from_str="--cache-from ghcr.io/paperless-ngx/paperless-ngx/builder/cache/jbig2enc:${jbig2enc_version}"
|
||||
;;
|
||||
|
||||
*.psycopg2)
|
||||
build_args_str="--build-arg PSYCOPG2_VERSION=${psycopg2_version}"
|
||||
cache_from_str="--cache-from ghcr.io/paperless-ngx/paperless-ngx/builder/cache/psycopg2:${psycopg2_version}"
|
||||
;;
|
||||
|
||||
*.qpdf)
|
||||
build_args_str="--build-arg QPDF_VERSION=${qpdf_version}"
|
||||
cache_from_str="--cache-from ghcr.io/paperless-ngx/paperless-ngx/builder/cache/qpdf:${qpdf_version}"
|
||||
;;
|
||||
|
||||
*.pikepdf)
|
||||
build_args_str="--build-arg QPDF_VERSION=${qpdf_version} --build-arg PIKEPDF_VERSION=${pikepdf_version} --build-arg PILLOW_VERSION=${pillow_version} --build-arg LXML_VERSION=${lxml_version}"
|
||||
cache_from_str="--cache-from ghcr.io/paperless-ngx/paperless-ngx/builder/cache/pikepdf:${pikepdf_version}"
|
||||
;;
|
||||
|
||||
Dockerfile)
|
||||
build_args_str="--build-arg QPDF_VERSION=${qpdf_version} --build-arg PIKEPDF_VERSION=${pikepdf_version} --build-arg PSYCOPG2_VERSION=${psycopg2_version} --build-arg JBIG2ENC_VERSION=${jbig2enc_version}"
|
||||
cache_from_str="--cache-from ghcr.io/paperless-ngx/paperless-ngx/builder/cache/app:${branch_name} --cache-from ghcr.io/paperless-ngx/paperless-ngx/builder/cache/app:dev"
|
||||
;;
|
||||
|
||||
*)
|
||||
echo "Unable to match ${base_filename}"
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
|
||||
read -r -a build_args_arr <<< "${build_args_str}"
|
||||
read -r -a cache_from_arr <<< "${cache_from_str}"
|
||||
|
||||
set -eux
|
||||
|
||||
docker buildx build --file "${1}" \
|
||||
--progress=plain \
|
||||
--output=type=docker \
|
||||
"${cache_from_arr[@]}" \
|
||||
"${build_args_arr[@]}" \
|
||||
"${@:2}" .
|
@@ -1,4 +1,6 @@
|
||||
commit_message: '[ci skip]'
|
||||
project_id_env: CROWDIN_PROJECT_ID
|
||||
api_token_env: CROWDIN_PERSONAL_TOKEN
|
||||
preserve_hierarchy: true
|
||||
files:
|
||||
- source: /src/locale/en_US/LC_MESSAGES/django.po
|
||||
translation: /src/locale/%locale_with_underscore%/LC_MESSAGES/django.po
|
||||
|
@@ -1,48 +0,0 @@
|
||||
# This Dockerfile compiles the jbig2enc library
|
||||
# Inputs:
|
||||
# - JBIG2ENC_VERSION - the Git tag to checkout and build
|
||||
|
||||
FROM debian:bullseye-slim as main
|
||||
|
||||
LABEL org.opencontainers.image.description="A intermediate image with jbig2enc built"
|
||||
|
||||
ARG DEBIAN_FRONTEND=noninteractive
|
||||
ARG JBIG2ENC_VERSION
|
||||
|
||||
ARG BUILD_PACKAGES="\
|
||||
build-essential \
|
||||
automake \
|
||||
libtool \
|
||||
libleptonica-dev \
|
||||
zlib1g-dev \
|
||||
git \
|
||||
ca-certificates"
|
||||
|
||||
WORKDIR /usr/src/jbig2enc
|
||||
|
||||
RUN set -eux \
|
||||
&& echo "Installing build tools" \
|
||||
&& apt-get update --quiet \
|
||||
&& apt-get install --yes --quiet --no-install-recommends ${BUILD_PACKAGES} \
|
||||
&& echo "Building jbig2enc" \
|
||||
&& git clone --quiet --branch $JBIG2ENC_VERSION https://github.com/agl/jbig2enc . \
|
||||
&& ./autogen.sh \
|
||||
&& ./configure \
|
||||
&& make \
|
||||
&& echo "Gathering package data" \
|
||||
&& dpkg-query -f '${Package;-40}${Version}\n' -W > ./pkg-list.txt \
|
||||
&& echo "Cleaning up image" \
|
||||
&& apt-get -y purge ${BUILD_PACKAGES} \
|
||||
&& apt-get -y autoremove --purge \
|
||||
&& rm -rf /var/lib/apt/lists/* \
|
||||
&& echo "Moving files around" \
|
||||
&& mkdir build \
|
||||
# Unlink a symlink that causes problems
|
||||
&& unlink ./src/.libs/libjbig2enc.la \
|
||||
# Move what the link pointed to
|
||||
&& mv ./src/libjbig2enc.la ./build/ \
|
||||
# Move the shared library .so files
|
||||
&& mv ./src/.libs/libjbig2enc* ./build/ \
|
||||
# And move the cli binary
|
||||
&& mv ./src/jbig2 ./build/ \
|
||||
&& mv ./pkg-list.txt ./build/
|
@@ -1,118 +0,0 @@
|
||||
# This Dockerfile builds the pikepdf wheel
|
||||
# Inputs:
|
||||
# - REPO - Docker repository to pull qpdf from
|
||||
# - QPDF_VERSION - The image qpdf version to copy .deb files from
|
||||
# - PIKEPDF_VERSION - Version of pikepdf to build wheel for
|
||||
|
||||
# Default to pulling from the main repo registry when manually building
|
||||
ARG REPO="paperless-ngx/paperless-ngx"
|
||||
|
||||
# This does nothing, except provide a name for a copy below
|
||||
ARG QPDF_VERSION
|
||||
FROM --platform=$BUILDPLATFORM ghcr.io/${REPO}/builder/qpdf:${QPDF_VERSION} as qpdf-builder
|
||||
|
||||
#
|
||||
# Stage: builder
|
||||
# Purpose:
|
||||
# - Build the pikepdf wheel
|
||||
# - Build any dependent wheels which can't be found
|
||||
#
|
||||
FROM python:3.9-slim-bullseye as builder
|
||||
|
||||
LABEL org.opencontainers.image.description="A intermediate image with pikepdf wheel built"
|
||||
|
||||
# Buildx provided
|
||||
ARG TARGETARCH
|
||||
ARG TARGETVARIANT
|
||||
|
||||
ARG DEBIAN_FRONTEND=noninteractive
|
||||
# Workflow provided
|
||||
ARG QPDF_VERSION
|
||||
ARG PIKEPDF_VERSION
|
||||
# These are not used, but will still bust the cache if one changes
|
||||
# Otherwise, the main image will try to build thing (and fail)
|
||||
ARG PILLOW_VERSION
|
||||
ARG LXML_VERSION
|
||||
|
||||
ARG BUILD_PACKAGES="\
|
||||
build-essential \
|
||||
python3-dev \
|
||||
python3-pip \
|
||||
# qpdf requirement - https://github.com/qpdf/qpdf#crypto-providers
|
||||
libgnutls28-dev \
|
||||
# lxml requrements - https://lxml.de/installation.html
|
||||
libxml2-dev \
|
||||
libxslt1-dev \
|
||||
# Pillow requirements - https://pillow.readthedocs.io/en/stable/installation.html#external-libraries
|
||||
# JPEG functionality
|
||||
libjpeg62-turbo-dev \
|
||||
# conpressed PNG
|
||||
zlib1g-dev \
|
||||
# compressed TIFF
|
||||
libtiff-dev \
|
||||
# type related services
|
||||
libfreetype-dev \
|
||||
# color management
|
||||
liblcms2-dev \
|
||||
# WebP format
|
||||
libwebp-dev \
|
||||
# JPEG 2000
|
||||
libopenjp2-7-dev \
|
||||
# improved color quantization
|
||||
libimagequant-dev \
|
||||
# complex text layout support
|
||||
libraqm-dev"
|
||||
|
||||
WORKDIR /usr/src
|
||||
|
||||
COPY --from=qpdf-builder /usr/src/qpdf/${QPDF_VERSION}/${TARGETARCH}${TARGETVARIANT}/*.deb ./
|
||||
|
||||
# As this is an base image for a multi-stage final image
|
||||
# the added size of the install is basically irrelevant
|
||||
|
||||
RUN set -eux \
|
||||
&& echo "Installing build tools" \
|
||||
&& apt-get update --quiet \
|
||||
&& apt-get install --yes --quiet --no-install-recommends ${BUILD_PACKAGES} \
|
||||
&& echo "Installing qpdf" \
|
||||
&& dpkg --install libqpdf29_*.deb \
|
||||
&& dpkg --install libqpdf-dev_*.deb \
|
||||
&& echo "Installing Python tools" \
|
||||
&& python3 -m pip install --no-cache-dir --upgrade \
|
||||
pip \
|
||||
wheel \
|
||||
# https://pikepdf.readthedocs.io/en/latest/installation.html#requirements
|
||||
pybind11 \
|
||||
&& echo "Building pikepdf wheel ${PIKEPDF_VERSION}" \
|
||||
&& mkdir wheels \
|
||||
&& python3 -m pip wheel \
|
||||
# Build the package at the required version
|
||||
pikepdf==${PIKEPDF_VERSION} \
|
||||
# Look to piwheels for additional pre-built wheels
|
||||
--extra-index-url https://www.piwheels.org/simple \
|
||||
# Output the *.whl into this directory
|
||||
--wheel-dir wheels \
|
||||
# Do not use a binary packge for the package being built
|
||||
--no-binary=pikepdf \
|
||||
# Do use binary packages for dependencies
|
||||
--prefer-binary \
|
||||
# Don't cache build files
|
||||
--no-cache-dir \
|
||||
&& ls -ahl wheels \
|
||||
&& echo "Gathering package data" \
|
||||
&& dpkg-query -f '${Package;-40}${Version}\n' -W > ./wheels/pkg-list.txt \
|
||||
&& echo "Cleaning up image" \
|
||||
&& apt-get -y purge ${BUILD_PACKAGES} \
|
||||
&& apt-get -y autoremove --purge \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
#
|
||||
# Stage: package
|
||||
# Purpose: Holds the compiled .whl files in a tiny image to pull
|
||||
#
|
||||
FROM alpine:3.17 as package
|
||||
|
||||
WORKDIR /usr/src/wheels/
|
||||
|
||||
COPY --from=builder /usr/src/wheels/*.whl ./
|
||||
COPY --from=builder /usr/src/wheels/pkg-list.txt ./
|
@@ -1,66 +0,0 @@
|
||||
# This Dockerfile builds the psycopg2 wheel
|
||||
# Inputs:
|
||||
# - PSYCOPG2_VERSION - Version to build
|
||||
|
||||
#
|
||||
# Stage: builder
|
||||
# Purpose:
|
||||
# - Build the psycopg2 wheel
|
||||
#
|
||||
FROM python:3.9-slim-bullseye as builder
|
||||
|
||||
LABEL org.opencontainers.image.description="A intermediate image with psycopg2 wheel built"
|
||||
|
||||
ARG PSYCOPG2_VERSION
|
||||
ARG DEBIAN_FRONTEND=noninteractive
|
||||
|
||||
ARG BUILD_PACKAGES="\
|
||||
build-essential \
|
||||
python3-dev \
|
||||
python3-pip \
|
||||
# https://www.psycopg.org/docs/install.html#prerequisites
|
||||
libpq-dev"
|
||||
|
||||
WORKDIR /usr/src
|
||||
|
||||
# As this is an base image for a multi-stage final image
|
||||
# the added size of the install is basically irrelevant
|
||||
|
||||
RUN set -eux \
|
||||
&& echo "Installing build tools" \
|
||||
&& apt-get update --quiet \
|
||||
&& apt-get install --yes --quiet --no-install-recommends ${BUILD_PACKAGES} \
|
||||
&& echo "Installing Python tools" \
|
||||
&& python3 -m pip install --no-cache-dir --upgrade pip wheel \
|
||||
&& echo "Building psycopg2 wheel ${PSYCOPG2_VERSION}" \
|
||||
&& cd /usr/src \
|
||||
&& mkdir wheels \
|
||||
&& python3 -m pip wheel \
|
||||
# Build the package at the required version
|
||||
psycopg2==${PSYCOPG2_VERSION} \
|
||||
# Output the *.whl into this directory
|
||||
--wheel-dir wheels \
|
||||
# Do not use a binary packge for the package being built
|
||||
--no-binary=psycopg2 \
|
||||
# Do use binary packages for dependencies
|
||||
--prefer-binary \
|
||||
# Don't cache build files
|
||||
--no-cache-dir \
|
||||
&& ls -ahl wheels/ \
|
||||
&& echo "Gathering package data" \
|
||||
&& dpkg-query -f '${Package;-40}${Version}\n' -W > ./wheels/pkg-list.txt \
|
||||
&& echo "Cleaning up image" \
|
||||
&& apt-get -y purge ${BUILD_PACKAGES} \
|
||||
&& apt-get -y autoremove --purge \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
#
|
||||
# Stage: package
|
||||
# Purpose: Holds the compiled .whl files in a tiny image to pull
|
||||
#
|
||||
FROM alpine:3.17 as package
|
||||
|
||||
WORKDIR /usr/src/wheels/
|
||||
|
||||
COPY --from=builder /usr/src/wheels/*.whl ./
|
||||
COPY --from=builder /usr/src/wheels/pkg-list.txt ./
|
@@ -1,156 +0,0 @@
|
||||
#
|
||||
# Stage: pre-build
|
||||
# Purpose:
|
||||
# - Installs common packages
|
||||
# - Sets common environment variables related to dpkg
|
||||
# - Aquires the qpdf source from bookwork
|
||||
# Useful Links:
|
||||
# - https://qpdf.readthedocs.io/en/stable/installation.html#system-requirements
|
||||
# - https://wiki.debian.org/Multiarch/HOWTO
|
||||
# - https://wiki.debian.org/CrossCompiling
|
||||
#
|
||||
|
||||
FROM debian:bullseye-slim as pre-build
|
||||
|
||||
ARG QPDF_VERSION
|
||||
|
||||
ARG COMMON_BUILD_PACKAGES="\
|
||||
cmake \
|
||||
debhelper\
|
||||
debian-keyring \
|
||||
devscripts \
|
||||
dpkg-dev \
|
||||
equivs \
|
||||
packaging-dev \
|
||||
libtool"
|
||||
|
||||
ENV DEB_BUILD_OPTIONS="terse nocheck nodoc parallel=2"
|
||||
|
||||
WORKDIR /usr/src
|
||||
|
||||
RUN set -eux \
|
||||
&& echo "Installing common packages" \
|
||||
&& apt-get update --quiet \
|
||||
&& apt-get install --yes --quiet --no-install-recommends ${COMMON_BUILD_PACKAGES} \
|
||||
&& echo "Getting qpdf source" \
|
||||
&& echo "deb-src http://deb.debian.org/debian/ bookworm main" > /etc/apt/sources.list.d/bookworm-src.list \
|
||||
&& apt-get update --quiet \
|
||||
&& apt-get source --yes --quiet qpdf=${QPDF_VERSION}-1/bookworm
|
||||
|
||||
#
|
||||
# Stage: amd64-builder
|
||||
# Purpose: Builds qpdf for x86_64 (native build)
|
||||
#
|
||||
FROM pre-build as amd64-builder
|
||||
|
||||
ARG AMD64_BUILD_PACKAGES="\
|
||||
build-essential \
|
||||
libjpeg62-turbo-dev:amd64 \
|
||||
libgnutls28-dev:amd64 \
|
||||
zlib1g-dev:amd64"
|
||||
|
||||
WORKDIR /usr/src/qpdf-${QPDF_VERSION}
|
||||
|
||||
RUN set -eux \
|
||||
&& echo "Beginning amd64" \
|
||||
&& echo "Install amd64 packages" \
|
||||
&& apt-get update --quiet \
|
||||
&& apt-get install --yes --quiet --no-install-recommends ${AMD64_BUILD_PACKAGES} \
|
||||
&& echo "Building amd64" \
|
||||
&& dpkg-buildpackage --build=binary --unsigned-source --unsigned-changes --post-clean \
|
||||
&& echo "Removing debug files" \
|
||||
&& rm -f ../libqpdf29-dbgsym* \
|
||||
&& rm -f ../qpdf-dbgsym* \
|
||||
&& echo "Gathering package data" \
|
||||
&& dpkg-query -f '${Package;-40}${Version}\n' -W > ../pkg-list.txt
|
||||
#
|
||||
# Stage: armhf-builder
|
||||
# Purpose:
|
||||
# - Sets armhf specific environment
|
||||
# - Builds qpdf for armhf (cross compile)
|
||||
#
|
||||
FROM pre-build as armhf-builder
|
||||
|
||||
ARG ARMHF_PACKAGES="\
|
||||
crossbuild-essential-armhf \
|
||||
libjpeg62-turbo-dev:armhf \
|
||||
libgnutls28-dev:armhf \
|
||||
zlib1g-dev:armhf"
|
||||
|
||||
WORKDIR /usr/src/qpdf-${QPDF_VERSION}
|
||||
|
||||
ENV CXX="/usr/bin/arm-linux-gnueabihf-g++" \
|
||||
CC="/usr/bin/arm-linux-gnueabihf-gcc"
|
||||
|
||||
RUN set -eux \
|
||||
&& echo "Beginning armhf" \
|
||||
&& echo "Install armhf packages" \
|
||||
&& dpkg --add-architecture armhf \
|
||||
&& apt-get update --quiet \
|
||||
&& apt-get install --yes --quiet --no-install-recommends ${ARMHF_PACKAGES} \
|
||||
&& echo "Building armhf" \
|
||||
&& dpkg-buildpackage --build=binary --unsigned-source --unsigned-changes --post-clean --host-arch armhf \
|
||||
&& echo "Removing debug files" \
|
||||
&& rm -f ../libqpdf29-dbgsym* \
|
||||
&& rm -f ../qpdf-dbgsym* \
|
||||
&& echo "Gathering package data" \
|
||||
&& dpkg-query -f '${Package;-40}${Version}\n' -W > ../pkg-list.txt
|
||||
|
||||
#
|
||||
# Stage: aarch64-builder
|
||||
# Purpose:
|
||||
# - Sets aarch64 specific environment
|
||||
# - Builds qpdf for aarch64 (cross compile)
|
||||
#
|
||||
FROM pre-build as aarch64-builder
|
||||
|
||||
ARG ARM64_PACKAGES="\
|
||||
crossbuild-essential-arm64 \
|
||||
libjpeg62-turbo-dev:arm64 \
|
||||
libgnutls28-dev:arm64 \
|
||||
zlib1g-dev:arm64"
|
||||
|
||||
ENV CXX="/usr/bin/aarch64-linux-gnu-g++" \
|
||||
CC="/usr/bin/aarch64-linux-gnu-gcc"
|
||||
|
||||
WORKDIR /usr/src/qpdf-${QPDF_VERSION}
|
||||
|
||||
RUN set -eux \
|
||||
&& echo "Beginning arm64" \
|
||||
&& echo "Install arm64 packages" \
|
||||
&& dpkg --add-architecture arm64 \
|
||||
&& apt-get update --quiet \
|
||||
&& apt-get install --yes --quiet --no-install-recommends ${ARM64_PACKAGES} \
|
||||
&& echo "Building arm64" \
|
||||
&& dpkg-buildpackage --build=binary --unsigned-source --unsigned-changes --post-clean --host-arch arm64 \
|
||||
&& echo "Removing debug files" \
|
||||
&& rm -f ../libqpdf29-dbgsym* \
|
||||
&& rm -f ../qpdf-dbgsym* \
|
||||
&& echo "Gathering package data" \
|
||||
&& dpkg-query -f '${Package;-40}${Version}\n' -W > ../pkg-list.txt
|
||||
|
||||
#
|
||||
# Stage: package
|
||||
# Purpose: Holds the compiled .deb files in arch/variant specific folders
|
||||
#
|
||||
FROM alpine:3.17 as package
|
||||
|
||||
LABEL org.opencontainers.image.description="A image with qpdf installers stored in architecture & version specific folders"
|
||||
|
||||
ARG QPDF_VERSION
|
||||
|
||||
WORKDIR /usr/src/qpdf/${QPDF_VERSION}/amd64
|
||||
|
||||
COPY --from=amd64-builder /usr/src/*.deb ./
|
||||
COPY --from=amd64-builder /usr/src/pkg-list.txt ./
|
||||
|
||||
# Note this is ${TARGETARCH}${TARGETVARIANT} for armv7
|
||||
WORKDIR /usr/src/qpdf/${QPDF_VERSION}/armv7
|
||||
|
||||
COPY --from=armhf-builder /usr/src/*.deb ./
|
||||
COPY --from=armhf-builder /usr/src/pkg-list.txt ./
|
||||
|
||||
WORKDIR /usr/src/qpdf/${QPDF_VERSION}/arm64
|
||||
|
||||
COPY --from=aarch64-builder /usr/src/*.deb ./
|
||||
COPY --from=aarch64-builder /usr/src/pkg-list.txt ./
|
@@ -1,57 +0,0 @@
|
||||
# Installer Library
|
||||
|
||||
This folder contains the Dockerfiles for building certain installers or libraries, which are then pulled into the main image.
|
||||
|
||||
## [jbig2enc](https://github.com/agl/jbig2enc)
|
||||
|
||||
### Why
|
||||
|
||||
JBIG is an image coding which can achieve better compression of images for PDFs.
|
||||
|
||||
### What
|
||||
|
||||
The Docker image builds a shared library file and utility, which is copied into the correct location in the final image.
|
||||
|
||||
### Updating
|
||||
|
||||
1. Ensure the given qpdf version is present in [Debian bookworm](https://packages.debian.org/bookworm/qpdf)
|
||||
2. Update `.build-config.json` to the given version
|
||||
3. If the Debian specific version has incremented, update `Dockerfile.qpdf`
|
||||
|
||||
See Also:
|
||||
|
||||
- [OCRMyPDF Documentation](https://ocrmypdf.readthedocs.io/en/latest/jbig2.html)
|
||||
|
||||
## [psycopg2](https://www.psycopg.org/)
|
||||
|
||||
### Why
|
||||
|
||||
The pre-built wheels of psycopg2 are built on Debian 9, which provides a quite old version of libpq-dev. This causes issue with authentication methods.
|
||||
|
||||
### What
|
||||
|
||||
The image builds psycopg2 wheels on Debian 10 and places the produced wheels into `/usr/src/wheels/`.
|
||||
|
||||
See Also:
|
||||
|
||||
- [Issue 266](https://github.com/paperless-ngx/paperless-ngx/issues/266)
|
||||
|
||||
## [qpdf](https://qpdf.readthedocs.io/en/stable/index.html)
|
||||
|
||||
### Why
|
||||
|
||||
qpdf and it's library provide tools to read, manipulate and fix up PDFs. Version 11 is also required by `pikepdf` 6+ and Debian 9 does not provide above version 10.
|
||||
|
||||
### What
|
||||
|
||||
The Docker image cross compiles .deb installers for each supported architecture of the main image. The installers are placed in `/usr/src/qpdf/${QPDF_VERSION}/${TARGETARCH}${TARGETVARIANT}/`
|
||||
|
||||
## [pikepdf](https://pikepdf.readthedocs.io/en/latest/)
|
||||
|
||||
### Why
|
||||
|
||||
Required by OCRMyPdf, this is a general purpose library for PDF manipulation in Python via the qpdf libraries.
|
||||
|
||||
### What
|
||||
|
||||
The built wheels are placed into `/usr/src/wheels/`
|
@@ -1,12 +1,12 @@
|
||||
# docker-compose file for running paperless testing with actual gotenberg
|
||||
# Docker Compose file for running paperless testing with actual gotenberg
|
||||
# and Tika containers for a more end to end test of the Tika related functionality
|
||||
# Can be used locally or by the CI to start the nessecary containers with the
|
||||
# Can be used locally or by the CI to start the necessary containers with the
|
||||
# correct networking for the tests
|
||||
|
||||
version: "3.7"
|
||||
services:
|
||||
gotenberg:
|
||||
image: docker.io/gotenberg/gotenberg:7.8
|
||||
image: docker.io/gotenberg/gotenberg:7.10
|
||||
hostname: gotenberg
|
||||
container_name: gotenberg
|
||||
network_mode: host
|
||||
@@ -17,6 +17,8 @@ services:
|
||||
- "gotenberg"
|
||||
- "--chromium-disable-javascript=true"
|
||||
- "--chromium-allow-list=file:///tmp/.*"
|
||||
- "--log-level=warn"
|
||||
- "--log-format=text"
|
||||
tika:
|
||||
image: ghcr.io/paperless-ngx/tika:latest
|
||||
hostname: tika
|
||||
|
@@ -1,4 +1,4 @@
|
||||
# docker-compose file for running paperless from the Docker Hub.
|
||||
# docker compose file for running paperless from the Docker Hub.
|
||||
# This file contains everything paperless needs to run.
|
||||
# Paperless supports amd64, arm and arm64 hardware.
|
||||
#
|
||||
@@ -10,7 +10,7 @@
|
||||
# as this file and mounted to the correct folders inside the container.
|
||||
# - Paperless listens on port 8000.
|
||||
#
|
||||
# In addition to that, this docker-compose file adds the following optional
|
||||
# In addition to that, this Docker Compose file adds the following optional
|
||||
# configurations:
|
||||
#
|
||||
# - Instead of SQLite (default), MariaDB is used as the database server.
|
||||
@@ -23,9 +23,9 @@
|
||||
#
|
||||
# - Copy this file as 'docker-compose.yml' and the files 'docker-compose.env'
|
||||
# and '.env' into a folder.
|
||||
# - Run 'docker-compose pull'.
|
||||
# - Run 'docker-compose run --rm webserver createsuperuser' to create a user.
|
||||
# - Run 'docker-compose up -d'.
|
||||
# - Run 'docker compose pull'.
|
||||
# - Run 'docker compose run --rm webserver createsuperuser' to create a user.
|
||||
# - Run 'docker compose up -d'.
|
||||
#
|
||||
# For more extensive installation and update instructions, refer to the
|
||||
# documentation.
|
||||
@@ -60,11 +60,6 @@ services:
|
||||
- tika
|
||||
ports:
|
||||
- "8000:8000"
|
||||
healthcheck:
|
||||
test: ["CMD", "curl", "-f", "http://localhost:8000"]
|
||||
interval: 30s
|
||||
timeout: 10s
|
||||
retries: 5
|
||||
volumes:
|
||||
- data:/usr/src/paperless/data
|
||||
- media:/usr/src/paperless/media
|
||||
@@ -83,7 +78,7 @@ services:
|
||||
PAPERLESS_TIKA_ENDPOINT: http://tika:9998
|
||||
|
||||
gotenberg:
|
||||
image: docker.io/gotenberg/gotenberg:7.8
|
||||
image: docker.io/gotenberg/gotenberg:7.10
|
||||
restart: unless-stopped
|
||||
# The gotenberg chromium route is used to convert .eml files. We do not
|
||||
# want to allow external content like tracking pixels or even javascript.
|
||||
|
@@ -1,4 +1,4 @@
|
||||
# docker-compose file for running paperless from the Docker Hub.
|
||||
# Docker Compose file for running paperless from the Docker Hub.
|
||||
# This file contains everything paperless needs to run.
|
||||
# Paperless supports amd64, arm and arm64 hardware.
|
||||
#
|
||||
@@ -10,7 +10,7 @@
|
||||
# as this file and mounted to the correct folders inside the container.
|
||||
# - Paperless listens on port 8000.
|
||||
#
|
||||
# In addition to that, this docker-compose file adds the following optional
|
||||
# In addition to that, this Docker Compose file adds the following optional
|
||||
# configurations:
|
||||
#
|
||||
# - Instead of SQLite (default), MariaDB is used as the database server.
|
||||
@@ -19,9 +19,9 @@
|
||||
#
|
||||
# - Copy this file as 'docker-compose.yml' and the files 'docker-compose.env'
|
||||
# and '.env' into a folder.
|
||||
# - Run 'docker-compose pull'.
|
||||
# - Run 'docker-compose run --rm webserver createsuperuser' to create a user.
|
||||
# - Run 'docker-compose up -d'.
|
||||
# - Run 'docker compose pull'.
|
||||
# - Run 'docker compose run --rm webserver createsuperuser' to create a user.
|
||||
# - Run 'docker compose up -d'.
|
||||
#
|
||||
# For more extensive installation and update instructions, refer to the
|
||||
# documentation.
|
||||
@@ -54,11 +54,6 @@ services:
|
||||
- broker
|
||||
ports:
|
||||
- "8000:8000"
|
||||
healthcheck:
|
||||
test: ["CMD", "curl", "-f", "http://localhost:8000"]
|
||||
interval: 30s
|
||||
timeout: 10s
|
||||
retries: 5
|
||||
volumes:
|
||||
- data:/usr/src/paperless/data
|
||||
- media:/usr/src/paperless/media
|
||||
@@ -73,7 +68,6 @@ services:
|
||||
PAPERLESS_DBPASS: paperless # only needed if non-default password
|
||||
PAPERLESS_DBPORT: 3306
|
||||
|
||||
|
||||
volumes:
|
||||
data:
|
||||
media:
|
||||
|
@@ -1,4 +1,4 @@
|
||||
# docker-compose file for running paperless from the Docker Hub.
|
||||
# Docker Compose file for running paperless from the Docker Hub.
|
||||
# This file contains everything paperless needs to run.
|
||||
# Paperless supports amd64, arm and arm64 hardware.
|
||||
#
|
||||
@@ -10,7 +10,7 @@
|
||||
# as this file and mounted to the correct folders inside the container.
|
||||
# - Paperless listens on port 8010.
|
||||
#
|
||||
# In addition to that, this docker-compose file adds the following optional
|
||||
# In addition to that, this Docker Compose file adds the following optional
|
||||
# configurations:
|
||||
#
|
||||
# - Instead of SQLite (default), PostgreSQL is used as the database server.
|
||||
@@ -18,7 +18,7 @@
|
||||
# To install and update paperless with this file, do the following:
|
||||
#
|
||||
# - Open portainer Stacks list and click 'Add stack'
|
||||
# - Paste the contents of this file and assign a name, e.g. 'Paperless'
|
||||
# - Paste the contents of this file and assign a name, e.g. 'paperless'
|
||||
# - Click 'Deploy the stack' and wait for it to be deployed
|
||||
# - Open the list of containers, select paperless_webserver_1
|
||||
# - Click 'Console' and then 'Connect' to open the command line inside the container
|
||||
@@ -37,7 +37,7 @@ services:
|
||||
- redisdata:/data
|
||||
|
||||
db:
|
||||
image: docker.io/library/postgres:13
|
||||
image: docker.io/library/postgres:15
|
||||
restart: unless-stopped
|
||||
volumes:
|
||||
- pgdata:/var/lib/postgresql/data
|
||||
@@ -54,11 +54,6 @@ services:
|
||||
- broker
|
||||
ports:
|
||||
- "8010:8000"
|
||||
healthcheck:
|
||||
test: ["CMD", "curl", "-fs", "-S", "--max-time", "2", "http://localhost:8000"]
|
||||
interval: 30s
|
||||
timeout: 10s
|
||||
retries: 5
|
||||
volumes:
|
||||
- data:/usr/src/paperless/data
|
||||
- media:/usr/src/paperless/media
|
||||
|
@@ -1,4 +1,4 @@
|
||||
# docker-compose file for running paperless from the docker container registry.
|
||||
# Docker Compose file for running paperless from the docker container registry.
|
||||
# This file contains everything paperless needs to run.
|
||||
# Paperless supports amd64, arm and arm64 hardware.
|
||||
#
|
||||
@@ -10,7 +10,7 @@
|
||||
# as this file and mounted to the correct folders inside the container.
|
||||
# - Paperless listens on port 8000.
|
||||
#
|
||||
# In addition to that, this docker-compose file adds the following optional
|
||||
# In addition to that, this Docker Compose file adds the following optional
|
||||
# configurations:
|
||||
#
|
||||
# - Instead of SQLite (default), PostgreSQL is used as the database server.
|
||||
@@ -23,9 +23,9 @@
|
||||
#
|
||||
# - Copy this file as 'docker-compose.yml' and the files 'docker-compose.env'
|
||||
# and '.env' into a folder.
|
||||
# - Run 'docker-compose pull'.
|
||||
# - Run 'docker-compose run --rm webserver createsuperuser' to create a user.
|
||||
# - Run 'docker-compose up -d'.
|
||||
# - Run 'docker compose pull'.
|
||||
# - Run 'docker compose run --rm webserver createsuperuser' to create a user.
|
||||
# - Run 'docker compose up -d'.
|
||||
#
|
||||
# For more extensive installation and update instructions, refer to the
|
||||
# documentation.
|
||||
@@ -39,7 +39,7 @@ services:
|
||||
- redisdata:/data
|
||||
|
||||
db:
|
||||
image: docker.io/library/postgres:13
|
||||
image: docker.io/library/postgres:15
|
||||
restart: unless-stopped
|
||||
volumes:
|
||||
- pgdata:/var/lib/postgresql/data
|
||||
@@ -58,11 +58,6 @@ services:
|
||||
- tika
|
||||
ports:
|
||||
- "8000:8000"
|
||||
healthcheck:
|
||||
test: ["CMD", "curl", "-fs", "-S", "--max-time", "2", "http://localhost:8000"]
|
||||
interval: 30s
|
||||
timeout: 10s
|
||||
retries: 5
|
||||
volumes:
|
||||
- data:/usr/src/paperless/data
|
||||
- media:/usr/src/paperless/media
|
||||
@@ -77,7 +72,7 @@ services:
|
||||
PAPERLESS_TIKA_ENDPOINT: http://tika:9998
|
||||
|
||||
gotenberg:
|
||||
image: docker.io/gotenberg/gotenberg:7.8
|
||||
image: docker.io/gotenberg/gotenberg:7.10
|
||||
restart: unless-stopped
|
||||
|
||||
# The gotenberg chromium route is used to convert .eml files. We do not
|
||||
|
@@ -1,4 +1,4 @@
|
||||
# docker-compose file for running paperless from the Docker Hub.
|
||||
# Docker Compose file for running paperless from the Docker Hub.
|
||||
# This file contains everything paperless needs to run.
|
||||
# Paperless supports amd64, arm and arm64 hardware.
|
||||
#
|
||||
@@ -10,7 +10,7 @@
|
||||
# as this file and mounted to the correct folders inside the container.
|
||||
# - Paperless listens on port 8000.
|
||||
#
|
||||
# In addition to that, this docker-compose file adds the following optional
|
||||
# In addition to that, this Docker Compose file adds the following optional
|
||||
# configurations:
|
||||
#
|
||||
# - Instead of SQLite (default), PostgreSQL is used as the database server.
|
||||
@@ -19,9 +19,9 @@
|
||||
#
|
||||
# - Copy this file as 'docker-compose.yml' and the files 'docker-compose.env'
|
||||
# and '.env' into a folder.
|
||||
# - Run 'docker-compose pull'.
|
||||
# - Run 'docker-compose run --rm webserver createsuperuser' to create a user.
|
||||
# - Run 'docker-compose up -d'.
|
||||
# - Run 'docker compose pull'.
|
||||
# - Run 'docker compose run --rm webserver createsuperuser' to create a user.
|
||||
# - Run 'docker compose up -d'.
|
||||
#
|
||||
# For more extensive installation and update instructions, refer to the
|
||||
# documentation.
|
||||
@@ -35,7 +35,7 @@ services:
|
||||
- redisdata:/data
|
||||
|
||||
db:
|
||||
image: docker.io/library/postgres:13
|
||||
image: docker.io/library/postgres:15
|
||||
restart: unless-stopped
|
||||
volumes:
|
||||
- pgdata:/var/lib/postgresql/data
|
||||
@@ -52,11 +52,6 @@ services:
|
||||
- broker
|
||||
ports:
|
||||
- "8000:8000"
|
||||
healthcheck:
|
||||
test: ["CMD", "curl", "-fs", "-S", "--max-time", "2", "http://localhost:8000"]
|
||||
interval: 30s
|
||||
timeout: 10s
|
||||
retries: 5
|
||||
volumes:
|
||||
- data:/usr/src/paperless/data
|
||||
- media:/usr/src/paperless/media
|
||||
@@ -67,7 +62,6 @@ services:
|
||||
PAPERLESS_REDIS: redis://broker:6379
|
||||
PAPERLESS_DBHOST: db
|
||||
|
||||
|
||||
volumes:
|
||||
data:
|
||||
media:
|
||||
|
@@ -1,4 +1,4 @@
|
||||
# docker-compose file for running paperless from the docker container registry.
|
||||
# Docker Compose file for running paperless from the docker container registry.
|
||||
# This file contains everything paperless needs to run.
|
||||
# Paperless supports amd64, arm and arm64 hardware.
|
||||
# All compose files of paperless configure paperless in the following way:
|
||||
@@ -11,7 +11,7 @@
|
||||
#
|
||||
# SQLite is used as the database. The SQLite file is stored in the data volume.
|
||||
#
|
||||
# In addition to that, this docker-compose file adds the following optional
|
||||
# In addition to that, this Docker Compose file adds the following optional
|
||||
# configurations:
|
||||
#
|
||||
# - Apache Tika and Gotenberg servers are started with paperless and paperless
|
||||
@@ -23,9 +23,9 @@
|
||||
#
|
||||
# - Copy this file as 'docker-compose.yml' and the files 'docker-compose.env'
|
||||
# and '.env' into a folder.
|
||||
# - Run 'docker-compose pull'.
|
||||
# - Run 'docker-compose run --rm webserver createsuperuser' to create a user.
|
||||
# - Run 'docker-compose up -d'.
|
||||
# - Run 'docker compose pull'.
|
||||
# - Run 'docker compose run --rm webserver createsuperuser' to create a user.
|
||||
# - Run 'docker compose up -d'.
|
||||
#
|
||||
# For more extensive installation and update instructions, refer to the
|
||||
# documentation.
|
||||
@@ -47,11 +47,6 @@ services:
|
||||
- tika
|
||||
ports:
|
||||
- "8000:8000"
|
||||
healthcheck:
|
||||
test: ["CMD", "curl", "-fs", "-S", "--max-time", "2", "http://localhost:8000"]
|
||||
interval: 30s
|
||||
timeout: 10s
|
||||
retries: 5
|
||||
volumes:
|
||||
- data:/usr/src/paperless/data
|
||||
- media:/usr/src/paperless/media
|
||||
@@ -65,7 +60,7 @@ services:
|
||||
PAPERLESS_TIKA_ENDPOINT: http://tika:9998
|
||||
|
||||
gotenberg:
|
||||
image: docker.io/gotenberg/gotenberg:7.8
|
||||
image: docker.io/gotenberg/gotenberg:7.10
|
||||
restart: unless-stopped
|
||||
|
||||
# The gotenberg chromium route is used to convert .eml files. We do not
|
||||
|
@@ -1,4 +1,4 @@
|
||||
# docker-compose file for running paperless from the Docker Hub.
|
||||
# Docker Compose file for running paperless from the Docker Hub.
|
||||
# This file contains everything paperless needs to run.
|
||||
# Paperless supports amd64, arm and arm64 hardware.
|
||||
#
|
||||
@@ -16,9 +16,9 @@
|
||||
#
|
||||
# - Copy this file as 'docker-compose.yml' and the files 'docker-compose.env'
|
||||
# and '.env' into a folder.
|
||||
# - Run 'docker-compose pull'.
|
||||
# - Run 'docker-compose run --rm webserver createsuperuser' to create a user.
|
||||
# - Run 'docker-compose up -d'.
|
||||
# - Run 'docker compose pull'.
|
||||
# - Run 'docker compose run --rm webserver createsuperuser' to create a user.
|
||||
# - Run 'docker compose up -d'.
|
||||
#
|
||||
# For more extensive installation and update instructions, refer to the
|
||||
# documentation.
|
||||
@@ -38,11 +38,6 @@ services:
|
||||
- broker
|
||||
ports:
|
||||
- "8000:8000"
|
||||
healthcheck:
|
||||
test: ["CMD", "curl", "-fs", "-S", "--max-time", "2", "http://localhost:8000"]
|
||||
interval: 30s
|
||||
timeout: 10s
|
||||
retries: 5
|
||||
volumes:
|
||||
- data:/usr/src/paperless/data
|
||||
- media:/usr/src/paperless/media
|
||||
@@ -52,7 +47,6 @@ services:
|
||||
environment:
|
||||
PAPERLESS_REDIS: redis://broker:6379
|
||||
|
||||
|
||||
volumes:
|
||||
data:
|
||||
media:
|
||||
|
@@ -86,17 +86,17 @@ initialize() {
|
||||
"${CONSUME_DIR}"; do
|
||||
if [[ ! -d "${dir}" ]]; then
|
||||
echo "Creating directory ${dir}"
|
||||
mkdir "${dir}"
|
||||
mkdir --parents "${dir}"
|
||||
fi
|
||||
done
|
||||
|
||||
local -r tmp_dir="/tmp/paperless"
|
||||
echo "Creating directory ${tmp_dir}"
|
||||
mkdir -p "${tmp_dir}"
|
||||
local -r tmp_dir="${PAPERLESS_SCRATCH_DIR:=/tmp/paperless}"
|
||||
echo "Creating directory scratch directory ${tmp_dir}"
|
||||
mkdir --parents "${tmp_dir}"
|
||||
|
||||
set +e
|
||||
echo "Adjusting permissions of paperless files. This may take a while."
|
||||
chown -R paperless:paperless ${tmp_dir}
|
||||
chown -R paperless:paperless "${tmp_dir}"
|
||||
for dir in \
|
||||
"${export_dir}" \
|
||||
"${DATA_DIR}" \
|
||||
|
@@ -80,7 +80,7 @@ django_checks() {
|
||||
|
||||
search_index() {
|
||||
|
||||
local -r index_version=3
|
||||
local -r index_version=8
|
||||
local -r index_version_file=${DATA_DIR}/.index_version
|
||||
|
||||
if [[ (! -f "${index_version_file}") || $(<"${index_version_file}") != "$index_version" ]]; then
|
||||
|
@@ -15,6 +15,10 @@ do
|
||||
env_name=${line%%=*}
|
||||
# Check if it starts with "PAPERLESS_" and ends in "_FILE"
|
||||
if [[ ${env_name} == PAPERLESS_*_FILE ]]; then
|
||||
# This should have been named different..
|
||||
if [[ ${env_name} == "PAPERLESS_OCR_SKIP_ARCHIVE_FILE" ]]; then
|
||||
continue
|
||||
fi
|
||||
# Extract the value of the environment
|
||||
env_value=${line#*=}
|
||||
|
||||
|
@@ -13,6 +13,7 @@ for command in decrypt_documents \
|
||||
document_retagger \
|
||||
document_thumbnails \
|
||||
document_sanity_checker \
|
||||
document_fuzzy_match \
|
||||
manage_superuser;
|
||||
do
|
||||
echo "installing $command..."
|
||||
|
@@ -1,14 +1,15 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
SUPERVISORD_WORKING_DIR="${PAPERLESS_SUPERVISORD_WORKING_DIR:-$PWD}"
|
||||
rootless_args=()
|
||||
if [ "$(id -u)" == "$(id -u paperless)" ]; then
|
||||
rootless_args=(
|
||||
--user
|
||||
paperless
|
||||
--logfile
|
||||
supervisord.log
|
||||
"${SUPERVISORD_WORKING_DIR}/supervisord.log"
|
||||
--pidfile
|
||||
supervisord.pid
|
||||
"${SUPERVISORD_WORKING_DIR}/supervisord.pid"
|
||||
)
|
||||
fi
|
||||
|
||||
|
@@ -15,6 +15,7 @@ stdout_logfile=/dev/stdout
|
||||
stdout_logfile_maxbytes=0
|
||||
stderr_logfile=/dev/stderr
|
||||
stderr_logfile_maxbytes=0
|
||||
environment = HOME="/usr/src/paperless",USER="paperless"
|
||||
|
||||
[program:consumer]
|
||||
command=python3 manage.py document_consumer
|
||||
@@ -25,10 +26,11 @@ stdout_logfile=/dev/stdout
|
||||
stdout_logfile_maxbytes=0
|
||||
stderr_logfile=/dev/stderr
|
||||
stderr_logfile_maxbytes=0
|
||||
environment = HOME="/usr/src/paperless",USER="paperless"
|
||||
|
||||
[program:celery]
|
||||
|
||||
command = celery --app paperless worker --loglevel INFO
|
||||
command = celery --app paperless worker --loglevel INFO --without-mingle --without-gossip
|
||||
user=paperless
|
||||
stopasgroup = true
|
||||
stopwaitsecs = 60
|
||||
@@ -37,6 +39,7 @@ stdout_logfile=/dev/stdout
|
||||
stdout_logfile_maxbytes=0
|
||||
stderr_logfile=/dev/stderr
|
||||
stderr_logfile_maxbytes=0
|
||||
environment = HOME="/usr/src/paperless",USER="paperless"
|
||||
|
||||
[program:celery-beat]
|
||||
|
||||
@@ -48,6 +51,7 @@ stdout_logfile=/dev/stdout
|
||||
stdout_logfile_maxbytes=0
|
||||
stderr_logfile=/dev/stderr
|
||||
stderr_logfile_maxbytes=0
|
||||
environment = HOME="/usr/src/paperless",USER="paperless"
|
||||
|
||||
[program:celery-flower]
|
||||
command = /usr/local/bin/flower-conditional.sh
|
||||
@@ -58,3 +62,4 @@ stdout_logfile=/dev/stdout
|
||||
stdout_logfile_maxbytes=0
|
||||
stderr_logfile=/dev/stderr
|
||||
stderr_logfile_maxbytes=0
|
||||
environment = HOME="/usr/src/paperless",USER="paperless"
|
||||
|
@@ -12,13 +12,12 @@ from typing import Final
|
||||
from redis import Redis
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
MAX_RETRY_COUNT: Final[int] = 5
|
||||
RETRY_SLEEP_SECONDS: Final[int] = 5
|
||||
|
||||
REDIS_URL: Final[str] = os.getenv("PAPERLESS_REDIS", "redis://localhost:6379")
|
||||
|
||||
print(f"Waiting for Redis...", flush=True)
|
||||
print("Waiting for Redis...", flush=True)
|
||||
|
||||
attempt = 0
|
||||
with Redis.from_url(url=REDIS_URL) as client:
|
||||
@@ -29,7 +28,7 @@ if __name__ == "__main__":
|
||||
except Exception as e:
|
||||
print(
|
||||
f"Redis ping #{attempt} failed.\n"
|
||||
f"Error: {str(e)}.\n"
|
||||
f"Error: {e!s}.\n"
|
||||
f"Waiting {RETRY_SLEEP_SECONDS}s",
|
||||
flush=True,
|
||||
)
|
||||
@@ -37,8 +36,8 @@ if __name__ == "__main__":
|
||||
attempt += 1
|
||||
|
||||
if attempt >= MAX_RETRY_COUNT:
|
||||
print(f"Failed to connect to redis using environment variable PAPERLESS_REDIS.")
|
||||
print("Failed to connect to redis using environment variable PAPERLESS_REDIS.")
|
||||
sys.exit(os.EX_UNAVAILABLE)
|
||||
else:
|
||||
print(f"Connected to Redis broker.")
|
||||
print("Connected to Redis broker.")
|
||||
sys.exit(os.EX_OK)
|
||||
|
@@ -5,17 +5,19 @@
|
||||
Multiple options exist for making backups of your paperless instance,
|
||||
depending on how you installed paperless.
|
||||
|
||||
Before making backups, make sure that paperless is not running.
|
||||
Before making a backup, it's probably best to make sure that paperless is not actively
|
||||
consuming documents at that time.
|
||||
|
||||
Options available to any installation of paperless:
|
||||
|
||||
- Use the [document exporter](#exporter). The document exporter exports all your documents,
|
||||
thumbnails and metadata to a specific folder. You may import your
|
||||
documents into a fresh instance of paperless again or store your
|
||||
documents in another DMS with this export.
|
||||
- The document exporter is also able to update an already existing
|
||||
export. Therefore, incremental backups with `rsync` are entirely
|
||||
possible.
|
||||
- Use the [document exporter](#exporter). The document exporter exports all your documents,
|
||||
thumbnails, metadata, and database contents to a specific folder. You may import your
|
||||
documents and settings into a fresh instance of paperless again or store your
|
||||
documents in another DMS with this export.
|
||||
|
||||
The document exporter is also able to update an already existing
|
||||
export. Therefore, incremental backups with `rsync` are entirely
|
||||
possible.
|
||||
|
||||
!!! caution
|
||||
|
||||
@@ -25,31 +27,37 @@ Options available to any installation of paperless:
|
||||
|
||||
Options available to docker installations:
|
||||
|
||||
- Backup the docker volumes. These usually reside within
|
||||
`/var/lib/docker/volumes` on the host and you need to be root in
|
||||
order to access them.
|
||||
- Backup the docker volumes. These usually reside within
|
||||
`/var/lib/docker/volumes` on the host and you need to be root in
|
||||
order to access them.
|
||||
|
||||
Paperless uses 4 volumes:
|
||||
Paperless uses 4 volumes:
|
||||
|
||||
- `paperless_media`: This is where your documents are stored.
|
||||
- `paperless_data`: This is where auxillary data is stored. This
|
||||
folder also contains the SQLite database, if you use it.
|
||||
- `paperless_pgdata`: Exists only if you use PostgreSQL and
|
||||
contains the database.
|
||||
- `paperless_dbdata`: Exists only if you use MariaDB and contains
|
||||
the database.
|
||||
- `paperless_media`: This is where your documents are stored.
|
||||
- `paperless_data`: This is where auxiliary data is stored. This
|
||||
folder also contains the SQLite database, if you use it.
|
||||
- `paperless_pgdata`: Exists only if you use PostgreSQL and
|
||||
contains the database.
|
||||
- `paperless_dbdata`: Exists only if you use MariaDB and contains
|
||||
the database.
|
||||
|
||||
Options available to bare-metal and non-docker installations:
|
||||
|
||||
- Backup the entire paperless folder. This ensures that if your
|
||||
paperless instance crashes at some point or your disk fails, you can
|
||||
simply copy the folder back into place and it works.
|
||||
- Backup the entire paperless folder. This ensures that if your
|
||||
paperless instance crashes at some point or your disk fails, you can
|
||||
simply copy the folder back into place and it works.
|
||||
|
||||
When using PostgreSQL or MariaDB, you'll also have to backup the
|
||||
database.
|
||||
When using PostgreSQL or MariaDB, you'll also have to backup the
|
||||
database.
|
||||
|
||||
### Restoring {#migrating-restoring}
|
||||
|
||||
If you've backed-up Paperless-ngx using the [document exporter](#exporter),
|
||||
restoring can simply be done with the [document importer](#importer).
|
||||
|
||||
Of course, other backup strategies require restoring any volumes, folders and database
|
||||
copies you created in the steps above.
|
||||
|
||||
## Updating Paperless {#updating}
|
||||
|
||||
### Docker Route {#docker-updating}
|
||||
@@ -59,34 +67,34 @@ you installed paperless-ngx in the first place. The releases are
|
||||
available at the [release
|
||||
page](https://github.com/paperless-ngx/paperless-ngx/releases).
|
||||
|
||||
First of all, ensure that paperless is stopped.
|
||||
First of all, make sure no active processes (like consumption) are running, then [make a backup](#backup).
|
||||
|
||||
After that, ensure that paperless is stopped:
|
||||
|
||||
```shell-session
|
||||
$ cd /path/to/paperless
|
||||
$ docker-compose down
|
||||
$ docker compose down
|
||||
```
|
||||
|
||||
After that, [make a backup](#backup).
|
||||
1. If you pull the image from the docker hub, all you need to do is:
|
||||
|
||||
1. If you pull the image from the docker hub, all you need to do is:
|
||||
```shell-session
|
||||
$ docker compose pull
|
||||
$ docker compose up
|
||||
```
|
||||
|
||||
```shell-session
|
||||
$ docker-compose pull
|
||||
$ docker-compose up
|
||||
```
|
||||
The Docker Compose files refer to the `latest` version, which is
|
||||
always the latest stable release.
|
||||
|
||||
The docker-compose files refer to the `latest` version, which is
|
||||
always the latest stable release.
|
||||
1. If you built the image yourself, do the following:
|
||||
|
||||
2. If you built the image yourself, do the following:
|
||||
```shell-session
|
||||
$ git pull
|
||||
$ docker compose build
|
||||
$ docker compose up
|
||||
```
|
||||
|
||||
```shell-session
|
||||
$ git pull
|
||||
$ docker-compose build
|
||||
$ docker-compose up
|
||||
```
|
||||
|
||||
Running `docker-compose up` will also apply any new database migrations.
|
||||
Running `docker compose up` will also apply any new database migrations.
|
||||
If you see everything working, press CTRL+C once to gracefully stop
|
||||
paperless. Then you can start paperless-ngx with `-d` to have it run in
|
||||
the background.
|
||||
@@ -94,11 +102,11 @@ the background.
|
||||
!!! note
|
||||
|
||||
In version 0.9.14, the update process was changed. In 0.9.13 and
|
||||
earlier, the docker-compose files specified exact versions and pull
|
||||
earlier, the Docker Compose files specified exact versions and pull
|
||||
won't automatically update to newer versions. In order to enable
|
||||
updates as described above, either get the new `docker-compose.yml`
|
||||
file from
|
||||
[here](https://github.com/paperless-ngx/paperless-ngx/tree/master/docker/compose)
|
||||
[here](https://github.com/paperless-ngx/paperless-ngx/tree/main/docker/compose)
|
||||
or edit the `docker-compose.yml` file, find the line that says
|
||||
|
||||
```
|
||||
@@ -139,7 +147,7 @@ following:
|
||||
1. Update dependencies. New paperless version may require additional
|
||||
dependencies. The dependencies required are listed in the section
|
||||
about
|
||||
[bare metal installations](/setup#bare_metal).
|
||||
[bare metal installations](setup.md#bare_metal).
|
||||
|
||||
2. Update python requirements. Keep in mind to activate your virtual
|
||||
environment before that, if you use one.
|
||||
@@ -148,6 +156,13 @@ following:
|
||||
$ pip install -r requirements.txt
|
||||
```
|
||||
|
||||
!!! note
|
||||
|
||||
At times, some dependencies will be removed from requirements.txt.
|
||||
Comparing the versions and removing no longer needed dependencies
|
||||
will keep your system or virtual environment clean and prevent
|
||||
possible conflicts.
|
||||
|
||||
3. Migrate the database.
|
||||
|
||||
```shell-session
|
||||
@@ -160,6 +175,16 @@ following:
|
||||
This might not actually do anything. Not every new paperless version
|
||||
comes with new database migrations.
|
||||
|
||||
### Database Upgrades
|
||||
|
||||
In general, paperless does not require a specific version of PostgreSQL or MariaDB and it is
|
||||
safe to update them to newer versions. However, you should always take a backup and follow
|
||||
the instructions from your database's documentation for how to upgrade between major versions.
|
||||
|
||||
For PostgreSQL, refer to [Upgrading a PostgreSQL Cluster](https://www.postgresql.org/docs/current/upgrading.html).
|
||||
|
||||
For MariaDB, refer to [Upgrading MariaDB](https://mariadb.com/kb/en/upgrading/)
|
||||
|
||||
## Downgrading Paperless {#downgrade-paperless}
|
||||
|
||||
Downgrades are possible. However, some updates also contain database
|
||||
@@ -195,11 +220,11 @@ Paperless comes with some management commands that perform various
|
||||
maintenance tasks on your paperless instance. You can invoke these
|
||||
commands in the following way:
|
||||
|
||||
With docker-compose, while paperless is running:
|
||||
With Docker Compose, while paperless is running:
|
||||
|
||||
```shell-session
|
||||
$ cd /path/to/paperless
|
||||
$ docker-compose exec webserver <command> <arguments>
|
||||
$ docker compose exec webserver <command> <arguments>
|
||||
```
|
||||
|
||||
With docker, while paperless is running:
|
||||
@@ -222,26 +247,28 @@ with the argument `--help`.
|
||||
|
||||
### Document exporter {#exporter}
|
||||
|
||||
The document exporter exports all your data from paperless into a folder
|
||||
for backup or migration to another DMS.
|
||||
The document exporter exports all your data (including your settings
|
||||
and database contents) from paperless into a folder for backup or
|
||||
migration to another DMS.
|
||||
|
||||
If you use the document exporter within a cronjob to backup your data
|
||||
you might use the `-T` flag behind exec to suppress "The input device
|
||||
is not a TTY" errors. For example:
|
||||
`docker-compose exec -T webserver document_exporter ../export`
|
||||
`docker compose exec -T webserver document_exporter ../export`
|
||||
|
||||
```
|
||||
document_exporter target [-c] [-d] [-f] [-na] [-nt] [-p] [-sm] [-z]
|
||||
|
||||
optional arguments:
|
||||
-c, --compare-checksums
|
||||
-d, --delete
|
||||
-f, --use-filename-format
|
||||
-c, --compare-checksums
|
||||
-d, --delete
|
||||
-f, --use-filename-format
|
||||
-na, --no-archive
|
||||
-nt, --no-thumbnail
|
||||
-p, --use-folder-prefix
|
||||
-p, --use-folder-prefix
|
||||
-sm, --split-manifest
|
||||
-z --zip
|
||||
-z, --zip
|
||||
-zn, --zip-name
|
||||
```
|
||||
|
||||
`target` is a folder to which the data gets written. This includes
|
||||
@@ -269,7 +296,7 @@ other files.
|
||||
|
||||
The filenames generated by this command follow the format
|
||||
`[date created] [correspondent] [title].[extension]`. If you want
|
||||
paperless to use `PAPERLESS_FILENAME_FORMAT` for exported filenames
|
||||
paperless to use [`PAPERLESS_FILENAME_FORMAT`](configuration.md#PAPERLESS_FILENAME_FORMAT) for exported filenames
|
||||
instead, specify `-f` or `--use-filename-format`.
|
||||
|
||||
If `-na` or `--no-archive` is provided, no archive files will be exported,
|
||||
@@ -296,8 +323,9 @@ will be placed in individual json files, instead of a single JSON file. The main
|
||||
manifest.json will still contain application wide information (e.g. tags, correspondent,
|
||||
documenttype, etc)
|
||||
|
||||
If `-z` or `--zip` is provided, the export will be a zipfile
|
||||
in the target directory, named according to the current date.
|
||||
If `-z` or `--zip` is provided, the export will be a zip file
|
||||
in the target directory, named according to the current local date or the
|
||||
value set in `-zn` or `--zip-name`.
|
||||
|
||||
!!! warning
|
||||
|
||||
@@ -334,7 +362,7 @@ currently-imported docs. This problem is common enough that there are
|
||||
tools for it.
|
||||
|
||||
```
|
||||
document_retagger [-h] [-c] [-T] [-t] [-i] [--use-first] [-f]
|
||||
document_retagger [-h] [-c] [-T] [-t] [-i] [--id-range] [--use-first] [-f]
|
||||
|
||||
optional arguments:
|
||||
-c, --correspondent
|
||||
@@ -342,6 +370,7 @@ optional arguments:
|
||||
-t, --document_type
|
||||
-s, --storage_path
|
||||
-i, --inbox-only
|
||||
--id-range
|
||||
--use-first
|
||||
-f, --overwrite
|
||||
```
|
||||
@@ -358,6 +387,11 @@ Specify `-i` to have the document retagger work on documents tagged with
|
||||
inbox tags only. This is useful when you don't want to mess with your
|
||||
already processed documents.
|
||||
|
||||
Specify `--id-range 1 100` to have the document retagger work only on a
|
||||
specific range of document id´s. This can be useful if you have a lot of
|
||||
documents and want to test the matching rules only on a subset of
|
||||
documents.
|
||||
|
||||
When multiple document types or correspondents match a single document,
|
||||
the retagger won't assign these to the document. Specify `--use-first`
|
||||
to override this behavior and just use the first correspondent or type
|
||||
@@ -374,7 +408,7 @@ that don't match a document anymore get removed as well.
|
||||
### Managing the Automatic matching algorithm
|
||||
|
||||
The _Auto_ matching algorithm requires a trained neural network to work.
|
||||
This network needs to be updated whenever somethings in your data
|
||||
This network needs to be updated whenever something in your data
|
||||
changes. The docker image takes care of that automatically with the task
|
||||
scheduler. You can manually renew the classifier by invoking the
|
||||
following management command:
|
||||
@@ -389,6 +423,9 @@ This command takes no arguments.
|
||||
|
||||
Use this command to re-create document thumbnails. Optionally include the ` --document {id}` option to generate thumbnails for a specific document only.
|
||||
|
||||
You may also specify `--processes` to control the number of processes used to generate new thumbnails. The default is to utilize
|
||||
a quarter of the available processors.
|
||||
|
||||
```
|
||||
document_thumbnails
|
||||
```
|
||||
@@ -416,7 +453,7 @@ task scheduler.
|
||||
### Managing filenames {#renamer}
|
||||
|
||||
If you use paperless' feature to
|
||||
[assign custom filenames to your documents](/advanced_usage#file-name-handling), you can use this command to move all your files after
|
||||
[assign custom filenames to your documents](advanced_usage.md#file-name-handling), you can use this command to move all your files after
|
||||
changing the naming scheme.
|
||||
|
||||
!!! warning
|
||||
@@ -441,19 +478,19 @@ collection for issues.
|
||||
|
||||
The issues detected by the sanity checker are as follows:
|
||||
|
||||
- Missing original files.
|
||||
- Missing archive files.
|
||||
- Inaccessible original files due to improper permissions.
|
||||
- Inaccessible archive files due to improper permissions.
|
||||
- Corrupted original documents by comparing their checksum against
|
||||
what is stored in the database.
|
||||
- Corrupted archive documents by comparing their checksum against what
|
||||
is stored in the database.
|
||||
- Missing thumbnails.
|
||||
- Inaccessible thumbnails due to improper permissions.
|
||||
- Documents without any content (warning).
|
||||
- Orphaned files in the media directory (warning). These are files
|
||||
that are not referenced by any document im paperless.
|
||||
- Missing original files.
|
||||
- Missing archive files.
|
||||
- Inaccessible original files due to improper permissions.
|
||||
- Inaccessible archive files due to improper permissions.
|
||||
- Corrupted original documents by comparing their checksum against
|
||||
what is stored in the database.
|
||||
- Corrupted archive documents by comparing their checksum against what
|
||||
is stored in the database.
|
||||
- Missing thumbnails.
|
||||
- Inaccessible thumbnails due to improper permissions.
|
||||
- Documents without any content (warning).
|
||||
- Orphaned files in the media directory (warning). These are files
|
||||
that are not referenced by any document in paperless.
|
||||
|
||||
```
|
||||
document_sanity_checker
|
||||
@@ -475,12 +512,13 @@ mail_fetcher
|
||||
The command takes no arguments and processes all your mail accounts and
|
||||
rules.
|
||||
|
||||
!!! note
|
||||
!!! tip
|
||||
|
||||
As of October 2022 Microsoft no longer supports IMAP authentication
|
||||
for Exchange servers, thus Exchange is no longer supported until a
|
||||
solution is implemented in the Python IMAP library used by Paperless.
|
||||
See [learn.microsoft.com](https://learn.microsoft.com/en-us/exchange/clients-and-mobile-in-exchange-online/deprecation-of-basic-authentication-exchange-online)
|
||||
To use OAuth access tokens for mail fetching,
|
||||
select the box to indicate the password is actually
|
||||
a token when creating or editing a mail account. The
|
||||
details for creating a token depend on your email
|
||||
provider.
|
||||
|
||||
### Creating archived documents {#archiver}
|
||||
|
||||
@@ -521,7 +559,7 @@ Documents can be stored in Paperless using GnuPG encryption.
|
||||
|
||||
!!! warning
|
||||
|
||||
Encryption is deprecated since [paperless-ng 0.9](/changelog#paperless-ng-090) and doesn't really
|
||||
Encryption is deprecated since [paperless-ng 0.9](changelog.md#paperless-ng-090) and doesn't really
|
||||
provide any additional security, since you have to store the passphrase
|
||||
in a configuration file on the same system as the encrypted documents
|
||||
for paperless to work. Furthermore, the entire text content of the
|
||||
@@ -542,9 +580,37 @@ Enabling encryption is no longer supported.
|
||||
|
||||
Basic usage to disable encryption of your document store:
|
||||
|
||||
(Note: If `PAPERLESS_PASSPHRASE` isn't set already, you need to specify
|
||||
(Note: If [`PAPERLESS_PASSPHRASE`](configuration.md#PAPERLESS_PASSPHRASE) isn't set already, you need to specify
|
||||
it here)
|
||||
|
||||
```
|
||||
decrypt_documents [--passphrase SECR3TP4SSPHRA$E]
|
||||
```
|
||||
|
||||
### Detecting duplicates {#fuzzy_duplicate}
|
||||
|
||||
Paperless already catches and prevents upload of exactly matching documents,
|
||||
however a new scan of an existing document may not produce an exact bit for bit
|
||||
duplicate. But the content should be exact or close, allowing detection.
|
||||
|
||||
This tool does a fuzzy match over document content, looking for
|
||||
those which look close according to a given ratio.
|
||||
|
||||
At this time, other metadata (such as correspondent or type) is not
|
||||
taken into account by the detection.
|
||||
|
||||
```
|
||||
document_fuzzy_match [--ratio] [--processes N]
|
||||
```
|
||||
|
||||
| Option | Required | Default | Description |
|
||||
| ----------- | -------- | ------------------- | ------------------------------------------------------------------------------------------------------------------------------ |
|
||||
| --ratio | No | 85.0 | a number between 0 and 100, setting how similar a document must be for it to be reported. Higher numbers mean more similarity. |
|
||||
| --processes | No | 1/4 of system cores | Number of processes to use for matching. Setting 1 disables multiple processes |
|
||||
| --delete | No | False | If provided, one document of a matched pair above the ratio will be deleted. |
|
||||
|
||||
!!! warning
|
||||
|
||||
If providing the `--delete` option, it is highly recommended to have a backup.
|
||||
While every effort has been taken to ensure proper operation, there is always the
|
||||
chance of deletion of a file you want to keep.
|
||||
|
@@ -1,6 +1,6 @@
|
||||
# Advanced Topics
|
||||
|
||||
Paperless offers a couple features that automate certain tasks and make
|
||||
Paperless offers a couple of features that automate certain tasks and make
|
||||
your life easier.
|
||||
|
||||
## Matching tags, correspondents, document types, and storage paths {#matching}
|
||||
@@ -9,7 +9,7 @@ Paperless will compare the matching algorithms defined by every tag,
|
||||
correspondent, document type, and storage path in your database to see
|
||||
if they apply to the text in a document. In other words, if you define a
|
||||
tag called `Home Utility` that had a `match` property of `bc hydro` and
|
||||
a `matching_algorithm` of `literal`, Paperless will automatically tag
|
||||
a `matching_algorithm` of `Exact`, Paperless will automatically tag
|
||||
your newly-consumed document with your `Home Utility` tag so long as the
|
||||
text `bc hydro` appears in the body of the document somewhere.
|
||||
|
||||
@@ -25,18 +25,20 @@ documents.
|
||||
|
||||
The following algorithms are available:
|
||||
|
||||
- **None:** No matching will be performed.
|
||||
- **Any:** Looks for any occurrence of any word provided in match in
|
||||
the PDF. If you define the match as `Bank1 Bank2`, it will match
|
||||
documents containing either of these terms.
|
||||
- **All:** Requires that every word provided appears in the PDF,
|
||||
albeit not in the order provided.
|
||||
- **Literal:** Matches only if the match appears exactly as provided
|
||||
- **Exact:** Matches only if the match appears exactly as provided
|
||||
(i.e. preserve ordering) in the PDF.
|
||||
- **Regular expression:** Parses the match as a regular expression and
|
||||
tries to find a match within the document.
|
||||
- **Fuzzy match:** I don't know. Look at the source.
|
||||
- **Fuzzy match:** Uses a partial matching based on locating the tag text
|
||||
inside the document, using a [partial ratio](https://maxbachmann.github.io/RapidFuzz/Usage/fuzz.html#partial-ratio)
|
||||
- **Auto:** Tries to automatically match new documents. This does not
|
||||
require you to set a match. See the notes below.
|
||||
require you to set a match. See the [notes below](#automatic-matching).
|
||||
|
||||
When using the _any_ or _all_ matching algorithms, you can search for
|
||||
terms that consist of multiple words by enclosing them in double quotes.
|
||||
@@ -91,7 +93,7 @@ when using this feature:
|
||||
decide when not to assign a certain tag, correspondent, document
|
||||
type, or storage path. This will usually be the case as you start
|
||||
filling up paperless with documents. Example: If all your documents
|
||||
are either from "Webshop" and "Bank", paperless will assign one
|
||||
are either from "Webshop" or "Bank", paperless will assign one
|
||||
of these correspondents to ANY new document, if both are set to
|
||||
automatic matching.
|
||||
|
||||
@@ -100,12 +102,12 @@ when using this feature:
|
||||
Sometimes you may want to do something arbitrary whenever a document is
|
||||
consumed. Rather than try to predict what you may want to do, Paperless
|
||||
lets you execute scripts of your own choosing just before or after a
|
||||
document is consumed using a couple simple hooks.
|
||||
document is consumed using a couple of simple hooks.
|
||||
|
||||
Just write a script, put it somewhere that Paperless can read & execute,
|
||||
and then put the path to that script in `paperless.conf` or
|
||||
`docker-compose.env` with the variable name of either
|
||||
`PAPERLESS_PRE_CONSUME_SCRIPT` or `PAPERLESS_POST_CONSUME_SCRIPT`.
|
||||
[`PAPERLESS_PRE_CONSUME_SCRIPT`](configuration.md#PAPERLESS_PRE_CONSUME_SCRIPT) or [`PAPERLESS_POST_CONSUME_SCRIPT`](configuration.md#PAPERLESS_POST_CONSUME_SCRIPT).
|
||||
|
||||
!!! info
|
||||
|
||||
@@ -125,6 +127,7 @@ script can access the following relevant environment variables set:
|
||||
| ----------------------- | ------------------------------------------------------------ |
|
||||
| `DOCUMENT_SOURCE_PATH` | Original path of the consumed document |
|
||||
| `DOCUMENT_WORKING_PATH` | Path to a copy of the original that consumption will work on |
|
||||
| `TASK_ID` | UUID of the task used to process the new document (if any) |
|
||||
|
||||
!!! note
|
||||
|
||||
@@ -133,6 +136,11 @@ script can access the following relevant environment variables set:
|
||||
be triggered, leading to failures as two tasks work on the
|
||||
same document path
|
||||
|
||||
!!! warning
|
||||
|
||||
If your script modifies `DOCUMENT_WORKING_PATH` in a non-deterministic
|
||||
way, this may allow duplicate documents to be stored
|
||||
|
||||
A simple but common example for this would be creating a simple script
|
||||
like this:
|
||||
|
||||
@@ -167,21 +175,22 @@ Executed after the consumer has successfully processed a document and
|
||||
has moved it into paperless. It receives the following environment
|
||||
variables:
|
||||
|
||||
| Environment Variable | Description |
|
||||
| ---------------------------- | --------------------------------------------- |
|
||||
| `DOCUMENT_ID` | Database primary key of the document |
|
||||
| `DOCUMENT_FILE_NAME` | Formatted filename, not including paths |
|
||||
| `DOCUMENT_CREATED` | Date & time when document created |
|
||||
| `DOCUMENT_MODIFIED` | Date & time when document was last modified |
|
||||
| `DOCUMENT_ADDED` | Date & time when document was added |
|
||||
| `DOCUMENT_SOURCE_PATH` | Path to the original document file |
|
||||
| `DOCUMENT_ARCHIVE_PATH` | Path to the generate archive file (if any) |
|
||||
| `DOCUMENT_THUMBNAIL_PATH` | Path to the generated thumbnail |
|
||||
| `DOCUMENT_DOWNLOAD_URL` | URL for document download |
|
||||
| `DOCUMENT_THUMBNAIL_URL` | URL for the document thumbnail |
|
||||
| `DOCUMENT_CORRESPONDENT` | Assigned correspondent (if any) |
|
||||
| `DOCUMENT_TAGS` | Comma separated list of tags applied (if any) |
|
||||
| `DOCUMENT_ORIGINAL_FILENAME` | Filename of original document |
|
||||
| Environment Variable | Description |
|
||||
| ---------------------------- | ---------------------------------------------- |
|
||||
| `DOCUMENT_ID` | Database primary key of the document |
|
||||
| `DOCUMENT_FILE_NAME` | Formatted filename, not including paths |
|
||||
| `DOCUMENT_CREATED` | Date & time when document created |
|
||||
| `DOCUMENT_MODIFIED` | Date & time when document was last modified |
|
||||
| `DOCUMENT_ADDED` | Date & time when document was added |
|
||||
| `DOCUMENT_SOURCE_PATH` | Path to the original document file |
|
||||
| `DOCUMENT_ARCHIVE_PATH` | Path to the generate archive file (if any) |
|
||||
| `DOCUMENT_THUMBNAIL_PATH` | Path to the generated thumbnail |
|
||||
| `DOCUMENT_DOWNLOAD_URL` | URL for document download |
|
||||
| `DOCUMENT_THUMBNAIL_URL` | URL for the document thumbnail |
|
||||
| `DOCUMENT_CORRESPONDENT` | Assigned correspondent (if any) |
|
||||
| `DOCUMENT_TAGS` | Comma separated list of tags applied (if any) |
|
||||
| `DOCUMENT_ORIGINAL_FILENAME` | Filename of original document |
|
||||
| `TASK_ID` | Task UUID used to import the document (if any) |
|
||||
|
||||
The script can be in any language, A simple shell script example:
|
||||
|
||||
@@ -196,7 +205,7 @@ The script can be in any language, A simple shell script example:
|
||||
!!! warning
|
||||
|
||||
The post consumption script should not modify the document files
|
||||
directly
|
||||
directly.
|
||||
|
||||
The script's stdout and stderr will be logged line by line to the
|
||||
webserver log, along with the exit code of the script.
|
||||
@@ -232,8 +241,8 @@ webserver:
|
||||
|
||||
Troubleshooting:
|
||||
|
||||
- Monitor the docker-compose log
|
||||
`cd ~/paperless-ngx; docker-compose logs -f`
|
||||
- Monitor the Docker Compose log
|
||||
`cd ~/paperless-ngx; docker compose logs -f`
|
||||
- Check your script's permission e.g. in case of permission error
|
||||
`sudo chmod 755 post-consumption-example.sh`
|
||||
- Pipe your scripts's output to a log file e.g.
|
||||
@@ -247,7 +256,8 @@ document. You will end up getting files like `0000123.pdf` in your media
|
||||
directory. This isn't necessarily a bad thing, because you normally
|
||||
don't have to access these files manually. However, if you wish to name
|
||||
your files differently, you can do that by adjusting the
|
||||
`PAPERLESS_FILENAME_FORMAT` configuration option. Paperless adds the
|
||||
[`PAPERLESS_FILENAME_FORMAT`](configuration.md#PAPERLESS_FILENAME_FORMAT) configuration option
|
||||
or using [storage paths (see below)](#storage-paths). Paperless adds the
|
||||
correct file extension e.g. `.pdf`, `.jpg` automatically.
|
||||
|
||||
This variable allows you to configure the filename (folders are allowed)
|
||||
@@ -280,6 +290,15 @@ will create a directory structure as follows:
|
||||
paperless will report your files as missing and won't be able to find
|
||||
them.
|
||||
|
||||
!!! tip
|
||||
|
||||
Paperless checks the filename of a document whenever it is saved. Changing (or deleting)
|
||||
a [storage paths](#storage-paths) will automatically be reflected in the file system. However,
|
||||
when changing `PAPERLESS_FILENAME_FORMAT` you will need to manually run the
|
||||
[`document renamer`](administration.md#renamer) to move any existing documents.
|
||||
|
||||
#### Placeholders
|
||||
|
||||
Paperless provides the following placeholders within filenames:
|
||||
|
||||
- `{asn}`: The archive serial number of the document, or "none".
|
||||
@@ -308,6 +327,15 @@ Paperless provides the following placeholders within filenames:
|
||||
- `{added_month_name_short}`: Month added abbreviated name, as per
|
||||
locale
|
||||
- `{added_day}`: Day added only (number 01-31).
|
||||
- `{owner_username}`: Username of document owner, if any, or "none"
|
||||
- `{original_name}`: Document original filename, minus the extension, if any, or "none"
|
||||
- `{doc_pk}`: The paperless identifier (primary key) for the document.
|
||||
|
||||
!!! warning
|
||||
|
||||
When using file name placeholders, in particular when using `{tag_list}`,
|
||||
you may run into the limits of your operating system's maximum path lengths.
|
||||
In that case, files will retain the previous path instead and the issue logged.
|
||||
|
||||
Paperless will try to conserve the information from your database as
|
||||
much as possible. However, some characters that you can use in document
|
||||
@@ -319,34 +347,12 @@ paperless will automatically append `_01`, `_02`, etc to the filename.
|
||||
This happens if all the placeholders in a filename evaluate to the same
|
||||
value.
|
||||
|
||||
!!! tip
|
||||
|
||||
You can affect how empty placeholders are treated by changing the
|
||||
following setting to `true`.
|
||||
|
||||
```
|
||||
PAPERLESS_FILENAME_FORMAT_REMOVE_NONE=True
|
||||
```
|
||||
|
||||
Doing this results in all empty placeholders resolving to "" instead
|
||||
of "none" as stated above. Spaces before empty placeholders are
|
||||
removed as well, empty directories are omitted.
|
||||
|
||||
!!! tip
|
||||
|
||||
Paperless checks the filename of a document whenever it is saved.
|
||||
Therefore, you need to update the filenames of your documents and move
|
||||
them after altering this setting by invoking the
|
||||
[`document renamer`](/administration#renamer).
|
||||
|
||||
!!! warning
|
||||
|
||||
Make absolutely sure you get the spelling of the placeholders right, or
|
||||
else paperless will use the default naming scheme instead.
|
||||
If there are any errors in the placeholders included in `PAPERLESS_FILENAME_FORMAT`,
|
||||
paperless will fallback to using the default naming scheme instead.
|
||||
|
||||
!!! caution
|
||||
|
||||
As of now, you could totally tell paperless to store your files anywhere
|
||||
As of now, you could potentially tell paperless to store your files anywhere
|
||||
outside the media directory by setting
|
||||
|
||||
```
|
||||
@@ -354,28 +360,25 @@ value.
|
||||
```
|
||||
|
||||
However, keep in mind that inside docker, if files get stored outside of
|
||||
the predefined volumes, they will be lost after a restart of paperless.
|
||||
the predefined volumes, they will be lost after a restart.
|
||||
|
||||
!!! warning
|
||||
##### Empty placeholders
|
||||
|
||||
When file naming handling, in particular when using `{tag_list}`,
|
||||
you may run into the limits of your operating system's maximum
|
||||
path lengths. Files will retain the previous path instead and
|
||||
the issue logged.
|
||||
You can affect how empty placeholders are treated by changing the
|
||||
[`PAPERLESS_FILENAME_FORMAT_REMOVE_NONE`](configuration.md#PAPERLESS_FILENAME_FORMAT_REMOVE_NONE) setting.
|
||||
|
||||
## Storage paths
|
||||
Enabling this results in all empty placeholders resolving to "" instead of "none" as stated above. Spaces
|
||||
before empty placeholders are removed as well, empty directories are omitted.
|
||||
|
||||
One of the best things in Paperless is that you can not only access the
|
||||
documents via the web interface, but also via the file system.
|
||||
### Storage paths
|
||||
|
||||
When as single storage layout is not sufficient for your use case,
|
||||
storage paths come to the rescue. Storage paths allow you to configure
|
||||
more precisely where each document is stored in the file system.
|
||||
When a single storage layout is not sufficient for your use case, storage paths allow for more complex
|
||||
structure to set precisely where each document is stored in the file system.
|
||||
|
||||
- Each storage path is a `PAPERLESS_FILENAME_FORMAT` and
|
||||
- Each storage path is a [`PAPERLESS_FILENAME_FORMAT`](configuration.md#PAPERLESS_FILENAME_FORMAT) and
|
||||
follows the rules described above
|
||||
- Each document is assigned a storage path using the matching
|
||||
algorithms described above, but can be overwritten at any time
|
||||
- Each document is assigned a storage path using the matching algorithms described above, but can be
|
||||
overwritten at any time
|
||||
|
||||
For example, you could define the following two storage paths:
|
||||
|
||||
@@ -400,7 +403,7 @@ structure as in the previous example above.
|
||||
Statement January.pdf
|
||||
Statement February.pdf
|
||||
|
||||
Insurances/ # Insurances
|
||||
Insurances/ # Insurances
|
||||
Healthcare 123/
|
||||
2022-01-01 Statement January.pdf
|
||||
2022-02-02 Letter.pdf
|
||||
@@ -412,14 +415,7 @@ structure as in the previous example above.
|
||||
!!! tip
|
||||
|
||||
Defining a storage path is optional. If no storage path is defined for a
|
||||
document, the global `PAPERLESS_FILENAME_FORMAT` is applied.
|
||||
|
||||
!!! warning
|
||||
|
||||
If you adjust the format of an existing storage path, old documents
|
||||
don't get relocated automatically. You need to run the
|
||||
[document renamer](/administration#renamer) to
|
||||
adjust their paths.
|
||||
document, the global [`PAPERLESS_FILENAME_FORMAT`](configuration.md#PAPERLESS_FILENAME_FORMAT) is applied.
|
||||
|
||||
## Celery Monitoring {#celery-monitoring}
|
||||
|
||||
@@ -429,8 +425,10 @@ to view more detailed information about the health of the celery workers
|
||||
used for asynchronous tasks. This includes details on currently running,
|
||||
queued and completed tasks, timing and more. Flower can also be used
|
||||
with Prometheus, as it exports metrics. For details on its capabilities,
|
||||
refer to the Flower documentation.
|
||||
refer to the [Flower](https://flower.readthedocs.io/en/latest/index.html)
|
||||
documentation.
|
||||
|
||||
Flower can be enabled with the setting [PAPERLESS_ENABLE_FLOWER](configuration.md#PAPERLESS_ENABLE_FLOWER).
|
||||
To configure Flower further, create a `flowerconfig.py` and
|
||||
place it into the `src/paperless` directory. For a Docker
|
||||
installation, you can use volumes to accomplish this:
|
||||
@@ -439,6 +437,8 @@ installation, you can use volumes to accomplish this:
|
||||
services:
|
||||
# ...
|
||||
webserver:
|
||||
environment:
|
||||
- PAPERLESS_ENABLE_FLOWER
|
||||
ports:
|
||||
- 5555:5555 # (2)!
|
||||
# ...
|
||||
@@ -447,7 +447,7 @@ services:
|
||||
```
|
||||
|
||||
1. Note the `:ro` tag means the file will be mounted as read only.
|
||||
2. `flower` runs by default on port 5555, but this can be configured
|
||||
2. By default, Flower runs on port 5555, but this can be configured.
|
||||
|
||||
## Custom Container Initialization
|
||||
|
||||
@@ -492,7 +492,7 @@ database to be case sensitive. This would prevent a user from creating a
|
||||
tag `Name` and `NAME` as they are considered the same.
|
||||
|
||||
Per Django documentation, to enable this requires manual intervention.
|
||||
To enable case sensetive tables, you can execute the following command
|
||||
To enable case sensitive tables, you can execute the following command
|
||||
against each table:
|
||||
|
||||
`ALTER TABLE <table_name> CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_bin;`
|
||||
@@ -507,3 +507,183 @@ existing tables) with:
|
||||
Using mariadb version 10.4+ is recommended. Using the `utf8mb3` character set on
|
||||
an older system may fix issues that can arise while setting up Paperless-ngx but
|
||||
`utf8mb3` can cause issues with consumption (where `utf8mb4` does not).
|
||||
|
||||
### Missing timezones
|
||||
|
||||
MySQL as well as MariaDB do not have any timezone information by default (though some
|
||||
docker images such as the official MariaDB image take care of this for you) which will
|
||||
cause unexpected behavior with date-based queries.
|
||||
|
||||
To fix this, execute one of the following commands:
|
||||
|
||||
MySQL: `mysql_tzinfo_to_sql /usr/share/zoneinfo | mysql -u root mysql -p`
|
||||
|
||||
MariaDB: `mariadb-tzinfo-to-sql /usr/share/zoneinfo | mariadb -u root mysql -p`
|
||||
|
||||
## Barcodes {#barcodes}
|
||||
|
||||
Paperless is able to utilize barcodes for automatically performing some tasks.
|
||||
|
||||
At this time, the library utilized for detection of barcodes supports the following types:
|
||||
|
||||
- AN-13/UPC-A
|
||||
- UPC-E
|
||||
- EAN-8
|
||||
- Code 128
|
||||
- Code 93
|
||||
- Code 39
|
||||
- Codabar
|
||||
- Interleaved 2 of 5
|
||||
- QR Code
|
||||
- SQ Code
|
||||
|
||||
You may check for updates on the [zbar library homepage](https://github.com/mchehab/zbar).
|
||||
For usage in Paperless, the type of barcode does not matter, only the contents of it.
|
||||
|
||||
For how to enable barcode usage, see [the configuration](configuration.md#barcodes).
|
||||
The two settings may be enabled independently, but do have interactions as explained
|
||||
below.
|
||||
|
||||
### Document Splitting {#document-splitting}
|
||||
|
||||
When enabled, Paperless will look for a barcode with the configured value and create a new document
|
||||
starting from the next page. The page with the barcode on it will _not_ be retained. It
|
||||
is expected to be a page existing only for triggering the split.
|
||||
|
||||
### Archive Serial Number Assignment
|
||||
|
||||
When enabled, the value of the barcode (as an integer) will be used to set the document's
|
||||
archive serial number, allowing quick reference back to the original, paper document.
|
||||
|
||||
If document splitting via barcode is also enabled, documents will be split when an ASN
|
||||
barcode is located. However, differing from the splitting, the page with the
|
||||
barcode _will_ be retained. This allows application of a barcode to any page, including
|
||||
one which holds data to keep in the document.
|
||||
|
||||
### Tag Assignment
|
||||
|
||||
When enabled, Paperless will parse barcodes and attempt to interpret and assign tags.
|
||||
|
||||
See the relevant settings [`PAPERLESS_CONSUMER_ENABLE_TAG_BARCODE`](configuration.md#PAPERLESS_CONSUMER_ENABLE_TAG_BARCODE)
|
||||
and [`PAPERLESS_CONSUMER_TAG_BARCODE_MAPPING`](configuration.md#PAPERLESS_CONSUMER_TAG_BARCODE_MAPPING)
|
||||
for more information.
|
||||
|
||||
## Automatic collation of double-sided documents {#collate}
|
||||
|
||||
!!! note
|
||||
|
||||
If your scanner supports double-sided scanning natively, you do not need this feature.
|
||||
|
||||
This feature is turned off by default, see [configuration](configuration.md#collate) on how to turn it on.
|
||||
|
||||
### Summary
|
||||
|
||||
If you have a scanner with an automatic document feeder (ADF) that only scans a single side,
|
||||
this feature makes scanning double-sided documents much more convenient by automatically
|
||||
collating two separate scans into one document, reordering the pages as necessary.
|
||||
|
||||
### Usage example
|
||||
|
||||
Suppose you have a double-sided document with 6 pages (3 sheets of paper). First,
|
||||
put the stack into your ADF as normal, ensuring that page 1 is scanned first. Your ADF
|
||||
will now scan pages 1, 3, and 5. Then you (or your scanner, if it supports it) upload
|
||||
the scan into the correct sub-directory of the consume folder (`double-sided` by default;
|
||||
keep in mind that Paperless will _not_ automatically create the directory for you.)
|
||||
Paperless will then process the scan and move it into an internal staging area.
|
||||
|
||||
The next step is to turn your stack upside down (without reordering the sheets of paper),
|
||||
and scan it once again, your ADF will now scan pages 6, 4, and 2, in that order. Once this
|
||||
scan is copied into the sub-directory, Paperless will collate the previous scan with the
|
||||
new one, reversing the order of the pages on the second, "even numbered" scan. The
|
||||
resulting document will have the pages 1-6 in the correct order, and this new file will
|
||||
then be processed as normal.
|
||||
|
||||
!!! tip
|
||||
|
||||
When scanning the even numbered pages, you can omit the last empty pages, if there are
|
||||
any. For example, if page 6 is empty, you only need to scan pages 2 and 4. _Do not_ omit
|
||||
empty pages in the middle of the document.
|
||||
|
||||
### Things that could go wrong
|
||||
|
||||
Paperless will notice when the first, "odd numbered" scan has less pages than the second
|
||||
scan (this can happen when e.g. the ADF skipped a few pages in the first pass). In that
|
||||
case, Paperless will remove the staging copy as well as the scan, and give you an error
|
||||
message asking you to restart the process from scratch, by scanning the odd pages again,
|
||||
followed by the even pages.
|
||||
|
||||
It's important that the scan files get consumed in the correct order, and one at a time.
|
||||
You therefore need to make sure that Paperless is running while you upload the files into
|
||||
the directory; and if you're using [polling](configuration.md#polling), make sure that
|
||||
`CONSUMER_POLLING` is set to a value lower than it takes for the second scan to appear,
|
||||
like 5-10 or even lower.
|
||||
|
||||
Another thing that might happen is that you start a double sided scan, but then forget
|
||||
to upload the second file. To avoid collating the wrong documents if you then come back
|
||||
a day later to scan a new double-sided document, Paperless will only keep an "odd numbered
|
||||
pages" file for up to 30 minutes. If more time passes, it will consider the next incoming
|
||||
scan a completely new "odd numbered pages" one. The old staging file will get discarded.
|
||||
|
||||
### Interaction with "subdirs as tags"
|
||||
|
||||
The collation feature can be used together with the [subdirs as tags](configuration.md#consume_config)
|
||||
feature (but this is not a requirement). Just create a correctly named double-sided subdir
|
||||
in the hierarchy and upload your scans there. For example, both `double-sided/foo/bar` as
|
||||
well as `foo/bar/double-sided` will cause the collated document to be treated as if it
|
||||
were uploaded into `foo/bar` and receive both `foo` and `bar` tags, but not `double-sided`.
|
||||
|
||||
### Interaction with document splitting
|
||||
|
||||
You can use the [document splitting](#document-splitting) feature, but if you use a normal
|
||||
single-sided split marker page, the split document(s) will have an empty page at the front (or
|
||||
whatever else was on the backside of the split marker page.) You can work around that by having
|
||||
a split marker page that has the split barcode on _both_ sides. This way, the extra page will
|
||||
get automatically removed.
|
||||
|
||||
## SSO and third party authentication with Paperless-ngx
|
||||
|
||||
Paperless-ngx has a built-in authentication system from Django but you can easily integrate an
|
||||
external authentication solution using one of the following methods:
|
||||
|
||||
### Remote User authentication
|
||||
|
||||
This is a simple option that uses remote user authentication made available by certain SSO
|
||||
applications. See the relevant configuration options for more information:
|
||||
[PAPERLESS_ENABLE_HTTP_REMOTE_USER](configuration.md#PAPERLESS_ENABLE_HTTP_REMOTE_USER) and
|
||||
[PAPERLESS_HTTP_REMOTE_USER_HEADER_NAME](configuration.md#PAPERLESS_HTTP_REMOTE_USER_HEADER_NAME)
|
||||
|
||||
### OpenID Connect and social authentication
|
||||
|
||||
Version 2.5.0 of Paperless-ngx added support for integrating other authentication systems via
|
||||
the [django-allauth](https://github.com/pennersr/django-allauth) package. Once set up, users
|
||||
can either log in or (optionally) sign up using any third party systems you integrate. See the
|
||||
relevant [configuration settings](configuration.md#PAPERLESS_SOCIALACCOUNT_PROVIDERS) and
|
||||
[django-allauth docs](https://docs.allauth.org/en/latest/socialaccount/configuration.html)
|
||||
for more information.
|
||||
|
||||
To associate an existing Paperless-ngx account with a social account, first login with your
|
||||
regular credentials and then choose "My Profile" from the user dropdown in the app and you
|
||||
will see options to connect social account(s). If enabled, signup options will be available
|
||||
on the login page.
|
||||
|
||||
As an example, to set up login via Github, the following environment variables would need to be
|
||||
set:
|
||||
|
||||
```conf
|
||||
PAPERLESS_APPS="allauth.socialaccount.providers.github"
|
||||
PAPERLESS_SOCIALACCOUNT_PROVIDERS='{"github": {"APPS": [{"provider_id": "github","name": "Github","client_id": "<CLIENT_ID>","secret": "<CLIENT_SECRET>"}]}}'
|
||||
```
|
||||
|
||||
Or, to use OpenID Connect ("OIDC"), via Keycloak in this example:
|
||||
|
||||
```conf
|
||||
PAPERLESS_APPS="allauth.socialaccount.providers.openid_connect"
|
||||
PAPERLESS_SOCIALACCOUNT_PROVIDERS='
|
||||
{"openid_connect": {"APPS": [{"provider_id": "keycloak","name": "Keycloak","client_id": "paperless","secret": "<CLIENT_SECRET>","settings": { "server_url": "https://<KEYCLOAK_SERVER>/realms/<REALM>/.well-known/openid-configuration"}}]}}'
|
||||
```
|
||||
|
||||
More details about configuration option for various providers can be found in the [allauth documentation](https://docs.allauth.org/en/latest/socialaccount/providers/index.html#provider-specifics).
|
||||
|
||||
### Disabling Regular Login
|
||||
|
||||
Once external auth is set up, 'regular' login can be disabled with the [PAPERLESS_DISABLE_REGULAR_LOGIN](configuration.md#PAPERLESS_DISABLE_REGULAR_LOGIN) setting.
|
||||
|
169
docs/api.md
@@ -6,20 +6,28 @@ provides a browsable API for most of its endpoints, which you can
|
||||
inspect at `http://<paperless-host>:<port>/api/`. This also documents
|
||||
most of the available filters and ordering fields.
|
||||
|
||||
The API provides 7 main endpoints:
|
||||
The API provides the following main endpoints:
|
||||
|
||||
- `/api/correspondents/`: Full CRUD support.
|
||||
- `/api/custom_fields/`: Full CRUD support.
|
||||
- `/api/documents/`: Full CRUD support, except POSTing new documents.
|
||||
See below.
|
||||
- `/api/correspondents/`: Full CRUD support.
|
||||
- `/api/document_types/`: Full CRUD support.
|
||||
- `/api/groups/`: Full CRUD support.
|
||||
- `/api/logs/`: Read-Only.
|
||||
- `/api/tags/`: Full CRUD support.
|
||||
- `/api/mail_accounts/`: Full CRUD support.
|
||||
- `/api/mail_rules/`: Full CRUD support.
|
||||
- `/api/profile/`: GET, PATCH
|
||||
- `/api/share_links/`: Full CRUD support.
|
||||
- `/api/storage_paths/`: Full CRUD support.
|
||||
- `/api/tags/`: Full CRUD support.
|
||||
- `/api/tasks/`: Read-only.
|
||||
- `/api/users/`: Full CRUD support.
|
||||
- `/api/workflows/`: Full CRUD support.
|
||||
|
||||
All of these endpoints except for the logging endpoint allow you to
|
||||
fetch, edit and delete individual objects by appending their primary key
|
||||
to the path, for example `/api/documents/454/`.
|
||||
fetch (and edit and delete where appropriate) individual objects by
|
||||
appending their primary key to the path, e.g. `/api/documents/454/`.
|
||||
|
||||
The objects served by the document endpoint contain the following
|
||||
fields:
|
||||
@@ -44,6 +52,15 @@ fields:
|
||||
Read-only.
|
||||
- `archived_file_name`: Verbose filename of the archived document.
|
||||
Read-only. Null if no archived document is available.
|
||||
- `notes`: Array of notes associated with the document.
|
||||
- `set_permissions`: Allows setting document permissions. Optional,
|
||||
write-only. See [below](#permissions).
|
||||
- `custom_fields`: Array of custom fields & values, specified as
|
||||
`{ field: CUSTOM_FIELD_ID, value: VALUE }`
|
||||
|
||||
!!! note
|
||||
|
||||
Note that all endpoint URLs must end with a `/`slash.
|
||||
|
||||
## Downloading documents
|
||||
|
||||
@@ -119,9 +136,14 @@ File metadata is reported as a list of objects in the following form:
|
||||
depends on the file type and the metadata available in that specific
|
||||
document. Paperless only reports PDF metadata at this point.
|
||||
|
||||
## Documents additional endpoints
|
||||
|
||||
- `/api/documents/<id>/notes/`: Retrieve notes for a document.
|
||||
- `/api/documents/<id>/share_links/`: Retrieve share links for a document.
|
||||
|
||||
## Authorization
|
||||
|
||||
The REST api provides three different forms of authentication.
|
||||
The REST api provides four different forms of authentication.
|
||||
|
||||
1. Basic authentication
|
||||
|
||||
@@ -142,6 +164,10 @@ The REST api provides three different forms of authentication.
|
||||
|
||||
3. Token authentication
|
||||
|
||||
You can create (or re-create) an API token by opening the "My Profile"
|
||||
link in the user dropdown found in the web UI and clicking the circular
|
||||
arrow button.
|
||||
|
||||
Paperless also offers an endpoint to acquire authentication tokens.
|
||||
|
||||
POST a username and password as a form or json string to
|
||||
@@ -153,7 +179,13 @@ The REST api provides three different forms of authentication.
|
||||
Authorization: Token <token>
|
||||
```
|
||||
|
||||
Tokens can be managed and revoked in the paperless admin.
|
||||
Tokens can also be managed in the Django admin.
|
||||
|
||||
4. Remote User authentication
|
||||
|
||||
If enabled (see
|
||||
[configuration](configuration.md#PAPERLESS_ENABLE_HTTP_REMOTE_USER_API)),
|
||||
you can authenticate against the API using Remote User auth.
|
||||
|
||||
## Searching for documents
|
||||
|
||||
@@ -162,8 +194,8 @@ specific query parameters cause the API to return full text search
|
||||
results:
|
||||
|
||||
- `/api/documents/?query=your%20search%20query`: Search for a document
|
||||
using a full text query. For details on the syntax, see [Basic Usage - Searching](/usage#basic-usage_searching).
|
||||
- `/api/documents/?more_like=1234`: Search for documents similar to
|
||||
using a full text query. For details on the syntax, see [Basic Usage - Searching](usage.md#basic-usage_searching).
|
||||
- `/api/documents/?more_like_id=1234`: Search for documents similar to
|
||||
the document with id 1234.
|
||||
|
||||
Pagination works exactly the same as it does for normal requests on this
|
||||
@@ -252,13 +284,114 @@ The endpoint supports the following optional form fields:
|
||||
- `correspondent`: Specify the ID of a correspondent that the consumer
|
||||
should use for the document.
|
||||
- `document_type`: Similar to correspondent.
|
||||
- `storage_path`: Similar to correspondent.
|
||||
- `tags`: Similar to correspondent. Specify this multiple times to
|
||||
have multiple tags added to the document.
|
||||
- `archive_serial_number`: An optional archive serial number to set.
|
||||
|
||||
The endpoint will immediately return "OK" if the document consumption
|
||||
process was started successfully. No additional status information about
|
||||
the consumption process itself is available, since that happens in a
|
||||
different process.
|
||||
The endpoint will immediately return HTTP 200 if the document consumption
|
||||
process was started successfully, with the UUID of the consumption task
|
||||
as the data. No additional status information about the consumption process
|
||||
itself is available immediately, since that happens in a different process.
|
||||
However, querying the tasks endpoint with the returned UUID e.g.
|
||||
`/api/tasks/?task_id={uuid}` will provide information on the state of the
|
||||
consumption including the ID of a created document if consumption succeeded.
|
||||
|
||||
## Permissions
|
||||
|
||||
All objects (documents, tags, etc.) allow setting object-level permissions
|
||||
with optional `owner` and / or a `set_permissions` parameters which are of
|
||||
the form:
|
||||
|
||||
```
|
||||
"owner": ...,
|
||||
"set_permissions": {
|
||||
"view": {
|
||||
"users": [...],
|
||||
"groups": [...],
|
||||
},
|
||||
"change": {
|
||||
"users": [...],
|
||||
"groups": [...],
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
!!! note
|
||||
|
||||
Arrays should contain user or group ID numbers.
|
||||
|
||||
If these parameters are supplied the object's permissions will be overwritten,
|
||||
assuming the authenticated user has permission to do so (the user must be
|
||||
the object owner or a superuser).
|
||||
|
||||
### Retrieving full permissions
|
||||
|
||||
By default, the API will return a truncated version of object-level
|
||||
permissions, returning `user_can_change` indicating whether the current user
|
||||
can edit the object (either because they are the object owner or have permissions
|
||||
granted). You can pass the parameter `full_perms=true` to API calls to view the
|
||||
full permissions of objects in a format that mirrors the `set_permissions`
|
||||
parameter above.
|
||||
|
||||
## Bulk Editing
|
||||
|
||||
The API supports various bulk-editing operations which are executed asynchronously.
|
||||
|
||||
### Documents
|
||||
|
||||
For bulk operations on documents, use the endpoint `/api/bulk_edit/` which accepts
|
||||
a json payload of the format:
|
||||
|
||||
```json
|
||||
{
|
||||
"documents": [LIST_OF_DOCUMENT_IDS],
|
||||
"method": METHOD, // see below
|
||||
"parameters": args // see below
|
||||
}
|
||||
```
|
||||
|
||||
The following methods are supported:
|
||||
|
||||
- `set_correspondent`
|
||||
- Requires `parameters`: `{ "correspondent": CORRESPONDENT_ID }`
|
||||
- `set_document_type`
|
||||
- Requires `parameters`: `{ "document_type": DOCUMENT_TYPE_ID }`
|
||||
- `set_storage_path`
|
||||
- Requires `parameters`: `{ "storage_path": STORAGE_PATH_ID }`
|
||||
- `add_tag`
|
||||
- Requires `parameters`: `{ "tag": TAG_ID }`
|
||||
- `remove_tag`
|
||||
- Requires `parameters`: `{ "tag": TAG_ID }`
|
||||
- `modify_tags`
|
||||
- Requires `parameters`: `{ "add_tags": [LIST_OF_TAG_IDS] }` and / or `{ "remove_tags": [LIST_OF_TAG_IDS] }`
|
||||
- `delete`
|
||||
- No `parameters` required
|
||||
- `redo_ocr`
|
||||
- No `parameters` required
|
||||
- `set_permissions`
|
||||
- Requires `parameters`:
|
||||
- `"permissions": PERMISSIONS_OBJ` (see format [above](#permissions)) and / or
|
||||
- `"owner": OWNER_ID or null`
|
||||
- `"merge": true or false` (defaults to false)
|
||||
- The `merge` flag determines if the supplied permissions will overwrite all existing permissions (including
|
||||
removing them) or be merged with existing permissions.
|
||||
|
||||
### Objects
|
||||
|
||||
Bulk editing for objects (tags, document types etc.) currently supports set permissions or delete
|
||||
operations, using the endpoint: `/api/bulk_edit_objects/`, which requires a json payload of the format:
|
||||
|
||||
```json
|
||||
{
|
||||
"objects": [LIST_OF_OBJECT_IDS],
|
||||
"object_type": "tags", "correspondents", "document_types" or "storage_paths",
|
||||
"operation": "set_permissions" or "delete",
|
||||
"owner": OWNER_ID, // optional
|
||||
"permissions": { "view": { "users": [] ... }, "change": { ... } }, // (see 'set_permissions' format above)
|
||||
"merge": true / false // defaults to false, see above
|
||||
}
|
||||
```
|
||||
|
||||
## API Versioning
|
||||
|
||||
@@ -316,3 +449,13 @@ Initial API version.
|
||||
color to use for a specific tag, which is either black or white
|
||||
depending on the brightness of `Tag.color`.
|
||||
- Removed field `Tag.colour`.
|
||||
|
||||
#### Version 3
|
||||
|
||||
- Permissions endpoints have been added.
|
||||
- The format of the `/api/ui_settings/` has changed.
|
||||
|
||||
#### Version 4
|
||||
|
||||
- Consumption templates were refactored to workflows and API endpoints
|
||||
changed as such.
|
||||
|
@@ -20,6 +20,28 @@
|
||||
margin-left: 4%;
|
||||
float: left;
|
||||
}
|
||||
|
||||
.grid-flipped-left {
|
||||
width: 66%;
|
||||
float: left;
|
||||
}
|
||||
|
||||
.grid-flipped-right {
|
||||
width: 29%;
|
||||
margin-left: 4%;
|
||||
float: left;
|
||||
}
|
||||
|
||||
.grid-half-left {
|
||||
width: 48%;
|
||||
float: left;
|
||||
}
|
||||
|
||||
.grid-half-right {
|
||||
width: 48%;
|
||||
margin-left: 4%;
|
||||
float: left;
|
||||
}
|
||||
}
|
||||
|
||||
.grid-left > p {
|
||||
@@ -31,6 +53,48 @@
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.clear {
|
||||
clear: both;
|
||||
margin-bottom: 20px;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.index-callout {
|
||||
margin-right: .5rem;
|
||||
}
|
||||
|
||||
/* make code in headers not bold */
|
||||
h4 code {
|
||||
font-weight: normal;
|
||||
}
|
||||
|
||||
/* Hide config vars from sidebar, toc and move the border on mobile case their hidden */
|
||||
.md-nav.md-nav--secondary .md-nav__item .md-nav__link[href*="PAPERLESS_"],
|
||||
.md-nav.md-nav--secondary .md-nav__item .md-nav__link[href*="USERMAP_"] {
|
||||
display: none;
|
||||
}
|
||||
|
||||
@media screen and (max-width: 76.1875em) {
|
||||
.md-nav--primary .md-nav__item {
|
||||
border-top: none;
|
||||
}
|
||||
|
||||
.md-nav--primary .md-nav__link {
|
||||
border-top: .05rem solid var(--md-default-fg-color--lightest);
|
||||
}
|
||||
}
|
||||
|
||||
/* Show search shortcut key */
|
||||
[data-md-toggle="search"]:not(:checked) ~ .md-header .md-search__form::after {
|
||||
position: absolute;
|
||||
top: .3rem;
|
||||
right: .3rem;
|
||||
display: block;
|
||||
padding: .1rem .4rem;
|
||||
color: var(--md-default-fg-color--lighter);
|
||||
font-weight: bold;
|
||||
font-size: .8rem;
|
||||
border: .05rem solid var(--md-default-fg-color--lighter);
|
||||
border-radius: .1rem;
|
||||
content: "/";
|
||||
}
|
||||
|
Before Width: | Height: | Size: 661 KiB After Width: | Height: | Size: 1.8 MiB |
Before Width: | Height: | Size: 457 KiB After Width: | Height: | Size: 501 KiB |
BIN
docs/assets/screenshots/custom_field1.png
Normal file
After Width: | Height: | Size: 21 KiB |
BIN
docs/assets/screenshots/custom_field2.png
Normal file
After Width: | Height: | Size: 2.2 MiB |
Before Width: | Height: | Size: 436 KiB After Width: | Height: | Size: 644 KiB |
Before Width: | Height: | Size: 462 KiB After Width: | Height: | Size: 667 KiB |
Before Width: | Height: | Size: 608 KiB After Width: | Height: | Size: 1003 KiB |
Before Width: | Height: | Size: 698 KiB After Width: | Height: | Size: 1.7 MiB |
BIN
docs/assets/screenshots/documents-smallcards-slimsidebar.png
Normal file
After Width: | Height: | Size: 2.1 MiB |
Before Width: | Height: | Size: 706 KiB After Width: | Height: | Size: 1.8 MiB |
Before Width: | Height: | Size: 480 KiB After Width: | Height: | Size: 925 KiB |
Before Width: | Height: | Size: 680 KiB After Width: | Height: | Size: 1.7 MiB |
Before Width: | Height: | Size: 686 KiB After Width: | Height: | Size: 1.8 MiB |
Before Width: | Height: | Size: 848 KiB After Width: | Height: | Size: 2.3 MiB |
Before Width: | Height: | Size: 703 KiB After Width: | Height: | Size: 726 KiB |
Before Width: | Height: | Size: 76 KiB After Width: | Height: | Size: 169 KiB |
Before Width: | Height: | Size: 388 KiB |
BIN
docs/assets/screenshots/mobile1.png
Normal file
After Width: | Height: | Size: 432 KiB |
BIN
docs/assets/screenshots/mobile2.png
Normal file
After Width: | Height: | Size: 280 KiB |
BIN
docs/assets/screenshots/mobile3.png
Normal file
After Width: | Height: | Size: 246 KiB |
BIN
docs/assets/screenshots/new-correspondent.png
Normal file
After Width: | Height: | Size: 27 KiB |
BIN
docs/assets/screenshots/new-document_type.png
Normal file
After Width: | Height: | Size: 29 KiB |
BIN
docs/assets/screenshots/new-storage_path.png
Normal file
After Width: | Height: | Size: 48 KiB |
Before Width: | Height: | Size: 26 KiB After Width: | Height: | Size: 45 KiB |
BIN
docs/assets/screenshots/permissions_document.png
Normal file
After Width: | Height: | Size: 559 KiB |
BIN
docs/assets/screenshots/permissions_global.png
Normal file
After Width: | Height: | Size: 116 KiB |
Before Width: | Height: | Size: 54 KiB After Width: | Height: | Size: 87 KiB |
Before Width: | Height: | Size: 517 KiB After Width: | Height: | Size: 792 KiB |
BIN
docs/assets/screenshots/workflow.png
Normal file
After Width: | Height: | Size: 137 KiB |
1981
docs/changelog.md
@@ -9,7 +9,7 @@ following way:
|
||||
- `main` always represents the latest release and will only see
|
||||
changes when a new release is made.
|
||||
- `dev` contains the code that will be in the next release.
|
||||
- `feature-X` contain bigger changes that will be in some release, but
|
||||
- `feature-X` contains bigger changes that will be in some release, but
|
||||
not necessarily the next one.
|
||||
|
||||
When making functional changes to Paperless-ngx, _always_ make your changes
|
||||
@@ -58,10 +58,10 @@ first-time setup.
|
||||
|
||||
!!! note
|
||||
|
||||
Every command is executed directly from the root folder of the project unless specified otherwise.
|
||||
Every command is executed directly from the root folder of the project unless specified otherwise.
|
||||
|
||||
1. Install prerequisites + pipenv as mentioned in
|
||||
[Bare metal route](/setup#bare_metal).
|
||||
[Bare metal route](setup.md#bare_metal).
|
||||
|
||||
2. Copy `paperless.conf.example` to `paperless.conf` and enable debug
|
||||
mode within the file via `PAPERLESS_DEBUG=true`.
|
||||
@@ -119,7 +119,9 @@ first-time setup.
|
||||
|
||||
## Back end development
|
||||
|
||||
The back end is a [Django](https://www.djangoproject.com/) application. [PyCharm](https://www.jetbrains.com/de-de/pycharm/) as well as [Visual Studio Code](https://code.visualstudio.com) work well for development, but you can use whatever you want.
|
||||
The back end is a [Django](https://www.djangoproject.com/) application.
|
||||
[PyCharm](https://www.jetbrains.com/de-de/pycharm/) as well as [Visual Studio Code](https://code.visualstudio.com)
|
||||
work well for development, but you can use whatever you want.
|
||||
|
||||
Configure the IDE to use the `src/`-folder as the base source folder.
|
||||
Configure the following launch configurations in your IDE:
|
||||
@@ -138,7 +140,10 @@ $ python3 manage.py runserver & \
|
||||
celery --app paperless worker -l DEBUG
|
||||
```
|
||||
|
||||
You might need the front end to test your back end code. This assumes that you have AngularJS installed on your system. Go to the [Front end development](#front-end-development) section for further details. To build the front end once use this commmand:
|
||||
You might need the front end to test your back end code.
|
||||
This assumes that you have AngularJS installed on your system.
|
||||
Go to the [Front end development](#front-end-development) section for further details.
|
||||
To build the front end once use this command:
|
||||
|
||||
```bash
|
||||
# src-ui/
|
||||
@@ -172,69 +177,69 @@ The front end is built using AngularJS. In order to get started, you need Node.j
|
||||
|
||||
The following commands are all performed in the `src-ui`-directory. You will need a running back end (including an active session) to connect to the back end API. To spin it up refer to the commands under the section [above](#back-end-development).
|
||||
|
||||
1. Install the Angular CLI. You might need sudo privileges
|
||||
to perform this command:
|
||||
1. Install the Angular CLI. You might need sudo privileges to perform this command:
|
||||
|
||||
```bash
|
||||
$ npm install -g @angular/cli
|
||||
```
|
||||
```bash
|
||||
$ npm install -g @angular/cli
|
||||
```
|
||||
|
||||
2. Make sure that it's on your path.
|
||||
2. Make sure that it's on your path.
|
||||
|
||||
3. Install all neccessary modules:
|
||||
3. Install all necessary modules:
|
||||
|
||||
```bash
|
||||
$ npm install
|
||||
```
|
||||
```bash
|
||||
$ npm install
|
||||
```
|
||||
|
||||
4. You can launch a development server by running:
|
||||
4. You can launch a development server by running:
|
||||
|
||||
```bash
|
||||
$ ng serve
|
||||
```
|
||||
```bash
|
||||
$ ng serve
|
||||
```
|
||||
|
||||
This will automatically update whenever you save. However, in-place
|
||||
compilation might fail on syntax errors, in which case you need to
|
||||
restart it.
|
||||
This will automatically update whenever you save. However, in-place
|
||||
compilation might fail on syntax errors, in which case you need to
|
||||
restart it.
|
||||
|
||||
By default, the development server is available on `http://localhost:4200/` and is configured to access the API at
|
||||
`http://localhost:8000/api/`, which is the default of the backend. If you enabled `DEBUG` on the back end, several security overrides for allowed hosts, CORS and X-Frame-Options are in place so that the front end behaves exactly as in production.
|
||||
By default, the development server is available on `http://localhost:4200/` and is configured to access the API at
|
||||
`http://localhost:8000/api/`, which is the default of the backend. If you enabled `DEBUG` on the back end, several security overrides for allowed hosts, CORS and X-Frame-Options are in place so that the front end behaves exactly as in production.
|
||||
|
||||
### Testing and code style
|
||||
|
||||
- The front end code (.ts, .html, .scss) use `prettier` for code
|
||||
formatting via the Git `pre-commit` hooks which run automatically on
|
||||
commit. See [above](#code-formatting-with-pre-commit-hooks) for installation instructions. You can also run this via the CLI with a
|
||||
command such as
|
||||
The front end code (.ts, .html, .scss) use `prettier` for code
|
||||
formatting via the Git `pre-commit` hooks which run automatically on
|
||||
commit. See [above](#code-formatting-with-pre-commit-hooks) for installation instructions. You can also run this via the CLI with a
|
||||
command such as
|
||||
|
||||
```bash
|
||||
$ git ls-files -- '*.ts' | xargs pre-commit run prettier --files
|
||||
```
|
||||
```bash
|
||||
$ git ls-files -- '*.ts' | xargs pre-commit run prettier --files
|
||||
```
|
||||
|
||||
- Front end testing uses jest and cypress. There is currently a need
|
||||
for significantly more front end tests. Unit tests and e2e tests,
|
||||
respectively, can be run non-interactively with:
|
||||
Front end testing uses Jest and Playwright. Unit tests and e2e tests,
|
||||
respectively, can be run non-interactively with:
|
||||
|
||||
```bash
|
||||
$ ng test
|
||||
$ npm run e2e:ci
|
||||
```
|
||||
```bash
|
||||
$ ng test
|
||||
$ npx playwright test
|
||||
```
|
||||
|
||||
- Cypress also includes a UI which can be run with:
|
||||
Playwright also includes a UI which can be run with:
|
||||
|
||||
```bash
|
||||
$ ./node_modules/.bin/cypress open
|
||||
```
|
||||
```bash
|
||||
$ npx playwright test --ui
|
||||
```
|
||||
|
||||
- In order to build the front end and serve it as part of Django, execute:
|
||||
### Building the frontend
|
||||
|
||||
```bash
|
||||
$ ng build --configuration production
|
||||
```
|
||||
In order to build the front end and serve it as part of Django, execute:
|
||||
|
||||
This will build the front end and put it in a location from which the
|
||||
Django server will serve it as static content. This way, you can verify
|
||||
that authentication is working.
|
||||
```bash
|
||||
$ ng build --configuration production
|
||||
```
|
||||
|
||||
This will build the front end and put it in a location from which the
|
||||
Django server will serve it as static content. This way, you can verify
|
||||
that authentication is working.
|
||||
|
||||
## Localization
|
||||
|
||||
@@ -251,7 +256,7 @@ these parts have to be translated separately.
|
||||
- The translated strings need to be placed in the
|
||||
`src-ui/src/locale/` folder.
|
||||
- In order to extract added or changed strings from the source files,
|
||||
call `ng xi18n --ivy`.
|
||||
call `ng extract-i18n`.
|
||||
|
||||
Adding new languages requires adding the translated files in the
|
||||
`src-ui/src/locale/` folder and adjusting a couple files.
|
||||
@@ -272,27 +277,17 @@ Adding new languages requires adding the translated files in the
|
||||
}
|
||||
```
|
||||
|
||||
2. Add the language to the available options in
|
||||
2. Add the language to the `LANGUAGE_OPTIONS` array in
|
||||
`src-ui/src/app/services/settings.service.ts`:
|
||||
|
||||
```typescript
|
||||
getLanguageOptions(): LanguageOption[] {
|
||||
return [
|
||||
{code: "en-us", name: $localize`English (US)`, englishName: "English (US)", dateInputFormat: "mm/dd/yyyy"},
|
||||
{code: "en-gb", name: $localize`English (GB)`, englishName: "English (GB)", dateInputFormat: "dd/mm/yyyy"},
|
||||
{code: "de", name: $localize`German`, englishName: "German", dateInputFormat: "dd.mm.yyyy"},
|
||||
{code: "nl", name: $localize`Dutch`, englishName: "Dutch", dateInputFormat: "dd-mm-yyyy"},
|
||||
{code: "fr", name: $localize`French`, englishName: "French", dateInputFormat: "dd/mm/yyyy"},
|
||||
{code: "pt-br", name: $localize`Portuguese (Brazil)`, englishName: "Portuguese (Brazil)", dateInputFormat: "dd/mm/yyyy"}
|
||||
// Add your new language here
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
`dateInputFormat` is a special string that defines the behavior of
|
||||
the date input fields and absolutely needs to contain "dd", "mm"
|
||||
and "yyyy".
|
||||
|
||||
```
|
||||
|
||||
3. Import and register the Angular data for this locale in
|
||||
`src-ui/src/app/app.module.ts`:
|
||||
|
||||
@@ -357,7 +352,7 @@ If you want to build the documentation locally, this is how you do it:
|
||||
|
||||
3. Serve the documentation. This will spin up a
|
||||
copy of the documentation at http://127.0.0.1:8000
|
||||
that will automatically refresh everytime you change
|
||||
that will automatically refresh every time you change
|
||||
something.
|
||||
|
||||
```bash
|
||||
@@ -369,13 +364,10 @@ If you want to build the documentation locally, this is how you do it:
|
||||
The docker image is primarily built by the GitHub actions workflow, but
|
||||
it can be faster when developing to build and tag an image locally.
|
||||
|
||||
To provide the build arguments automatically, build the image using the
|
||||
helper script `build-docker-image.sh`.
|
||||
Building the image works as with any image:
|
||||
|
||||
Building the docker image from source:
|
||||
|
||||
```bash
|
||||
./build-docker-image.sh Dockerfile -t <your-tag>
|
||||
```
|
||||
docker build --file Dockerfile --tag paperless:local --progress simple .
|
||||
```
|
||||
|
||||
## Extending Paperless-ngx
|
||||
@@ -393,7 +385,7 @@ responsible for:
|
||||
- Retrieving the content from the original
|
||||
- Creating a thumbnail
|
||||
- _optional:_ Retrieving a created date from the original
|
||||
- _optional:_ Creainge an archived document from the original
|
||||
- _optional:_ Creating an archived document from the original
|
||||
|
||||
Custom parsers can be added to Paperless-ngx to support more file types. In
|
||||
order to do that, you need to write the parser itself and announce its
|
||||
|
45
docs/faq.md
@@ -3,15 +3,16 @@
|
||||
## _What's the general plan for Paperless-ngx?_
|
||||
|
||||
**A:** While Paperless-ngx is already considered largely
|
||||
"feature-complete" it is a community-driven project and development
|
||||
will be guided in this way. New features can be submitted via GitHub
|
||||
discussions and "up-voted" by the community but this is not a
|
||||
guarantee the feature will be implemented. This project will always be
|
||||
"feature-complete", it is a community-driven project and development
|
||||
will be guided in this way. New features can be submitted via
|
||||
[GitHub discussions](https://github.com/paperless-ngx/paperless-ngx/discussions)
|
||||
and "up-voted" by the community, but this is not a
|
||||
guarantee that the feature will be implemented. This project will always be
|
||||
open to collaboration in the form of PRs, ideas etc.
|
||||
|
||||
## _I'm using docker. Where are my documents?_
|
||||
|
||||
**A:** Your documents are stored inside the docker volume
|
||||
**A:** By default, your documents are stored inside the docker volume
|
||||
`paperless_media`. Docker manages this volume automatically for you. It
|
||||
is a persistent storage and will persist as long as you don't
|
||||
explicitly delete it. The actual location depends on your host operating
|
||||
@@ -27,6 +28,12 @@ system. On Linux, chances are high that this location is
|
||||
files around manually. This folder is meant to be entirely managed by
|
||||
docker and paperless.
|
||||
|
||||
!!! note
|
||||
|
||||
Files consumed from the consumption directory are re-created inside
|
||||
this media directory and are removed from the consumption directory
|
||||
itself.
|
||||
|
||||
## Let's say I want to switch tools in a year. Can I easily move to other systems?
|
||||
|
||||
**A:** Your documents are stored as plain files inside the media folder.
|
||||
@@ -39,8 +46,8 @@ elsewhere. Here are a couple notes about that.
|
||||
- By default, paperless uses the internal ID of each document as its
|
||||
filename. This might not be very convenient for export. However, you
|
||||
can adjust the way files are stored in paperless by
|
||||
[configuring the filename format](/advanced_usage#file-name-handling).
|
||||
- [The exporter](/administration#exporter) is
|
||||
[configuring the filename format](advanced_usage.md#file-name-handling).
|
||||
- [The exporter](administration.md#exporter) is
|
||||
another easy way to get your files out of paperless with reasonable
|
||||
file names.
|
||||
|
||||
@@ -52,7 +59,7 @@ elsewhere. Here are a couple notes about that.
|
||||
WebP images are processed with OCR and converted into PDF documents.
|
||||
- Plain text documents are supported as well and are added verbatim to
|
||||
paperless.
|
||||
- With the optional Tika integration enabled (see [Tika configuration](/configuration#tika),
|
||||
- With the optional Tika integration enabled (see [Tika configuration](https://docs.paperless-ngx.com/configuration#tika)),
|
||||
Paperless also supports various Office documents (.docx, .doc, odt,
|
||||
.ppt, .pptx, .odp, .xls, .xlsx, .ods).
|
||||
|
||||
@@ -71,20 +78,27 @@ has to do much less work to serve the data.
|
||||
!!! note
|
||||
|
||||
You can adjust some of the settings so that paperless uses less
|
||||
processing power. See [setup](/setup#less-powerful-devices) for details.
|
||||
processing power. See [setup](setup.md#less-powerful-devices) for details.
|
||||
|
||||
## _How do I install paperless-ngx on Raspberry Pi?_
|
||||
|
||||
**A:** Docker images are available for armv7 and arm64 hardware, so just
|
||||
follow the docker-compose instructions. Apart from more required disk
|
||||
**A:** Docker images are available for arm64 hardware, so just
|
||||
follow the [Docker Compose instructions](https://docs.paperless-ngx.com/setup/#installation). Apart from more required disk
|
||||
space compared to a bare metal installation, docker comes with close to
|
||||
zero overhead, even on Raspberry Pi.
|
||||
|
||||
If you decide to got with the bare metal route, be aware that some of
|
||||
If you decide to go with the bare metal route, be aware that some of
|
||||
the python requirements do not have precompiled packages for ARM /
|
||||
ARM64. Installation of these will require additional development
|
||||
libraries and compilation will take a long time.
|
||||
|
||||
!!! note
|
||||
|
||||
For ARMv7 (32-bit) systems, paperless may still function, but it could require
|
||||
modifications to the Dockerfile (if using Docker) or additional
|
||||
tools for installing bare metal. It is suggested to upgrade to arm64
|
||||
instead.
|
||||
|
||||
## _How do I run this on Unraid?_
|
||||
|
||||
**A:** Paperless-ngx is available as [community
|
||||
@@ -96,14 +110,11 @@ Fahrer](https://github.com/Tooa) created a container template for that.
|
||||
**A:** I honestly don't know! As for all other devices that might be
|
||||
able to run paperless, you're a bit on your own. If you can't run the
|
||||
docker image, the documentation has instructions for bare metal
|
||||
installs. I'm running paperless on an i3 processor from 2015 or so.
|
||||
This is also what I use to test new releases with. Apart from that, I
|
||||
also have a Raspberry Pi, which I occasionally build the image on and
|
||||
see if it works.
|
||||
installs.
|
||||
|
||||
## _How do I proxy this with NGINX?_
|
||||
|
||||
**A:** See [here](/setup#nginx).
|
||||
**A:** See [the wiki](https://github.com/paperless-ngx/paperless-ngx/wiki/Using-a-Reverse-Proxy-with-Paperless-ngx#nginx).
|
||||
|
||||
## _How do I get WebSocket support with Apache mod_wsgi_?
|
||||
|
||||
|
198
docs/index.md
@@ -5,9 +5,16 @@
|
||||
**Paperless-ngx** is a _community-supported_ open-source document management system that transforms your
|
||||
physical documents into a searchable online archive so you can keep, well, _less paper_.
|
||||
|
||||
[Get started](/setup){ .md-button .md-button--primary .index-callout }
|
||||
[Get started](setup.md){ .md-button .md-button--primary .index-callout }
|
||||
[Demo](https://demo.paperless-ngx.com){ .md-button .md-button--secondary target=\_blank }
|
||||
|
||||
<div style="display: flex; justify-content: end; margin-top: -1.5rem;">
|
||||
<a href="https://m.do.co/c/8d70b916d462" target="_blank">
|
||||
<img src="https://opensource.nyc3.cdn.digitaloceanspaces.com/attribution/assets/PoweredByDO/DO_Powered_by_Badge_white.svg#only-dark" class="no-lightbox" width="150px">
|
||||
<img src="https://opensource.nyc3.cdn.digitaloceanspaces.com/attribution/assets/PoweredByDO/DO_Powered_by_Badge_black.svg#only-light" class="no-lightbox" width="150px">
|
||||
</a>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<div class="grid-right" markdown>
|
||||
{.index-screenshot}
|
||||
@@ -15,103 +22,162 @@ physical documents into a searchable online archive so you can keep, well, _less
|
||||
</div>
|
||||
<div class="clear"></div>
|
||||
|
||||
## Why This Exists
|
||||
## Features
|
||||
|
||||
Paper is a nightmare. Environmental issues aside, there's no excuse for
|
||||
it in the 21st century. It takes up space, collects dust, doesn't
|
||||
support any form of a search feature, indexing is tedious, it's heavy
|
||||
and prone to damage & loss.
|
||||
- **Organize and index** your scanned documents with tags, correspondents, types, and more.
|
||||
- _Your_ data is stored locally on _your_ server and is never transmitted or shared in any way.
|
||||
- Performs **OCR** on your documents, adding searchable and selectable text, even to documents scanned with only images.
|
||||
- Utilizes the open-source Tesseract engine to recognize more than 100 languages.
|
||||
- Documents are saved as PDF/A format which is designed for long term storage, alongside the unaltered originals.
|
||||
- Uses machine-learning to automatically add tags, correspondents and document types to your documents.
|
||||
- Supports PDF documents, images, plain text files, Office documents (Word, Excel, Powerpoint, and LibreOffice equivalents)[^1] and more.
|
||||
- Paperless stores your documents plain on disk. Filenames and folders are managed by paperless and their format can be configured freely with different configurations assigned to different documents.
|
||||
- **Beautiful, modern web application** that features:
|
||||
- Customizable dashboard with statistics.
|
||||
- Filtering by tags, correspondents, types, and more.
|
||||
- Bulk editing of tags, correspondents, types and more.
|
||||
- Drag-and-drop uploading of documents throughout the app.
|
||||
- Customizable views can be saved and displayed on the dashboard and / or sidebar.
|
||||
- Support for custom fields of various data types.
|
||||
- Shareable public links with optional expiration.
|
||||
- **Full text search** helps you find what you need:
|
||||
- Auto completion suggests relevant words from your documents.
|
||||
- Results are sorted by relevance to your search query.
|
||||
- Highlighting shows you which parts of the document matched the query.
|
||||
- Searching for similar documents ("More like this")
|
||||
- **Email processing**[^1]: import documents from your email accounts:
|
||||
- Configure multiple accounts and rules for each account.
|
||||
- After processing, paperless can perform actions on the messages such as marking as read, deleting and more.
|
||||
- A built-in robust **multi-user permissions** system that supports 'global' permissions as well as per document or object.
|
||||
- A powerful workflow system that gives you even more control.
|
||||
- **Optimized** for multi core systems: Paperless-ngx consumes multiple documents in parallel.
|
||||
- The integrated sanity checker makes sure that your document archive is in good health.
|
||||
|
||||
This software is designed to make "going paperless" easier. No more worrying
|
||||
about finding stuff again, feed documents right from the post box into
|
||||
the scanner and then shred them. Perhaps you might find it useful too.
|
||||
[^1]: Office document and email consumption support is optional and provided by Apache Tika (see [configuration](https://docs.paperless-ngx.com/configuration/#tika))
|
||||
|
||||
## Paperless, a history
|
||||
|
||||
Paperless is a simple Django application running in two parts: a
|
||||
_Consumer_ (the thing that does the indexing) and the _Web server_ (the
|
||||
part that lets you search & download already-indexed documents). If you
|
||||
want to learn more about its functions keep on reading after the
|
||||
installation section.
|
||||
Paperless-ngx is the official successor to the original [Paperless](https://github.com/the-paperless-project/paperless) & [Paperless-ng](https://github.com/jonaswinkler/paperless-ng) projects and is designed to distribute the responsibility of advancing and supporting the project among a team of people. [Consider joining us!](https://github.com/paperless-ngx/paperless-ngx#community-support)
|
||||
|
||||
Paperless-ngx is a document management system that transforms your
|
||||
physical documents into a searchable online archive so you can keep,
|
||||
well, _less paper_.
|
||||
|
||||
Paperless-ngx forked from paperless-ng to continue the great work and
|
||||
distribute responsibility of supporting and advancing the project among
|
||||
a team of people.
|
||||
|
||||
NG stands for both Angular (the framework used for the Frontend) and
|
||||
next-gen. Publishing this project under a different name also avoids
|
||||
confusion between paperless and paperless-ngx.
|
||||
|
||||
If you want to learn about what's different in paperless-ngx from
|
||||
Paperless, check out these resources in the documentation:
|
||||
|
||||
- [Some screenshots](#screenshots) of the new UI are available.
|
||||
- Read [this section](/advanced_usage#automatic-matching) if you want to learn about how paperless automates all
|
||||
tagging using machine learning.
|
||||
- Paperless now comes with a [proper email consumer](/usage#usage-email) that's fully tested and production ready.
|
||||
- Paperless creates searchable PDF/A documents from whatever you put into the consumption directory. This means
|
||||
that you can select text in image-only documents coming from your scanner.
|
||||
- See [this note](/administration#encryption) about GnuPG encryption in paperless-ngx.
|
||||
- Paperless is now integrated with a
|
||||
[task processing queue](/setup#task_processor) that tells you at a glance when and why something is not working.
|
||||
- The [changelog](/changelog) contains a detailed list of all changes in paperless-ngx.
|
||||
Further discussion of the transition between these projects can be found at
|
||||
[ng#1599](https://github.com/jonaswinkler/paperless-ng/issues/1599) and [ng#1632](https://github.com/jonaswinkler/paperless-ng/issues/1632).
|
||||
|
||||
## Screenshots
|
||||
|
||||
This is what Paperless-ngx looks like.
|
||||
Paperless-ngx aims to be as nice to use as it is useful. Check out some screenshots below.
|
||||
|
||||
The dashboard shows customizable views on your document and allows
|
||||
document uploads:
|
||||
<div class="grid-flipped-left" markdown>
|
||||

|
||||
</div>
|
||||
<div class="grid-flipped-right" markdown>
|
||||
The dashboard shows saved views which can be sorted. Documents can be uploaded with the button or dropped anywhere in the application.
|
||||
</div>
|
||||
<div class="clear"></div>
|
||||
|
||||
[](assets/screenshots/dashboard.png)
|
||||
The document list provides three different styles to browse your documents.
|
||||
|
||||
The document list provides three different styles to scroll through your
|
||||
documents:
|
||||
{: style="width:32%"}
|
||||
{: style="width:32%"}
|
||||
{: style="width:32%"}
|
||||
|
||||
[](assets/screenshots/documents-table.png)
|
||||
<div class="clear"></div>
|
||||
|
||||
[](assets/screenshots/documents-smallcards.png)
|
||||
<div class="grid-left" markdown>
|
||||
Use the 'slim' sidebar to focus on your docs and minimize the UI.
|
||||
</div>
|
||||
<div class="grid-right" markdown>
|
||||

|
||||
</div>
|
||||
<div class="clear"></div>
|
||||
|
||||
[](assets/screenshots/documents-largecards.png)
|
||||
Of course, Paperless-ngx also supports dark mode:
|
||||
|
||||
Paperless-ngx also supports dark mode:
|
||||

|
||||
|
||||
[](assets/screenshots/documents-smallcards-dark.png)
|
||||
<div class="clear"></div>
|
||||
|
||||
Extensive filtering mechanisms:
|
||||
<div class="grid-left" markdown>
|
||||
Quickly find documents with extensive filtering mechanisms.
|
||||
</div>
|
||||
<div class="grid-right" markdown>
|
||||

|
||||
</div>
|
||||
<div class="clear"></div>
|
||||
<div class="grid-left" markdown>
|
||||
And perform bulk edit operations to set tags, correspondents, etc. as well as permissions.
|
||||
</div>
|
||||
<div class="grid-right" markdown>
|
||||

|
||||
</div>
|
||||
<div class="clear"></div>
|
||||
|
||||
[](assets/screenshots/documents-filter.png)
|
||||
Side-by-side editing of documents.
|
||||
|
||||
Bulk editing of document tags, correspondents, etc.:
|
||||

|
||||
|
||||
[](assets/screenshots/bulk-edit.png)
|
||||
<div class="grid-left" markdown>
|
||||
Support for custom fields.
|
||||
|
||||
Side-by-side editing of documents:
|
||||

|
||||
|
||||
[](assets/screenshots/editing.png)
|
||||
</div>
|
||||
<div class="grid-right" markdown>
|
||||

|
||||
</div>
|
||||
<div class="clear"></div>
|
||||
|
||||
Tag editing. This looks about the same for correspondents and document
|
||||
types.
|
||||
<div class="grid-left" markdown>
|
||||
A robust permissions system with support for 'global' and document / object permissions.
|
||||
|
||||
[](assets/screenshots/new-tag.png)
|
||||

|
||||
|
||||
Searching provides auto complete and highlights the results.
|
||||
</div>
|
||||
<div class="grid-right" markdown>
|
||||

|
||||
</div>
|
||||
<div class="clear"></div>
|
||||
|
||||
[](assets/screenshots/search-preview.png)
|
||||
<div class="grid-left" markdown>
|
||||
Searching provides auto complete and highlights the results.
|
||||
|
||||
[](assets/screenshots/search-results.png)
|
||||

|
||||
|
||||
Fancy mail filters!
|
||||
</div>
|
||||
<div class="grid-right" markdown>
|
||||

|
||||
</div>
|
||||
<div class="clear"></div>
|
||||
|
||||
[](assets/screenshots/mail-rules-edited.png)
|
||||
Tag, correspondent, document type and storage path editing.
|
||||
|
||||
{: style="width:21%; float: left"}
|
||||
{: style="width:21%; margin-left: 4%; float: left"}
|
||||
{: style="width:21%; margin-left: 4%; float: left"}
|
||||
{: style="width:21%; margin-left: 4%; float: left"}
|
||||
|
||||
<div class="clear"></div>
|
||||
|
||||
<div class="grid-half-left" markdown>
|
||||
Mail rules support various filters and actions for incoming e-mails.
|
||||
|
||||

|
||||
|
||||
</div>
|
||||
<div class="grid-half-right" markdown>
|
||||
Workflows provide finer control over the document pipeline and trigger actions.
|
||||
|
||||

|
||||
|
||||
</div>
|
||||
<div class="clear"></div>
|
||||
|
||||
<div class="clear"></div>
|
||||
|
||||
Mobile devices are supported.
|
||||
|
||||
[](assets/screenshots/mobile.png)
|
||||
{: style="width:32%"}
|
||||
{: style="width:32%"}
|
||||
{: style="width:32%"}
|
||||
|
||||
## Support
|
||||
|
||||
@@ -131,7 +197,7 @@ People interested in continuing the work on paperless-ngx are encouraged to reac
|
||||
|
||||
### Translation
|
||||
|
||||
Paperless-ngx is available in many languages that are coordinated on [Crowdin](https://crwd.in/paperless-ngx). If you want to help out by translating paperless-ngx into your language, please head over to https://crwd.in/paperless-ngx, and thank you!
|
||||
Paperless-ngx is available in many languages that are coordinated on [Crowdin](https://crwd.in/paperless-ngx). If you want to help out by translating paperless-ngx into your language, please head over to the [Paperless-ngx project at Crowdin](https://crwd.in/paperless-ngx), and thank you!
|
||||
|
||||
## Scanners & Software
|
||||
|
||||
|
224
docs/setup.md
@@ -25,14 +25,23 @@ necessary configuration files, pull the docker image, start paperless
|
||||
and create your user account. This script essentially performs all the
|
||||
steps described in [Docker setup](#docker_hub) automatically.
|
||||
|
||||
1. Make sure that docker and docker-compose are installed.
|
||||
1. Make sure that Docker and Docker Compose are installed.
|
||||
|
||||
!!! tip
|
||||
|
||||
See the Docker installation instructions at https://docs.docker.com/engine/install/
|
||||
|
||||
2. Download and run the installation script:
|
||||
|
||||
```shell-session
|
||||
$ bash -c "$(curl -L https://raw.githubusercontent.com/paperless-ngx/paperless-ngx/main/install-paperless-ngx.sh)"
|
||||
$ bash -c "$(curl --location --silent --show-error https://raw.githubusercontent.com/paperless-ngx/paperless-ngx/main/install-paperless-ngx.sh)"
|
||||
```
|
||||
|
||||
!!! note
|
||||
|
||||
macOS users will need to install e.g. [gnu-sed](https://formulae.brew.sh/formula/gnu-sed) with support
|
||||
for running as `sed`.
|
||||
|
||||
### From GHCR / Docker Hub {#docker_hub}
|
||||
|
||||
1. Login with your user and create a folder in your home-directory to have a place for your
|
||||
@@ -43,7 +52,7 @@ steps described in [Docker setup](#docker_hub) automatically.
|
||||
```
|
||||
|
||||
2. Go to the [/docker/compose directory on the project
|
||||
page](https://github.com/paperless-ngx/paperless-ngx/tree/master/docker/compose)
|
||||
page](https://github.com/paperless-ngx/paperless-ngx/tree/main/docker/compose)
|
||||
and download one of the `docker-compose.*.yml` files,
|
||||
depending on which database backend you want to use. Rename this
|
||||
file to `docker-compose.yml`. If you want to enable
|
||||
@@ -57,19 +66,19 @@ steps described in [Docker setup](#docker_hub) automatically.
|
||||
For new installations, it is recommended to use PostgreSQL as the
|
||||
database backend.
|
||||
|
||||
3. Install [Docker](https://www.docker.com/) and
|
||||
[docker-compose](https://docs.docker.com/compose/install/).
|
||||
3. Install [Docker](https://docs.docker.com/engine/install/) and
|
||||
[Docker Compose](https://docs.docker.com/compose/install/).
|
||||
|
||||
!!! warning
|
||||
|
||||
If you want to use the included `docker-compose.*.yml` file, you
|
||||
need to have at least Docker version **17.09.0** and docker-compose
|
||||
version **1.17.0**. To check do: `docker-compose -v` or `docker -v`
|
||||
need to have at least Docker version **17.09.0** and Docker Compose
|
||||
version **v2**. To check do: `docker compose version` or `docker -v`
|
||||
|
||||
See the [Docker installation guide](https://docs.docker.com/engine/install/) on how to install the current
|
||||
version of Docker for your operating system or Linux distribution of
|
||||
choice. To get the latest version of docker-compose, follow the
|
||||
[docker-compose installation guide](https://docs.docker.com/compose/install/linux/) if your package repository
|
||||
choice. To get the latest version of Docker Compose, follow the
|
||||
[Docker Compose installation guide](https://docs.docker.com/compose/install/linux/) if your package repository
|
||||
doesn't include it.
|
||||
|
||||
4. Modify `docker-compose.yml` to your preferences. You may want to
|
||||
@@ -87,7 +96,7 @@ steps described in [Docker setup](#docker_hub) automatically.
|
||||
- /home/jonaswinkler/paperless-inbox:/usr/src/paperless/consume
|
||||
```
|
||||
|
||||
Don't change the part after the colon or paperless wont find your
|
||||
Don't change the part after the colon or paperless won't find your
|
||||
documents.
|
||||
|
||||
You may also need to change the default port that the webserver will
|
||||
@@ -112,6 +121,10 @@ steps described in [Docker setup](#docker_hub) automatically.
|
||||
|
||||
**Rootless**
|
||||
|
||||
!!! warning
|
||||
|
||||
It is currently not possible to run the container rootless if additional languages are specified via `PAPERLESS_OCR_LANGUAGES`.
|
||||
|
||||
If you want to run Paperless as a rootless container, you will need
|
||||
to do the following in your `docker-compose.yml`:
|
||||
|
||||
@@ -119,7 +132,7 @@ steps described in [Docker setup](#docker_hub) automatically.
|
||||
user in the container. This value (`user_id` below), should be
|
||||
the same id that `USERMAP_UID` and `USERMAP_GID` are set to in
|
||||
the next step. See `USERMAP_UID` and `USERMAP_GID`
|
||||
[here](/configuration#docker).
|
||||
[here](configuration.md#docker).
|
||||
|
||||
Your entry for Paperless should contain something like:
|
||||
|
||||
@@ -143,12 +156,12 @@ steps described in [Docker setup](#docker_hub) automatically.
|
||||
!!! note
|
||||
|
||||
You can copy any setting from the file `paperless.conf.example` and
|
||||
paste it here. Have a look at [configuration](/configuration) to see what's available.
|
||||
paste it here. Have a look at [configuration](configuration.md) to see what's available.
|
||||
|
||||
!!! note
|
||||
|
||||
You can utilize Docker secrets for configuration settings by
|
||||
appending `_FILE` to configuration values. For example `PAPERLESS_DBUSER`
|
||||
appending `_FILE` to configuration values. For example [`PAPERLESS_DBUSER`](configuration.md#PAPERLESS_DBUSER)
|
||||
can be set using `PAPERLESS_DBUSER_FILE=/var/run/secrets/password.txt`.
|
||||
|
||||
!!! warning
|
||||
@@ -157,23 +170,30 @@ steps described in [Docker setup](#docker_hub) automatically.
|
||||
system notifications with `inotify`. When storing the consumption
|
||||
directory on such a file system, paperless will not pick up new
|
||||
files with the default configuration. You will need to use
|
||||
`PAPERLESS_CONSUMER_POLLING`, which will disable inotify. See
|
||||
[here](/configuration#polling).
|
||||
[`PAPERLESS_CONSUMER_POLLING`](configuration.md#PAPERLESS_CONSUMER_POLLING), which will disable inotify. See
|
||||
[here](configuration.md#polling).
|
||||
|
||||
6. Run `docker-compose pull`, followed by `docker-compose up -d`. This
|
||||
will pull the image, create and start the necessary containers.
|
||||
6. Run `docker compose pull`. This will pull the image.
|
||||
|
||||
7. To be able to login, you will need a super user. To create it,
|
||||
execute the following command:
|
||||
|
||||
```shell-session
|
||||
$ docker-compose run --rm webserver createsuperuser
|
||||
$ docker compose run --rm webserver createsuperuser
|
||||
```
|
||||
|
||||
or using docker exec from within the container:
|
||||
|
||||
```shell-session
|
||||
$ python3 manage.py createsuperuser
|
||||
```
|
||||
|
||||
This will prompt you to set a username, an optional e-mail address
|
||||
and finally a password (at least 8 characters).
|
||||
|
||||
8. The default `docker-compose.yml` exports the webserver on your local
|
||||
8. Run `docker compose up -d`. This will create and start the necessary containers.
|
||||
|
||||
9. The default `docker-compose.yml` exports the webserver on your local
|
||||
port
|
||||
|
||||
8000\. If you did not change this, you should now be able to visit
|
||||
@@ -189,7 +209,7 @@ steps described in [Docker setup](#docker_hub) automatically.
|
||||
git clone https://github.com/paperless-ngx/paperless-ngx
|
||||
```
|
||||
|
||||
The master branch always reflects the latest stable version.
|
||||
The main branch always reflects the latest stable version.
|
||||
|
||||
2. Copy one of the `docker/compose/docker-compose.*.yml` to
|
||||
`docker-compose.yml` in the root folder, depending on which database
|
||||
@@ -197,39 +217,27 @@ steps described in [Docker setup](#docker_hub) automatically.
|
||||
root as well.
|
||||
|
||||
3. In the `docker-compose.yml` file, find the line that instructs
|
||||
docker-compose to pull the paperless image from Docker Hub:
|
||||
Docker Compose to pull the paperless image from Docker Hub:
|
||||
|
||||
```yaml
|
||||
webserver:
|
||||
image: ghcr.io/paperless-ngx/paperless-ngx:latest
|
||||
```
|
||||
|
||||
and replace it with a line that instructs docker-compose to build
|
||||
and replace it with a line that instructs Docker Compose to build
|
||||
the image from the current working directory instead:
|
||||
|
||||
```yaml
|
||||
webserver:
|
||||
build:
|
||||
context: .
|
||||
args:
|
||||
QPDF_VERSION: x.y.x
|
||||
PIKEPDF_VERSION: x.y.z
|
||||
PSYCOPG2_VERSION: x.y.z
|
||||
JBIG2ENC_VERSION: 0.29
|
||||
```
|
||||
|
||||
!!! note
|
||||
|
||||
You should match the build argument versions to the version for the
|
||||
release you have checked out. These are pre-built images with
|
||||
certain, more updated software. If you want to build these images
|
||||
your self, that is possible, but beyond the scope of these steps.
|
||||
|
||||
4. Follow steps 3 to 8 of [Docker Setup](#docker_hub). When asked to run
|
||||
`docker-compose pull` to pull the image, do
|
||||
`docker compose pull` to pull the image, do
|
||||
|
||||
```shell-session
|
||||
$ docker-compose build
|
||||
$ docker compose build
|
||||
```
|
||||
|
||||
instead to build the image.
|
||||
@@ -243,10 +251,11 @@ supported.
|
||||
|
||||
1. Install dependencies. Paperless requires the following packages.
|
||||
|
||||
- `python3` 3.8, 3.9
|
||||
- `python3` - 3.9 - 3.11 are supported
|
||||
- `python3-pip`
|
||||
- `python3-dev`
|
||||
- `default-libmysqlclient-dev` for MariaDB
|
||||
- `pkg-config` for mysqlclient (python dependency)
|
||||
- `fonts-liberation` for generating thumbnails for plain text
|
||||
files
|
||||
- `imagemagick` >= 6 for PDF conversion
|
||||
@@ -261,7 +270,7 @@ supported.
|
||||
Use this list for your preferred package management:
|
||||
|
||||
```
|
||||
python3 python3-pip python3-dev imagemagick fonts-liberation gnupg libpq-dev default-libmysqlclient-dev libmagic-dev mime-support libzbar0 poppler-utils
|
||||
python3 python3-pip python3-dev imagemagick fonts-liberation gnupg libpq-dev default-libmysqlclient-dev pkg-config libmagic-dev mime-support libzbar0 poppler-utils
|
||||
```
|
||||
|
||||
These dependencies are required for OCRmyPDF, which is used for text
|
||||
@@ -329,41 +338,45 @@ supported.
|
||||
home folder of the user you created before (`/opt/paperless`).
|
||||
|
||||
Optional: If you cloned the git repo, you will have to
|
||||
compile the frontend yourself, see [here](/development#front-end-development)
|
||||
compile the frontend yourself, see [here](development.md#front-end-development)
|
||||
and use the `build` step, not `serve`.
|
||||
|
||||
6. Configure paperless. See [configuration](/configuration) for details.
|
||||
6. Configure paperless. See [configuration](configuration.md) for details.
|
||||
Edit the included `paperless.conf` and adjust the settings to your
|
||||
needs. Required settings for getting
|
||||
paperless running are:
|
||||
|
||||
- `PAPERLESS_REDIS` should point to your redis server, such as
|
||||
- [`PAPERLESS_REDIS`](configuration.md#PAPERLESS_REDIS) should point to your redis server, such as
|
||||
<redis://localhost:6379>.
|
||||
- `PAPERLESS_DBENGINE` optional, and should be one of `postgres`,
|
||||
- [`PAPERLESS_DBENGINE`](configuration.md#PAPERLESS_DBENGINE) optional, and should be one of `postgres`,
|
||||
`mariadb`, or `sqlite`
|
||||
- `PAPERLESS_DBHOST` should be the hostname on which your
|
||||
- [`PAPERLESS_DBHOST`](configuration.md#PAPERLESS_DBHOST) should be the hostname on which your
|
||||
PostgreSQL server is running. Do not configure this to use
|
||||
SQLite instead. Also configure port, database name, user and
|
||||
password as necessary.
|
||||
- `PAPERLESS_CONSUMPTION_DIR` should point to a folder which
|
||||
- [`PAPERLESS_CONSUMPTION_DIR`](configuration.md#PAPERLESS_CONSUMPTION_DIR) should point to a folder which
|
||||
paperless should watch for documents. You might want to have
|
||||
this somewhere else. Likewise, `PAPERLESS_DATA_DIR` and
|
||||
`PAPERLESS_MEDIA_ROOT` define where paperless stores its data.
|
||||
this somewhere else. Likewise, [`PAPERLESS_DATA_DIR`](configuration.md#PAPERLESS_DATA_DIR) and
|
||||
[`PAPERLESS_MEDIA_ROOT`](configuration.md#PAPERLESS_MEDIA_ROOT) define where paperless stores its data.
|
||||
If you like, you can point both to the same directory.
|
||||
- `PAPERLESS_SECRET_KEY` should be a random sequence of
|
||||
- [`PAPERLESS_SECRET_KEY`](configuration.md#PAPERLESS_SECRET_KEY) should be a random sequence of
|
||||
characters. It's used for authentication. Failure to do so
|
||||
allows third parties to forge authentication credentials.
|
||||
- `PAPERLESS_URL` if you are behind a reverse proxy. This should
|
||||
- [`PAPERLESS_URL`](configuration.md#PAPERLESS_URL) if you are behind a reverse proxy. This should
|
||||
point to your domain. Please see
|
||||
[configuration](/configuration) for more
|
||||
[configuration](configuration.md) for more
|
||||
information.
|
||||
|
||||
Many more adjustments can be made to paperless, especially the OCR
|
||||
part. The following options are recommended for everyone:
|
||||
|
||||
- Set `PAPERLESS_OCR_LANGUAGE` to the language most of your
|
||||
- Set [`PAPERLESS_OCR_LANGUAGE`](configuration.md#PAPERLESS_OCR_LANGUAGE) to the language most of your
|
||||
documents are written in.
|
||||
- Set `PAPERLESS_TIME_ZONE` to your local time zone.
|
||||
- Set [`PAPERLESS_TIME_ZONE`](configuration.md#PAPERLESS_TIME_ZONE) to your local time zone.
|
||||
|
||||
!!! warning
|
||||
|
||||
Ensure your Redis instance [is secured](https://redis.io/docs/getting-started/#securing-redis).
|
||||
|
||||
7. Create the following directories if they are missing:
|
||||
|
||||
@@ -467,7 +480,7 @@ supported.
|
||||
in front of gunicorn instead.
|
||||
|
||||
For instructions on how to use nginx for that,
|
||||
[see the instructions below](/setup#nginx).
|
||||
[see the wiki](https://github.com/paperless-ngx/paperless-ngx/wiki/Using-a-Reverse-Proxy-with-Paperless-ngx#nginx).
|
||||
|
||||
!!! warning
|
||||
|
||||
@@ -505,7 +518,7 @@ supported.
|
||||
not available for most distributions.
|
||||
|
||||
15. Optional: If using the NLTK machine learning processing (see
|
||||
`PAPERLESS_ENABLE_NLTK` in [configuration](/configuration#software_tweaks) for details),
|
||||
[`PAPERLESS_ENABLE_NLTK`](configuration.md#PAPERLESS_ENABLE_NLTK) for details),
|
||||
download the NLTK data for the Snowball
|
||||
Stemmer, Stopwords and Punkt tokenizer to your
|
||||
`PAPERLESS_DATA_DIR/nltk`. Refer to the [NLTK
|
||||
@@ -536,14 +549,14 @@ to
|
||||
image: ghcr.io/paperless-ngx/paperless-ngx:latest
|
||||
```
|
||||
|
||||
and then run `docker-compose up -d` which will pull the new image
|
||||
and then run `docker compose up -d` which will pull the new image
|
||||
recreate the container. That's it!
|
||||
|
||||
Users who installed with the bare-metal route should also update their
|
||||
Git clone to point to `https://github.com/paperless-ngx/paperless-ngx`,
|
||||
e.g. using the command
|
||||
`git remote set-url origin https://github.com/paperless-ngx/paperless-ngx`
|
||||
and then pull the lastest version.
|
||||
and then pull the latest version.
|
||||
|
||||
## Migrating from Paperless
|
||||
|
||||
@@ -554,7 +567,7 @@ your setup depending on how you installed paperless.
|
||||
This setup describes how to update an existing paperless Docker
|
||||
installation. The important things to keep in mind are as follows:
|
||||
|
||||
- Read the [changelog](/changelog) and
|
||||
- Read the [changelog](changelog.md) and
|
||||
take note of breaking changes.
|
||||
- You should decide if you want to stick with SQLite or want to
|
||||
migrate your database to PostgreSQL. See [documentation](#sqlite_to_psql)
|
||||
@@ -565,7 +578,7 @@ installation. The important things to keep in mind are as follows:
|
||||
- The task scheduler of paperless, which is used to execute periodic
|
||||
tasks such as email checking and maintenance, requires a
|
||||
[redis](https://redis.io/) message broker instance. The
|
||||
docker-compose route takes care of that.
|
||||
Docker Compose route takes care of that.
|
||||
- The layout of the folder structure for your documents and data
|
||||
remains the same, so you can just plug your old docker volumes into
|
||||
paperless-ngx and expect it to find everything where it should be.
|
||||
@@ -576,7 +589,7 @@ Migration to paperless-ngx is then performed in a few simple steps:
|
||||
|
||||
```bash
|
||||
$ cd /path/to/current/paperless
|
||||
$ docker-compose down
|
||||
$ docker compose down
|
||||
```
|
||||
|
||||
2. Do a backup for two purposes: If something goes wrong, you still
|
||||
@@ -584,8 +597,8 @@ Migration to paperless-ngx is then performed in a few simple steps:
|
||||
switch back to paperless.
|
||||
|
||||
3. Download the latest release of paperless-ngx. You can either go with
|
||||
the docker-compose files from
|
||||
[here](https://github.com/paperless-ngx/paperless-ngx/tree/master/docker/compose)
|
||||
the Docker Compose files from
|
||||
[here](https://github.com/paperless-ngx/paperless-ngx/tree/main/docker/compose)
|
||||
or clone the repository to build the image yourself (see
|
||||
[above](#docker_build)). You can
|
||||
either replace your current paperless folder or put paperless-ngx in
|
||||
@@ -614,14 +627,14 @@ Migration to paperless-ngx is then performed in a few simple steps:
|
||||
See [Docker setup](#docker_hub) details on
|
||||
which edits are advised.
|
||||
|
||||
6. [Update paperless.](/administration#updating)
|
||||
6. [Update paperless.](administration.md#updating)
|
||||
|
||||
7. In order to find your existing documents with the new search
|
||||
feature, you need to invoke a one-time operation that will create
|
||||
the search index:
|
||||
|
||||
```shell-session
|
||||
$ docker-compose run --rm webserver document_index reindex
|
||||
$ docker compose run --rm webserver document_index reindex
|
||||
```
|
||||
|
||||
This will migrate your database and create the search index. After
|
||||
@@ -630,7 +643,7 @@ Migration to paperless-ngx is then performed in a few simple steps:
|
||||
8. Start paperless-ngx.
|
||||
|
||||
```bash
|
||||
$ docker-compose up -d
|
||||
$ docker compose up -d
|
||||
```
|
||||
|
||||
This will run paperless in the background and automatically start it
|
||||
@@ -653,28 +666,28 @@ commands as well.
|
||||
1. Stop and remove the paperless container
|
||||
2. If using an external database, stop the container
|
||||
3. Update Redis configuration
|
||||
a) If `REDIS_URL` is already set, change it to `PAPERLESS_REDIS`
|
||||
a) If `REDIS_URL` is already set, change it to [`PAPERLESS_REDIS`](configuration.md#PAPERLESS_REDIS)
|
||||
and continue to step 4.
|
||||
b) Otherwise, in the `docker-compose.yml` add a new service for
|
||||
Redis, following [the example compose
|
||||
files](https://github.com/paperless-ngx/paperless-ngx/tree/main/docker/compose)
|
||||
c) Set the environment variable `PAPERLESS_REDIS` so it points to
|
||||
c) Set the environment variable [`PAPERLESS_REDIS`](configuration.md#PAPERLESS_REDIS) so it points to
|
||||
the new Redis container
|
||||
4. Update user mapping
|
||||
a) If set, change the environment variable `PUID` to `USERMAP_UID`
|
||||
b) If set, change the environment variable `PGID` to `USERMAP_GID`
|
||||
5. Update configuration paths
|
||||
a) Set the environment variable `PAPERLESS_DATA_DIR` to `/config`
|
||||
a) Set the environment variable [`PAPERLESS_DATA_DIR`](configuration.md#PAPERLESS_DATA_DIR) to `/config`
|
||||
6. Update media paths
|
||||
a) Set the environment variable `PAPERLESS_MEDIA_ROOT` to
|
||||
a) Set the environment variable [`PAPERLESS_MEDIA_ROOT`](configuration.md#PAPERLESS_MEDIA_ROOT) to
|
||||
`/data/media`
|
||||
7. Update timezone
|
||||
a) Set the environment variable `PAPERLESS_TIME_ZONE` to the same
|
||||
a) Set the environment variable [`PAPERLESS_TIME_ZONE`](configuration.md#PAPERLESS_TIME_ZONE) to the same
|
||||
value as `TZ`
|
||||
8. Modify the `image:` to point to
|
||||
`ghcr.io/paperless-ngx/paperless-ngx:latest` or a specific version
|
||||
if preferred.
|
||||
9. Start the containers as before, using `docker-compose`.
|
||||
9. Start the containers as before, using `docker compose`.
|
||||
|
||||
## Moving data from SQLite to PostgreSQL or MySQL/MariaDB {#sqlite_to_psql}
|
||||
|
||||
@@ -701,7 +714,7 @@ below use PostgreSQL, but are applicable to MySQL/MariaDB with the
|
||||
!!! warning
|
||||
|
||||
MySQL is case insensitive by default, treating values like "Name" and
|
||||
"NAME" as identical. See [MySQL caveats](/advanced_usage#mysql-caveats) for details.
|
||||
"NAME" as identical. See [MySQL caveats](advanced_usage.md#mysql-caveats) for details.
|
||||
|
||||
!!! warning
|
||||
|
||||
@@ -722,7 +735,7 @@ below use PostgreSQL, but are applicable to MySQL/MariaDB with the
|
||||
file to `docker-compose.yml`. Remember to adjust the consumption
|
||||
directory, if necessary.
|
||||
b) Without docker, configure the database in your `paperless.conf`
|
||||
file. See [configuration](/configuration) for
|
||||
file. See [configuration](configuration.md) for
|
||||
details.
|
||||
|
||||
3. Open a shell and initialize the database:
|
||||
@@ -732,7 +745,7 @@ below use PostgreSQL, but are applicable to MySQL/MariaDB with the
|
||||
|
||||
``` shell-session
|
||||
$ cd /path/to/paperless
|
||||
$ docker-compose run --rm webserver /bin/bash
|
||||
$ docker compose run --rm webserver /bin/bash
|
||||
```
|
||||
|
||||
This will launch the container and initialize the PostgreSQL
|
||||
@@ -785,7 +798,7 @@ Execute this:
|
||||
|
||||
```shell-session
|
||||
$ cd /path/to/paperless
|
||||
$ docker-compose run --rm webserver migrate documents 0023
|
||||
$ docker compose run --rm webserver migrate documents 0023
|
||||
```
|
||||
|
||||
Or without docker:
|
||||
@@ -806,36 +819,36 @@ the Pi and configuring some options in paperless can help improve
|
||||
performance immensely:
|
||||
|
||||
- Stick with SQLite to save some resources.
|
||||
- Consider setting `PAPERLESS_OCR_PAGES` to 1, so that paperless will
|
||||
- Consider setting [`PAPERLESS_OCR_PAGES`](configuration.md#PAPERLESS_OCR_PAGES) to 1, so that paperless will
|
||||
only OCR the first page of your documents. In most cases, this page
|
||||
contains enough information to be able to find it.
|
||||
- `PAPERLESS_TASK_WORKERS` and `PAPERLESS_THREADS_PER_WORKER` are
|
||||
- [`PAPERLESS_TASK_WORKERS`](configuration.md#PAPERLESS_TASK_WORKERS) and [`PAPERLESS_THREADS_PER_WORKER`](configuration.md#PAPERLESS_THREADS_PER_WORKER) are
|
||||
configured to use all cores. The Raspberry Pi models 3 and up have 4
|
||||
cores, meaning that paperless will use 2 workers and 2 threads per
|
||||
worker. This may result in sluggish response times during
|
||||
consumption, so you might want to lower these settings (example: 2
|
||||
workers and 1 thread to always have some computing power left for
|
||||
other tasks).
|
||||
- Keep `PAPERLESS_OCR_MODE` at its default value `skip` and consider
|
||||
- Keep [`PAPERLESS_OCR_MODE`](configuration.md#PAPERLESS_OCR_MODE) at its default value `skip` and consider
|
||||
OCR'ing your documents before feeding them into paperless. Some
|
||||
scanners are able to do this! You might want to even specify
|
||||
`skip_noarchive` to skip archive file generation for already ocr'ed
|
||||
documents entirely.
|
||||
scanners are able to do this!
|
||||
- Set [`PAPERLESS_OCR_SKIP_ARCHIVE_FILE`](configuration.md#PAPERLESS_OCR_SKIP_ARCHIVE_FILE) to `with_text` to skip archive
|
||||
file generation for already ocr'ed documents, or `always` to skip it
|
||||
for all documents.
|
||||
- If you want to perform OCR on the device, consider using
|
||||
`PAPERLESS_OCR_CLEAN=none`. This will speed up OCR times and use
|
||||
less memory at the expense of slightly worse OCR results.
|
||||
- If using docker, consider setting `PAPERLESS_WEBSERVER_WORKERS` to
|
||||
1. This will save some memory.
|
||||
- Consider setting `PAPERLESS_ENABLE_NLTK` to false, to disable the
|
||||
- If using docker, consider setting [`PAPERLESS_WEBSERVER_WORKERS`](configuration.md#PAPERLESS_WEBSERVER_WORKERS) to 1. This will save some memory.
|
||||
- Consider setting [`PAPERLESS_ENABLE_NLTK`](configuration.md#PAPERLESS_ENABLE_NLTK) to false, to disable the
|
||||
more advanced language processing, which can take more memory and
|
||||
processing time.
|
||||
|
||||
For details, refer to [configuration](/configuration).
|
||||
For details, refer to [configuration](configuration.md).
|
||||
|
||||
!!! note
|
||||
|
||||
Updating the
|
||||
[automatic matching algorithm](/advanced_usage#automatic-matching) takes quite a bit of time. However, the update mechanism
|
||||
[automatic matching algorithm](advanced_usage.md#automatic-matching) takes quite a bit of time. However, the update mechanism
|
||||
checks if your data has changed before doing the heavy lifting. If you
|
||||
experience the algorithm taking too much cpu time, consider changing the
|
||||
schedule in the admin interface to daily. You can also manually invoke
|
||||
@@ -846,45 +859,8 @@ For details, refer to [configuration](/configuration).
|
||||
|
||||
# Using nginx as a reverse proxy {#nginx}
|
||||
|
||||
If you want to expose paperless to the internet, you should hide it
|
||||
behind a reverse proxy with SSL enabled.
|
||||
Please see [the wiki](https://github.com/paperless-ngx/paperless-ngx/wiki/Using-a-Reverse-Proxy-with-Paperless-ngx#nginx) for user-maintained documentation of using nginx with Paperless-ngx.
|
||||
|
||||
In addition to the usual configuration for SSL, the following
|
||||
configuration is required for paperless to operate:
|
||||
# Enhancing security {#security}
|
||||
|
||||
```nginx
|
||||
http {
|
||||
|
||||
# Adjust as required. This is the maximum size for file uploads.
|
||||
# The default value 1M might be a little too small.
|
||||
client_max_body_size 10M;
|
||||
|
||||
server {
|
||||
|
||||
location / {
|
||||
|
||||
# Adjust host and port as required.
|
||||
proxy_pass http://localhost:8000/;
|
||||
|
||||
# These configuration options are required for WebSockets to work.
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Upgrade $http_upgrade;
|
||||
proxy_set_header Connection "upgrade";
|
||||
|
||||
proxy_redirect off;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Host $server_name;
|
||||
add_header P3P 'CP=""'; # may not be required in all setups
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
The `PAPERLESS_URL` configuration variable is also required when using a
|
||||
reverse proxy. Please refer to the [hosting and security](/configuration#hosting-and-security) docs.
|
||||
|
||||
Also read
|
||||
[this](https://channels.readthedocs.io/en/stable/deploying.html#nginx-supervisor-ubuntu),
|
||||
towards the end of the section.
|
||||
Please see [the wiki](https://github.com/paperless-ngx/paperless-ngx/wiki/Using-Security-Tools-with-Paperless-ngx) for user-maintained documentation of how to configure security tools like Fail2ban with Paperless-ngx.
|
||||
|
@@ -6,7 +6,7 @@ Check for the following issues:
|
||||
|
||||
- Ensure that the directory you're putting your documents in is the
|
||||
folder paperless is watching. With docker, this setting is performed
|
||||
in the `docker-compose.yml` file. Without docker, look at the
|
||||
in the `docker-compose.yml` file. Without Docker, look at the
|
||||
`CONSUMPTION_DIR` setting. Don't adjust this setting if you're
|
||||
using docker.
|
||||
|
||||
@@ -46,8 +46,7 @@ run:
|
||||
If you notice that the consumer will only pickup files in the
|
||||
consumption directory at startup, but won't find any other files added
|
||||
later, you will need to enable filesystem polling with the configuration
|
||||
option `PAPERLESS_CONSUMER_POLLING`, see
|
||||
`[here](/configuration#polling).
|
||||
option [`PAPERLESS_CONSUMER_POLLING`](configuration.md#PAPERLESS_CONSUMER_POLLING).
|
||||
|
||||
This will disable listening to filesystem changes with inotify and
|
||||
paperless will manually check the consumption directory for changes
|
||||
@@ -121,7 +120,7 @@ Gotenberg raises this error.
|
||||
|
||||
You can increase the timeout by configuring a command flag for Gotenberg
|
||||
(see also [here](https://gotenberg.dev/docs/modules/api#properties)). If
|
||||
using docker-compose, this is achieved by the following configuration
|
||||
using Docker Compose, this is achieved by the following configuration
|
||||
change in the `docker-compose.yml` file:
|
||||
|
||||
```yaml
|
||||
@@ -139,13 +138,13 @@ command:
|
||||
You might encounter errors such as:
|
||||
|
||||
```shell-session
|
||||
The following error occured while consuming document.pdf: [Errno 13] Permission denied: '/usr/src/paperless/src/../consume/document.pdf'
|
||||
The following error occurred while consuming document.pdf: [Errno 13] Permission denied: '/usr/src/paperless/src/../consume/document.pdf'
|
||||
```
|
||||
|
||||
This happens when paperless does not have permission to delete files
|
||||
inside the consumption directory. Ensure that `USERMAP_UID` and
|
||||
`USERMAP_GID` are set to the user id and group id you use on the host
|
||||
operating system, if these are different from `1000`. See [Docker setup](/setup#docker_hub).
|
||||
operating system, if these are different from `1000`. See [Docker setup](setup.md#docker_hub).
|
||||
|
||||
Also ensure that you are able to read and write to the consumption
|
||||
directory on the host.
|
||||
@@ -265,8 +264,8 @@ This probably indicates paperless tried to consume the same file twice.
|
||||
This can happen for a number of reasons, depending on how documents are
|
||||
placed into the consume folder. If paperless is using inotify (the
|
||||
default) to check for documents, try adjusting the
|
||||
[inotify configuration](/configuration#inotify). If polling is enabled, try adjusting the
|
||||
[polling configuration](/configuration#polling).
|
||||
[inotify configuration](configuration.md#inotify). If polling is enabled, try adjusting the
|
||||
[polling configuration](configuration.md#polling).
|
||||
|
||||
## Consumer fails waiting for file to remain unmodified.
|
||||
|
||||
@@ -278,7 +277,7 @@ You might find messages like these in your log files:
|
||||
|
||||
This indicates paperless timed out while waiting for the file to be
|
||||
completely written to the consume folder. Adjusting
|
||||
[polling configuration](/configuration#polling) values should resolve the issue.
|
||||
[polling configuration](configuration.md#polling) values should resolve the issue.
|
||||
|
||||
!!! note
|
||||
|
||||
@@ -297,8 +296,8 @@ This indicates paperless was unable to open the file, as the OS reported
|
||||
the file as still being in use. To prevent a crash, paperless did not
|
||||
try to consume the file. If paperless is using inotify (the default) to
|
||||
check for documents, try adjusting the
|
||||
[inotify configuration](/configuration#inotify). If polling is enabled, try adjusting the
|
||||
[polling configuration](/configuration#polling).
|
||||
[inotify configuration](configuration.md#inotify). If polling is enabled, try adjusting the
|
||||
[polling configuration](configuration.md#polling).
|
||||
|
||||
!!! note
|
||||
|
||||
@@ -320,7 +319,7 @@ many workers attempting to access the database simultaneously.
|
||||
|
||||
Consider changing to the PostgreSQL database if you will be processing
|
||||
many documents at once often. Otherwise, try tweaking the
|
||||
`PAPERLESS_DB_TIMEOUT` setting to allow more time for the database to
|
||||
[`PAPERLESS_DB_TIMEOUT`](configuration.md#PAPERLESS_DB_TIMEOUT) setting to allow more time for the database to
|
||||
unlock. This may have minor performance implications.
|
||||
|
||||
## gunicorn fails to start with "is not a valid port number"
|
||||
@@ -330,5 +329,30 @@ environment variable named `${serviceName}_PORT`. This is
|
||||
the same environment variable which is used by Paperless to optionally
|
||||
change the port gunicorn listens on.
|
||||
|
||||
To fix this, set `PAPERLESS_PORT` again to your desired port, or the
|
||||
To fix this, set [`PAPERLESS_PORT`](configuration.md#PAPERLESS_PORT) again to your desired port, or the
|
||||
default of 8000.
|
||||
|
||||
## Database Warns about unique constraint "documents_tag_name_uniq
|
||||
|
||||
You may see database log lines like:
|
||||
|
||||
```
|
||||
ERROR: duplicate key value violates unique constraint "documents_tag_name_uniq"
|
||||
DETAIL: Key (name)=(NameF) already exists.
|
||||
STATEMENT: INSERT INTO "documents_tag" ("owner_id", "name", "match", "matching_algorithm", "is_insensitive", "color", "is_inbox_tag") VALUES (NULL, 'NameF', '', 1, true, '#a6cee3', false) RETURNING "documents_tag"."id"
|
||||
```
|
||||
|
||||
This can happen during heavy consumption when using polling. Paperless will handle it correctly and the file
|
||||
will still be consumed
|
||||
|
||||
## Consumption fails with "Ghostscript PDF/A rendering failed"
|
||||
|
||||
Newer versions of OCRmyPDF will fail if it encounters errors during processing.
|
||||
This is intentional as the output archive file may differ in unexpected or undesired
|
||||
ways from the original. As the logs indicate, if you encounter this error you can set
|
||||
`PAPERLESS_OCR_USER_ARGS: '{"continue_on_soft_render_error": true}'` to try to 'force'
|
||||
processing documents with this issue.
|
||||
|
||||
## Platform-Specific Deployment Troubleshooting
|
||||
|
||||
A user-maintained wiki page is available to help troubleshoot issues that may arise when trying to deploy Paperless-ngx on specific platforms, for example SELinux. Please see [the wiki](https://github.com/paperless-ngx/paperless-ngx/wiki/Platform%E2%80%90Specific-Troubleshooting).
|
||||
|
284
docs/usage.md
@@ -60,16 +60,18 @@ following operations on your documents:
|
||||
|
||||
This process can be configured to fit your needs. If you don't want
|
||||
paperless to create archived versions for digital documents, you can
|
||||
configure that by configuring `PAPERLESS_OCR_MODE=skip_noarchive`.
|
||||
Please read the
|
||||
[relevant section in the documentation](/configuration#ocr).
|
||||
configure that by configuring
|
||||
`PAPERLESS_OCR_SKIP_ARCHIVE_FILE=with_text`. Please read the
|
||||
[relevant section in the documentation](configuration.md#ocr).
|
||||
|
||||
!!! note
|
||||
|
||||
No matter which options you choose, Paperless will always store the
|
||||
original document that it found in the consumption directory or in the
|
||||
mail and will never overwrite that document. Archived versions are
|
||||
stored alongside the original versions.
|
||||
stored alongside the original versions. Any files found in the
|
||||
consumption directory will stored inside the Paperless-ngx file
|
||||
structure and will not be retained in the consumption directory.
|
||||
|
||||
### The consumption directory
|
||||
|
||||
@@ -77,7 +79,9 @@ The primary method of getting documents into your database is by putting
|
||||
them in the consumption directory. The consumer waits patiently, looking
|
||||
for new additions to this directory. When it finds them,
|
||||
the consumer goes about the process of parsing them with the OCR,
|
||||
indexing what it finds, and storing it in the media directory.
|
||||
indexing what it finds, and storing it in the media directory. You should
|
||||
think of this folder as a temporary location, as files will be re-created
|
||||
inside Paperless-ngx and removed from the consumption folder.
|
||||
|
||||
Getting stuff into this directory is up to you. If you're running
|
||||
Paperless on your local computer, you might just want to drag and drop
|
||||
@@ -88,27 +92,25 @@ Typically, you're looking at an FTP server like
|
||||
[Proftpd](http://www.proftpd.org/) or a Windows folder share with
|
||||
[Samba](https://www.samba.org/).
|
||||
|
||||
!!! warning
|
||||
|
||||
Files found in the consumption directory that are consumed will be
|
||||
removed from the consumption directory and stored inside the
|
||||
Paperless-ngx file structure using any settings / storage paths
|
||||
you have specified. This action is performed as safely as possible
|
||||
but this means it is expected that files in the consumption
|
||||
directory will no longer exist (there) after being consumed.
|
||||
|
||||
### Web UI Upload
|
||||
|
||||
The dashboard has a file drop field to upload documents to paperless.
|
||||
Simply drag a file onto this field or select a file with the file
|
||||
dialog. Multiple files are supported.
|
||||
|
||||
You can also upload documents on any other page of the web UI by
|
||||
dragging-and-dropping files into your browser window.
|
||||
The dashboard has a button to upload documents to paperless or you
|
||||
can simply drag a file anywhere into the app to initiate the consumption
|
||||
process.
|
||||
|
||||
### Mobile upload {#usage-mobile_upload}
|
||||
|
||||
The mobile app over at [https://github.com/qcasey/paperless_share](https://github.com/qcasey/paperless_share)
|
||||
allows Android users to share any documents with paperless. This can be
|
||||
combined with any of the mobile scanning apps out there, such as Office
|
||||
Lens.
|
||||
|
||||
Furthermore, there is the [Paperless
|
||||
App](https://github.com/bauerj/paperless_app) as well, which not only
|
||||
has document upload, but also document browsing and download features.
|
||||
|
||||
Another option is [Paperless Mobile](https://github.com/astubenbord/paperless-mobile), an Android app that supports document upload, scanning, management of labels and more.
|
||||
Please see [the wiki](https://github.com/paperless-ngx/paperless-ngx/wiki/Affiliated-Projects) for a user-maintained list of affiliated projects and
|
||||
software (e.g. for mobile devices) that is compatible with Paperless-ngx.
|
||||
|
||||
### IMAP (Email) {#usage-email}
|
||||
|
||||
@@ -132,9 +134,9 @@ These rules perform the following:
|
||||
5. If documents were consumed from a mail, the rule action is performed
|
||||
on that mail.
|
||||
|
||||
Paperless will completely ignore mails that do not match your filters.
|
||||
It will also only perform the action on mails that it has consumed
|
||||
documents from.
|
||||
Paperless will check all emails only once and completely ignore messages
|
||||
that do not match your filters. It will also only perform the rule action
|
||||
on e-mails that it has consumed documents from.
|
||||
|
||||
The actions all ensure that the same mail is not consumed twice by
|
||||
different means. These are as follows:
|
||||
@@ -147,7 +149,7 @@ different means. These are as follows:
|
||||
- **Flag:** Sets the 'important' flag on mails with consumed
|
||||
documents. Paperless will not consume flagged mails.
|
||||
- **Move to folder:** Moves consumed mails out of the way so that
|
||||
paperless wont consume them again.
|
||||
paperless won't consume them again.
|
||||
- **Add custom Tag:** Adds a custom tag to mails with consumed
|
||||
documents (the IMAP standard calls these "keywords"). Paperless
|
||||
will not consume mails already tagged. Not all mail servers support
|
||||
@@ -195,13 +197,239 @@ different means. These are as follows:
|
||||
them further.
|
||||
|
||||
Paperless is set up to check your mails every 10 minutes. This can be
|
||||
configured via `PAPERLESS_EMAIL_TASK_CRON` (see [software tweaks](/configuration#software_tweaks))
|
||||
configured via [`PAPERLESS_EMAIL_TASK_CRON`](configuration.md#PAPERLESS_EMAIL_TASK_CRON)
|
||||
|
||||
### REST API
|
||||
|
||||
You can also submit a document using the REST API, see [POSTing documents](/api#file-uploads)
|
||||
You can also submit a document using the REST API, see [POSTing documents](api.md#file-uploads)
|
||||
for details.
|
||||
|
||||
## Permissions
|
||||
|
||||
As of version 1.14.0 Paperless-ngx added core support for user / group permissions. Permissions is
|
||||
based around 'global' permissions as well as 'object-level' permissions. Global permissions designate
|
||||
which parts of the application a user can access (e.g. Documents, Tags, Settings) and object-level
|
||||
determine which objects are visible or editable. All objects have an 'owner' and 'view' and 'edit'
|
||||
permissions which can be granted to other users or groups. The paperless-ngx permissions system uses
|
||||
the built-in user model of the backend framework, Django.
|
||||
|
||||
!!! tip
|
||||
|
||||
Object-level permissions only apply to the object itself. In other words, setting permissions
|
||||
for a Tag will _not_ affect the permissions of documents that have the Tag.
|
||||
|
||||
Permissions can be set using the new "Permissions" tab when editing documents, or bulk-applied
|
||||
in the UI by selecting documents and choosing the "Permissions" button. Owner can also optionally
|
||||
be set for documents uploaded via the API. Documents consumed via the consumption dir currently
|
||||
do not have an owner set.
|
||||
|
||||
!!! note
|
||||
|
||||
After migration to version 1.14.0 all existing documents, tags etc. will have no explicit owner
|
||||
set which means they will be visible / editable by all users. Once an object has an owner set,
|
||||
only the owner can explicitly grant / revoke permissions.
|
||||
|
||||
!!! note
|
||||
|
||||
When first migrating to permissions it is recommended to use a 'superuser' account (which
|
||||
would usually have been setup during installation) to ensure you have full permissions.
|
||||
|
||||
Note that superusers have access to all objects.
|
||||
|
||||
### Default permissions
|
||||
|
||||
Default permissions for documents can be set using workflows.
|
||||
|
||||
For objects created via the web UI (tags, doc types, etc.) the default is to set the current user
|
||||
as owner and no extra permissions, but you explicitly set these under Settings > Permissions.
|
||||
|
||||
### Users and Groups
|
||||
|
||||
Paperless-ngx versions after 1.14.0 allow creating and editing users and groups via the 'frontend' UI.
|
||||
These can be found under Settings > Users & Groups, assuming the user has access. If a user is designated
|
||||
as a member of a group those permissions will be inherited and this is reflected in the UI. Explicit
|
||||
permissions can be granted to limit access to certain parts of the UI (and corresponding API endpoints).
|
||||
|
||||
### Password reset
|
||||
|
||||
In order to enable the password reset feature you will need to setup an SMTP backend, see
|
||||
[`PAPERLESS_EMAIL_HOST`](configuration.md#PAPERLESS_EMAIL_HOST). If your installation does not have
|
||||
[`PAPERLESS_URL`](configuration.md#PAPERLESS_URL) set, the reset link included in emails will use the server host.
|
||||
|
||||
## Workflows
|
||||
|
||||
!!! note
|
||||
|
||||
v2.3 added "Workflows" and existing "Consumption Templates" were converted automatically to the new more powerful format.
|
||||
|
||||
Workflows allow hooking into the Paperless-ngx document pipeline, for example to alter what metadata (tags, doc types) and
|
||||
permissions (owner, privileges) are assigned to documents. Workflows can have multiple 'triggers' and 'actions'. Triggers
|
||||
are events (with optional filtering rules) that will cause the workflow to be run and actions are the set of sequential
|
||||
actions to apply.
|
||||
|
||||
In general, workflows and any actions they contain are applied sequentially by sort order. For "assignment" actions, subsequent
|
||||
workflow actions will override previous assignments, except for assignments that accept multiple items e.g. tags, custom
|
||||
fields and permissions, which will be merged.
|
||||
|
||||
### Workflow Triggers
|
||||
|
||||
Currently, there are three events that correspond to workflow trigger 'types':
|
||||
|
||||
1. **Consumption Started**: _before_ a document is consumed, so events can include filters by source (mail, consumption
|
||||
folder or API), file path, file name, mail rule
|
||||
2. **Document Added**: _after_ a document is added. At this time, file path and source information is no longer available,
|
||||
but the document content has been extracted and metadata such as document type, tags, etc. have been set, so these can now
|
||||
be used for filtering.
|
||||
3. **Document Updated**: when a document is updated. Similar to 'added' events, triggers can include filtering by content matching,
|
||||
tags, doc type, or correspondent.
|
||||
|
||||
The following flow diagram illustrates the three trigger types:
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
consumption{"Matching
|
||||
'Consumption'
|
||||
trigger(s)"}
|
||||
|
||||
added{"Matching
|
||||
'Added'
|
||||
trigger(s)"}
|
||||
|
||||
updated{"Matching
|
||||
'Updated'
|
||||
trigger(s)"}
|
||||
|
||||
A[New Document] --> consumption
|
||||
consumption --> |Yes| C[Workflow Actions Run]
|
||||
consumption --> |No| D
|
||||
C --> D[Document Added]
|
||||
D -- Paperless-ngx 'matching' of tags, etc. --> added
|
||||
added --> |Yes| F[Workflow Actions Run]
|
||||
added --> |No| G
|
||||
F --> G[Document Finalized]
|
||||
H[Existing Document Changed] --> updated
|
||||
updated --> |Yes| J[Workflow Actions Run]
|
||||
updated --> |No| K
|
||||
J --> K[Document Saved]
|
||||
```
|
||||
|
||||
#### Filters {#workflow-trigger-filters}
|
||||
|
||||
Workflows allow you to filter by:
|
||||
|
||||
- Source, e.g. documents uploaded via consume folder, API (& the web UI) and mail fetch
|
||||
- File name, including wildcards e.g. \*.pdf will apply to all pdfs
|
||||
- File path, including wildcards. Note that enabling `PAPERLESS_CONSUMER_RECURSIVE` would allow, for
|
||||
example, automatically assigning documents to different owners based on the upload directory.
|
||||
- Mail rule. Choosing this option will force 'mail fetch' to be the workflow source.
|
||||
- Content matching (`Added` and `Updated` triggers only). Filter document content using the matching settings.
|
||||
- Tags (`Added` and `Updated` triggers only). Filter for documents with any of the specified tags
|
||||
- Document type (`Added` and `Updated` triggers only). Filter documents with this doc type
|
||||
- Correspondent (`Added` and `Updated` triggers only). Filter documents with this correspondent
|
||||
|
||||
### Workflow Actions
|
||||
|
||||
There are currently two types of workflow actions, "Assignment", which can assign:
|
||||
|
||||
- Title, see [title placeholders](usage.md#title-placeholders) below
|
||||
- Tags, correspondent, document type and storage path
|
||||
- Document owner
|
||||
- View and / or edit permissions to users or groups
|
||||
- Custom fields. Note that no value for the field will be set
|
||||
|
||||
and "Removal" actions, which can remove either all of or specific sets of the following:
|
||||
|
||||
- Tags, correspondents, document types or storage paths
|
||||
- Document owner
|
||||
- View and / or edit permissions
|
||||
- Custom fields
|
||||
|
||||
#### Title placeholders
|
||||
|
||||
Workflow titles can include placeholders but the available options differ depending on the type of
|
||||
workflow trigger. This is because at the time of consumption (when the title is to be set), no automatic tags etc. have been
|
||||
applied. You can use the following placeholders with any trigger type:
|
||||
|
||||
- `{correspondent}`: assigned correspondent name
|
||||
- `{document_type}`: assigned document type name
|
||||
- `{owner_username}`: assigned owner username
|
||||
- `{added}`: added datetime
|
||||
- `{added_year}`: added year
|
||||
- `{added_year_short}`: added year
|
||||
- `{added_month}`: added month
|
||||
- `{added_month_name}`: added month name
|
||||
- `{added_month_name_short}`: added month short name
|
||||
- `{added_day}`: added day
|
||||
- `{added_time}`: added time in HH:MM format
|
||||
- `{original_filename}`: original file name without extension
|
||||
|
||||
The following placeholders are only available for "added" or "updated" triggers
|
||||
|
||||
- `{created}`: created datetime
|
||||
- `{created_year}`: created year
|
||||
- `{created_year_short}`: created year
|
||||
- `{created_month}`: created month
|
||||
- `{created_month_name}`: created month name
|
||||
- `{created_month_name_short}`: created month short name
|
||||
- `{created_day}`: created day
|
||||
- `{created_time}`: created time in HH:MM format
|
||||
|
||||
### Workflow permissions
|
||||
|
||||
All users who have application permissions for editing workflows can see the same set
|
||||
of workflows. In other words, workflows themselves intentionally do not have an owner or permissions.
|
||||
|
||||
Given their potentially far-reaching capabilities, you may want to restrict access to workflows.
|
||||
|
||||
Upon migration, existing installs will grant access to workflows to users who can add
|
||||
documents (and superusers who can always access all parts of the app).
|
||||
|
||||
## Custom Fields {#custom-fields}
|
||||
|
||||
Paperless-ngx supports the use of custom fields for documents as of v2.0, allowing a user
|
||||
to optionally attach data to documents which does not fit in the existing set of fields
|
||||
Paperless-ngx provides.
|
||||
|
||||
1. First, create a custom field (under "Manage"), with a given name and data type. This could be something like "Invoice Number" or "Date Paid", with a data type of "Number", "Date", "String", etc.
|
||||
2. Once created, a field can be used with documents and data stored. To do so, use the "Custom Fields" menu on the document detail page, choose your existing field and click "Add". Once the field is visible in the form you can enter the appropriate
|
||||
data which will be validated according to the custom field "data type".
|
||||
3. Fields can be removed by hovering over the field name revealing a "Remove" button.
|
||||
|
||||
!!! important
|
||||
|
||||
Added / removed fields, as well as any data is not saved to the document until you
|
||||
actually hit the "Save" button, similar to other changes on the document details page.
|
||||
|
||||
!!! note
|
||||
|
||||
Once the data type for a field is set, it cannot be changed.
|
||||
|
||||
Multiple fields may be attached to a document but the same field name cannot be assigned multiple times to the a single document.
|
||||
|
||||
The following custom field types are supported:
|
||||
|
||||
- `Text`: any text
|
||||
- `Boolean`: true / false (check / unchecked) field
|
||||
- `Date`: date
|
||||
- `URL`: a valid url
|
||||
- `Integer`: integer number e.g. 12
|
||||
- `Number`: float number e.g. 12.3456
|
||||
- `Monetary`: [ISO 4217 currency code](https://en.wikipedia.org/wiki/ISO_4217#List_of_ISO_4217_currency_codes) and a number with exactly two decimals, e.g. USD12.30
|
||||
- `Document Link`: reference(s) to other document(s) displayed as links, automatically creates a symmetrical link in reverse
|
||||
|
||||
## Share Links
|
||||
|
||||
Paperless-ngx added the ability to create shareable links to files in version 2.0. You can find the button for this on the document detail screen.
|
||||
|
||||
- Share links do not require a user to login and thus link directly to a file.
|
||||
- Links are unique and are of the form `{paperless-url}/share/{randomly-generated-slug}`.
|
||||
- Links can optionally have an expiration time set.
|
||||
- After a link expires or is deleted users will be redirected to the regular paperless-ngx login.
|
||||
|
||||
!!! tip
|
||||
|
||||
If your paperless-ngx instance is behind a reverse-proxy you may want to create an exception to bypass any authentication layers that are part of your setup in order to make links truly publicly-accessible. Of course, do so with caution.
|
||||
|
||||
## Best practices {#basic-searching}
|
||||
|
||||
Paperless offers a couple tools that help you organize your document
|
||||
@@ -410,7 +638,7 @@ Once you have scanned in a document, proceed in paperless as follows.
|
||||
paperless will assign them automatically. After consuming a couple
|
||||
documents, you can even ask paperless to *learn* when to assign tags and
|
||||
correspondents by itself. For details on this feature, see
|
||||
[advanced matching](/advanced_usage#matching).
|
||||
[advanced matching](advanced_usage.md#matching).
|
||||
|
||||
### Task management
|
||||
|
||||
|
@@ -30,7 +30,9 @@ def worker_int(worker):
|
||||
worker.log.info("worker received INT or QUIT signal")
|
||||
|
||||
## get traceback info
|
||||
import threading, sys, traceback
|
||||
import sys
|
||||
import threading
|
||||
import traceback
|
||||
|
||||
id2name = {th.ident: th.name for th in threading.enumerate()}
|
||||
code = []
|
||||
|
@@ -38,7 +38,6 @@ ask_docker_folder() {
|
||||
echo "Invalid folder: $result"
|
||||
fi
|
||||
|
||||
|
||||
done
|
||||
}
|
||||
|
||||
@@ -57,14 +56,9 @@ if ! command -v docker &> /dev/null ; then
|
||||
exit 1
|
||||
fi
|
||||
|
||||
DOCKER_COMPOSE_CMD="docker-compose"
|
||||
if ! command -v ${DOCKER_COMPOSE_CMD} ; then
|
||||
if docker compose version &> /dev/null ; then
|
||||
DOCKER_COMPOSE_CMD="docker compose"
|
||||
else
|
||||
echo "docker-compose executable not found. Is docker-compose installed?"
|
||||
exit 1
|
||||
fi
|
||||
if ! docker compose &> /dev/null ; then
|
||||
echo "docker compose plugin not found. Is docker compose installed?"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Check if user has permissions to run Docker by trying to get the status of Docker (docker status).
|
||||
@@ -72,7 +66,7 @@ fi
|
||||
if ! docker stats --no-stream &> /dev/null ; then
|
||||
echo ""
|
||||
echo "WARN: It look like the current user does not have Docker permissions."
|
||||
echo "WARN: Use 'sudo usermod -aG docker $USER' to assign Docker permissions to the user."
|
||||
echo "WARN: Use 'sudo usermod -aG docker $USER' to assign Docker permissions to the user (may require restarting shell)."
|
||||
echo ""
|
||||
sleep 3
|
||||
fi
|
||||
@@ -321,7 +315,8 @@ fi
|
||||
wget "https://raw.githubusercontent.com/paperless-ngx/paperless-ngx/main/docker/compose/docker-compose.$DOCKER_COMPOSE_VERSION.yml" -O docker-compose.yml
|
||||
wget "https://raw.githubusercontent.com/paperless-ngx/paperless-ngx/main/docker/compose/.env" -O .env
|
||||
|
||||
SECRET_KEY=$(tr --delete --complement 'a-zA-Z0-9' < /dev/urandom 2>/dev/null | head --bytes 64)
|
||||
SECRET_KEY=$(LC_ALL=C tr -dc 'a-zA-Z0-9!#$%&()*+,-./:;<=>?@[\]^_`{|}~' < /dev/urandom | dd bs=1 count=64 2>/dev/null)
|
||||
|
||||
|
||||
DEFAULT_LANGUAGES=("deu eng fra ita spa")
|
||||
|
||||
@@ -382,8 +377,16 @@ if [ "$l1" -eq "$l2" ] ; then
|
||||
fi
|
||||
|
||||
|
||||
${DOCKER_COMPOSE_CMD} pull
|
||||
docker compose pull
|
||||
|
||||
${DOCKER_COMPOSE_CMD} run --rm -e DJANGO_SUPERUSER_PASSWORD="$PASSWORD" webserver createsuperuser --noinput --username "$USERNAME" --email "$EMAIL"
|
||||
if [ "$DATABASE_BACKEND" == "postgres" ] || [ "$DATABASE_BACKEND" == "mariadb" ] ; then
|
||||
echo "Starting DB first for initialization"
|
||||
docker compose up --detach db
|
||||
# hopefully enough time for even the slower systems
|
||||
sleep 15
|
||||
docker compose stop
|
||||
fi
|
||||
|
||||
${DOCKER_COMPOSE_CMD} up --detach
|
||||
docker compose run --rm -e DJANGO_SUPERUSER_PASSWORD="$PASSWORD" webserver createsuperuser --noinput --username "$USERNAME" --email "$EMAIL"
|
||||
|
||||
docker compose up --detach
|
||||
|
12
mkdocs.yml
@@ -28,6 +28,7 @@ theme:
|
||||
repo: fontawesome/brands/github
|
||||
favicon: assets/favicon.png
|
||||
repo_url: https://github.com/paperless-ngx/paperless-ngx
|
||||
repo_name: paperless-ngx/paperless-ngx
|
||||
edit_uri: blob/main/docs/
|
||||
extra_css:
|
||||
- assets/extra.css
|
||||
@@ -42,6 +43,12 @@ markdown_extensions:
|
||||
- pymdownx.superfences
|
||||
- pymdownx.inlinehilite
|
||||
- pymdownx.snippets
|
||||
- footnotes
|
||||
- pymdownx.superfences:
|
||||
custom_fences:
|
||||
- name: mermaid
|
||||
class: mermaid
|
||||
format: !!python/name:pymdownx.superfences.fence_code_format
|
||||
strict: true
|
||||
nav:
|
||||
- index.md
|
||||
@@ -64,3 +71,8 @@ extra:
|
||||
link: https://hub.docker.com/r/paperlessngx/paperless-ngx
|
||||
- icon: material/chat
|
||||
link: https://matrix.to/#/#paperless:matrix.org
|
||||
plugins:
|
||||
- search
|
||||
- glightbox:
|
||||
skip_classes:
|
||||
- no-lightbox
|
||||
|